aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKarel Kočí <karel.koci@nic.cz>2018-01-30 16:36:54 +0100
committerKarel Kočí <karel.koci@nic.cz>2018-01-30 16:36:54 +0100
commite027b3020140fa4d9a599521d2ab49d0578b85bd (patch)
tree3e4b080abf5e95baa3e2617213ddd563d2ec470a
parent1dae42389b97b27e6c11d5bb093270e07e6fe05e (diff)
downloadturris-tetris-e027b3020140fa4d9a599521d2ab49d0578b85bd.tar.gz
turris-tetris-e027b3020140fa4d9a599521d2ab49d0578b85bd.tar.bz2
turris-tetris-e027b3020140fa4d9a599521d2ab49d0578b85bd.zip
Add replay mode
-rw-r--r--turtetris_master/__init__.py4
-rw-r--r--turtetris_master/led_output.py35
-rw-r--r--turtetris_master/recorder.py99
-rw-r--r--turtetris_master/state_machine.py61
4 files changed, 190 insertions, 9 deletions
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)