diff --git a/SavedGames/02072024_073340_Gamestate.json b/SavedGames/02072024_073340_Gamestate.json new file mode 100644 index 0000000..0e4e6f0 --- /dev/null +++ b/SavedGames/02072024_073340_Gamestate.json @@ -0,0 +1,423 @@ +{ + "currently_playing": "White", + "show_symbols": true, + "board_state": { + "0": { + "piece": "Rook", + "colour": "Black", + "moved": false, + "position": 0 + }, + "1": { + "piece": null, + "symbol": null, + "colour": null, + "moved": null, + "position": null + }, + "2": { + "piece": "Bishop", + "colour": "Black", + "moved": false, + "position": 2 + }, + "3": { + "piece": "Queen", + "colour": "Black", + "moved": false, + "position": 3 + }, + "4": { + "piece": "King", + "colour": "Black", + "moved": false, + "position": 4 + }, + "5": { + "piece": "Bishop", + "colour": "Black", + "moved": false, + "position": 5 + }, + "6": { + "piece": "Knight", + "colour": "Black", + "moved": false, + "position": 6 + }, + "7": { + "piece": "Rook", + "colour": "Black", + "moved": false, + "position": 7 + }, + "8": { + "piece": "Pawn", + "colour": "Black", + "moved": false, + "position": 8 + }, + "9": { + "piece": "Pawn", + "colour": "Black", + "moved": false, + "position": 9 + }, + "10": { + "piece": "Pawn", + "colour": "Black", + "moved": false, + "position": 10 + }, + "11": { + "piece": "Pawn", + "colour": "Black", + "moved": false, + "position": 11 + }, + "12": { + "piece": "Pawn", + "colour": "Black", + "moved": false, + "position": 12 + }, + "13": { + "piece": "Pawn", + "colour": "Black", + "moved": false, + "position": 13 + }, + "14": { + "piece": "Pawn", + "colour": "Black", + "moved": false, + "position": 14 + }, + "15": { + "piece": "Pawn", + "colour": "Black", + "moved": false, + "position": 15 + }, + "16": { + "piece": null, + "symbol": null, + "colour": null, + "moved": null, + "position": null + }, + "17": { + "piece": null, + "symbol": null, + "colour": null, + "moved": null, + "position": null + }, + "18": { + "piece": "Knight", + "colour": "Black", + "moved": true, + "position": 18 + }, + "19": { + "piece": null, + "symbol": null, + "colour": null, + "moved": null, + "position": null + }, + "20": { + "piece": null, + "symbol": null, + "colour": null, + "moved": null, + "position": null + }, + "21": { + "piece": null, + "symbol": null, + "colour": null, + "moved": null, + "position": null + }, + "22": { + "piece": null, + "symbol": null, + "colour": null, + "moved": null, + "position": null + }, + "23": { + "piece": null, + "symbol": null, + "colour": null, + "moved": null, + "position": null + }, + "24": { + "piece": null, + "symbol": null, + "colour": null, + "moved": null, + "position": null + }, + "25": { + "piece": null, + "symbol": null, + "colour": null, + "moved": null, + "position": null + }, + "26": { + "piece": null, + "symbol": null, + "colour": null, + "moved": null, + "position": null + }, + "27": { + "piece": null, + "symbol": null, + "colour": null, + "moved": null, + "position": null + }, + "28": { + "piece": null, + "symbol": null, + "colour": null, + "moved": null, + "position": null + }, + "29": { + "piece": null, + "symbol": null, + "colour": null, + "moved": null, + "position": null + }, + "30": { + "piece": null, + "symbol": null, + "colour": null, + "moved": null, + "position": null + }, + "31": { + "piece": null, + "symbol": null, + "colour": null, + "moved": null, + "position": null + }, + "32": { + "piece": null, + "symbol": null, + "colour": null, + "moved": null, + "position": null + }, + "33": { + "piece": null, + "symbol": null, + "colour": null, + "moved": null, + "position": null + }, + "34": { + "piece": null, + "symbol": null, + "colour": null, + "moved": null, + "position": null + }, + "35": { + "piece": null, + "symbol": null, + "colour": null, + "moved": null, + "position": null + }, + "36": { + "piece": "Pawn", + "colour": "White", + "moved": true, + "position": 36 + }, + "37": { + "piece": null, + "symbol": null, + "colour": null, + "moved": null, + "position": null + }, + "38": { + "piece": null, + "symbol": null, + "colour": null, + "moved": null, + "position": null + }, + "39": { + "piece": null, + "symbol": null, + "colour": null, + "moved": null, + "position": null + }, + "40": { + "piece": null, + "symbol": null, + "colour": null, + "moved": null, + "position": null + }, + "41": { + "piece": null, + "symbol": null, + "colour": null, + "moved": null, + "position": null + }, + "42": { + "piece": null, + "symbol": null, + "colour": null, + "moved": null, + "position": null + }, + "43": { + "piece": null, + "symbol": null, + "colour": null, + "moved": null, + "position": null + }, + "44": { + "piece": null, + "symbol": null, + "colour": null, + "moved": null, + "position": null + }, + "45": { + "piece": null, + "symbol": null, + "colour": null, + "moved": null, + "position": null + }, + "46": { + "piece": null, + "symbol": null, + "colour": null, + "moved": null, + "position": null + }, + "47": { + "piece": null, + "symbol": null, + "colour": null, + "moved": null, + "position": null + }, + "48": { + "piece": "Pawn", + "colour": "White", + "moved": false, + "position": 48 + }, + "49": { + "piece": "Pawn", + "colour": "White", + "moved": false, + "position": 49 + }, + "50": { + "piece": "Pawn", + "colour": "White", + "moved": false, + "position": 50 + }, + "51": { + "piece": "Pawn", + "colour": "White", + "moved": false, + "position": 51 + }, + "52": { + "piece": null, + "symbol": null, + "colour": null, + "moved": null, + "position": null + }, + "53": { + "piece": "Pawn", + "colour": "White", + "moved": false, + "position": 53 + }, + "54": { + "piece": "Pawn", + "colour": "White", + "moved": false, + "position": 54 + }, + "55": { + "piece": "Pawn", + "colour": "White", + "moved": false, + "position": 55 + }, + "56": { + "piece": "Rook", + "colour": "White", + "moved": false, + "position": 56 + }, + "57": { + "piece": "Knight", + "colour": "White", + "moved": false, + "position": 57 + }, + "58": { + "piece": "Bishop", + "colour": "White", + "moved": false, + "position": 58 + }, + "59": { + "piece": "Queen", + "colour": "White", + "moved": false, + "position": 59 + }, + "60": { + "piece": "King", + "colour": "White", + "moved": false, + "position": 60 + }, + "61": { + "piece": "Bishop", + "colour": "White", + "moved": false, + "position": 61 + }, + "62": { + "piece": "Knight", + "colour": "White", + "moved": false, + "position": 62 + }, + "63": { + "piece": "Rook", + "colour": "White", + "moved": false, + "position": 63 + } + }, + "ai": true +} \ No newline at end of file diff --git a/SavedGames/02072024_075916_Gamestate.json b/SavedGames/02072024_075916_Gamestate.json new file mode 100644 index 0000000..0e4e6f0 --- /dev/null +++ b/SavedGames/02072024_075916_Gamestate.json @@ -0,0 +1,423 @@ +{ + "currently_playing": "White", + "show_symbols": true, + "board_state": { + "0": { + "piece": "Rook", + "colour": "Black", + "moved": false, + "position": 0 + }, + "1": { + "piece": null, + "symbol": null, + "colour": null, + "moved": null, + "position": null + }, + "2": { + "piece": "Bishop", + "colour": "Black", + "moved": false, + "position": 2 + }, + "3": { + "piece": "Queen", + "colour": "Black", + "moved": false, + "position": 3 + }, + "4": { + "piece": "King", + "colour": "Black", + "moved": false, + "position": 4 + }, + "5": { + "piece": "Bishop", + "colour": "Black", + "moved": false, + "position": 5 + }, + "6": { + "piece": "Knight", + "colour": "Black", + "moved": false, + "position": 6 + }, + "7": { + "piece": "Rook", + "colour": "Black", + "moved": false, + "position": 7 + }, + "8": { + "piece": "Pawn", + "colour": "Black", + "moved": false, + "position": 8 + }, + "9": { + "piece": "Pawn", + "colour": "Black", + "moved": false, + "position": 9 + }, + "10": { + "piece": "Pawn", + "colour": "Black", + "moved": false, + "position": 10 + }, + "11": { + "piece": "Pawn", + "colour": "Black", + "moved": false, + "position": 11 + }, + "12": { + "piece": "Pawn", + "colour": "Black", + "moved": false, + "position": 12 + }, + "13": { + "piece": "Pawn", + "colour": "Black", + "moved": false, + "position": 13 + }, + "14": { + "piece": "Pawn", + "colour": "Black", + "moved": false, + "position": 14 + }, + "15": { + "piece": "Pawn", + "colour": "Black", + "moved": false, + "position": 15 + }, + "16": { + "piece": null, + "symbol": null, + "colour": null, + "moved": null, + "position": null + }, + "17": { + "piece": null, + "symbol": null, + "colour": null, + "moved": null, + "position": null + }, + "18": { + "piece": "Knight", + "colour": "Black", + "moved": true, + "position": 18 + }, + "19": { + "piece": null, + "symbol": null, + "colour": null, + "moved": null, + "position": null + }, + "20": { + "piece": null, + "symbol": null, + "colour": null, + "moved": null, + "position": null + }, + "21": { + "piece": null, + "symbol": null, + "colour": null, + "moved": null, + "position": null + }, + "22": { + "piece": null, + "symbol": null, + "colour": null, + "moved": null, + "position": null + }, + "23": { + "piece": null, + "symbol": null, + "colour": null, + "moved": null, + "position": null + }, + "24": { + "piece": null, + "symbol": null, + "colour": null, + "moved": null, + "position": null + }, + "25": { + "piece": null, + "symbol": null, + "colour": null, + "moved": null, + "position": null + }, + "26": { + "piece": null, + "symbol": null, + "colour": null, + "moved": null, + "position": null + }, + "27": { + "piece": null, + "symbol": null, + "colour": null, + "moved": null, + "position": null + }, + "28": { + "piece": null, + "symbol": null, + "colour": null, + "moved": null, + "position": null + }, + "29": { + "piece": null, + "symbol": null, + "colour": null, + "moved": null, + "position": null + }, + "30": { + "piece": null, + "symbol": null, + "colour": null, + "moved": null, + "position": null + }, + "31": { + "piece": null, + "symbol": null, + "colour": null, + "moved": null, + "position": null + }, + "32": { + "piece": null, + "symbol": null, + "colour": null, + "moved": null, + "position": null + }, + "33": { + "piece": null, + "symbol": null, + "colour": null, + "moved": null, + "position": null + }, + "34": { + "piece": null, + "symbol": null, + "colour": null, + "moved": null, + "position": null + }, + "35": { + "piece": null, + "symbol": null, + "colour": null, + "moved": null, + "position": null + }, + "36": { + "piece": "Pawn", + "colour": "White", + "moved": true, + "position": 36 + }, + "37": { + "piece": null, + "symbol": null, + "colour": null, + "moved": null, + "position": null + }, + "38": { + "piece": null, + "symbol": null, + "colour": null, + "moved": null, + "position": null + }, + "39": { + "piece": null, + "symbol": null, + "colour": null, + "moved": null, + "position": null + }, + "40": { + "piece": null, + "symbol": null, + "colour": null, + "moved": null, + "position": null + }, + "41": { + "piece": null, + "symbol": null, + "colour": null, + "moved": null, + "position": null + }, + "42": { + "piece": null, + "symbol": null, + "colour": null, + "moved": null, + "position": null + }, + "43": { + "piece": null, + "symbol": null, + "colour": null, + "moved": null, + "position": null + }, + "44": { + "piece": null, + "symbol": null, + "colour": null, + "moved": null, + "position": null + }, + "45": { + "piece": null, + "symbol": null, + "colour": null, + "moved": null, + "position": null + }, + "46": { + "piece": null, + "symbol": null, + "colour": null, + "moved": null, + "position": null + }, + "47": { + "piece": null, + "symbol": null, + "colour": null, + "moved": null, + "position": null + }, + "48": { + "piece": "Pawn", + "colour": "White", + "moved": false, + "position": 48 + }, + "49": { + "piece": "Pawn", + "colour": "White", + "moved": false, + "position": 49 + }, + "50": { + "piece": "Pawn", + "colour": "White", + "moved": false, + "position": 50 + }, + "51": { + "piece": "Pawn", + "colour": "White", + "moved": false, + "position": 51 + }, + "52": { + "piece": null, + "symbol": null, + "colour": null, + "moved": null, + "position": null + }, + "53": { + "piece": "Pawn", + "colour": "White", + "moved": false, + "position": 53 + }, + "54": { + "piece": "Pawn", + "colour": "White", + "moved": false, + "position": 54 + }, + "55": { + "piece": "Pawn", + "colour": "White", + "moved": false, + "position": 55 + }, + "56": { + "piece": "Rook", + "colour": "White", + "moved": false, + "position": 56 + }, + "57": { + "piece": "Knight", + "colour": "White", + "moved": false, + "position": 57 + }, + "58": { + "piece": "Bishop", + "colour": "White", + "moved": false, + "position": 58 + }, + "59": { + "piece": "Queen", + "colour": "White", + "moved": false, + "position": 59 + }, + "60": { + "piece": "King", + "colour": "White", + "moved": false, + "position": 60 + }, + "61": { + "piece": "Bishop", + "colour": "White", + "moved": false, + "position": 61 + }, + "62": { + "piece": "Knight", + "colour": "White", + "moved": false, + "position": 62 + }, + "63": { + "piece": "Rook", + "colour": "White", + "moved": false, + "position": 63 + } + }, + "ai": true +} \ No newline at end of file diff --git a/algorithm.py b/algorithm.py index 099b6c4..bbf5c44 100644 --- a/algorithm.py +++ b/algorithm.py @@ -7,9 +7,9 @@ class GameAI: - def __init__(self, model, view, color, enemy): + def __init__(self, board, view, color, enemy): - self.model = model + self.board = board self.view = view self.color = color self.enemy = enemy @@ -17,18 +17,18 @@ def __init__(self, model, view, color, enemy): def alpha_beta(self, state, depth, alpha, beta, ai_playing): # calcs the score of the current board - if depth == 0 or not self.model.check_for_king(): + if depth == 0 or not self.board.check_for_king(): return self.calculate_board_value(state) if ai_playing: ai_value = -math.inf - self.model.currently_playing = "White" + self.board.currently_playing = "White" # calcs the score of every possible move for next_move in self.get_possible_moves(self.enemy, state): x_move, y_move = next_move - temp = self.model.get_copy_board_state(state) + temp = self.board.get_copy_board_state(state) change_position = None @@ -47,7 +47,7 @@ def alpha_beta(self, state, depth, alpha, beta, ai_playing): if change_position is not None: temp[y_move].position = change_position - self.model.currently_playing = "Black" + self.board.currently_playing = "Black" # White want the score as high as possible ai_value = max(ai_value, value) @@ -58,12 +58,12 @@ def alpha_beta(self, state, depth, alpha, beta, ai_playing): return ai_value player_value = math.inf - self.model.currently_playing = "Black" + self.board.currently_playing = "Black" for next_move in self.get_possible_moves(self.color, state): x_move, y_move = next_move - temp = self.model.get_copy_board_state(state) + temp = self.board.get_copy_board_state(state) change_position = None @@ -188,7 +188,7 @@ def calc_best_move(self, moves, queue, state): for next_move in moves: - temp = self.model.get_copy_board_state(state) + temp = self.board.get_copy_board_state(state) x_move, y_move = next_move change_position = None @@ -215,7 +215,7 @@ def calc_best_move(self, moves, queue, state): def move(self): - state = self.model.get_copy_board_state() + state = self.board.get_copy_board_state() possible_moves = self.get_possible_moves(self.color, state) result = [] @@ -248,4 +248,4 @@ def move(self): same_score.sort() x_move, y_move = same_score[0][0] output.close() - self.model.move_piece(x_move, y_move) \ No newline at end of file + return x_move, y_move \ No newline at end of file diff --git a/board.py b/board.py index 460a9e9..78b2b7c 100644 --- a/board.py +++ b/board.py @@ -1,11 +1,21 @@ from player import HumanPlayer, ComputerPlayer from pieces import Rook, Knight, Bishop, Queen, King, Pawn +from view import GameView +from controller import GameManager class GameBoard: """Class that handles the game board""" def __init__(self): self.board_state = [None] * 64 + self.show_symbols = True + self.view = GameView() + self.game_manager = GameManager(self.view) + self.correlation = {f"{chr(65 + col)}{8 - row}": row * 8 + col for row in range(8) for col in range(8)} + self.pieces = [] + self.currently_playing = 'White' + self.show_symbols = True + self.set_initial_pieces() def set_initial_pieces(self): """Set the initial pieces on the board""" @@ -22,7 +32,7 @@ def set_initial_pieces(self): self.board_state[pos] = None self.pieces = [piece for piece in self.board_state if piece is not None] - def _update_positions(self, piece, start_pos, goal_pos, update): + def update_positions(self, piece, start_pos, goal_pos, update): """Update the positions of the pieces on the board""" killed_piece = self.board_state[goal_pos] self.board_state[goal_pos], self.board_state[start_pos] = piece, None @@ -33,6 +43,12 @@ def _update_positions(self, piece, start_pos, goal_pos, update): self.pieces.remove(killed_piece) piece.moved = True + def toggle_player(self): + if self.currently_playing == 'White': + self.currently_playing = 'Black' + else: + self.currently_playing = 'White' + def check_for_king(self): """Check if the king is alive on the board""" king_alive = False diff --git a/controller.py b/controller.py index 3d8dd7f..349c561 100644 --- a/controller.py +++ b/controller.py @@ -1,19 +1,21 @@ -from algorithm import GameAI import sys import os from pathlib import Path -import datetime +from datetime import datetime +import json from view import GameView from pieces import * -import json +from player import HumanPlayer, ComputerPlayer + class GameManager: def __init__(self, view): - self.model = None + self.board = None self.view = view self.ai = None - self.user_ai = None + self.player_white = None + self.player_black = None self.load_game = False def get_after_game_choice(self): @@ -34,102 +36,116 @@ def get_menu_choice(self): selection = input('Please enter the number that corresponds to your desired menu: ') if selection == '1': - num_player = input('Enter number of players [1-2]: ') + num_player = GameView.input_prompt('Enter number of players [1-2]: ') if num_player == '1': - self.model.ai = True - self.user_ai = GameAI(self.model, self.view, "Black", "White") - self.model.show_symbols = self.get_symbol_preference() + self.ai = True + self.player_white = HumanPlayer('White', self.view, self) + self.player_black = ComputerPlayer('Black', self.board, self.view, 'White', self) + self.board.show_symbols = self.get_symbol_preference() self.start_game() elif num_player == '2': - self.model.ai = False - self.model.show_symbols = self.get_symbol_preference() + self.ai = False + self.player_white = HumanPlayer('White', self.view, self) + self.player_black = HumanPlayer('Black', self.view, self) + self.board.show_symbols = self.get_symbol_preference() self.start_game() else: - print('Your choice is not valid! Please try again!') + GameView.display_message('Your choice is not valid! Please try again!') self.get_menu_choice() elif selection == '2': - pass # game loading stuff goes here i guess + self.load_gamestate() + self.start_game() elif selection == '3': - self.model.view.clear_console() + self.view.clear_console() sys.exit() else: - print('Your choice is not valid! Please try again!') + GameView.display_message('Your choice is not valid! Please try again!') self.get_menu_choice() def start_game(self): if not self.load_game: - self.model.set_initial_pieces() - self.view.last_board = self.model.get_copy_board_state() + self.board.set_initial_pieces() + self.view.last_board = self.board.get_copy_board_state() else: for _ in range(64): - if self.model.board_state[_] is not None: - self.model.pieces.append(self.model.board_state[_]) + if self.board.board_state[_] is not None: + self.board.pieces.append(self.board.board_state[_]) - self.model.view.update_board() + self.view.update_board() self.get_input() - if self.model.ai: - while self.model.check_for_king(): - if self.model.currently_playing == 'Black': - self.user_ai.move() + if self.ai: + while self.board.check_for_king(): + if self.board.currently_playing == 'Black': + self.player_black.make_move(self.board) else: self.get_input() else: - while self.model.check_for_king(): + while self.board.check_for_king(): self.get_input() - print(self.model.currently_playing + ' lost because his king died!') + GameView.display_message(self.board.currently_playing + ' lost because his king died!') self.get_after_game_choice() @staticmethod def get_symbol_preference(): while True: - choice = input('Do you want to use symbols? If not, letters will be used instead. (Y/N): ') + choice = GameView.input_prompt('Do you want to use symbols? If not, letters will be used instead. (Y/N): ') if choice.lower() == 'y' or choice.lower() == 'yes': return True elif choice.lower() == 'n' or choice.lower() == 'no': return False else: - print('Invalid input! Please answer the question with "yes" or "no"') + GameView.display_message('Invalid input! Please answer the question with "yes" or "no"') def get_input(self): - choice = input('Please enter your desired Move e.g. "e2e4" (type "s" to save): ').upper() + choice = GameView.input_prompt('Please enter your desired Move e.g. "e2e4" (type "s" to save): ').upper() if choice == "Q": - self.model.view.clear_console() + self.board.view.clear_console() sys.exit() if choice == "S": - pass # Needs some savintg logic stuff + self.save_gamestate() + self.view.clear_console() + self.view.print_menu() + self.get_menu_choice() if choice == "M": self.view.clear_console() self.view.print_menu() if len(choice) < 4: - print('Invalid Choice') + GameView.display_message('Invalid Choice') self.get_input() else: lines = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'] columns = ['1', '2', '3', '4', '5', '6', '7', '8'] start_pos = choice[:2] goal_pos = choice[-2:] - if start_pos[0] in lines and goal_pos[0] in lines and start_pos[1] in columns and goal_pos[1] in columns: - self.model.move_piece(self.model.correlation[start_pos], self.model.correlation[goal_pos]) - else: - print('Invalid Choice') - self.get_input() + if self.board.currently_playing == 'White': + if start_pos[0] in lines and goal_pos[0] in lines and start_pos[1] in columns and goal_pos[1] in columns: + self.player_white.make_move(self.board.correlation[start_pos], self.board.correlation[goal_pos], self.board) + else: + GameView.display_message('Invalid Choice') + self.get_input() + elif self.board.currently_playing == 'Black' and self.ai == False: + if start_pos[0] in lines and goal_pos[0] in lines and start_pos[1] in columns and goal_pos[1] in columns: + self.player_black.make_move(self.board.correlation[start_pos], self.board.correlation[goal_pos], self.board) + else: + GameView.display_message('Invalid Choice') + self.get_input() @staticmethod def get_symbol_preference(): while True: - choice = GameView.get_user_input('Do you want to use symbols? If not, letters will be used instead. (Y/N): ') + choice = GameView.input_prompt('Do you want to use symbols? If not, letters will be used instead. (Y/N): ') if choice.lower() == 'y' or choice.lower() == 'yes': return True elif choice.lower() == 'n' or choice.lower() == 'no': @@ -139,42 +155,58 @@ def get_symbol_preference(): def list_saved_games(self): """List all saved games in the saved_games directory.""" - saved_games_dir = 'saved_games' - saved_games = os.listdir(saved_games_dir) - if len(saved_games == 0): - self.view.display_message('No saved games found!') - else: - for game in saved_games: - GameView.display_message(game) - return saved_games + saved_games_dir = Path.cwd() / 'SavedGames' + saved_games = list(saved_games_dir.glob('*.json')) + if not saved_games: + print("No saved games found.") + return None + for index, game_file in enumerate(saved_games, start=1): + print(f"{index}) {game_file.name}") + return saved_games def save_gamestate(self): """Save the current game state to a file.""" - save_name = self.view.get_user_input("Please enter a name for the save file: " - "(leave blank for a timestamped filename): ") - - if not save_name: - save_name = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") + ".json" + GameSave = { + 'currently_playing': self.board.currently_playing, + 'show_symbols': self.board.show_symbols, + 'board_state': {str(i): self.piece_to_dict(self.board.board_state[i]) for i in range(64)}, + 'ai': self.ai + } + + # Ensure the 'SavedGames' folder exists + saved_games_dir = Path.cwd() / 'SavedGames' + saved_games_dir.mkdir(exist_ok=True) + + file_name = GameView.input_prompt("Enter a name for your save" + "(leave blank to use the current datetime): ") + if not file_name: + # Generate a detailed timestamped filename + timestamp = datetime.now().strftime('%d%m%Y_%H%M%S') + file_name = f"{timestamp}_Gamestate.json" else: - if not save_name.endswith(".json"): - save_name += ".json" - #OS independent path - filename = Path("saved_games", save_name) - self.create_directory_if_not_exists() + if not file_name.endswith('.json'): + file_name += ".json" - gamestate = { - 'currently_playing': self.model.currently_playing, - 'show_symbols': self.model.show_symbols, - 'board_state': {str(i): self.piece_to_dict(self.model.board_state[i]) for i in range(64)}, - 'ai': self.model.ai - } + file_path = saved_games_dir / file_name try: - with open(filename, 'w', encoding='utf-8') as file: - json.dump(gamestate, file, indent=4) - self.view.display_message(f"Game saved as {filename}") + with file_path.open("w") as json_file: + json.dump(GameSave, json_file, indent=4) + print("Game saved successfully!") except IOError as e: - self.view.display_message(f"Failed to save game: {e}") + print(f"Failed to save game: {e}") + json.dump(GameSave, json_file) + + def piece_to_dict(self, piece): + + if piece is None: + return {'piece': None, 'symbol': None, 'colour': None, 'moved': None, 'position': None} + return { + 'piece': type(piece).__name__, + 'colour': piece.colour, + 'moved': piece.moved, + 'position': piece.position + } def load_gamestate(self): """Load a game state from a file.""" @@ -182,55 +214,66 @@ def load_gamestate(self): if saved_games is None: return # Return to the main menu or handle appropriately try: - selection = int(self.view.get_user_input('Please enter the number of the game to load: ')) + selection = int(GameView.input_prompt('Please enter the number of the game to load: ')) game_save_path = saved_games[selection - 1] except (IndexError, ValueError): - self.view.display_message("Invalid selection. Please try again.") + GameView.display_message("Invalid selection. Please try again.") self.load_gamestate() if game_save_path.exists() and game_save_path.is_file(): - self.view.display_message(f"Loading game from {game_save_path}") + GameView.display_message(f"Loading game from {game_save_path}") with game_save_path.open("r") as file: GameSave = json.load(file) # Set the current player and symbol preference - self.model.currently_playing = GameSave['currently_playing'] - self.model.show_symbols = GameSave['show_symbols'] + self.board.currently_playing = GameSave['currently_playing'] + self.board.show_symbols = GameSave['show_symbols'] self.load_game = True - self.user_ai = GameAI(self.model, self.view, "Black", "White") + self.player_white = HumanPlayer('White', self.view, self) + if 'Ai' in GameSave: self.ai = True - self.model.ai = True + self.player_black = ComputerPlayer('Black', self.board, self.view, 'White', self) + else: + self.ai = False + self.player_black = HumanPlayer('Black', self.view, self) for i in range(64): # If the piece is None, the position is empty if GameSave['board_state'][str(i)]['piece'] == 'None': # Moved wird nicht übernommen - self.model.board_state[i] = None + self.board.board_state[i] = None else: # If the piece is not None, create a new piece object - if GameSave['board_state'][str(i)]['piece'] == 'Rooks': - self.model.board_state[i] = Rook(GameSave['board_state'][str(i)]['colour'], - i, self.model) - if GameSave['board_state'][str(i)]['piece'] == 'Knights': - self.model.board_state[i] = Knight(GameSave['board_state'][str(i)]['colour'], - i, self.model) - if GameSave['board_state'][str(i)]['piece'] == 'Bishops': - self.model.board_state[i] = Bishop(GameSave['board_state'][str(i)]['colour'], - i, self.model) - if GameSave['board_state'][str(i)]['piece'] == 'Queens': - self.model.board_state[i] = Queen(GameSave['board_state'][str(i)]['colour'], - i, self.model) - if GameSave['board_state'][str(i)]['piece'] == 'Kings': - self.model.board_state[i] = King(GameSave['board_state'][str(i)]['colour'], - i, self.model) - if GameSave['board_state'][str(i)]['piece'] == 'Pawns': - self.model.board_state[i] = Pawn(GameSave['board_state'][str(i)]['colour'], - i, self.model) + if GameSave['board_state'][str(i)]['piece'] == 'Rook': + self.board.board_state[i] = Rook(GameSave['board_state'][str(i)]['colour'], + i, self.board) + if GameSave['board_state'][str(i)]['piece'] == 'Knight': + self.board.board_state[i] = Knight(GameSave['board_state'][str(i)]['colour'], + i, self.board) + if GameSave['board_state'][str(i)]['piece'] == 'Bishop': + self.board.board_state[i] = Bishop(GameSave['board_state'][str(i)]['colour'], + i, self.board) + if GameSave['board_state'][str(i)]['piece'] == 'Queen': + self.board.board_state[i] = Queen(GameSave['board_state'][str(i)]['colour'], + i, self.board) + if GameSave['board_state'][str(i)]['piece'] == 'King': + self.board.board_state[i] = King(GameSave['board_state'][str(i)]['colour'], + i, self.board) + if GameSave['board_state'][str(i)]['piece'] == 'Pawn': + self.board.board_state[i] = Pawn(GameSave['board_state'][str(i)]['colour'], + i, self.board) else: - self.view.display_message("There's no Save File for your Game!") + GameView.display_message("There's no Save File for your Game!") return False - self.view.last_board = self.model.get_copy_board_state() - return True \ No newline at end of file + self.view.last_board = self.board.get_copy_board_state() + return True + + @staticmethod + def create_directory_if_not_exists(): + """ + Create the directory for saved games if it does not exist. + """ + Path("savedGames").mkdir(parents=True, exist_ok=True) \ No newline at end of file diff --git a/main.py b/main.py index e6d1661..d699ed8 100644 --- a/main.py +++ b/main.py @@ -1,8 +1,7 @@ - -from model import Model +from board import GameBoard if __name__ == "__main__": - model = Model() - model.game_manager.model = model - model.view.model = model - model.view.print_menu() \ No newline at end of file + board = GameBoard() + board.game_manager.board = board + board.view.board = board + board.view.print_menu() \ No newline at end of file diff --git a/model.py b/model.py deleted file mode 100644 index f192771..0000000 --- a/model.py +++ /dev/null @@ -1,74 +0,0 @@ -from view import GameView -from controller import GameManager -from pieces import Rook, Knight, Bishop, Pawn, King, Queen - -class Model: - - def __init__(self): - self.board_state = [None] * 64 - self.view = GameView() - self.game_manager = GameManager(self.view) - self.show_symbols = True - self.correlation = {f"{chr(65 + col)}{8 - row}": row * 8 + col for row in range(8) for col in range(8)} - self.pieces = [] - self.currently_playing = 'White' - self.ai = None - self.set_initial_pieces() - - def set_initial_pieces(self): - positions = {'Rook': [0, 7, 56, 63], 'Knight': [1, 6, 57, 62], 'Bishop': [2, 5, 58, 61], - 'Queen': [3, 59], 'King': [4, 60]} - for piece, places in positions.items(): - for position in places: - color = 'White' if position > 7 else 'Black' - self.board_state[position] = globals()[piece](color, position, self) - for i in range(8): - self.board_state[8 + i] = Pawn('Black', 8 + i, self) - self.board_state[48 + i] = Pawn('White', 48 + i, self) - for pos in range(16, 48): - self.board_state[pos] = None - self.pieces = [piece for piece in self.board_state if piece is not None] - - def toggle_player(self): - if self.currently_playing == 'White': - self.currently_playing = 'Black' - else: - self.currently_playing = 'White' - - def move_piece(self, start_pos, goal_pos, update=True): - piece = self.board_state[start_pos] - if piece and piece.colour == self.currently_playing: - if piece.check_legal_move(goal_pos): - self._update_positions(piece, start_pos, goal_pos, update) - self.toggle_player() - else: - print('Illegal move! Please try again!') - self.game_manager.get_input() - else: - print('There is no piece of your color on this space. Please try again!') - self.game_manager.get_input() - if update: - self.view.update_board() - - def _update_positions(self, piece, start_pos, goal_pos, update): - killed_piece = self.board_state[goal_pos] - self.board_state[goal_pos], self.board_state[start_pos] = piece, None - piece.position = goal_pos - if isinstance(piece, Pawn) and piece.upgrade(): - self.board_state[goal_pos] = Queen(self.currently_playing, goal_pos, self) - if killed_piece: - self.pieces.remove(killed_piece) - piece.moved = True - - def check_for_king(self): - king_alive = False - for i in self.pieces: - if type(i) == King and i.colour == self.currently_playing: - king_alive = True - break - return king_alive - - def get_copy_board_state(self, state=None): - if state is None: - state = self.board_state - return state.copy() \ No newline at end of file diff --git a/pieces.py b/pieces.py index 4afdaf4..259d40b 100644 --- a/pieces.py +++ b/pieces.py @@ -5,7 +5,7 @@ class Piece(metaclass=ABCMeta): def __init__(self): - self.model = None + self.board = None self.symbol = None self.colour = None self.moved = False @@ -18,7 +18,7 @@ def check_legal_move(self, position): def check_occupied_friendly(self, position, state): if position in range(64): if state[position] is not None: - if state[position].colour == self.model.currently_playing: + if state[position].colour == self.board.currently_playing: return True else: return False @@ -30,7 +30,7 @@ def check_occupied_friendly(self, position, state): def check_occupied_hostile(self, position, state): if position in range(64): if state[position] is not None: - if state[position].colour != self.model.currently_playing: + if state[position].colour != self.board.currently_playing: return True else: return False @@ -230,9 +230,9 @@ def check_diagonal(self, state): class Rook(Piece): - def __init__(self, colour, position, model): + def __init__(self, colour, position, board): Piece.__init__(self) - self.model = model + self.board = board self.colour = colour self.symbol = self.set_symbol() self.position = position @@ -248,7 +248,7 @@ def __init__(self, colour, position, model): ] def set_symbol(self): - if self.model.show_symbols: + if self.board.show_symbols: if self.colour == 'White': return '\u2656' else: @@ -261,7 +261,7 @@ def set_symbol(self): def check_legal_move(self, position, state="", return_all=False): if state == "": - state = self.model.board_state + state = self.board.board_state allowed = self.check_linear(state) @@ -275,9 +275,9 @@ def check_legal_move(self, position, state="", return_all=False): class Knight(Piece): - def __init__(self, colour, position, model): + def __init__(self, colour, position, board): Piece.__init__(self) - self.model = model + self.board = board self.colour = colour self.symbol = self.set_symbol() self.position = position @@ -294,7 +294,7 @@ def __init__(self, colour, position, model): ] def set_symbol(self): - if self.model.show_symbols: + if self.board.show_symbols: if self.colour == 'White': return '\u2658' else: @@ -309,7 +309,7 @@ def check_legal_move(self, position, state="", return_all=False): allowed = [] if state == "": - state = self.model.board_state + state = self.board.board_state row = math.floor(self.position / 8) column = self.position - (row * 7) - row @@ -341,9 +341,9 @@ def check_legal_move(self, position, state="", return_all=False): class Bishop(Piece): - def __init__(self, colour, position, model): + def __init__(self, colour, position, board): Piece.__init__(self) - self.model = model + self.board = board self.colour = colour self.symbol = self.set_symbol() self.position = position @@ -360,7 +360,7 @@ def __init__(self, colour, position, model): ] def set_symbol(self): - if self.model.show_symbols: + if self.board.show_symbols: if self.colour == 'White': return '\u2657' else: @@ -373,7 +373,7 @@ def set_symbol(self): def check_legal_move(self, position, state="", return_all=False): if state == "": - state = self.model.board_state + state = self.board.board_state allowed = self.check_diagonal(state) @@ -387,9 +387,9 @@ def check_legal_move(self, position, state="", return_all=False): class Pawn(Piece): - def __init__(self, colour, position, model): + def __init__(self, colour, position, board): Piece.__init__(self) - self.model = model + self.board = board self.colour = colour self.symbol = self.set_symbol() self.position = position @@ -406,7 +406,7 @@ def __init__(self, colour, position, model): ] def set_symbol(self): - if self.model.show_symbols: + if self.board.show_symbols: if self.colour == 'White': return '\u2659' else: @@ -420,7 +420,7 @@ def set_symbol(self): def check_legal_move(self, position, state="", return_all=False): allowed = [] if state == "": - state = self.model.board_state + state = self.board.board_state row = math.floor(self.position / 8) column = self.position - (row * 7) - row @@ -471,9 +471,9 @@ def upgrade(self): class Queen(Piece): - def __init__(self, colour, position, model): + def __init__(self, colour, position, board): Piece.__init__(self) - self.model = model + self.board = board self.colour = colour self.symbol = self.set_symbol() self.position = position @@ -489,7 +489,7 @@ def __init__(self, colour, position, model): ] def set_symbol(self): - if self.model.show_symbols: + if self.board.show_symbols: if self.colour == 'White': return '\u2655' else: @@ -502,7 +502,7 @@ def set_symbol(self): def check_legal_move(self, position, state="", return_all=False): if state == "": - state = self.model.board_state + state = self.board.board_state allowed = self.check_linear(state) + self.check_diagonal(state) if return_all: @@ -515,16 +515,16 @@ def check_legal_move(self, position, state="", return_all=False): class King(Piece): - def __init__(self, colour, position, model): + def __init__(self, colour, position, board): Piece.__init__(self) - self.model = model + self.board = board self.colour = colour self.symbol = self.set_symbol() self.position = position self.moved = False def set_symbol(self): - if self.model.show_symbols: + if self.board.show_symbols: if self.colour == 'White': return '\u2654' else: @@ -539,7 +539,7 @@ def check_legal_move(self, position, state="", return_all=False): allowed = [] if state == "": - state = self.model.board_state + state = self.board.board_state if not self.check_occupied_friendly(self.position - 9, state): allowed.append(self.position - 9) diff --git a/player.py b/player.py index 1f12943..87b29dc 100644 --- a/player.py +++ b/player.py @@ -1,24 +1,64 @@ +from view import GameView +from algorithm import GameAI + + class Player(): def __init__(self, color): self.color = color - self.pieces = [] - self.captured_pieces = [] - self.check = False - self.checkmate = False - self.stalemate = False - self.is_currently_playing = False + + def make_move(self, board): + """Abstract method to be implemented by subclasses to make a move.""" + raise NotImplementedError("Subclasses must implement this method") class HumanPlayer(Player): - def __init__(self, color): + def __init__(self, color, view, game_manager): super().__init__(color) - self.is_currently_playing = False + self.view = view + self.game_manager = game_manager + + def make_move(self, start_pos, goal_pos, board, update=True): + """Method to make a move.""" + piece = board.board_state[start_pos] + if piece and piece.colour == self.color: + if piece.check_legal_move(goal_pos): + board.update_positions(piece, start_pos, goal_pos, update) + board.toggle_player() + else: + GameView.display_message('Illegal move! Please try again!') + self.game_manager.get_input() + else: + GameView.display_message('There is no piece of your color on this space. Please try again!') + self.game_manager.get_input() + if update: + self.view.update_board() class ComputerPlayer(Player): - def __init__(self, color): + def __init__(self, color, board, view, enemy, game_manager): super().__init__(color) self.is_currently_playing = False + self.game_ai = GameAI(board, view, color, enemy) + self.view = view + self.game_manager = game_manager + + def make_move(self, board, update=True): + """Method to make a move.""" + start_pos, goal_pos = self.game_ai.move() + + piece = board.board_state[start_pos] + if piece and piece.colour == self.color: + if piece.check_legal_move(goal_pos): + board.update_positions(piece, start_pos, goal_pos, update) + board.toggle_player() + else: + GameView.display_message('Illegal move! Please try again!') + self.game_manager.get_input() + else: + GameView.display_message('There is no piece of your color on this space. Please try again!') + self.game_manager.get_input() + if update: + self.view.update_board() \ No newline at end of file diff --git a/prog2-chess b/prog2-chess new file mode 160000 index 0000000..8eea927 --- /dev/null +++ b/prog2-chess @@ -0,0 +1 @@ +Subproject commit 8eea9279d80725b6da2cb2317536ab649191dfb3 diff --git a/view.py b/view.py index 3d5da73..7a2c340 100644 --- a/view.py +++ b/view.py @@ -3,15 +3,15 @@ class GameView: def __init__(self): - self.model = None + self.board = None self.last_board = None def update_board(self, state=None): self.clear_console() - state = state or self.model.board_state - print(f'Current turn: {self.model.currently_playing}\n') + state = state or self.board.board_state + print(f'Current turn: {self.board.currently_playing}\n') self.print_board(state) - self.last_board = self.model.get_copy_board_state() + self.last_board = self.board.get_copy_board_state() def print_board(self, state): header = ' ' + ' '.join(' A B C D E F G H'.split()) @@ -53,6 +53,14 @@ def build_box(self, part): return ' \u2560' + '\u2550\u2550\u2550\u256C' * 7 + '\u2550\u2550\u2550\u2563' return ' \u255A' + '\u2550\u2550\u2550\u2569' * 7 + '\u2550\u2550\u2550\u255D' + @staticmethod + def display_message(message): + print(message) + + @staticmethod + def input_prompt(message): + return input(message) + @staticmethod def clear_console(): """ @@ -65,13 +73,14 @@ def clear_console(): os.system(command) def print_menu(self): + self.clear_console() print(""" ██████╗██╗ ██╗███████╗███████╗███████╗ ██╔════╝██║ ██║██╔════╝██╔════╝██╔════╝ ██║ ███████║█████╗ ███████╗███████╗ ██║ ██╔══██║██╔══╝ ╚════██║╚════██║ ╚██████╗██║ ██║███████╗███████║███████║ - ╚═════╝╚═╝ ╚══════╝╚══════╝╚══════╝ + ╚═════╝╚═╝ ╚═╝╚══════╝╚══════╝╚══════╝ by Christof & Manuel """) print(""" Welcome to Chess: @@ -79,4 +88,4 @@ def print_menu(self): 2) Load Game 3) Exit """) - self.model.game_manager.get_menu_choice() \ No newline at end of file + self.board.game_manager.get_menu_choice() \ No newline at end of file