diff --git a/difficulty.py b/difficulty.py index 89f4f4bacd0b7d5abd8083cab86363cac417394e..2de9e2a579d172d58d84ec25e033f27bbb5ec6f8 100644 --- a/difficulty.py +++ b/difficulty.py @@ -24,6 +24,7 @@ class DifficultyView(BaseView): if len(self.song.difficulties) > 2: self._sc.set_position(1) self._scroll_pos = 0 + self.song.readScores() def draw(self, ctx: Context) -> None: @@ -96,15 +97,23 @@ class DifficultyView(BaseView): ctx.move_to (0, -78) ctx.text("DIFFICULTY") - """ - ctx.font_size = 15 - ctx.move_to(0, 78) - ctx.text("Put songs into") - ctx.move_to(0, 94) - ctx.font_size = 15 - ctx.gray(0.75) - ctx.text("/sd/PetalHero") - """ + if self.song.difficulties: + score = self.song.scores[self.song.difficulties[self._sc.target_position()]] + if not score.empty(): + ctx.font_size = 15 + ctx.gray(0.5) + ctx.text_align = ctx.RIGHT + ctx.font = "Material Icons" + ctx.move_to(-7, 86) + ctx.text("\ue885") + ctx.move_to(-7, 102) + ctx.text("\uea0b") + ctx.font = "Camp Font 3" + ctx.text_align = ctx.LEFT + ctx.move_to(-5, 83) + ctx.text(f"{int(score.max_acc.accuracy * 100)}%") + ctx.move_to(-5, 99) + ctx.text(str(score.max_streak.streak)) def think(self, ins: InputState, delta_ms: int) -> None: super().think(ins, delta_ms) diff --git a/score.py b/score.py index 4a308164ae4a0d4246d462bffbcfcd8b50be766e..6c9b76e8a6888f81428a8c9399156164dc46847f 100644 --- a/score.py +++ b/score.py @@ -37,6 +37,9 @@ class ScoreView(BaseView): events = data.track.getAllEvents() self.accuracy = len(set(filter(lambda x: x.played and not x.missed, events))) / (len(events) + badnotes) self.stars = int(5.0 * (self.accuracy + 0.05)) + + self.highscore = self.song.recordScore(difficulty, self.accuracy, streak, badnotes) + self.timeout = 5 if self.highscore else 0 def draw(self, ctx: Context) -> None: #utils.background(ctx) @@ -95,20 +98,34 @@ class ScoreView(BaseView): ctx.text(f"Accuracy: {int(self.accuracy * 100)}%") ctx.move_to(0,58) ctx.text(f"Longest streak: {self.streak}") - + ctx.font = "Camp Font 3" - ctx.font_size = 16 ctx.text_align = ctx.CENTER ctx.text_baseline = ctx.MIDDLE - ctx.gray(0.5) - ctx.move_to(0, 84 - math.sin(self.time * 4) * 2) - ctx.text("Press the button...") + + alpha = min(self.timeout, 0.5) / 0.5 + if alpha > 0: + ctx.global_alpha = alpha + utils.fire_gradient(ctx) + ctx.font_size = 18 + ctx.move_to(0, 85 - math.sin(self.time * 4) * 2) + ctx.text("New highscore!") + + if alpha < 1: + ctx.global_alpha = 1.0 - alpha + ctx.gray(0.5) + ctx.font_size = 16 + ctx.move_to(0, 84 - math.sin(self.time * 4) * 2) + ctx.text("Press the button...") def think(self, ins: InputState, delta_ms: int) -> None: super().think(ins, delta_ms) media.think(delta_ms) utils.blm_timeout(self, delta_ms) self.time += delta_ms / 1000 + self.timeout -= delta_ms / 1000 + if self.timeout < 0: + self.timeout = 0 if self.time > 1.5 and not self.played: self.played = True diff --git a/songinfo.py b/songinfo.py index 1f62758fd2f4fb19a57e8446c267a0b482fc3d8b..e076b6902d7ed099396b05c7b25ee9b017de5593 100644 --- a/songinfo.py +++ b/songinfo.py @@ -1,5 +1,6 @@ import os import sys +import struct from .configparser import ConfigParser @@ -40,6 +41,41 @@ class MidiInfoReader(MidiOutStream): if len(self.difficulties) == len(difficulties): raise MidiInfoReader.Done() +class Score(): + accuracy = None + streak = None + badnotes = None + + def __init__(self, score = None): + if score is not None: + self.accuracy = score.accuracy + self.streak = score.streak + self.badnotes = score.badnotes + + def __eq__(self, score): + return self.accuracy == score.accuracy and self.streak == score.streak and self.badnotes == score.badnotes + + def empty(self): + return self.accuracy is None or self.streak is None or self.badnotes is None + + def __repr__(self): + if self.empty(): + return "Score()" + return f"Score(acc: {self.accuracy}, str: {self.streak}, bn: {self.badnotes})" + +class ScoreSet(): + def __init__(self): + self.max_acc = Score() + self.max_streak = Score() + + def empty(self): + return self.max_acc.empty() and self.max_streak.empty() + + def __repr__(self): + if self.empty(): + return "ScoreSet()" + return f"ScoreSet(max_acc: {str(self.max_acc)}, max_str: {str(self.max_streak)})" + class SongInfo(object): def __init__(self, dirName): infoFileName = dirName + "/song.ini" @@ -48,6 +84,7 @@ class SongInfo(object): self.fileName = infoFileName self.info = ConfigParser() self._difficulties = None + self._scores = None try: self.info.read(infoFileName) @@ -142,6 +179,91 @@ class SongInfo(object): f.write(bytes([diff.id])) except Exception as e: sys.print_exception(e) + + def readScores(self): + self._scores = {} + for diff in difficulties.values(): + self._scores[diff] = ScoreSet() + + scoreFileName = os.path.join(os.path.dirname(self.fileName), ".score.pet") + if not os.path.exists(scoreFileName): + return self._scores + + try: + with open(scoreFileName, "rb") as f: + magic = f.read(7) + assert(magic == b"PHSCORE") + ver = f.read(1) + assert(ver == b"\0") + length = f.read(1)[0] + for i in range(length): + diff = f.read(1)[0] + assert(diff in difficulties) + assert(difficulties[diff] in self._scores) + score = self._scores[difficulties[diff]] + score.max_acc.accuracy, score.max_acc.streak, score.max_acc.badnotes = struct.unpack('!fII', f.read(12)) + score.max_streak.accuracy, score.max_streak.streak, score.max_streak.badnotes = struct.unpack('!fII', f.read(12)) + except Exception as e: + sys.print_exception(e) + + return self._scores + + def saveScores(self): + try: + scoreFileName = os.path.join(os.path.dirname(self.fileName), ".score.pet") + with open(scoreFileName, "wb") as f: + f.write(b"PHSCORE\0") + diffs = tuple(filter(lambda x: not self._scores[x].empty(), self._scores.keys())) + f.write(bytes((len(diffs),))) + for diff in diffs: + score = self._scores[diff] + f.write(bytes((diff.id,))) + f.write(struct.pack("!fII", score.max_acc.accuracy, score.max_acc.streak, score.max_acc.badnotes)) + f.write(struct.pack("!fII", score.max_streak.accuracy, score.max_streak.streak, score.max_streak.badnotes)) + except Exception as e: + sys.print_exception(e) + + def recordScore(self, difficulty, accuracy, streak, badnotes): + if self._scores is None: + self.readScores() + + max_acc = self._scores[difficulty].max_acc + max_streak = self._scores[difficulty].max_streak + + save = False + + if max_acc.empty() or accuracy > max_acc.accuracy: + max_acc.accuracy = accuracy + max_acc.streak = streak + max_acc.badnotes = badnotes + save = True + elif accuracy == max_acc.accuracy: + if streak > max_acc.streak: + max_acc.streak = streak + max_acc.badnotes = badnotes + save = True + elif badnotes < max_acc.badnotes: + max_acc.badnotes = badnotes + save = True + + if max_streak.empty() or streak > max_streak.streak: + max_streak.streak = streak + max_streak.accuracy = accuracy + max_streak.badnotes = badnotes + save = True + elif streak == max_streak.streak: + if accuracy > max_streak.accuracy: + max_streak.accuracy = accuracy + max_streak.badnotes = badnotes + save = True + elif badnotes < max_streak.badnotes: + max_streak.badnotes = badnotes + save = True + + if save: + self.saveScores() + + return save def getName(self): return self._get("name") @@ -167,9 +289,15 @@ class SongInfo(object): if preview is None or preview < 0 or length is None or length <= 0: return 0.1 return preview / length + + def getScores(self): + if self._scores is None: + self.readScores() + return self._scores name = property(getName, setName) artist = property(getArtist, setArtist) delay = property(getDelay, setDelay) difficulties = property(getDifficulties) preview = property(getPreview) + scores = property(getScores)