Coverage for zombie_nomnom_api/game.py: 100%
76 statements
« prev ^ index » next coverage.py v7.6.9, created at 2024-12-07 04:25 +0000
« prev ^ index » next coverage.py v7.6.9, created at 2024-12-07 04:25 +0000
1from enum import Enum
2from typing import Protocol, runtime_checkable
3import uuid
5from pydantic_settings import BaseSettings
6from pymongo.collection import Collection
7from pymongo import MongoClient
9from zombie_nomnom import ZombieDieGame
10from zombie_nomnom.engine.serialization import format_to_json_dict, parse_game_json_dict
13class Game:
14 game: ZombieDieGame
15 id: str
17 def __init__(self, *, game: ZombieDieGame, id: str = None) -> None:
18 self.game = game
19 self.id = id or str(uuid.uuid4())
21 def __eq__(self, value: object) -> bool:
22 return isinstance(value, Game) and self.id == value.id
24 @classmethod
25 def from_dict(cls, game_data: dict) -> "Game":
26 id = game_data.pop("id", None)
27 game = parse_game_json_dict(game_data["game"])
28 return cls(game=game, id=id)
30 def to_dict(self) -> dict:
31 return {
32 "id": self.id,
33 "game": format_to_json_dict(self.game),
34 }
37@runtime_checkable
38class GameMakerInterface(Protocol):
39 """
40 Defines the methods that are used by game makers to create and load games currently.
41 """
43 def make_game(self, players: list[str]) -> Game: ...
45 def __getitem__(self, key: str) -> Game: ...
47 def __iter__(self): ...
50class InMemoryGameMaker:
51 def __init__(self) -> None:
52 self.session = {}
54 def make_game(self, players: list[str]) -> Game:
55 game = Game(game=ZombieDieGame(players))
56 self.session[game.id] = game
57 return game
59 def __getitem__(self, key: str) -> Game:
60 return self.session.get(key, None)
62 def __iter__(self):
63 return iter(self.session.values())
66class MongoGameMaker:
67 def __init__(self, mongo_client: MongoClient, game_collection_name) -> None:
68 self.mongo_client = mongo_client
69 self.game_collection: Collection = mongo_client.get_database().get_collection(
70 game_collection_name
71 )
73 def make_game(self, players: list[str]) -> Game:
74 game = Game(game=ZombieDieGame(players))
75 self.game_collection.insert_one(game.to_dict())
76 return game
78 def load_game(self, game_id: str) -> Game | None:
79 game_data = self.game_collection.find_one({"id": game_id})
80 if game_data is None:
81 return None
83 game = Game.from_dict(game_data)
84 return game
86 def get_all_games(self) -> list[Game]:
87 games_data = self.game_collection.find()
88 games = [Game.from_dict(game_data) for game_data in games_data]
89 return games
91 def __getitem__(self, key: str) -> Game:
92 return self.load_game(key)
94 def __iter__(self):
95 return iter(self.get_all_games())
98class MongoGameMakerConfig(BaseSettings):
99 """Environment Config settings for MongoGameMakers to allow us to not have to
100 need mongo connection details unless we are loading the mongo engine.
101 """
103 mongo_connection: str = "mongodb://localhost:27017/zombie_nomnom"
104 """
105 Full mongo connection string uri ex. mongodb://localhost:27017/zombie_nomnom
106 """
107 game_collection_name: str = "games"
108 """
109 Name of the collection where we store game data.
110 """
113class GameMakerType(str, Enum):
114 """
115 Enum for the supported types of game makers we have implemented.
116 """
118 memory = "memory"
119 mongo = "mongo"
122def create_maker(kind: GameMakerType) -> GameMakerInterface:
123 """Translates the game maker type to the implementation of the GameMakerInterface
125 **Parameters**
126 - kind (GameMakerType): The kind of game maker we want.
128 **Raises**
129 - ValueError: Given a GameMakerType that we do not have mapped.
131 **Returns**
132 - GameMakerInterface: GameMaker implementation
133 """
134 if kind == GameMakerType.mongo:
135 config = MongoGameMakerConfig()
136 return MongoGameMaker(
137 game_collection_name=config.game_collection_name,
138 mongo_client=MongoClient(config.mongo_connection),
139 )
140 elif kind == GameMakerType.memory:
141 return InMemoryGameMaker()
142 raise ValueError(f"Invalid game maker type: {kind}")