import collections
def calculate_osu_replay(objects, clicks):
# 將物件放入 Queue 方便操作
obj_queue = collections.deque(objects)
# 初始化統計數據
stats = {
"count_300": 0,
"count_100": 0,
"count_50": 0,
"count_miss": 0,
"max_combo": 0
}
current_combo = 0
total_objects = len(objects)
# 處理玩家的每一次點擊
for click in clicks:
if not obj_queue:
break # 譜面已經沒有物件了,忽略後續所有點擊
# 1. 檢查在這次點擊之前,是否有物件已經「過期 Miss」了
# (即該物件的最晚判定時間 < 這次點擊的時間)
while obj_queue and obj_queue[0] + 100 < click:
obj_queue.popleft()
stats["count_miss"] += 1
current_combo = 0 # 斷康
if not obj_queue:
break
target_obj = obj_queue[0]
dt
= abs(click
- target_obj
)
# 2. 判斷點擊是否落在判定區間內
if dt <= 100:
# 命中物件,從 queue 中移除
obj_queue.popleft()
current_combo += 1
stats["max_combo"] = max(stats["max_combo"], current_combo)
if dt <= 20:
stats["count_300"] += 1
elif dt <= 60:
stats["count_100"] += 1
else: # 60 < dt <= 100
stats["count_50"] += 1
# 3. 如果 dt > 100 (表示過早點擊),不處理,保留物件等待下一次點擊
# 迴圈結束後,如果 queue 裡面還有物件,代表玩家根本沒點,全部算 Miss
while obj_queue:
obj_queue.popleft()
stats["count_miss"] += 1
current_combo = 0
# 計算準確度
if total_objects == 0:
accuracy = 0.0
else:
total_score = (stats["count_300"] * 300) + (stats["count_100"] * 100) + (stats["count_50"] * 50)
max_possible_score = total_objects * 300
accuracy = (total_score / max_possible_score) * 100
stats["accuracy"] = round(accuracy, 2)
return stats
# ==========================================
# 測試區塊
# ==========================================
if __name__ == "__main__":
# 測資 1: 完美全接 (Perfect SS)
print("Test 1: Perfect SS")
objects1 = [1000, 2000, 3000]
clicks1 = [1005, 1990, 3000]
print(calculate_osu_replay(objects1, clicks1))
# 測資 2: 包含空揮、Miss、斷Combo (範例測資)
print("\nTest 2: Normal Play with Misses")
objects2 = [1000, 1500, 2000, 2200, 3000]
clicks2 = [500, 1015, 1450, 2150, 3090, 3150]
print(calculate_osu_replay(objects2, clicks2))
# 測資 3: 中途放棄遊戲 (後面物件全部放推)
print("\nTest 3: Quit halfway")
objects3 = [1000, 1500, 2000, 2500, 3000]
clicks3 = [1000, 1500]
print(calculate_osu_replay(objects3, clicks3))
aW1wb3J0IGNvbGxlY3Rpb25zCgpkZWYgY2FsY3VsYXRlX29zdV9yZXBsYXkob2JqZWN0cywgY2xpY2tzKToKICAgICMg5bCH54mp5Lu25pS+5YWlIFF1ZXVlIOaWueS+v+aTjeS9nAogICAgb2JqX3F1ZXVlID0gY29sbGVjdGlvbnMuZGVxdWUob2JqZWN0cykKICAgIAogICAgIyDliJ3lp4vljJbntbHoqIjmlbjmk5oKICAgIHN0YXRzID0gewogICAgICAgICJjb3VudF8zMDAiOiAwLAogICAgICAgICJjb3VudF8xMDAiOiAwLAogICAgICAgICJjb3VudF81MCI6IDAsCiAgICAgICAgImNvdW50X21pc3MiOiAwLAogICAgICAgICJtYXhfY29tYm8iOiAwCiAgICB9CiAgICAKICAgIGN1cnJlbnRfY29tYm8gPSAwCiAgICB0b3RhbF9vYmplY3RzID0gbGVuKG9iamVjdHMpCiAgICAKICAgICMg6JmV55CG546p5a6255qE5q+P5LiA5qyh6bue5pOKCiAgICBmb3IgY2xpY2sgaW4gY2xpY2tzOgogICAgICAgIGlmIG5vdCBvYmpfcXVldWU6CiAgICAgICAgICAgIGJyZWFrICMg6K2c6Z2i5bey57aT5rKS5pyJ54mp5Lu25LqG77yM5b+955Wl5b6M57qM5omA5pyJ6bue5pOKCiAgICAgICAgICAgIAogICAgICAgICMgMS4g5qqi5p+l5Zyo6YCZ5qyh6bue5pOK5LmL5YmN77yM5piv5ZCm5pyJ54mp5Lu25bey57aT44CM6YGO5pyfIE1pc3PjgI3kuoYKICAgICAgICAjICjljbPoqbLnianku7bnmoTmnIDmmZrliKTlrprmmYLplpMgPCDpgJnmrKHpu57mk4rnmoTmmYLplpMpCiAgICAgICAgd2hpbGUgb2JqX3F1ZXVlIGFuZCBvYmpfcXVldWVbMF0gKyAxMDAgPCBjbGljazoKICAgICAgICAgICAgb2JqX3F1ZXVlLnBvcGxlZnQoKQogICAgICAgICAgICBzdGF0c1siY291bnRfbWlzcyJdICs9IDEKICAgICAgICAgICAgY3VycmVudF9jb21ibyA9IDAgIyDmlrflurcKICAgICAgICAgICAgCiAgICAgICAgaWYgbm90IG9ial9xdWV1ZToKICAgICAgICAgICAgYnJlYWsKICAgICAgICAgICAgCiAgICAgICAgdGFyZ2V0X29iaiA9IG9ial9xdWV1ZVswXQogICAgICAgIGR0ID0gYWJzKGNsaWNrIC0gdGFyZ2V0X29iaikKICAgICAgICAKICAgICAgICAjIDIuIOWIpOaWt+m7nuaTiuaYr+WQpuiQveWcqOWIpOWumuWNgOmWk+WFpwogICAgICAgIGlmIGR0IDw9IDEwMDoKICAgICAgICAgICAgIyDlkb3kuK3nianku7bvvIzlvp4gcXVldWUg5Lit56e76ZmkCiAgICAgICAgICAgIG9ial9xdWV1ZS5wb3BsZWZ0KCkKICAgICAgICAgICAgY3VycmVudF9jb21ibyArPSAxCiAgICAgICAgICAgIHN0YXRzWyJtYXhfY29tYm8iXSA9IG1heChzdGF0c1sibWF4X2NvbWJvIl0sIGN1cnJlbnRfY29tYm8pCiAgICAgICAgICAgIAogICAgICAgICAgICBpZiBkdCA8PSAyMDoKICAgICAgICAgICAgICAgIHN0YXRzWyJjb3VudF8zMDAiXSArPSAxCiAgICAgICAgICAgIGVsaWYgZHQgPD0gNjA6CiAgICAgICAgICAgICAgICBzdGF0c1siY291bnRfMTAwIl0gKz0gMQogICAgICAgICAgICBlbHNlOiAjIDYwIDwgZHQgPD0gMTAwCiAgICAgICAgICAgICAgICBzdGF0c1siY291bnRfNTAiXSArPSAxCiAgICAgICAgICAgICAgICAKICAgICAgICAjIDMuIOWmguaenCBkdCA+IDEwMCAo6KGo56S66YGO5pep6bue5pOKKe+8jOS4jeiZleeQhu+8jOS/neeVmeeJqeS7tuetieW+heS4i+S4gOasoem7nuaTigogICAgICAgIAogICAgIyDov7TlnIjntZDmnZ/lvozvvIzlpoLmnpwgcXVldWUg6KOh6Z2i6YKE5pyJ54mp5Lu277yM5Luj6KGo546p5a625qC55pys5rKS6bue77yM5YWo6YOo566XIE1pc3MKICAgIHdoaWxlIG9ial9xdWV1ZToKICAgICAgICBvYmpfcXVldWUucG9wbGVmdCgpCiAgICAgICAgc3RhdHNbImNvdW50X21pc3MiXSArPSAxCiAgICAgICAgY3VycmVudF9jb21ibyA9IDAKICAgICAgICAKICAgICMg6KiI566X5rqW56K65bqmCiAgICBpZiB0b3RhbF9vYmplY3RzID09IDA6CiAgICAgICAgYWNjdXJhY3kgPSAwLjAKICAgIGVsc2U6CiAgICAgICAgdG90YWxfc2NvcmUgPSAoc3RhdHNbImNvdW50XzMwMCJdICogMzAwKSArIChzdGF0c1siY291bnRfMTAwIl0gKiAxMDApICsgKHN0YXRzWyJjb3VudF81MCJdICogNTApCiAgICAgICAgbWF4X3Bvc3NpYmxlX3Njb3JlID0gdG90YWxfb2JqZWN0cyAqIDMwMAogICAgICAgIGFjY3VyYWN5ID0gKHRvdGFsX3Njb3JlIC8gbWF4X3Bvc3NpYmxlX3Njb3JlKSAqIDEwMAogICAgICAgIAogICAgc3RhdHNbImFjY3VyYWN5Il0gPSByb3VuZChhY2N1cmFjeSwgMikKICAgIAogICAgcmV0dXJuIHN0YXRzCgojID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQojIOa4rOippuWNgOWhigojID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQppZiBfX25hbWVfXyA9PSAiX19tYWluX18iOgogICAgIyDmuKzos4cgMTog5a6M576O5YWo5o6lIChQZXJmZWN0IFNTKQogICAgcHJpbnQoIlRlc3QgMTogUGVyZmVjdCBTUyIpCiAgICBvYmplY3RzMSA9IFsxMDAwLCAyMDAwLCAzMDAwXQogICAgY2xpY2tzMSA9IFsxMDA1LCAxOTkwLCAzMDAwXQogICAgcHJpbnQoY2FsY3VsYXRlX29zdV9yZXBsYXkob2JqZWN0czEsIGNsaWNrczEpKQogICAgCiAgICAjIOa4rOizhyAyOiDljIXlkKvnqbrmj67jgIFNaXNz44CB5pa3Q29tYm8gKOevhOS+i+a4rOizhykKICAgIHByaW50KCJcblRlc3QgMjogTm9ybWFsIFBsYXkgd2l0aCBNaXNzZXMiKQogICAgb2JqZWN0czIgPSBbMTAwMCwgMTUwMCwgMjAwMCwgMjIwMCwgMzAwMF0KICAgIGNsaWNrczIgPSBbNTAwLCAxMDE1LCAxNDUwLCAyMTUwLCAzMDkwLCAzMTUwXQogICAgcHJpbnQoY2FsY3VsYXRlX29zdV9yZXBsYXkob2JqZWN0czIsIGNsaWNrczIpKQoKICAgICMg5ris6LOHIDM6IOS4remAlOaUvuajhOmBiuaIsiAo5b6M6Z2i54mp5Lu25YWo6YOo5pS+5o6oKQogICAgcHJpbnQoIlxuVGVzdCAzOiBRdWl0IGhhbGZ3YXkiKQogICAgb2JqZWN0czMgPSBbMTAwMCwgMTUwMCwgMjAwMCwgMjUwMCwgMzAwMF0KICAgIGNsaWNrczMgPSBbMTAwMCwgMTUwMF0gCiAgICBwcmludChjYWxjdWxhdGVfb3N1X3JlcGxheShvYmplY3RzMywgY2xpY2tzMykp
Test 1: Perfect SS
{'count_300': 3, 'count_100': 0, 'count_50': 0, 'count_miss': 0, 'max_combo': 3, 'accuracy': 100.0}
Test 2: Normal Play with Misses
{'count_300': 1, 'count_100': 2, 'count_50': 1, 'count_miss': 1, 'max_combo': 2, 'accuracy': 36.67}
Test 3: Quit halfway
{'count_300': 2, 'count_100': 0, 'count_50': 0, 'count_miss': 3, 'max_combo': 2, 'accuracy': 40.0}