Pensieve: 2406

2024-06-30 08:42

所读所玩所听

这个月游戏玩得多, 书读得少, 只有两本. 一本是孔庆东的金庸者谁, 挺水的可以不看. 另一本是在手机上碎片时间里读完的朱镕基答记者问, 这本值得重读, 因为有些话十年前读的时候没什么感觉, 过去也就过去了, 十年后失去了才知道可惜.

游戏除了单独成文的Diablo 4以外, 还有怪奇小店, 恶魔城晓月和Klei家的缺氧. 怪奇小店是AppStore里看到以前的已购游戏时下载下来玩的. 最大的感想是几年过去了, 当时很不少的游戏攻略已经基本绝迹了, 剩下的大都是内容农场里的作物. 无聊时随便玩玩时体验还好, 但是如果要去刷红名人物或刷挑战的话需要的反复读档实在太让人不爽. 单机游戏很多时候为了游戏时间只能这样通过随机数来控制, 我能理解, 就好比我偶尔会打开家里一台Windows电脑去刷几轮无尽模式的植物大战僵尸来刷植物种子(虽然n年前也已经完成过全收集了不过档没了不是吗). 恶魔城晓月是刷reddit时看到有不少人推荐iOS下的模拟器Delta, 于是下载下来, 然后下载了几个游戏过来测试(掩嘴笑). 大概十几个小时通关了, 魂收集大概有80-90%的样子, 反正之前实现过全魂收集, 这次就没强迫自己了. 手机上直接触屏玩的手感稍差, 有时候认为自己按到了但实际上没按到, 如果有物理按键肯定会好一些, 但是为了这个小游戏去拿手柄感觉又缺了点什么. 缺氧的设计和饥荒是一脉相承, 沙盒里通过各种系统和复杂度来留住玩家, 而且当一个管理者的感觉也挺好, 所以一直能够让玩家有正向反馈. 但是, 我还是觉得看视频才能学会玩游戏不算是一个好的体验. 哦对了当然也还在继续原神, 每天上线做日常, 然后在仆人的圣遗物本里坐牢. 这个月终于有了一个双爆的攻击沙, 而至今为止只见过两个双爆头也是挺让人无奈的.

这个月发现了一个古早的治愈系音乐家矢野显子, 日本对西方文化的吸收使得有很多人会喜欢用爵士乐去翻唱英文的名曲, 往往听起来有耳目一新的感觉. 另外一个发现是一个德国钢琴家Martin Stadtfeld的哥德堡变奏, 推荐9和30.

玩PTV的API

维洲的公共交通提供了API供爱好者查询使用. API的申请不是全自动的, 大概等了一两周才收到回信. 我针对我的需求, 写了一个脚本, 供下班时查询要乘坐的906/907线. 脚本和对应的注释在下面:

#!/usr/bin/env python3
from datetime import datetime
from datetime import timezone
import hmac
import http.client
import json
import math
from urllib.parse import urlencode

API_ID = "your-api-id-that-looks-like-a-number"
API_KEY = "your-api-key-that-looks-like-a-uuid"
API_HOST = "timetableapi.ptv.vic.gov.au"
# route_id available from /v3/routes, direction_id available from /v3/runs/route/route_id
ROUTES = {
    "907": {"route_id": 8591, "direction_id": 31},
    "906": {"route_id": 8596, "direction_id": 54},
}
MY_POSITION = {"longitude": 144.989958, "latitude": -37.809515}
# For weekdays around 5pm, it will take 8 minutes to travel 1.34KM according to the schedule.
BUS_SPEED = 1340 / (8 * 60)


def get_response(path, **kwargs):
    kwargs["devid"] = API_ID
    raw = f"{path}?{urlencode(kwargs)}"
    kwargs["signature"] = hmac.new(API_KEY.encode(), raw.encode(), "sha1").hexdigest()

    conn = http.client.HTTPSConnection(API_HOST)
    conn.request("GET", f"{path}?{urlencode(kwargs)}")
    return conn.getresponse()


def haversine(coord1, coord2):
    # radius of the earth in m
    R = 6371.0 * 1000

    lat1 = math.radians(coord1["latitude"])
    lon1 = math.radians(coord1["longitude"])
    lat2 = math.radians(coord2["latitude"])
    lon2 = math.radians(coord2["longitude"])

    dlat = lat2 - lat1
    dlon = lon2 - lon1

    a = math.sin(dlat / 2) ** 2 + math.cos(lat1) * math.cos(lat2) * math.sin(dlon / 2) ** 2
    c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
    return R * c


def main():
    print(f"Now it's {datetime.now().strftime('%H:%M')}")
    utc_now = datetime.now(timezone.utc)
    for route_name, route_info in ROUTES.items():
        response = get_response(f"/v3/runs/route/{route_info['route_id']}", expand="All")
        if response.getcode() != 200:
            raise RuntimeError(
                f"Bad response({response.getcode()}) from PTV API: {response.read().decode()}"
            )
        runs = json.load(response)["runs"]
        time_remaining = []
        for run in runs:
            if run["vehicle_position"] == None or run["direction_id"] != route_info["direction_id"]:
                continue
            if run["vehicle_position"]["longitude"] > MY_POSITION["longitude"]:
                continue
            distance = haversine(run["vehicle_position"], MY_POSITION)
            try:
                utc_time = datetime.strptime(
                    run["vehicle_position"]["datetime_utc"], "%Y-%m-%dT%H:%M:%SZ"
                ).replace(tzinfo=timezone.utc)
            except ValueError:
                utc_time = datetime.strptime(
                    run["vehicle_position"]["datetime_utc"], "%Y-%m-%dT%H:%M:%S.%fZ"
                ).replace(tzinfo=timezone.utc)
            distance -= (utc_now - utc_time).seconds * BUS_SPEED
            time_remaining.append(distance / BUS_SPEED / 60)
        time_remaining.sort()
        if len(time_remaining) == 0:
            print(f"[{route_name}] No bus info found.")
        elif time_remaining[0] > 10:
            print(f"[{route_name}] Closest bus is {min(time_remaining):.1f} mins away.")
        else:
            print(
                f"[{route_name}] Incoming buses: [{'/'.join(format(t, '.1f') for t in time_remaining[:2])}] mins."
            )


if __name__ == "__main__":
    main()

有一说一, 这个脚本没写得很好, 不少地方很粗糙. 不过在ChagGPT的帮助下两个小时就从零上手写完测完了, 开发体验还可以. 现在下班时就会打开Pythonista跑一遍这个脚本. 如果发现公交车还有不到三分钟到站的话就得小跑一段了.