-
Notifications
You must be signed in to change notification settings - Fork 3
/
manager.py
163 lines (137 loc) · 4.8 KB
/
manager.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
import os
import errno
import itertools
from threading import Event, Thread
try:
from lock import FileLock
from utils import write_to_disk
except ImportError:
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()