-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathgame_state.py
131 lines (98 loc) · 3.34 KB
/
game_state.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
import time
from dataclasses import dataclass
from typing import Any, Callable, Literal, Type, TypeAlias, TypedDict
from utils.misc_utils import first_in_dict
StateCategory = Literal["GAME", "PLAYER", "TOWER", "UNIT"]
_STATE_CATEGORIES: list[StateCategory] = ["GAME", "PLAYER", "TOWER", "UNIT"]
class _Entry(TypedDict):
cls: Type[Any]
data: dict[str, Any]
_EntriesById = dict[int, _Entry]
_DataByCategory = dict[StateCategory, _EntriesById]
_OnEvent: TypeAlias = "Callable[[StateEvent], None]"
class GameState:
"""
Basically a 3-layer dict with this shape
{
category_1: {
id_1: {
attr_1: ...,
attr2_: ...,
},
id_2: {
...
},
},
category_2: {
...
}
}
all entries should have an id
all entries for a category may not have the same type
operations on this data are classified as one of...
creation - new entry with unique id is added under a category
update - existing entry has one of its existing values updated
delete - entry is removed from a category
all operations generate an "event" describing this change
this event is pushed into a queue that's eventually flushed
the motivation being that this allows the renderer process to both
maintain a copy of this state and also animate the changes
animations based on a state change usually only involve a single entity (eg unit movement)
animations that involve multiple entities (eg tower attacking unit)
are triggered by more specific events not generated by this class
"""
data: _DataByCategory
events: "list[StateEvent]"
on_event: _OnEvent
def __init__(self, on_event: _OnEvent):
self.data = {cat: dict() for cat in _STATE_CATEGORIES}
self.on_event = on_event
def create(
self,
category: StateCategory,
cls: Type[Any],
data: dict[str, Any],
):
self.data[category][data["id"]] = _Entry(
cls=cls,
data=data,
)
ev = StateCreated(category=category, cls=cls, data=data)
self._log_event(ev)
def update(self, category: StateCategory, id: int, key: str, value: Any):
self.data[category][id]["data"][key] = value
ev = StateUpdated(category=category, id=id, key=key, value=value)
self._log_event(ev)
def delete(self, category: StateCategory, id: int, **kwargs: Any):
data = self.data[category][id]["data"]
del self.data[category][id]
ev = StateDeleted(category=category, id=id, data=data)
self._log_event(ev)
@property
def game(self):
return first_in_dict(self.data["GAME"])["data"]
@property
def until_tick(self) -> float:
return self.game["next_tick"] - time.time()
def _log_event(self, event: "StateEvent"):
self.on_event(event)
@dataclass
class StateCreated:
category: StateCategory
cls: Type[Any]
data: dict[str, Any]
@property
def id(self):
return self.data["id"]
@dataclass
class StateUpdated:
category: StateCategory
id: int
key: str
value: Any
@dataclass
class StateDeleted:
category: StateCategory
id: int
data: Any
StateEvent = StateCreated | StateUpdated | StateDeleted