2048/_2048/manager.py
2017-11-19 02:55:57 -05:00

161 lines
4.7 KiB
Python

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 as 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()
self.created = False
__del__ = close
def dispatch(self, event):
self.game.on_event(event)
def draw(self):
self.game.on_draw()