Skip to content

Commit 98d87cb

Browse files
authored
Merge pull request #44 from FlysonBot/dev
Dev
2 parents 872d613 + 4d47225 commit 98d87cb

28 files changed

+606
-643
lines changed

src/mastermind/main/game_controller.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from mastermind.game.game import Game
44
from mastermind.main.game_history import GameHistoryManager
5-
from mastermind.storage.user_data import UserDataManager
5+
from mastermind.storage import userdata
66
from mastermind.validation import (
77
MaximumAttempts,
88
NumberOfColors,
@@ -57,15 +57,15 @@ def start_new_game(cls, game_mode: str) -> None:
5757
def resume_game(cls, game_index: int) -> None:
5858
"""Resume a saved game."""
5959
# Retrieve game
60-
game = UserDataManager().saved_games[game_index]["game"]
60+
game = userdata.saved_games[game_index]["game"]
6161

6262
# Resume game and retrieve exit state
6363
exit_state = game.resume_game()
6464

6565
# Update saved games if not game discarded
6666
if exit_state == "d": # or delete it if discarded
67-
UserDataManager().saved_games.pop(game_index)
67+
userdata.saved_games.pop(game_index)
6868
else:
69-
UserDataManager().saved_games[game_index] = (
70-
GameHistoryManager().generate_meta_data(game)
69+
userdata.saved_games[game_index] = GameHistoryManager().generate_meta_data(
70+
game
7171
)

src/mastermind/main/game_history.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
from mastermind.game.game import Game
66
from mastermind.main.game_storage import list_continuable_games, retrieve_stored_games
7-
from mastermind.storage.user_data import UserDataManager
7+
from mastermind.storage import userdata
88

99

1010
def game_list_to_pandas(games: List[dict]) -> Optional[pd.DataFrame]:
@@ -57,10 +57,10 @@ def generate_meta_data(game: Game) -> dict:
5757
@staticmethod
5858
def save_game(game: Game) -> None:
5959
"""Save the game to a file."""
60-
if "saved_games" not in UserDataManager(): # if the list is empty
61-
UserDataManager().saved_games = [] # initialize the list
60+
if "saved_games" not in userdata: # if the list is empty
61+
userdata.saved_games = [] # initialize the list
6262

63-
UserDataManager().saved_games.append(
63+
userdata.saved_games.append(
6464
GameHistoryManager.generate_meta_data(game)
6565
) # store the meta data
6666

src/mastermind/main/game_storage.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
from mastermind.storage.user_data import UserDataManager
1+
from mastermind.storage import userdata
22

33

44
def retrieve_stored_games():
55
"""Retrieve all stored games"""
6-
saved_games = UserDataManager().saved_games
6+
saved_games = userdata.saved_games
77
return saved_games or []
88

99

src/mastermind/main/main.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
list_continuable_games_index,
44
retrieve_stored_games,
55
)
6-
from mastermind.storage import UserDataManager
7-
from mastermind.ui.menu.concrete_menus import (
6+
from mastermind.storage import userdata
7+
from mastermind.ui.menu import (
88
GameHistoryMenu,
99
MainMenu,
1010
NewGameMenu,
@@ -83,7 +83,7 @@ def run(self):
8383
while self.main_menu():
8484
pass # keep calling self.main_menu() until it return False
8585
print("Thank you for playing!")
86-
UserDataManager().save_data()
86+
userdata._save_data()
8787

8888

8989
def main():

src/mastermind/players/abstract_player.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from abc import ABC, abstractmethod
22

3-
from mastermind.utils import FStringTemplate, Stack
3+
from mastermind.utils import Stack
44

55

66
class Player(ABC):
@@ -41,21 +41,21 @@ def undo(self) -> None:
4141

4242

4343
class CodeCracker(Player, ABC):
44-
def __init__(
45-
self,
46-
player_logic: "PlayerLogic", # noqa: F821 # type: ignore
47-
win_msg: str,
48-
lose_msg: str, # noqa: F821 # type: ignore
49-
) -> None: # type: ignore # noqa: F821
50-
super().__init__(player_logic)
51-
self._win_message = FStringTemplate(win_msg)
52-
self._lose_message = FStringTemplate(lose_msg)
44+
@property
45+
@abstractmethod
46+
def _WIN_MESSAGE(self) -> str:
47+
pass
48+
49+
@property
50+
@abstractmethod
51+
def _LOSE_MESSAGE(self) -> str:
52+
pass
5353

5454
def win_message(self) -> None:
55-
print(self._win_message.eval(step=len(self.game_state)))
55+
print(self._WIN_MESSAGE.format(step=len(self.game_state)))
5656

5757
def lose_message(self) -> None:
58-
print(self._lose_message.eval(step=len(self.game_state)))
58+
print(self._LOSE_MESSAGE.format(step=len(self.game_state)))
5959

6060
@abstractmethod
6161
def obtain_guess(self) -> tuple:

src/mastermind/players/ai_player.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,8 @@ def get_feedback(self, guess: tuple) -> tuple:
2121

2222

2323
class AICodeCracker(CodeCracker):
24-
def __init__(self, player_logic: "PlayerLogic") -> None: # type: ignore # noqa: F821
25-
win_message = "Congratulations! You won in {step} steps!"
26-
lose_message = "Sorry, you lost. The secret code was {step}."
27-
super().__init__(player_logic, win_message, lose_message)
24+
_WIN_MESSAGE = "Congratulations! You won in {step} steps!"
25+
_LOSE_MESSAGE = "Sorry, you lost. The secret code was {step}."
2826

2927
def obtain_guess(self) -> tuple:
3028
# TODO: Implement AI solver logic to generate guess.

src/mastermind/players/human_player.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,10 +69,8 @@ def get_feedback(self, guess: tuple) -> tuple:
6969

7070

7171
class HumanCodeCracker(CodeCracker):
72-
def __init__(self, player_logic: "PlayerLogic") -> None: # type: ignore # noqa: F821
73-
win_message = "Congratulations! You won in {step} steps!"
74-
lose_message = "Sorry, you lost. The secret code was {step}."
75-
super().__init__(player_logic, win_message, lose_message)
72+
_WIN_MESSAGE = "Congratulations! You won in {step} steps!"
73+
_LOSE_MESSAGE = "Sorry, you lost. The secret code was {step}."
7674

7775
def obtain_guess(self) -> Union[tuple, str]:
7876
valid_guess = ValidCombination(

src/mastermind/storage/__init__.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
from mastermind.storage.persistent_cache import PersistentCacheManager
2-
from mastermind.storage.user_data import UserDataManager
2+
from mastermind.storage.user_data import get_user_data_manager
33

4-
__all__ = ["PersistentCacheManager", "UserDataManager"]
4+
userdata = get_user_data_manager()
5+
6+
__all__ = ["PersistentCacheManager", "get_user_data_manager"]

src/mastermind/storage/persistent_cache.py

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
11
import glob
22
import os
3-
import pickle
43
from typing import Any
54

5+
from mastermind.storage.pickle_io import (
6+
read_pickled_data,
7+
write_pickled_data,
8+
)
9+
610

711
class PersistentCacheManager:
812
"""
913
Manages a persistent cache of Python objects on the file system.
1014
11-
The PersistentCacheManager class provides a simple interface for caching and retrieving data, using the pickle module to serialize and deserialize the objects.
15+
The PersistentCacheManager class provides a simple interface for caching and retrieving data,
16+
using the pickle_io module for pickling to serialize and deserialize the objects.
1217
1318
The cache files are stored in the "data" directory, which is created automatically if it does not exist.
1419
"""
@@ -32,11 +37,10 @@ def _get_cache_file_path(cls, key: str) -> str:
3237
Returns:
3338
str: The file path for the cache file.
3439
"""
35-
3640
return os.path.join(cls._cache_directory, f"{key}.cache")
3741

3842
@classmethod
39-
def clear_cache(cls) -> None:
43+
def clear_all_cache(cls) -> None:
4044
"""Clears the entire cache by deleting all cache files."""
4145
for cache_file in glob.glob(os.path.join(cls._cache_directory, "*.cache")):
4246
os.remove(cache_file)
@@ -52,25 +56,15 @@ def __getattr__(cls, key: str) -> Any:
5256
Returns:
5357
Any: The cached value, or None if the key does not exist.
5458
"""
55-
56-
file_path = cls._get_cache_file_path(key)
57-
if os.path.exists(file_path):
58-
with open(file_path, "rb") as file:
59-
return pickle.load(file)
60-
return None
59+
return read_pickled_data(cls._get_cache_file_path(key))
6160

6261
@classmethod
63-
def set(cls, key: str, value: Any) -> Any:
62+
def set(cls, key: str, value: Any) -> None:
6463
"""
6564
Sets the cached value for the given key.
6665
6766
Args:
6867
key (str): The key associated with the cached value.
6968
value (Any): The value to be cached.
70-
71-
Returns:
72-
Any: The cached value.
7369
"""
74-
75-
with open(cls._get_cache_file_path(key), "wb") as file:
76-
pickle.dump(value, file)
70+
write_pickled_data(cls._get_cache_file_path(key), value)
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import os
2+
import pickle
3+
from typing import Any
4+
5+
6+
def read_pickled_data(filepath: str) -> Any:
7+
"""Read pickled data from a file and return it.
8+
9+
Args:
10+
filepath (str): The path to the file to read from.
11+
12+
Returns:
13+
Any: The pickled data being read, or None if the file does not exist.
14+
"""
15+
16+
ensure_parent_directory_exists(filepath)
17+
try:
18+
with open(filepath, "rb") as file:
19+
return pickle.load(file)
20+
21+
except FileNotFoundError:
22+
return None
23+
24+
25+
def ensure_parent_directory_exists(filepath: str) -> None:
26+
"""Create the parent directory of a file if it doesn't exist.
27+
28+
Args:
29+
filepath (str): The path to the file.
30+
"""
31+
if directory := os.path.dirname(filepath):
32+
os.makedirs(directory, exist_ok=True)
33+
34+
else:
35+
raise ValueError("filepath must include a directory component")
36+
37+
38+
def write_pickled_data(filepath: str, data: dict) -> None:
39+
"""Write pickled data to a file.
40+
41+
Args:
42+
filepath (str): The path to the file to write to.
43+
data (dict): The data to be pickled and written to the file.
44+
"""
45+
46+
ensure_parent_directory_exists(filepath)
47+
with open(filepath, "wb") as file:
48+
pickle.dump(data, file)
49+
50+
51+
def delete_pickled_data(filepath: str) -> None:
52+
"""Delete a pickled file.
53+
54+
Args:
55+
filepath (str): The path to the file to delete.
56+
"""
57+
os.remove(filepath)

0 commit comments

Comments
 (0)