From e027b3020140fa4d9a599521d2ab49d0578b85bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Ko=C4=8D=C3=AD?= Date: Tue, 30 Jan 2018 16:36:54 +0100 Subject: Add replay mode --- turtetris_master/__init__.py | 4 +- turtetris_master/led_output.py | 35 ++++++++++++++ turtetris_master/recorder.py | 99 +++++++++++++++++++++++++++++++++++++++ turtetris_master/state_machine.py | 61 ++++++++++++++++++++---- 4 files changed, 190 insertions(+), 9 deletions(-) create mode 100644 turtetris_master/recorder.py diff --git a/turtetris_master/__init__.py b/turtetris_master/__init__.py index b389525..d6d8bdc 100644 --- a/turtetris_master/__init__.py +++ b/turtetris_master/__init__.py @@ -1,3 +1,4 @@ +import sys import time from .led_output import Matrix from .usb_input import Gamepad @@ -12,11 +13,12 @@ def main(): while True: tstart = time.time() sm.tick() - trest = (1/60) - (time.time() - tstart) + trest = (1/30) - (time.time() - tstart) if trest > 0: time.sleep(trest) else: print("Output took too long!!") + sys.stdout.flush() if __name__ == '__main__': diff --git a/turtetris_master/led_output.py b/turtetris_master/led_output.py index 1b3c435..ecc4d57 100644 --- a/turtetris_master/led_output.py +++ b/turtetris_master/led_output.py @@ -1,5 +1,6 @@ import zmq import json +import copy class Matrix: @@ -35,3 +36,37 @@ class Matrix: for x in range(self.width): for y in range(self.height): self.pixel(x, y, color) + + def set_matrix(self, matrix): + "Set given matrix as matrix" + for x in range(self.width): + for y in range(self.height): + self.pixel(x, y, matrix[y][x]) + + def copy_matrix(self): + "Return copy of current matrix" + return copy.deepcopy(self.__mat__) + + def matrix_diff(self, matrix): + """ + Returns set of changes in matrix + Every item in set is dictionary with following items: + x: X position of led + y: Y position of led + color: string defining color of given led + """ + change = list() + for x in range(self.width): + for y in range(self.height): + if matrix[y][x] != self.__mat__[y][x]: + change.append({ + 'x': x, + 'y': y, + 'color': self.__mat__[y][x] + }) + return change + + def matrix_apply_diff(self, diff): + "Apply diff generated by matrix_diff" + for change in diff: + self.pixel(change['x'], change['y'], change['color']) diff --git a/turtetris_master/recorder.py b/turtetris_master/recorder.py new file mode 100644 index 0000000..9c44dd2 --- /dev/null +++ b/turtetris_master/recorder.py @@ -0,0 +1,99 @@ +import os +import json +import time +import copy +from random import randrange + +STORAGE = "/usr/share/turtetris" +MAX_RECORDS = 100 + + +def recorded_minimum(): + "Returns tupple with minimal recorded scores and its respective file" + if not os.path.isdir(STORAGE): + return False + records = os.listdir(STORAGE) + if len(records) < MAX_RECORDS: + return False + smin = 0 + min_file = None + for record in records: + with open(os.path.join(STORAGE, record)) as file: + score = int(file.readline()) + if not min_file or smin > score: + smin = score + min_file = record + return (smin, min_file) + + +class Recorder: + "Game recorder" + + def __init__(self, matrix): + self._matrix = matrix + self._init_state = matrix.copy_matrix() + self._prev_state = matrix.copy_matrix() + self._changes = list() + self._times = list() + self._start_time = time.time() + + def tick(self): + "Store one tick" + change = self._matrix.matrix_diff(self._prev_state) + if change: + self._changes.append(change) + self._times.append(time.time() - self._start_time) + self._prev_state = self._matrix.copy_matrix() + + def store(self, score): + "Store this recording to permanent storage" + rmin = recorded_minimum() + if rmin and rmin[0] > score: + return # Don't store this one + if not os.path.isdir(STORAGE): + os.makedirs(STORAGE) + data = { + "init": self._init_state, + "changes": self._changes, + "times": self._times + } + with open(os.path.join(STORAGE, str(int(time.time()))), 'w') as file: + file.write(str(score) + '\n') + file.write(json.dumps(data)) + if rmin: # If we have some marked as minimal then remove it + os.remove(os.path.join(STORAGE, rmin[1])) + + +class Replayer: + "Game records loader and replayer" + def __init__(self, matrix): + self._matrix = matrix + self._changes = None + self._times = None + + replayes = os.listdir(STORAGE) + if len(replayes) > 0: + reply = replayes[randrange(len(replayes))] + with open(os.path.join(STORAGE, reply)) as file: + file.readline() + data = json.loads(file.read()) + self._changes = copy.deepcopy(data['changes']) + self._times = copy.deepcopy(data['times']) + self._matrix.set_matrix(data['init']) + self._matrix.display() + + self._start_time = time.time() + self._index = 0 + + def tick(self): + "Run recorded tick" + if not self._changes: + return False + while self._index < len(self._times) and \ + (time.time() - + self._start_time - + self._times[self._index]) >= 0: + self._matrix.matrix_apply_diff(self._changes[self._index]) + self._matrix.display() + self._index += 1 + return self._index < len(self._times) # edit when we have no more diff --git a/turtetris_master/state_machine.py b/turtetris_master/state_machine.py index 7cbfcaa..e0cc6ee 100644 --- a/turtetris_master/state_machine.py +++ b/turtetris_master/state_machine.py @@ -1,5 +1,10 @@ from .screen_checker import ScreenChecker from .game import Game +from .recorder import Recorder +from .recorder import Replayer + +# We run 1/30 loops per second so this gives us one minute delay +SWITCH_TIME_DELAY = 1800 class StateMachine: @@ -10,21 +15,43 @@ class StateMachine: self.state = "initializing" self.matrix = matrix self.input = input - self.__update_state__('screen_checker') + self.game = None + self.recorder = None + self.replayer = None + self.timeout = SWITCH_TIME_DELAY + self.__update_state__('screen-checker') + + def __new_game__(self): + "Initialize new game" + self.game = Game(self.matrix) + self.recorder = Recorder(self.matrix) + + def __reset_delay__(self): + self.timeout = SWITCH_TIME_DELAY + + def __delay__(self): + if self.timeout > 0: + self.timeout -= 1 + return False + else: + self.__reset_delay__() + return True def __update_state__(self, state): "Applies given state" def __exception__(): - raise Exception('Can\'t transfer from ' + self.state + ' to ' - + state) - if state == "screen_checker": + raise Exception('Can\'t transfer from ' + self.state + ' to ' + + state) + if state == "screen-checker": if self.state == "initializing": self.screen_checker = ScreenChecker(self.matrix) else: __exception__() elif state == "game": - if self.state == "screen_checker" or self.state == "game-over": - self.game = Game(self.matrix) + if self.state == "screen-checker" or \ + self.state == "game-over" or \ + self.state == "replay": + self.__new_game__() elif self.state == "game-pause": pass else: @@ -35,6 +62,8 @@ class StateMachine: elif state == "game-over": if self.state != "game": __exception__() + self.__reset_delay__() + self.recorder.store(self.game.score) self.matrix.fill('red') i = self.matrix.height - 2 # Show score @@ -45,6 +74,10 @@ class StateMachine: for s in range(self.game.score % 5): self.matrix.pixel(s + 3, i, 'green') self.matrix.display() + elif state == "replay": + if self.state != "game-over" and self.state != "screen-checker": + __exception__() + self.replayer = Replayer(self.matrix) else: __exception__() self.state = state @@ -52,12 +85,17 @@ class StateMachine: def tick(self): "Do tick" inpt = self.input.check() - if self.state == "screen_checker": + if self.state == "screen-checker": if inpt['start']: self.__update_state__('game') + if inpt['select']: + self.__update_state__('replay') else: self.screen_checker.tick() elif self.state == "game": + # Note: This records previous tick output but that doens't matter + # as loosing latest frame is harmless + self.recorder.tick() if inpt['start']: self.__update_state__('game-pause') elif not self.game.tick(inpt): @@ -66,10 +104,17 @@ class StateMachine: if inpt['start']: self.__update_state__('game') elif inpt['select']: - self.game = Game(self.matrix) + self.__new_game__() self.__update_state__('game') elif self.state == "game-over": if inpt['start']: self.__update_state__('game') + elif self.__delay__(): + self.__update_state__('replay') + elif self.state == "replay": + if inpt['start']: + self.__update_state__('game') + elif not self.replayer.tick(): + self.replayer = Replayer(self.matrix) else: raise Exception('Invalid state ' + self.state) -- cgit v1.2.3