changing how to handle message and tests
This commit is contained in:
+338
-305
@@ -7,7 +7,7 @@ import os
|
|||||||
import select
|
import select
|
||||||
import sys
|
import sys
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Literal
|
from typing import Literal, Tuple
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
from chroma_case.Key import Key
|
from chroma_case.Key import Key
|
||||||
@@ -29,341 +29,374 @@ PRACTICE = 1
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class InvalidMessage:
|
class InvalidMessage:
|
||||||
message: str
|
message: str
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class StartMessage(ValidatedDC):
|
class StartMessage(ValidatedDC):
|
||||||
id: int
|
id: int
|
||||||
bearer: str
|
bearer: str
|
||||||
mode: Literal["normal", "practice"]
|
mode: Literal["normal", "practice"]
|
||||||
type: Literal["start"] = "start"
|
type: Literal["start"] = "start"
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class EndMessage(ValidatedDC):
|
class EndMessage(ValidatedDC):
|
||||||
type: Literal["end"] = "end"
|
type: Literal["end"] = "end"
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class NoteOnMessage(ValidatedDC):
|
class NoteOnMessage(ValidatedDC):
|
||||||
time: int
|
time: int
|
||||||
note: int
|
note: int
|
||||||
id: int
|
id: int
|
||||||
type: Literal["note_on"] = "note_on"
|
type: Literal["note_on"] = "note_on"
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class NoteOffMessage(ValidatedDC):
|
class NoteOffMessage(ValidatedDC):
|
||||||
time: int
|
time: int
|
||||||
note: int
|
note: int
|
||||||
id: int
|
id: int
|
||||||
type: Literal["note_off"] = "note_off"
|
type: Literal["note_off"] = "note_off"
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class PauseMessage(ValidatedDC):
|
class PauseMessage(ValidatedDC):
|
||||||
paused: bool
|
paused: bool
|
||||||
time: int
|
time: int
|
||||||
type: Literal["pause"] = "pause"
|
type: Literal["pause"] = "pause"
|
||||||
|
|
||||||
|
|
||||||
def send(o):
|
|
||||||
print(json.dumps(o), flush=True)
|
|
||||||
|
|
||||||
|
|
||||||
class Scorometer:
|
|
||||||
def __init__(self, mode, midiFile) -> None:
|
|
||||||
self.partition = self.getPartition(midiFile)
|
|
||||||
self.keys_down = []
|
|
||||||
self.score = 0
|
|
||||||
self.mode = mode
|
|
||||||
if mode == PRACTICE:
|
|
||||||
get_start = operator.attrgetter("start")
|
|
||||||
self.practice_partition = [
|
|
||||||
list(g)
|
|
||||||
for _, g in itertools.groupby(
|
|
||||||
sorted(self.partition.notes, key=get_start), get_start
|
|
||||||
)
|
|
||||||
]
|
|
||||||
else:
|
|
||||||
self.practice_partition: list[list[Key]] = []
|
|
||||||
|
|
||||||
def getPartition(self, midiFile):
|
|
||||||
notes = []
|
|
||||||
s = 3500
|
|
||||||
notes_on = {}
|
|
||||||
prev_note_on = {}
|
|
||||||
for msg in MidiFile(midiFile):
|
|
||||||
d = msg.dict()
|
|
||||||
s += d["time"] * 1000 * RATIO
|
|
||||||
|
|
||||||
if d["type"] == "note_on":
|
|
||||||
prev_note_on[d["note"]] = 0
|
|
||||||
if d["note"] in notes_on:
|
|
||||||
prev_note_on[d["note"]] = notes_on[d["note"]] # 500
|
|
||||||
notes_on[d["note"]] = s # 0
|
|
||||||
|
|
||||||
if d["type"] == "note_off":
|
|
||||||
duration = s - notes_on[d["note"]]
|
|
||||||
note_start = notes_on[d["note"]]
|
|
||||||
notes.append(Key(d["note"], note_start, duration - 10))
|
|
||||||
notes_on[d["note"]] = s # 500
|
|
||||||
return Partition(midiFile, notes)
|
|
||||||
|
|
||||||
def handleNote(self, obj):
|
|
||||||
_key = obj["note"]
|
|
||||||
status = obj["type"]
|
|
||||||
timestamp = obj["time"]
|
|
||||||
is_down = any(x[0] == _key for x in self.keys_down)
|
|
||||||
key = None
|
|
||||||
if status == "note_on" and not is_down:
|
|
||||||
self.keys_down.append((_key, timestamp))
|
|
||||||
debug({"note": _key})
|
|
||||||
elif status == "note_off" or is_down:
|
|
||||||
down_since = next(
|
|
||||||
since for (h_key, since) in self.keys_down if h_key == _key
|
|
||||||
)
|
|
||||||
self.keys_down.remove((_key, down_since))
|
|
||||||
key = Key(_key, down_since, (timestamp - down_since))
|
|
||||||
# debug({key: key})
|
|
||||||
if key is None:
|
|
||||||
return
|
|
||||||
to_play = next(
|
|
||||||
(
|
|
||||||
i
|
|
||||||
for i in self.partition.notes
|
|
||||||
if i.key == key.key and self.is_timing_close(key, i) and i.done is False
|
|
||||||
),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
if to_play is None:
|
|
||||||
self.score -= 50
|
|
||||||
debug("Invalid key.")
|
|
||||||
else:
|
|
||||||
timingScore, timingInformation = self.getTiming(key, to_play)
|
|
||||||
self.score += (
|
|
||||||
100
|
|
||||||
if timingScore == "perfect"
|
|
||||||
else 75
|
|
||||||
if timingScore == "great"
|
|
||||||
else 50
|
|
||||||
)
|
|
||||||
to_play.done = True
|
|
||||||
self.sendScore(obj["id"], timingScore, timingInformation)
|
|
||||||
|
|
||||||
def handleNotePractice(self, obj):
|
|
||||||
_key = obj["note"]
|
|
||||||
status = obj["type"]
|
|
||||||
timestamp = obj["time"]
|
|
||||||
is_down = any(x[0] == _key for x in self.keys_down)
|
|
||||||
key = None
|
|
||||||
if status == "note_on" and not is_down:
|
|
||||||
self.keys_down.append((_key, timestamp))
|
|
||||||
debug({"note": _key})
|
|
||||||
elif status == "note_off" or is_down:
|
|
||||||
down_since = next(
|
|
||||||
since for (h_key, since) in self.keys_down if h_key == _key
|
|
||||||
)
|
|
||||||
self.keys_down.remove((_key, down_since))
|
|
||||||
key = Key(_key, down_since, (timestamp - down_since))
|
|
||||||
# debug({key: key})
|
|
||||||
if key is None:
|
|
||||||
return
|
|
||||||
keys_to_play = next(
|
|
||||||
(i for i in self.practice_partition if any(x.done != True for x in i)), None
|
|
||||||
)
|
|
||||||
if keys_to_play is None:
|
|
||||||
warn("Key sent but there is no keys to play")
|
|
||||||
self.score -= 50
|
|
||||||
return
|
|
||||||
to_play = next(
|
|
||||||
(i for i in keys_to_play if i.key == key.key and i.done != True), None
|
|
||||||
)
|
|
||||||
if to_play == None:
|
|
||||||
self.score -= 50
|
|
||||||
debug(f"Invalid key.")
|
|
||||||
else:
|
|
||||||
timingScore, _ = self.getTiming(key, to_play)
|
|
||||||
self.score += (
|
|
||||||
100
|
|
||||||
if timingScore == "perfect"
|
|
||||||
else 75
|
|
||||||
if timingScore == "great"
|
|
||||||
else 50
|
|
||||||
)
|
|
||||||
to_play.done = True
|
|
||||||
self.sendScore(obj["id"], timingScore, "practice")
|
|
||||||
|
|
||||||
def getTiming(self, key: Key, to_play: Key):
|
|
||||||
return self.getTimingScore(key, to_play), self.getTimingInfo(key, to_play)
|
|
||||||
|
|
||||||
def getTimingScore(self, key: Key, to_play: Key):
|
|
||||||
tempo_percent = abs((key.duration / to_play.duration) - 1)
|
|
||||||
if tempo_percent < 0.3:
|
|
||||||
timingScore = "perfect"
|
|
||||||
elif tempo_percent < 0.5:
|
|
||||||
timingScore = f"great"
|
|
||||||
else:
|
|
||||||
timingScore = "good"
|
|
||||||
return timingScore
|
|
||||||
|
|
||||||
def getTimingInfo(self, key: Key, to_play: Key):
|
|
||||||
return (
|
|
||||||
"perfect"
|
|
||||||
if abs(key.start - to_play.start) < 200
|
|
||||||
else "fast"
|
|
||||||
if key.start < to_play.start
|
|
||||||
else "late"
|
|
||||||
)
|
|
||||||
|
|
||||||
# is it in the 500 ms range
|
|
||||||
def is_timing_close(self, key: Key, i: Key):
|
|
||||||
return abs(i.start - key.start) < 500
|
|
||||||
|
|
||||||
def handleMessage(self, message: str):
|
|
||||||
obj = json.loads(message)
|
|
||||||
if "type" not in obj.keys():
|
|
||||||
warn(f"Could not handle message {message}")
|
|
||||||
return
|
|
||||||
if obj["type"] == "note_on" or obj["type"] == "note_off":
|
|
||||||
if self.mode == NORMAL:
|
|
||||||
self.handleNote(obj)
|
|
||||||
elif self.mode == PRACTICE:
|
|
||||||
self.handleNotePractice(obj)
|
|
||||||
if obj["type"] == "pause":
|
|
||||||
pass
|
|
||||||
|
|
||||||
def sendScore(self, id, timingScore, timingInformation):
|
|
||||||
send(
|
|
||||||
{
|
|
||||||
"id": id,
|
|
||||||
"timingScore": timingScore,
|
|
||||||
"timingInformation": timingInformation,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
def gameLoop(self):
|
|
||||||
while True:
|
|
||||||
if select.select(
|
|
||||||
[
|
|
||||||
sys.stdin,
|
|
||||||
],
|
|
||||||
[],
|
|
||||||
[],
|
|
||||||
0.0,
|
|
||||||
)[0]:
|
|
||||||
line = input()
|
|
||||||
if not line:
|
|
||||||
break
|
|
||||||
info(f"handling message {line}")
|
|
||||||
self.handleMessage(line.rstrip())
|
|
||||||
else:
|
|
||||||
pass
|
|
||||||
for i in self.partition.notes:
|
|
||||||
if i.done == False:
|
|
||||||
self.score -= 50
|
|
||||||
return self.score, {}
|
|
||||||
|
|
||||||
|
|
||||||
def handleStartMessage(start_message):
|
|
||||||
for key in ["type", "id", "mode", "bearer"]:
|
|
||||||
if key not in start_message.keys():
|
|
||||||
raise Exception(f"{key} is not specified in start message")
|
|
||||||
if start_message["type"] != "start":
|
|
||||||
raise Exception("start message is not of type start")
|
|
||||||
mode = PRACTICE if start_message["mode"] == "practice" else NORMAL
|
|
||||||
song_id = start_message["id"]
|
|
||||||
bearer = start_message["bearer"]
|
|
||||||
try:
|
|
||||||
r = requests.get(f"{BACK_URL}/auth/me")
|
|
||||||
r.raise_for_status()
|
|
||||||
user_id = r.json()["id"]
|
|
||||||
except Exception:
|
|
||||||
fatal("Could not get user id with given bearer")
|
|
||||||
send({"error": "Could not get user id with given bearer"})
|
|
||||||
exit()
|
|
||||||
|
|
||||||
try:
|
|
||||||
r = requests.get(f"{BACK_URL}/song/{song_id}")
|
|
||||||
r.raise_for_status()
|
|
||||||
song_path = r.json()["midiPath"]
|
|
||||||
song_path = song_path.replace("/musics/", MUSICS_FOLDER)
|
|
||||||
except Exception:
|
|
||||||
fatal("Invalid song id")
|
|
||||||
send({"error": "Invalid song id"})
|
|
||||||
exit()
|
|
||||||
return mode, song_path, song_id, user_id
|
|
||||||
|
|
||||||
|
|
||||||
def sendScore(score, difficulties, song_id, user_id):
|
|
||||||
send({"overallScore": score, "score": difficulties})
|
|
||||||
requests.post(
|
|
||||||
f"{BACK_URL}/history",
|
|
||||||
json={
|
|
||||||
"songID": song_id,
|
|
||||||
"userID": user_id,
|
|
||||||
"score": score,
|
|
||||||
"difficulties": difficulties,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
message_map = {
|
message_map = {
|
||||||
"start": StartMessage,
|
"start": StartMessage,
|
||||||
"end": EndMessage,
|
"end": EndMessage,
|
||||||
"note_on": NoteOnMessage,
|
"note_on": NoteOnMessage,
|
||||||
"note_off": NoteOffMessage,
|
"note_off": NoteOffMessage,
|
||||||
"pause": PauseMessage,
|
"pause": PauseMessage,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def getMessage() -> (
|
def getMessage() -> (
|
||||||
StartMessage
|
Tuple[
|
||||||
| EndMessage
|
StartMessage
|
||||||
| NoteOnMessage
|
| EndMessage
|
||||||
| NoteOffMessage
|
| NoteOnMessage
|
||||||
| PauseMessage
|
| NoteOffMessage
|
||||||
| InvalidMessage
|
| PauseMessage
|
||||||
|
| InvalidMessage,
|
||||||
|
str,
|
||||||
|
]
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
msg = input()
|
msg = input()
|
||||||
obj = json.loads(msg)
|
obj = json.loads(msg)
|
||||||
res = message_map[obj["type"]](**obj)
|
res = message_map[obj["type"]](**obj)
|
||||||
if is_valid(res):
|
if is_valid(res):
|
||||||
return res
|
return res, msg
|
||||||
else:
|
else:
|
||||||
return InvalidMessage(get_errors(res))
|
return InvalidMessage(str(get_errors(res))), msg
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return InvalidMessage(str(e))
|
return InvalidMessage(str(e)), ""
|
||||||
|
|
||||||
|
|
||||||
|
def send(o):
|
||||||
|
print(json.dumps(o), flush=True)
|
||||||
|
|
||||||
|
|
||||||
|
class Scorometer:
|
||||||
|
def __init__(self, mode, midiFile, song_id, user_id) -> None:
|
||||||
|
self.partition = self.getPartition(midiFile)
|
||||||
|
self.keys_down = []
|
||||||
|
self.mode = mode
|
||||||
|
self.song_id = song_id
|
||||||
|
self.user_id = user_id
|
||||||
|
self.score = 0
|
||||||
|
self.missed = 0
|
||||||
|
self.perfect = 0
|
||||||
|
self.great = 0
|
||||||
|
self.good = 0
|
||||||
|
self.difficulties = {}
|
||||||
|
if mode == PRACTICE:
|
||||||
|
get_start = operator.attrgetter("start")
|
||||||
|
self.practice_partition = [
|
||||||
|
list(g)
|
||||||
|
for _, g in itertools.groupby(
|
||||||
|
sorted(self.partition.notes, key=get_start), get_start
|
||||||
|
)
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
self.practice_partition: list[list[Key]] = []
|
||||||
|
|
||||||
|
def getPartition(self, midiFile):
|
||||||
|
notes = []
|
||||||
|
s = 3500
|
||||||
|
notes_on = {}
|
||||||
|
prev_note_on = {}
|
||||||
|
for msg in MidiFile(midiFile):
|
||||||
|
d = msg.dict()
|
||||||
|
s += d["time"] * 1000 * RATIO
|
||||||
|
|
||||||
|
if d["type"] == "note_on":
|
||||||
|
prev_note_on[d["note"]] = 0
|
||||||
|
if d["note"] in notes_on:
|
||||||
|
prev_note_on[d["note"]] = notes_on[d["note"]] # 500
|
||||||
|
notes_on[d["note"]] = s # 0
|
||||||
|
|
||||||
|
if d["type"] == "note_off":
|
||||||
|
duration = s - notes_on[d["note"]]
|
||||||
|
note_start = notes_on[d["note"]]
|
||||||
|
notes.append(Key(d["note"], note_start, duration - 10))
|
||||||
|
notes_on[d["note"]] = s # 500
|
||||||
|
return Partition(midiFile, notes)
|
||||||
|
|
||||||
|
def handleNoteOn(self, message: NoteOnMessage):
|
||||||
|
_key = message.note
|
||||||
|
timestamp = message.time
|
||||||
|
is_down = any(x[0] == _key for x in self.keys_down)
|
||||||
|
if not is_down:
|
||||||
|
self.keys_down.append((_key, timestamp))
|
||||||
|
logging.debug({"note": _key})
|
||||||
|
|
||||||
|
def handleNoteOff(self, message: NoteOffMessage):
|
||||||
|
_key = message.note
|
||||||
|
timestamp = message.time
|
||||||
|
down_since = next(since for (h_key, since) in self.keys_down if h_key == _key)
|
||||||
|
self.keys_down.remove((_key, down_since))
|
||||||
|
key = Key(_key, down_since, (timestamp - down_since))
|
||||||
|
# debug({key: key})
|
||||||
|
to_play = next(
|
||||||
|
(
|
||||||
|
i
|
||||||
|
for i in self.partition.notes
|
||||||
|
if i.key == key.key and self.is_timing_close(key, i) and i.done is False
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
if to_play is None:
|
||||||
|
self.score -= 50
|
||||||
|
logging.debug("Invalid key.")
|
||||||
|
else:
|
||||||
|
timingScore, timingInformation = self.getTiming(key, to_play)
|
||||||
|
self.score += (
|
||||||
|
100
|
||||||
|
if timingScore == "perfect"
|
||||||
|
else 75
|
||||||
|
if timingScore == "great"
|
||||||
|
else 50
|
||||||
|
)
|
||||||
|
to_play.done = True
|
||||||
|
self.sendScore(message.id, timingScore, timingInformation)
|
||||||
|
|
||||||
|
def handleNoteOnPractice(self, message: NoteOnMessage):
|
||||||
|
_key = message.note
|
||||||
|
timestamp = message.time
|
||||||
|
is_down = any(x[0] == _key for x in self.keys_down)
|
||||||
|
if not is_down:
|
||||||
|
self.keys_down.append((_key, timestamp))
|
||||||
|
logging.debug({"note": _key})
|
||||||
|
|
||||||
|
def handleNoteOffPractice(self, message: NoteOffMessage):
|
||||||
|
_key = message.note
|
||||||
|
timestamp = message.time
|
||||||
|
# is_down = any(x[0] == _key for x in self.keys_down)
|
||||||
|
down_since = next(since for (h_key, since) in self.keys_down if h_key == _key)
|
||||||
|
self.keys_down.remove((_key, down_since))
|
||||||
|
key = Key(_key, down_since, (timestamp - down_since))
|
||||||
|
keys_to_play = next(
|
||||||
|
(i for i in self.practice_partition if any(x.done is not True for x in i)),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
if keys_to_play is None:
|
||||||
|
logging.warning("Key sent but there is no keys to play")
|
||||||
|
self.score -= 50
|
||||||
|
return
|
||||||
|
to_play = next(
|
||||||
|
(i for i in keys_to_play if i.key == key.key and i.done is not True), None
|
||||||
|
)
|
||||||
|
if to_play is None:
|
||||||
|
self.score -= 50
|
||||||
|
logging.debug("Invalid key.")
|
||||||
|
else:
|
||||||
|
timingScore, _ = self.getTiming(key, to_play)
|
||||||
|
self.score += (
|
||||||
|
100
|
||||||
|
if timingScore == "perfect"
|
||||||
|
else 75
|
||||||
|
if timingScore == "great"
|
||||||
|
else 50
|
||||||
|
)
|
||||||
|
to_play.done = True
|
||||||
|
self.sendScore(message.id, timingScore, "practice")
|
||||||
|
|
||||||
|
def getTiming(self, key: Key, to_play: Key):
|
||||||
|
return self.getTimingScore(key, to_play), self.getTimingInfo(key, to_play)
|
||||||
|
|
||||||
|
def getTimingScore(self, key: Key, to_play: Key):
|
||||||
|
tempo_percent = abs((key.duration / to_play.duration) - 1)
|
||||||
|
if tempo_percent < 0.3:
|
||||||
|
timingScore = "perfect"
|
||||||
|
elif tempo_percent < 0.5:
|
||||||
|
timingScore = "great"
|
||||||
|
else:
|
||||||
|
timingScore = "good"
|
||||||
|
return timingScore
|
||||||
|
|
||||||
|
def getTimingInfo(self, key: Key, to_play: Key):
|
||||||
|
return (
|
||||||
|
"perfect"
|
||||||
|
if abs(key.start - to_play.start) < 200
|
||||||
|
else "fast"
|
||||||
|
if key.start < to_play.start
|
||||||
|
else "late"
|
||||||
|
)
|
||||||
|
|
||||||
|
# is it in the 500 ms range
|
||||||
|
def is_timing_close(self, key: Key, i: Key):
|
||||||
|
return abs(i.start - key.start) < 500
|
||||||
|
|
||||||
|
def handleMessage(
|
||||||
|
self,
|
||||||
|
message: StartMessage
|
||||||
|
| EndMessage
|
||||||
|
| NoteOnMessage
|
||||||
|
| NoteOffMessage
|
||||||
|
| PauseMessage
|
||||||
|
| InvalidMessage,
|
||||||
|
line: str,
|
||||||
|
):
|
||||||
|
match message:
|
||||||
|
case InvalidMessage(error):
|
||||||
|
logging.warning(f"Invalid message {line} with error: {error}")
|
||||||
|
send({"error": "Invalid message sent"})
|
||||||
|
case NoteOnMessage():
|
||||||
|
if self.mode == NORMAL:
|
||||||
|
self.handleNoteOn(message)
|
||||||
|
elif self.mode == PRACTICE:
|
||||||
|
self.handleNoteOnPractice(message)
|
||||||
|
case NoteOffMessage():
|
||||||
|
if self.mode == NORMAL:
|
||||||
|
self.handleNoteOff(message)
|
||||||
|
elif self.mode == PRACTICE:
|
||||||
|
self.handleNoteOffPractice(message)
|
||||||
|
case PauseMessage():
|
||||||
|
pass
|
||||||
|
case EndMessage():
|
||||||
|
self.endGame()
|
||||||
|
case _:
|
||||||
|
logging.warning(
|
||||||
|
f"Expected note_on note_off or pause message but got {message.type} instead"
|
||||||
|
)
|
||||||
|
|
||||||
|
def sendScore(self, id, timingScore, timingInformation):
|
||||||
|
send(
|
||||||
|
{
|
||||||
|
"id": id,
|
||||||
|
"timingScore": timingScore,
|
||||||
|
"timingInformation": timingInformation,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def gameLoop(self):
|
||||||
|
while True:
|
||||||
|
if select.select(
|
||||||
|
[
|
||||||
|
sys.stdin,
|
||||||
|
],
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
0.0,
|
||||||
|
)[0]:
|
||||||
|
message, line = getMessage()
|
||||||
|
logging.info(f"handling message {line}")
|
||||||
|
self.handleMessage(message, line)
|
||||||
|
else:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def endGame(self):
|
||||||
|
for i in self.partition.notes:
|
||||||
|
if i.done is False:
|
||||||
|
self.score -= 50
|
||||||
|
send(
|
||||||
|
{
|
||||||
|
"overallScore": self.score,
|
||||||
|
"score": {
|
||||||
|
"missed": self.missed,
|
||||||
|
"good": self.good,
|
||||||
|
"great": self.great,
|
||||||
|
"perfect": self.perfect,
|
||||||
|
"maxScore": len(self.partition.notes) * 100,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if self.user_id != -1:
|
||||||
|
requests.post(
|
||||||
|
f"{BACK_URL}/history",
|
||||||
|
json={
|
||||||
|
"songID": self.song_id,
|
||||||
|
"userID": self.user_id,
|
||||||
|
"score": self.score,
|
||||||
|
"difficulties": self.difficulties,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
exit()
|
||||||
|
|
||||||
|
|
||||||
|
def handleStartMessage(start_message: StartMessage):
|
||||||
|
mode = PRACTICE if start_message.mode == "practice" else NORMAL
|
||||||
|
song_id = start_message.id
|
||||||
|
user_id = -1
|
||||||
|
try:
|
||||||
|
if start_message.bearer != "":
|
||||||
|
r = requests.get(
|
||||||
|
f"{BACK_URL}/auth/me",
|
||||||
|
headers={"Authorization": f"Bearer {start_message.bearer}"},
|
||||||
|
)
|
||||||
|
r.raise_for_status()
|
||||||
|
user_id = r.json()["id"]
|
||||||
|
except Exception as e:
|
||||||
|
logging.fatal("Could not get user id with given bearer", exc_info=e)
|
||||||
|
send({"error": "Could not get user id with given bearer"})
|
||||||
|
exit()
|
||||||
|
|
||||||
|
try:
|
||||||
|
r = requests.get(f"{BACK_URL}/song/{song_id}")
|
||||||
|
r.raise_for_status()
|
||||||
|
song_path = r.json()["midiPath"]
|
||||||
|
song_path = song_path.replace("/musics/", MUSICS_FOLDER)
|
||||||
|
except Exception as e:
|
||||||
|
logging.fatal("Invalid song id", exc_info=e)
|
||||||
|
send({"error": "Invalid song id, song does not exist"})
|
||||||
|
exit()
|
||||||
|
return mode, song_path, song_id, user_id
|
||||||
|
|
||||||
|
|
||||||
|
def startGame(start_message: StartMessage):
|
||||||
|
mode, song_path, song_id, user_id = handleStartMessage(start_message)
|
||||||
|
sc = Scorometer(mode, song_path, song_id, user_id)
|
||||||
|
sc.gameLoop()
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
try:
|
try:
|
||||||
while True:
|
msg, _ = getMessage()
|
||||||
msg = getMessage()
|
match msg:
|
||||||
match msg:
|
case StartMessage():
|
||||||
case StartMessage(mode, song_id, bearer):
|
startGame(msg)
|
||||||
print("start", song_id, mode, bearer)
|
case EndMessage():
|
||||||
case EndMessage():
|
logging.info("scorometer ended before a start message")
|
||||||
print("end")
|
send({"error": "Did not receive a start message"})
|
||||||
case NoteOnMessage(id, note, time):
|
exit()
|
||||||
print("note_on", id, note, time)
|
case InvalidMessage(error):
|
||||||
case NoteOffMessage():
|
logging.warning(f"invalid message with error: {error}")
|
||||||
print("note_off")
|
send({"error": "Invalid input, expected a start message"})
|
||||||
case PauseMessage():
|
case _:
|
||||||
print("pause")
|
logging.warning(f"invalid message with type: {msg.type}")
|
||||||
case InvalidMessage(error):
|
send({"error": "Invalid input, expected a start message"})
|
||||||
print(error)
|
except Exception as e:
|
||||||
exit()
|
logging.fatal("error", exc_info=e)
|
||||||
mode, song_path, song_id, user_id = handleStartMessage(start_message)
|
send({"error": "a fatal error occured"})
|
||||||
sc = Scorometer(mode, song_path)
|
|
||||||
score, difficulties = sc.gameLoop()
|
|
||||||
sendScore(score, difficulties, song_id, user_id)
|
|
||||||
except Exception as e:
|
|
||||||
logging.fatal("error", exc_info=e)
|
|
||||||
send({"error": "a fatal error occured"})
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
{"type":"start", "id": 1, "mode": "normal", "user_id": 1}
|
{"type":"start", "id": 1, "mode": "normal", "bearer": ""}
|
||||||
{"type": "note_on", "id": 2, "time": 3750, "note": 67}
|
{"type": "note_on", "id": 2, "time": 3750, "note": 67}
|
||||||
{"type": "note_off", "id": 2, "time": 3980, "note": 67}
|
{"type": "note_off", "id": 2, "time": 3980, "note": 67}
|
||||||
{"type": "note_on", "id": 3, "time": 4000, "note": 62}
|
{"type": "note_on", "id": 3, "time": 4000, "note": 62}
|
||||||
@@ -17,5 +17,5 @@
|
|||||||
{"type": "note_off", "id": 9, "time": 6990, "note": 62}
|
{"type": "note_off", "id": 9, "time": 6990, "note": 62}
|
||||||
{"type": "note_on", "id": 10, "time": 6750, "note": 60}
|
{"type": "note_on", "id": 10, "time": 6750, "note": 60}
|
||||||
{"type": "note_off", "id": 10, "time": 7240, "note": 60}
|
{"type": "note_off", "id": 10, "time": 7240, "note": 60}
|
||||||
{}
|
{"type": "end"}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
{"type":"start", "id": 1, "mode": "normal", "user_id": 1}
|
{"type":"start", "id": 1, "mode": "normal", "bearer": ""}
|
||||||
{"type": "note_on", "id": 1, "time": 3250, "note": 68}
|
{"type": "note_on", "id": 1, "time": 3250, "note": 68}
|
||||||
{"type": "note_off", "id": 1, "time": 3490, "note": 68}
|
{"type": "note_off", "id": 1, "time": 3490, "note": 68}
|
||||||
{"type": "note_on", "id": 2, "time": 3500, "note": 67}
|
{"type": "note_on", "id": 2, "time": 3500, "note": 67}
|
||||||
@@ -19,5 +19,5 @@
|
|||||||
{"type": "note_off", "id": 9, "time": 6740, "note": 62}
|
{"type": "note_off", "id": 9, "time": 6740, "note": 62}
|
||||||
{"type": "note_on", "id": 10, "time": 6500, "note": 60}
|
{"type": "note_on", "id": 10, "time": 6500, "note": 60}
|
||||||
{"type": "note_off", "id": 10, "time": 6990, "note": 60}
|
{"type": "note_off", "id": 10, "time": 6990, "note": 60}
|
||||||
{}
|
{"type": "end"}
|
||||||
|
|
||||||
|
|||||||
@@ -8,4 +8,4 @@
|
|||||||
{"id": 8, "timingScore": "perfect", "timingInformation": "fast"}
|
{"id": 8, "timingScore": "perfect", "timingInformation": "fast"}
|
||||||
{"id": 9, "timingScore": "perfect", "timingInformation": "fast"}
|
{"id": 9, "timingScore": "perfect", "timingInformation": "fast"}
|
||||||
{"id": 10, "timingScore": "perfect", "timingInformation": "fast"}
|
{"id": 10, "timingScore": "perfect", "timingInformation": "fast"}
|
||||||
{"overallScore": 1000, "score": {}}
|
{"overallScore": 1000, "score": {"missed": 0, "good": 0, "great": 0, "perfect": 0, "maxScore": 1000}}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
{"type":"start", "id": 1, "mode": "normal", "user_id": 1}
|
{"type":"start", "id": 1, "mode": "normal", "bearer": ""}
|
||||||
{"type": "note_on", "id": 1, "time": 3500, "note": 68}
|
{"type": "note_on", "id": 1, "time": 3500, "note": 68}
|
||||||
{"type": "note_off", "id": 1, "time": 3740, "note": 68}
|
{"type": "note_off", "id": 1, "time": 3740, "note": 68}
|
||||||
{"type": "note_on", "id": 2, "time": 3750, "note": 67}
|
{"type": "note_on", "id": 2, "time": 3750, "note": 67}
|
||||||
@@ -15,5 +15,5 @@
|
|||||||
{"type": "note_off", "id": 7, "time": 4980, "note": 63}
|
{"type": "note_off", "id": 7, "time": 4980, "note": 63}
|
||||||
{"type": "note_on", "id": 8, "time": 5000, "note": 63}
|
{"type": "note_on", "id": 8, "time": 5000, "note": 63}
|
||||||
{"type": "note_off", "id": 8, "time": 5990, "note": 63}
|
{"type": "note_off", "id": 8, "time": 5990, "note": 63}
|
||||||
{}
|
{"type": "end"}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
{"type":"start", "id": 1, "mode": "normal", "user_id": 1}
|
{"type":"start", "id": 1, "mode": "normal", "bearer": ""}
|
||||||
{"type": "note_on", "id": 1, "time": 3500, "note": 68}
|
{"type": "note_on", "id": 1, "time": 3500, "note": 68}
|
||||||
{"type": "note_off", "id": 1, "time": 3540, "note": 68}
|
{"type": "note_off", "id": 1, "time": 3540, "note": 68}
|
||||||
{"type": "note_on", "id": 2, "time": 3750, "note": 67}
|
{"type": "note_on", "id": 2, "time": 3750, "note": 67}
|
||||||
@@ -19,5 +19,5 @@
|
|||||||
{"type": "note_off", "id": 9, "time": 6690, "note": 62}
|
{"type": "note_off", "id": 9, "time": 6690, "note": 62}
|
||||||
{"type": "note_on", "id": 10, "time": 6750, "note": 60}
|
{"type": "note_on", "id": 10, "time": 6750, "note": 60}
|
||||||
{"type": "note_off", "id": 10, "time": 6840, "note": 60}
|
{"type": "note_off", "id": 10, "time": 6840, "note": 60}
|
||||||
{}
|
{"type": "end"}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
{"type":"start", "id": 1, "mode": "normal", "user_id": 1}
|
{"type":"start", "id": 1, "mode": "normal", "bearer": ""}
|
||||||
{"type": "note_on", "id": 1, "time": 3500, "note": 68}
|
{"type": "note_on", "id": 1, "time": 3500, "note": 68}
|
||||||
{"type": "note_off", "id": 1, "time": 3990, "note": 68}
|
{"type": "note_off", "id": 1, "time": 3990, "note": 68}
|
||||||
{"type": "note_on", "id": 2, "time": 3750, "note": 67}
|
{"type": "note_on", "id": 2, "time": 3750, "note": 67}
|
||||||
@@ -19,5 +19,5 @@
|
|||||||
{"type": "note_off", "id": 9, "time": 7240, "note": 62}
|
{"type": "note_off", "id": 9, "time": 7240, "note": 62}
|
||||||
{"type": "note_on", "id": 10, "time": 6750, "note": 60}
|
{"type": "note_on", "id": 10, "time": 6750, "note": 60}
|
||||||
{"type": "note_off", "id": 10, "time": 7490, "note": 60}
|
{"type": "note_off", "id": 10, "time": 7490, "note": 60}
|
||||||
{}
|
{"type": "end"}
|
||||||
|
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
{"type":"start", "id": 0, "mode": "normal", "user_id": 1}
|
{"type":"start", "id": 0, "mode": "normal", "bearer": ""}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
{"type":"start", "id": 1, "mode": "normal", "user_id": 1}
|
{"type":"start", "id": 1, "mode": "normal", "bearer": ""}
|
||||||
{"type": "note_on", "id": 1, "time": 3750, "note": 68}
|
{"type": "note_on", "id": 1, "time": 3750, "note": 68}
|
||||||
{"type": "note_off", "id": 1, "time": 3990, "note": 68}
|
{"type": "note_off", "id": 1, "time": 3990, "note": 68}
|
||||||
{"type": "note_on", "id": 2, "time": 4000, "note": 67}
|
{"type": "note_on", "id": 2, "time": 4000, "note": 67}
|
||||||
@@ -19,5 +19,5 @@
|
|||||||
{"type": "note_off", "id": 9, "time": 7240, "note": 62}
|
{"type": "note_off", "id": 9, "time": 7240, "note": 62}
|
||||||
{"type": "note_on", "id": 10, "time": 7000, "note": 60}
|
{"type": "note_on", "id": 10, "time": 7000, "note": 60}
|
||||||
{"type": "note_off", "id": 10, "time": 7490, "note": 60}
|
{"type": "note_off", "id": 10, "time": 7490, "note": 60}
|
||||||
{}
|
{"type": "end"}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
{"type":"start", "id": 1, "mode": "normal", "user_id": 1}
|
{"type":"start", "id": 1, "mode": "normal", "bearer": ""}
|
||||||
{"type": "note_on", "id": 1, "time": 3500, "note": 68}
|
{"type": "note_on", "id": 1, "time": 3500, "note": 68}
|
||||||
{"type": "note_off", "id": 1, "time": 3740, "note": 68}
|
{"type": "note_off", "id": 1, "time": 3740, "note": 68}
|
||||||
{"type": "note_on", "id": 2, "time": 3750, "note": 67}
|
{"type": "note_on", "id": 2, "time": 3750, "note": 67}
|
||||||
@@ -19,5 +19,5 @@
|
|||||||
{"type": "note_off", "id": 9, "time": 6990, "note": 62}
|
{"type": "note_off", "id": 9, "time": 6990, "note": 62}
|
||||||
{"type": "note_on", "id": 10, "time": 6750, "note": 60}
|
{"type": "note_on", "id": 10, "time": 6750, "note": 60}
|
||||||
{"type": "note_off", "id": 10, "time": 7240, "note": 60}
|
{"type": "note_off", "id": 10, "time": 7240, "note": 60}
|
||||||
{}
|
{"type": "end"}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
{"type":"start", "id": 1, "mode": "normal", "user_id": 1}
|
{"type":"start", "id": 1, "mode": "normal", "bearer": ""}
|
||||||
{"type": "note_on", "id": 1, "time": 3500, "note": 68}
|
{"type": "note_on", "id": 1, "time": 3500, "note": 68}
|
||||||
{"type": "note_off", "id": 1, "time": 3740, "note": 68}
|
{"type": "note_off", "id": 1, "time": 3740, "note": 68}
|
||||||
{"type": "note_on", "id": 3, "time": 4000, "note": 62}
|
{"type": "note_on", "id": 3, "time": 4000, "note": 62}
|
||||||
@@ -15,5 +15,5 @@
|
|||||||
{"type": "note_off", "id": 9, "time": 6990, "note": 62}
|
{"type": "note_off", "id": 9, "time": 6990, "note": 62}
|
||||||
{"type": "note_on", "id": 10, "time": 6750, "note": 60}
|
{"type": "note_on", "id": 10, "time": 6750, "note": 60}
|
||||||
{"type": "note_off", "id": 10, "time": 7240, "note": 60}
|
{"type": "note_off", "id": 10, "time": 7240, "note": 60}
|
||||||
{}
|
{"type": "end"}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ TESTS_SUCCESS=0
|
|||||||
TESTS_FAILED=0
|
TESTS_FAILED=0
|
||||||
|
|
||||||
function test {
|
function test {
|
||||||
cat $1/input | BACK_URL="http://localhost:3000" MUSICS_FOLDER="../../musics/" python3 ../main.py 1> /tmp/scorometer_res 2> /dev/null
|
cat $1/input | BACK_URL="http://localhost:3000" MUSICS_FOLDER="../../musics/" python3 ../main.py 1> /tmp/scorometer_res 2> /tmp/scorometer_log
|
||||||
TESTS_DONE=$((TESTS_DONE + 1))
|
TESTS_DONE=$((TESTS_DONE + 1))
|
||||||
if ! diff $1/output /tmp/scorometer_res &>/dev/null; then
|
if ! diff $1/output /tmp/scorometer_res &>/dev/null; then
|
||||||
echo "$t failed, do runner.sh $t for more info"
|
echo "$t failed, do runner.sh $t for more info"
|
||||||
@@ -27,7 +27,7 @@ then
|
|||||||
done
|
done
|
||||||
exit $TESTS_FAILED
|
exit $TESTS_FAILED
|
||||||
else
|
else
|
||||||
cat $1/input | BACK_URL="http://localhost:3000" MUSICS_FOLDER="../../musics/" python3 ../main.py 1> /tmp/scorometer_res 2> /dev/null
|
cat $1/input | BACK_URL="http://localhost:3000" MUSICS_FOLDER="../../musics/" python3 ../main.py 1> /tmp/scorometer_res 2> /tmp/scorometer_log
|
||||||
echo "=========== CURRENT OUTPUT ==========="
|
echo "=========== CURRENT OUTPUT ==========="
|
||||||
cat /tmp/scorometer_res
|
cat /tmp/scorometer_res
|
||||||
echo "======================================"
|
echo "======================================"
|
||||||
|
|||||||
Reference in New Issue
Block a user