Coverage for backend/models.py: 97%

100 statements  

« prev     ^ index     » next       coverage.py v7.6.12, created at 2025-04-17 17:55 +0000

1from __future__ import annotations 

2 

3from enum import StrEnum, auto 

4from typing import Annotated, Literal 

5 

6from pydantic import BaseModel, Field 

7 

8 

9class Role(StrEnum): 

10 """Player roles.""" 

11 

12 manager = auto() 

13 burger = auto() 

14 drink = auto() 

15 side = auto() 

16 

17 

18class MessageKind(StrEnum): 

19 """Message kinds.""" 

20 

21 initializer = "initializer" 

22 game_state = "game_state" 

23 lobby_lifecycle = "lobby_lifecycle" 

24 chat = "chat" 

25 

26 

27class Initializer(BaseModel): 

28 """ 

29 Initializes player state. 

30 

31 Links the websocket connection to a specific player and lobby. 

32 

33 Attributes: 

34 code: Lobby join code. 

35 id: Player id. 

36 """ 

37 

38 type: Literal[MessageKind.initializer] = MessageKind.initializer 

39 

40 code: tuple[str, ...] 

41 id: str 

42 

43 

44class LobbyJoinRequest(BaseModel): 

45 """ 

46 Request to join a lobby. 

47 

48 Attributes: 

49 code: Lobby join code. 

50 """ 

51 

52 code: tuple[str, ...] 

53 

54 

55class GameStateUpdateKind(StrEnum): 

56 """Game state update kinds.""" 

57 

58 new_order = "new_order" 

59 role_assignment = "role_assignment" 

60 order_score = "order_score" 

61 order_submission = "order_submission" 

62 order_component = "order_component" 

63 day_end = "day_end" 

64 

65 

66class NewOrder(BaseModel): 

67 """A new order for the manager.""" 

68 

69 type: Literal[MessageKind.game_state] = MessageKind.game_state 

70 game_state_update_type: Literal[GameStateUpdateKind.new_order] = GameStateUpdateKind.new_order 

71 

72 order: Order 

73 

74 

75class RoleAssignment(BaseModel): 

76 """Assign a role to the player.""" 

77 

78 type: Literal[MessageKind.game_state] = MessageKind.game_state 

79 game_state_update_type: Literal[GameStateUpdateKind.role_assignment] = GameStateUpdateKind.role_assignment 

80 

81 role: Role 

82 

83 

84class OrderScore(BaseModel): 

85 """A score for the order.""" 

86 

87 type: Literal[MessageKind.game_state] = MessageKind.game_state 

88 game_state_update_type: Literal[GameStateUpdateKind.order_score] = GameStateUpdateKind.order_score 

89 

90 score: int 

91 

92 

93class OrderComponent(BaseModel): 

94 """A finished component is submitted to the manager.""" 

95 

96 type: Literal[MessageKind.game_state] = MessageKind.game_state 

97 game_state_update_type: Literal[GameStateUpdateKind.order_component] = GameStateUpdateKind.order_component 

98 

99 component_type: str 

100 component: Burger | Side | Drink 

101 

102 

103class OrderSubmission(BaseModel): 

104 """An order is submitted for review.""" 

105 

106 type: Literal[MessageKind.game_state] = MessageKind.game_state 

107 game_state_update_type: Literal[GameStateUpdateKind.order_submission] = GameStateUpdateKind.order_submission 

108 

109 order: Order 

110 

111 

112class Order(BaseModel): 

113 """A complete order.""" 

114 

115 burger: Burger | None 

116 drink: Drink | None 

117 side: Side | None 

118 

119 

120class Burger(BaseModel): 

121 """A burger.""" 

122 

123 ingredients: list[str] 

124 

125 

126class Drink(BaseModel): 

127 """A drink.""" 

128 

129 color: str 

130 fill: float 

131 size: str 

132 

133 

134class Side(BaseModel): 

135 """A side.""" 

136 

137 table_state: str 

138 

139 

140class DayEnd(BaseModel): 

141 """No more orders for this day.""" 

142 

143 type: Literal[MessageKind.game_state] = MessageKind.game_state 

144 game_state_update_type: Literal[GameStateUpdateKind.day_end] = GameStateUpdateKind.day_end 

145 

146 day: int 

147 

148 

149type GameStateUpdate = Annotated[ 

150 NewOrder | RoleAssignment | OrderScore | OrderSubmission | OrderComponent | DayEnd, 

151 Field(discriminator="game_state_update_type"), 

152] 

153 

154 

155class LobbyLifecycleEventKind(StrEnum): 

156 """Lobby lifecycle event kinds.""" 

157 

158 player_join = "player_join" 

159 player_leave = "player_leave" 

160 player_count = "player_count" 

161 game_start = "game_start" 

162 game_end = "game_end" 

163 

164 

165class PlayerJoin(BaseModel): 

166 """A player joining a lobby.""" 

167 

168 type: Literal[MessageKind.lobby_lifecycle] = MessageKind.lobby_lifecycle 

169 lifecycle_type: Literal[LobbyLifecycleEventKind.player_join] = LobbyLifecycleEventKind.player_join 

170 

171 id: str 

172 

173 

174class PlayerLeave(BaseModel): 

175 """A player leaving a lobby.""" 

176 

177 type: Literal[MessageKind.lobby_lifecycle] = MessageKind.lobby_lifecycle 

178 lifecycle_type: Literal[LobbyLifecycleEventKind.player_leave] = LobbyLifecycleEventKind.player_leave 

179 

180 id: str 

181 

182 

183class PlayerCount(BaseModel): 

184 """Current number of players in the lobby.""" 

185 

186 type: Literal[MessageKind.lobby_lifecycle] = MessageKind.lobby_lifecycle 

187 lifecycle_type: Literal[LobbyLifecycleEventKind.player_count] = LobbyLifecycleEventKind.player_count 

188 

189 count: int 

190 player_ids: list[str] = Field(default_factory=list) 

191 

192 

193class GameStart(BaseModel): 

194 """The host starts the game.""" 

195 

196 type: Literal[MessageKind.lobby_lifecycle] = MessageKind.lobby_lifecycle 

197 lifecycle_type: Literal[LobbyLifecycleEventKind.game_start] = LobbyLifecycleEventKind.game_start 

198 

199 

200class GameEnd(BaseModel): 

201 """All days are completed.""" 

202 

203 type: Literal[MessageKind.lobby_lifecycle] = MessageKind.lobby_lifecycle 

204 lifecycle_type: Literal[LobbyLifecycleEventKind.game_end] = LobbyLifecycleEventKind.game_end 

205 

206 

207type LifecycleEvent = Annotated[ 

208 PlayerJoin | PlayerLeave | PlayerCount | GameStart | GameEnd, Field(discriminator="lifecycle_type") 

209] 

210 

211 

212class Chat(BaseModel): 

213 """The manager is typing.""" 

214 

215 type: Literal[MessageKind.chat] = MessageKind.chat 

216 

217 typing: bool 

218 

219 

220class Message(BaseModel): 

221 """Wrapper for message models.""" 

222 

223 data: Initializer | GameStateUpdate | LifecycleEvent | Chat = Field(discriminator="type") 

224 

225 

226if __name__ == "__main__": 

227 import json 

228 

229 message_schema = Message.model_json_schema() 

230 print(json.dumps(message_schema, indent=2)) # noqa: T201