def __init__(self, screen): """ Create pygame main menu widget. Params: screen, An initialised pygame.display. """ self.__screen = screen self.__font = pygame.font.Font(pygame.font.match_font("Topaz-8"), 15) # Create the menu. self.__menu = pygame.Surface(self.menu_size) self.__menu_rect = self.__menu.get_rect() self.__menu_rect.x = self.menu_x self.__menu_rect.y = self.menu_y # Set the title bold to stand out. self.__title_font = pygame.font.Font(pygame.font.match_font("Topaz-8"), 50) # Create the title. self.__title = Title(self.__screen, self.__title_font, "AI SETTINGS", 300, 50, True, True) # Create the sub title at the bottom to indicate what the difficulty is already set to. self.__sub_title_text = None self.__sub_title_font = pygame.font.Font( pygame.font.match_font("Topaz-8"), 20) self.__sub_title = Title(self.__screen, self.__sub_title_font, self.__sub_title_text, 300, 550, True, True) # Button positioning x_offset = 20 y_offset = 60 b_width = 360 b_height = 60 # EASY BUTTON self.__easy = Button(self.__menu, self.__font, b_width, b_height, x_offset, 0, "EASY", False, self.easy_button_bg, self.easy_button_fg) # MEDIUM BUTTON self.__medium = Button(self.__menu, self.__font, b_width, b_height, x_offset, y_offset, "MEDIUM", False, self.medium_button_bg, self.medium_button_fg) # HARD BUTTON self.__hard = Button(self.__menu, self.__font, b_width, b_height, x_offset, 2 * y_offset, "HARD", False, self.hard_button_bg, self.hard_button_fg) # IMPOSSIBLE BUTTON self.__impossible = Button(self.__menu, self.__font, b_width, b_height, x_offset, 3 * y_offset, "IMPOSSIBLE", False, self.impossible_button_bg, self.impossible_button_fg) # BACK BUTTON self.__back = Button(self.__menu, self.__font, b_width, b_height, x_offset, 4 * y_offset, "BACK", False, self.back_button_bg, self.back_button_fg) self.__exit = False # When the user wants to go back. self.__clicked = False # When ther user has clicked on a button.
def __init__(self, screen): """ Set up the game. Params: screen, root pygame surface. """ # Create the main screen surface. self.__screen = screen self.__screen.fill((0, 0, 85)) # Fill the background with colour. # Initialize Board self.__board_surface = pygame.Surface(BOARD_SIZE) self.__board_rect = self.__board_surface.get_rect() self.__board_rect.x, self.__board_rect.y = BOARD_POS # Font filepath. topaz_filepath = pygame.font.match_font(FONTFAMILY) # Create the font for the title. self.__title_font = pygame.font.Font(topaz_filepath, 30) # Create the title and center it so that it displays above the board. self.__title = Title(self.__screen, self.__title_font, "", 300, 50, True, True, red) # Creating the Buttons offset = 150 x = SCREEN_W // 2 y = SCREEN_H - 50 # Font for the buttons. self.__button_font = pygame.font.Font(topaz_filepath, 15) # Restart button for restarting the game, but keeping the same gamemode. self.__restart_button = Button(self.__screen, self.__button_font, 100, 30, x - offset, y, "RESTART", True, (light_red, red), (white, black)) # Quit button for backing out into the main menu. self.__quit_button = Button(self.__screen, self.__button_font, 100, 30, x + offset, y, "BACK", True, (light_red, red), (white, black)) # Single player variables self.__computer_peice = None # The computer's peice. self.__player_peice = None # The player's peice. # State variables self.__wait = True # Display the screen before running the minimax algorithm. self.__finished = False self.__board = Board() # Create an empty 3x3 board. self.__win = self.__is_draw = self.__game_over = self.__visible = False self.__current_peice = CROSS # The first peice to make a move.
def __init__(self, screen): """ Create pygame main menu widget. Params: screen, An initialised pygame.display. """ self.__screen = screen self.__font = pygame.font.Font(pygame.font.match_font("Topaz-8"), 25) # Create the menu. self.__menu = pygame.Surface(self.menu_size) self.__menu_rect = self.__menu.get_rect() self.__menu_rect.x = self.menu_x self.__menu_rect.y = self.menu_y # Set the title bold to stand out. self.__title_font = pygame.font.Font(pygame.font.match_font("Topaz-8"), 50) # Create the title. self.__title = Title(self.__screen, self.__title_font, "TIC TAC TOE", 300, 50, True, True) # Positioning of the buttons x_offset = 20 y_offset = 75 b_width = 360 b_height = 75 # VERSUS AI BUTTON self.__sp = Button(self.__menu, self.__font, b_width, b_height, x_offset, 0, "VERSUS A.I", False, self.sp_button_bg, self.sp_button_fg) # VERSUS HUMAN BUTTON self.__mp = Button(self.__menu, self.__font, b_width, b_height, x_offset, y_offset, "VERSUS HUMAN", False, self.mp_button_bg, self.mp_button_fg) # SETTINGS button self.__settings = Button(self.__menu, self.__font, b_width, b_height, x_offset, y_offset * 2, "A.I SETTINGS", False, self.settings_button_bg, self.settings_button_fg) # QUIT BUTTON self.__quit = Button(self.__menu, self.__font, b_width, b_height, x_offset, y_offset * 3, "QUIT", False, self.quit_button_bg, self.quit_button_fg) # Set true if clicked. self.__clicked = False
def __init__(self, screen): """ params: screen - The pygame.display surface. """ self.__screen = screen self.__font = pygame.font.Font(pygame.font.match_font("Topaz-8"), 25) self.__title_font = pygame.font.Font(pygame.font.match_font("Topaz-8"), 30) self.__title = Title(self.__screen, self.__title_font, "Who goes first?", 300, 50, True, True) # Create buttons self.__left_bttn = Button(self.__screen, self.__font, 190, 50, 200, 300, "YOU", True, self.left_button_bg, self.left_button_fg) self.__right_bttn = Button(self.__screen, self.__font, 190, 50, 400, 300, "COMPUTER", True, self.right_button_bg, self.right_button_fg)
class Game: def __init__(self, screen): """ Set up the game. Params: screen, root pygame surface. """ # Create the main screen surface. self.__screen = screen self.__screen.fill((0, 0, 85)) # Fill the background with colour. # Initialize Board self.__board_surface = pygame.Surface(BOARD_SIZE) self.__board_rect = self.__board_surface.get_rect() self.__board_rect.x, self.__board_rect.y = BOARD_POS # Font filepath. topaz_filepath = pygame.font.match_font(FONTFAMILY) # Create the font for the title. self.__title_font = pygame.font.Font(topaz_filepath, 30) # Create the title and center it so that it displays above the board. self.__title = Title(self.__screen, self.__title_font, "", 300, 50, True, True, red) # Creating the Buttons offset = 150 x = SCREEN_W // 2 y = SCREEN_H - 50 # Font for the buttons. self.__button_font = pygame.font.Font(topaz_filepath, 15) # Restart button for restarting the game, but keeping the same gamemode. self.__restart_button = Button(self.__screen, self.__button_font, 100, 30, x - offset, y, "RESTART", True, (light_red, red), (white, black)) # Quit button for backing out into the main menu. self.__quit_button = Button(self.__screen, self.__button_font, 100, 30, x + offset, y, "BACK", True, (light_red, red), (white, black)) # Single player variables self.__computer_peice = None # The computer's peice. self.__player_peice = None # The player's peice. # State variables self.__wait = True # Display the screen before running the minimax algorithm. self.__finished = False self.__board = Board() # Create an empty 3x3 board. self.__win = self.__is_draw = self.__game_over = self.__visible = False self.__current_peice = CROSS # The first peice to make a move. def board_pos(self, mouse_pos): """ Description: Get a valid board position from mouse position. Usage: Get the board cell clicked by the user. Params: mouse_pos, (x, y), the position of the mouse when clicked. Returns: x, y coordinates relative to the game board. """ # Check if the cursor position has collided with the board surface. if self.__board_rect.collidepoint(mouse_pos[0], mouse_pos[1]): # Now convert this mouse position into a valid position on the board. y = int((mouse_pos[1] - self.__board_rect.y) / (self.__board_rect.height // ROWS)) x = int((mouse_pos[0] - self.__board_rect.x) / (self.__board_rect.width // COLUMNS)) return x, y return False def __sp_setup(self, max_ai_depth, computer_first): """ Setup the singleplayer gamemode so that either the computer or the user can play first. Params: max_ai_depth, int, The maximum recursion depth the minimax will go before stopping. computer_first, bool, Will the computer go first? """ self.__max_ai_depth = max_ai_depth if computer_first: # if computer starts first, then bost the computer and current peice are the same. self.__computer_peice = self.__current_peice = CROSS self.__player_peice = NOUGHT else: self.__computer_peice = NOUGHT self.__current_peice = self.__player_peice = CROSS # Player starts first. def __input(self): """ Get input from the player. """ # It's the computers turn when the current peice is equal to the # peice used by the computer. computers_turn = self.__current_peice == self.__computer_peice # wait if we have to. # When it's the computer's turn, make a move. if not self.__wait and computers_turn: # Run the minimax algorithm and retrieve it's best move. move = get_move(self.__board, self.__max_ai_depth, self.__computer_peice, self.__player_peice) # Then place it. self.__board.place_move(self.__current_peice, move) # Check for ESC key press. # Check for any mouse input on the board. for event in pygame.event.get(): # Check if user has clicked. # Don't do this unless it's the players turn and the game is not over. if not computers_turn and not self.__game_over and event.type == MOUSEBUTTONUP and event.button == 1: # Get the cell chosen and place. coords = self.board_pos(event.pos) # Are these coords valid? if coords: self.__board.place_move(self.__current_peice, coords) elif event.type == KEYDOWN and event.key == K_ESCAPE: self.__finished = True # Update the state of the buttons. self.__restart_button.update(event) self.__quit_button.update(event) if self.__wait: self.__wait = False def __update(self): """ Update the state """ # Change the state. self.__winfo = self.__board.win(self.__current_peice) self.__win = (self.__winfo != False) self.__is_draw = self.__board.draw() self.__game_over = self.__win or self.__is_draw # Switch to the next peice if the game has not already ended. if not self.__game_over: self.__current_peice = self.__board.active_player def __draw(self): """ Draws the current state of the game. """ # Clear the screen. self.__screen.fill(black) # Draw board and peices. draw_board(self.__board_surface, self.__board, white, (17, 0, 34)) draw_peices(self.__board_surface, self.__board, red, blue) # Reset the title message. self.__title.text = "" self.__title.colours = (white, None) # Display a message to the user about the current state of the game, # i.e. when a player has won, who's turn it is, when a draw has occured. if self.__game_over: if self.__win: # Check if either a player or computer won. if self.__current_peice == self.__computer_peice: self.__title.text = "Computer has won!" else: self.__title.text = "Player {} has won!".format(self.__current_peice) # Set the colour based on the player. colour = red if self.__current_peice == NOUGHT else blue # change title colours (fg, bg) self.__title.colours = (colour, None) # Draw a line straight through the winning three peices. draw_win(self.__board_surface, self.__winfo, colour) elif self.__is_draw: self.__title.text = "Draw!" elif self.__current_peice == self.__computer_peice: self.__title.text = "Computer's turn!" else: self.__title.text = "Player {}'s turn!".format(self.__current_peice) self.__title.draw() # Draw the title. self.__restart_button.draw() # Draw 'restart' button. self.__quit_button.draw() # Draw 'quit' button. self.__screen.blit(self.__board_surface, self.__board_rect) def __restart(self): """ Restart the game state with the previous settings, i.e. computer_peice, player_peice stay the same. """ self.__screen.fill(black) # Fill screen black self.__board = Board() # Create new empty board. # Reset game state. self.__wait = True self.__winfo = self.__win = self.__is_draw = self.__game_over = False # Reset to the default starting peice: Nought. self.__current_peice = CROSS # Reset the button state. self.__restart_button.reset() self.__quit_button.reset() def __reset(self): """ Completely reset the game state. """ self.__screen.fill(black) # Fill the screen with black. self.__board = Board() # Create an empty 3x3 board. self.__wait = True self.__win = self.__is_draw = self.__game_over = self.__visible = False # Reset variables used in singleplayer self.__computer_peice = self.__player_peice = None # Reset to the default starting peice: Nought. self.__current_peice = CROSS # Reset the button state. self.__restart_button.reset() self.__quit_button.reset() def play(self, gamemode, max_ai_depth, computer_first = False): """ Starts a loop displaying the game on the given surface until the game is over. Params: gamemode, int, 1 for singleplayer, 2 for multiplayer. max_ai_depth, int, The maximum recursion depth the minimax can go before stopping. computer_first, bool, Will the computer play first if the gamemode is single player? Returns: None """ # Setup the game if gamemode == SINGLEPLAYER: self.__sp_setup(max_ai_depth, computer_first) # When computer goes first, the minimax algorithm will be ran immediately # in order to fix this, display one frame then run the minimax. self.__wait = True # Conditional for main loop. # When true this indicates that the user wants to go back to the # main menu. clock = pygame.time.Clock() self.__finished = False # Main loop. while not self.__finished: clock.tick(20) if self.__restart_button.clicked: self.__restart() elif self.__quit_button.clicked: self.__finished = True # Poll the event queue for input. self.__input() # Update the game state self.__update() # Draw self.__draw() # Update the screen to display everything that had just been drawn. pygame.display.update() # Reset the game state variables for the next use. self.__reset()
class MainMenu: """ Decription: The main menu for the game. Usage: Once the main function is called, the main menu will display itself. When a user clicks on a button, the main function will return an integer value. The integer value will correspond to the button which the user clicked. """ # Menu size, positioning, and colours. menu_size = 400, 300 # Dimensions of the surface containing the buttons. menu_pos = menu_x, menu_y = (100, 150) # The position of the menu surface. menu_bg = blue # Menu colours menu_fg = white # Multiplayer button colours. mp_button_bg = (light_red, red) mp_button_fg = (black, white) # Single player button colours. sp_button_bg = (light_green, green) sp_button_fg = (black, white) # Settings button colours. settings_button_bg = (light_navy, navy) settings_button_fg = (black, white) # Quit button colours. quit_button_bg = (light_orange, orange) quit_button_fg = (black, white) def __init__(self, screen): """ Create pygame main menu widget. Params: screen, An initialised pygame.display. """ self.__screen = screen self.__font = pygame.font.Font(pygame.font.match_font("Topaz-8"), 25) # Create the menu. self.__menu = pygame.Surface(self.menu_size) self.__menu_rect = self.__menu.get_rect() self.__menu_rect.x = self.menu_x self.__menu_rect.y = self.menu_y # Set the title bold to stand out. self.__title_font = pygame.font.Font(pygame.font.match_font("Topaz-8"), 50) # Create the title. self.__title = Title(self.__screen, self.__title_font, "TIC TAC TOE", 300, 50, True, True) # Positioning of the buttons x_offset = 20 y_offset = 75 b_width = 360 b_height = 75 # VERSUS AI BUTTON self.__sp = Button(self.__menu, self.__font, b_width, b_height, x_offset, 0, "VERSUS A.I", False, self.sp_button_bg, self.sp_button_fg) # VERSUS HUMAN BUTTON self.__mp = Button(self.__menu, self.__font, b_width, b_height, x_offset, y_offset, "VERSUS HUMAN", False, self.mp_button_bg, self.mp_button_fg) # SETTINGS button self.__settings = Button(self.__menu, self.__font, b_width, b_height, x_offset, y_offset * 2, "A.I SETTINGS", False, self.settings_button_bg, self.settings_button_fg) # QUIT BUTTON self.__quit = Button(self.__menu, self.__font, b_width, b_height, x_offset, y_offset * 3, "QUIT", False, self.quit_button_bg, self.quit_button_fg) # Set true if clicked. self.__clicked = False def update(self, event): """ Description: Updates the main menu once a MOUSEBUTTONDOWN/MOUSEMOTION event has been triggered. - Check the state of the buttons (by updating them with a new event from the queue). - Change the look of each button if the cursor has hovered over it. - If the button has been clicked then update the state of the menu. (This causes something to happen) Params: event - The most recent event from the event queue. """ # Reset the state of the menu. self.__clicked = False # Used for transforming the position of which the event occured: relative to the buttons. x, y = self.__menu_rect.x, self.__menu_rect.y # Convert x,position so that it is relative to the button. event.pos = (event.pos[0] - x, event.pos[1] - y) # Now update each button respectively. self.__sp.update(event) self.__mp.update(event) self.__settings.update(event) self.__quit.update(event) # Check if their states have changed and respectively change the state # of the menu. if self.__sp.clicked: self.__clicked = 1 # SINGLE PLAYER BUTTON CLICKED elif self.__mp.clicked: self.__clicked = 2 # MULTI PLAYER BUTTON CLICKED elif self.__settings.clicked: self.__clicked = 3 elif self.__quit.clicked: self.__clicked = 4 # QUIT BUTTON CLICKED def draw(self): self.__screen.fill((0, 0, 85)) self.__title.draw() self.__menu.fill(self.menu_bg) self.__sp.draw() self.__mp.draw() self.__settings.draw() self.__quit.draw() self.__screen.blit(self.__menu, self.__menu_rect) pygame.display.update() def __clear(self): self.__screen.fill(black) self.__clicked = False def main(self): """ Displays the main menu and returns an int representing the button pressed. """ self.__clear() while not self.__clicked: for event in pygame.event.get(): if event.type == QUIT: exit() elif event.type == MOUSEMOTION or event.type == MOUSEBUTTONUP: self.update(event) self.draw() return self.__clicked
class GameSettingsMenu: left_button_fg = (light_red, red) left_button_bg = (black, white) right_button_fg = (light_orange, orange) right_button_bg = (black, white) def __init__(self, screen): """ params: screen - The pygame.display surface. """ self.__screen = screen self.__font = pygame.font.Font(pygame.font.match_font("Topaz-8"), 25) self.__title_font = pygame.font.Font(pygame.font.match_font("Topaz-8"), 30) self.__title = Title(self.__screen, self.__title_font, "Who goes first?", 300, 50, True, True) # Create buttons self.__left_bttn = Button(self.__screen, self.__font, 190, 50, 200, 300, "YOU", True, self.left_button_bg, self.left_button_fg) self.__right_bttn = Button(self.__screen, self.__font, 190, 50, 400, 300, "COMPUTER", True, self.right_button_bg, self.right_button_fg) def draw(self): self.__screen.fill(blue) self.__title.draw() self.__left_bttn.draw() self.__right_bttn.draw() pygame.display.update() def update(self, event): # Reset state of the menu self.__clicked = False self.__left_bttn.update(event) self.__right_bttn.update(event) if self.__left_bttn.clicked: self.__clicked = 1 elif self.__right_bttn.clicked: self.__clicked = 2 def __clear(self): self.__screen.fill(black) self.__clicked = False def main(self): self.__clear() while not self.__clicked: for event in pygame.event.get(): if event.type == QUIT: exit() elif event.type == KEYDOWN and event.key == K_ESCAPE: return False elif event.type == MOUSEMOTION or event.type == MOUSEBUTTONUP: self.update(event) self.draw() return self.__clicked
class AISettingsMenu: # Menu size, positioning, and colours. menu_size = 400, 300 menu_pos = menu_x, menu_y = (100, 150) menu_bg = blue menu_fg = white # Easy button colours. easy_button_bg = (light_green, green) easy_button_fg = (black, white) # Medium button colours. medium_button_bg = (light_orange, orange) medium_button_fg = (black, white) # Hard button colours. hard_button_bg = (light_red, red) hard_button_fg = (black, white) # Impossible button colours. impossible_button_bg = (dark_red, darker_red) impossible_button_fg = (black, white) # Back button colours. back_button_bg = (light_cyan, cyan) back_button_fg = (black, white) def __init__(self, screen): """ Create pygame main menu widget. Params: screen, An initialised pygame.display. """ self.__screen = screen self.__font = pygame.font.Font(pygame.font.match_font("Topaz-8"), 15) # Create the menu. self.__menu = pygame.Surface(self.menu_size) self.__menu_rect = self.__menu.get_rect() self.__menu_rect.x = self.menu_x self.__menu_rect.y = self.menu_y # Set the title bold to stand out. self.__title_font = pygame.font.Font(pygame.font.match_font("Topaz-8"), 50) # Create the title. self.__title = Title(self.__screen, self.__title_font, "AI SETTINGS", 300, 50, True, True) # Create the sub title at the bottom to indicate what the difficulty is already set to. self.__sub_title_text = None self.__sub_title_font = pygame.font.Font( pygame.font.match_font("Topaz-8"), 20) self.__sub_title = Title(self.__screen, self.__sub_title_font, self.__sub_title_text, 300, 550, True, True) # Button positioning x_offset = 20 y_offset = 60 b_width = 360 b_height = 60 # EASY BUTTON self.__easy = Button(self.__menu, self.__font, b_width, b_height, x_offset, 0, "EASY", False, self.easy_button_bg, self.easy_button_fg) # MEDIUM BUTTON self.__medium = Button(self.__menu, self.__font, b_width, b_height, x_offset, y_offset, "MEDIUM", False, self.medium_button_bg, self.medium_button_fg) # HARD BUTTON self.__hard = Button(self.__menu, self.__font, b_width, b_height, x_offset, 2 * y_offset, "HARD", False, self.hard_button_bg, self.hard_button_fg) # IMPOSSIBLE BUTTON self.__impossible = Button(self.__menu, self.__font, b_width, b_height, x_offset, 3 * y_offset, "IMPOSSIBLE", False, self.impossible_button_bg, self.impossible_button_fg) # BACK BUTTON self.__back = Button(self.__menu, self.__font, b_width, b_height, x_offset, 4 * y_offset, "BACK", False, self.back_button_bg, self.back_button_fg) self.__exit = False # When the user wants to go back. self.__clicked = False # When ther user has clicked on a button. def get_difficulty_from_depth(self, depth): """ Convert the current depth to the name of the Difficulty Params: depth - int Returns: str message """ if depth == EASY_DEPTH: return "EASY" elif depth == MEDIUM_DEPTH: return "MEDIUM" elif depth == HARD_DEPTH: return "HARD" elif depth == IMPOSSIBLE_DEPTH: return "IMPOSSIBLE" def update(self, event): """ Description: Updates the main menu once a MOUSEBUTTONDOWN/MOUSEMOTION event has been triggered. - Check the state of the buttons (by updating them with a new event from the queue). - Change the look of each button if the cursor has hovered over it. - If the button has been clicked then update the state of the menu. (This causes something to happen) Params: event - The most recent event from the event queue. """ # Reset the state of the menu. self.__clicked = False # Used for transforming the position of which the event occured: relative to the buttons. x, y = self.__menu_rect.x, self.__menu_rect.y # Convert x,position so that it is relative to the button. event.pos = (event.pos[0] - x, event.pos[1] - y) # Now update each button respectively. self.__easy.update(event) self.__medium.update(event) self.__hard.update(event) self.__impossible.update(event) self.__back.update(event) # Check if their states have changed and respectively change the state # of the menu. if self.__easy.clicked: self.__clicked = EASY_DEPTH # SINGLE PLAYER BUTTON CLICKED elif self.__medium.clicked: self.__clicked = MEDIUM_DEPTH # MULTI PLAYER BUTTON CLICKED elif self.__hard.clicked: self.__clicked = HARD_DEPTH # QUIT BUTTON CLICKED elif self.__impossible.clicked: self.__clicked = IMPOSSIBLE_DEPTH elif self.__back.clicked: self.__exit = True def draw(self): self.__screen.fill((0, 0, 85)) self.__title.draw() self.__sub_title.draw() self.__menu.fill(self.menu_bg) self.__easy.draw() self.__medium.draw() self.__hard.draw() self.__impossible.draw() self.__back.draw() self.__screen.blit(self.__menu, self.__menu_rect) pygame.display.update() def __clear(self): self.__screen.fill(black) # Reset the state self.__clicked = False self.__exit = False def main(self, current_depth): """ Displays the main menu and returns an int representing the button pressed. """ # Clear the screen before drawing. self.__clear() # Change the sub title text self.__sub_title.text = "Current difficulty: {}".format( self.get_difficulty_from_depth(current_depth)) while not (self.__clicked or self.__exit): for event in pygame.event.get(): if event.type == QUIT: # User wants to exit the game. exit() elif event.type == KEYDOWN and event.key == K_ESCAPE: self.__exit = True elif event.type == MOUSEMOTION or event.type == MOUSEBUTTONUP: self.update(event) self.draw() if self.__exit: return current_depth return self.__clicked