import os import errno import itertools from threading import Event, Thread from .lock import FileLock from .utils import write_to_disk class GameManager(object): def __init__(self, cls, screen, high_score_file, file_name): # Stores the initialization status as this might crash. self.created = False self.score_name = high_score_file self.screen = screen self.save_name = file_name self.game_class = cls self._score_changed = False self._running = True self._change_event = Event() self._saved_event = Event() try: self.score_fd = self.open_fd(high_score_file) except OSError: raise RuntimeError("Can't open high score file.") self.score_file = os.fdopen(self.score_fd, 'r+') self.score_lock = FileLock(self.score_fd) with self.score_lock: try: self._score = self._load_score() except ValueError: self._score = 0 self._score_changed = True self.save() # Try opening save files from zero and counting up. for i in itertools.count(0): name = file_name % (i,) try: save = self.open_fd(name) except IOError: continue else: self.save_lock = FileLock(save) try: self.save_lock.acquire(False) except IOError: del self.save_lock os.close(save) continue self.save_fd = save self.save_file = os.fdopen(save, 'r+') read = self.save_file.read() if read: self.game = self.game_class.from_save(read, self, screen) else: self.new_game() self.save_file.seek(0, os.SEEK_SET) print 'Running as instance #%d.' % i break self._worker = Thread(target=self._save_daemon) self._worker.start() self._saved_event.set() self.created = True @classmethod def open_fd(cls, name): """Open a file or create it.""" # Try to create it, if can't, try to open. try: return os.open(name, os.O_CREAT | os.O_RDWR | os.O_EXCL) except OSError, e: if e.errno != errno.EEXIST: raise return os.open(name, os.O_RDWR | os.O_EXCL) def new_game(self): """Creates a new game of 2048.""" self.game = self.game_class(self, self.screen) self.save() def _load_score(self): """Load the best score from file.""" score = int(self.score_file.read()) self.score_file.seek(0, os.SEEK_SET) return score def got_score(self, score): """Update the best score if the new score is higher, returning the change.""" if score > self._score: delta = score - self._score self._score = score self._score_changed = True self.save() return delta return 0 @property def score(self): return self._score def save(self): self._saved_event.clear() self._change_event.set() def _save_daemon(self): while self._running: self._change_event.wait() if self._score_changed: with self.score_lock: try: score = self._load_score() self._score = max(score, self._score) except ValueError: pass self.score_file.write(str(self._score)) self.score_file.truncate() self.score_file.seek(0, os.SEEK_SET) write_to_disk(self.score_file) self._score_changed = False if self.game.lost: self.save_file.truncate() else: self.save_file.write(self.game.serialize()) self.save_file.truncate() self.save_file.seek(0, os.SEEK_SET) write_to_disk(self.save_file) self._change_event.clear() self._saved_event.set() def close(self): if self.created: self._running = False self._saved_event.wait() self.save() self._worker.join() self.save_lock.release() self.score_file.close() self.save_file.close() __del__ = close def dispatch(self, event): self.game.on_event(event) def draw(self): self.game.on_draw()