def __init__(self, surf): self.surf = surf self.font = pygame.font.Font('resources/Amatic-Bold.ttf', 120) self.title = self.font.render('Teeko Rules', True, BLACK) self.title_rect = self.title.get_rect() self.title_rect.center = (SCREEN_SIZE[0] / 2, 130) with open('resources/rules.txt', 'r') as f: self.rulestxt = f.readlines() self.font = pygame.font.Font('resources/Amatic-Bold.ttf', 40) self.leave_btn = Button(LEAVE_BTN_POS, LEAVE_BTN_SIZE, 'Leave', BACKGROUND)
def initRender(self): self.players_tokens = [] for k in range(2): x = (SCREEN_SIZE[0] - SQUARE_WIDTH * GRID_SIZE) // 4 + ( k * int(SQUARE_WIDTH * GRID_SIZE + (SCREEN_SIZE[0] - SQUARE_WIDTH * GRID_SIZE) / 2)) self.players_tokens.extend( (k + 1, m + 1, TokenView(self.surf, x, m * (TOKEN_RADIUS * 2 + 30) + 250, COLORS[self.players[k].color_index]), self.players[k].ptype != 0) for m in range(4)) self.last_played = [None, None] self.font = pygame.font.Font('resources/Amatic-Bold.ttf', 50) self.player_one = self.font.render('Player 1', True, BLACK) self.player_one_rect = self.player_one.get_rect() self.player_one_rect.center = GAME_PLAYER_ONE_CENTER self.player_two = self.font.render('Player 2', True, BLACK) self.player_two_rect = self.player_two.get_rect() self.player_two_rect.center = GAME_PLAYER_TWO_CENTER self.currentlyplaying = pygame.image.load("resources/hourglass.png") self.angle = 0 self.back_btn = Button(BACK_BTN_POS, BACK_BTN_SIZE, '< Back', BACKGROUND) self.plate = Plate(self.surf, PLATE_POS, PLATE_W) self.error_txt_1 = self.font.render( 'You can\'t place your token here :/', True, BLACK) self.error_txt_1_rect = self.error_txt_1.get_rect() self.error_txt_1_rect.center = ERROR_TXT_CENTER self.error_txt_2 = self.font.render( 'You can\'t move your tokens yet :/', True, BLACK) self.error_txt_2_rect = self.error_txt_2.get_rect() self.error_txt_2_rect.center = ERROR_TXT_CENTER self.font = pygame.font.Font('resources/Amatic-Bold.ttf', 80) self.winner_annouced = self.font.render( f'Player {self.turn_to.idt} won !', True, BLACK) self.winner_annouced_rect = self.winner_annouced.get_rect() self.winner_annouced_rect.center = WINNER_CENTER self.retry_btn = Button(RETRY_BTN_POS, RETRY_BTN_SIZE, 'Retry', BACKGROUND) self.goto_menu = Button(MENU_BTN_POS, MENU_BTN_SIZE, 'Go to Menu', BACKGROUND) self.leave_game = Button(QUIT_BTN_POS, QUIT_BTN_SIZE, 'Quit', BACKGROUND) self.selected_token = None self.selection_offset_y = 0 self.selection_offset_x = 0
class Rules: def __init__(self, surf): self.surf = surf self.font = pygame.font.Font('resources/Amatic-Bold.ttf', 120) self.title = self.font.render('Teeko Rules', True, BLACK) self.title_rect = self.title.get_rect() self.title_rect.center = (SCREEN_SIZE[0] / 2, 130) with open('resources/rules.txt', 'r') as f: self.rulestxt = f.readlines() self.font = pygame.font.Font('resources/Amatic-Bold.ttf', 40) self.leave_btn = Button(LEAVE_BTN_POS, LEAVE_BTN_SIZE, 'Leave', BACKGROUND) def parseEvent(self, event): if event.type == pygame.MOUSEBUTTONDOWN: pos = pygame.mouse.get_pos() if self.leave_btn.onButton(pos): return CODE_TO_MENU def render(self): self.surf.fill(BACKGROUND) if self.leave_btn.get_rect().collidepoint(pygame.mouse.get_pos()): self.leave_btn.hover(self.surf) else: self.leave_btn.drawRect(self.surf) self.surf.blit(self.title, self.title_rect) i = 0 for lines in self.rulestxt: line = self.font.render(lines, True, BLACK) line_rect = line.get_rect() line_rect.center = (SCREEN_SIZE[0] / 2, SCREEN_SIZE[1] / 2 - 100 + i * 70) self.surf.blit(line, line_rect) i += 1
class Teeko: def __init__(self, surf=None): self.grid = None self.index_difficulty = None self.players = None self.turn_to = None self.minmax_thread = None self.kill_thread = False self.turn = None self.render_enabled = surf is not None self.surf = surf self.players_tokens = None self.last_played = None self.font = None self.player_one = None self.player_one_rect = None self.player_two = None self.player_two_rect = None self.back_btn = None self.plate = None self.error_txt_1 = None self.error_txt_1_rect = None self.error_txt_2 = None self.error_txt_2_rect = None self.currentlyplaying = None self.currentlyplaying_rect = None self.angle = None self.winner_annouced = None self.winner_annouced_rect = None self.retry_btn = None self.goto_menu = None self.leave_game = None self.selected_token = None self.selection_offset_y = None self.selection_offset_x = None self.game_ended = False self.error_trigger_code = False def reset(self, players=None, index_difficulty=(1, 1)): self.grid = np.zeros(GRID_SIZE * GRID_SIZE, dtype=np.int) self.index_difficulty = index_difficulty if players is not None: self.players = players for player in self.players: player.tokens.clear() else: self.players = [Player(i, 1, i - 1) for i in [1, 2]] self.turn_to = randomChoice(self.players) self.minmax_thread = None self.kill_thread = False self.turn = 0 if self.render_enabled: self.initRender() def initRender(self): self.players_tokens = [] for k in range(2): x = (SCREEN_SIZE[0] - SQUARE_WIDTH * GRID_SIZE) // 4 + ( k * int(SQUARE_WIDTH * GRID_SIZE + (SCREEN_SIZE[0] - SQUARE_WIDTH * GRID_SIZE) / 2)) self.players_tokens.extend( (k + 1, m + 1, TokenView(self.surf, x, m * (TOKEN_RADIUS * 2 + 30) + 250, COLORS[self.players[k].color_index]), self.players[k].ptype != 0) for m in range(4)) self.last_played = [None, None] self.font = pygame.font.Font('resources/Amatic-Bold.ttf', 50) self.player_one = self.font.render('Player 1', True, BLACK) self.player_one_rect = self.player_one.get_rect() self.player_one_rect.center = GAME_PLAYER_ONE_CENTER self.player_two = self.font.render('Player 2', True, BLACK) self.player_two_rect = self.player_two.get_rect() self.player_two_rect.center = GAME_PLAYER_TWO_CENTER self.currentlyplaying = pygame.image.load("resources/hourglass.png") self.angle = 0 self.back_btn = Button(BACK_BTN_POS, BACK_BTN_SIZE, '< Back', BACKGROUND) self.plate = Plate(self.surf, PLATE_POS, PLATE_W) self.error_txt_1 = self.font.render( 'You can\'t place your token here :/', True, BLACK) self.error_txt_1_rect = self.error_txt_1.get_rect() self.error_txt_1_rect.center = ERROR_TXT_CENTER self.error_txt_2 = self.font.render( 'You can\'t move your tokens yet :/', True, BLACK) self.error_txt_2_rect = self.error_txt_2.get_rect() self.error_txt_2_rect.center = ERROR_TXT_CENTER self.font = pygame.font.Font('resources/Amatic-Bold.ttf', 80) self.winner_annouced = self.font.render( f'Player {self.turn_to.idt} won !', True, BLACK) self.winner_annouced_rect = self.winner_annouced.get_rect() self.winner_annouced_rect.center = WINNER_CENTER self.retry_btn = Button(RETRY_BTN_POS, RETRY_BTN_SIZE, 'Retry', BACKGROUND) self.goto_menu = Button(MENU_BTN_POS, MENU_BTN_SIZE, 'Go to Menu', BACKGROUND) self.leave_game = Button(QUIT_BTN_POS, QUIT_BTN_SIZE, 'Quit', BACKGROUND) self.selected_token = None self.selection_offset_y = 0 self.selection_offset_x = 0 def won(self): self.game_ended = True self.winner_annouced = self.font.render( f'Player {self.turn_to.idt} won !', True, BLACK) # print(f'Game finished. Player {self.turn_to.idt} won\n', self.rectGrid()) def calculating(self): return self.minmax_thread is not None def killMinMax(self): if self.calculating(): self.kill_thread = True while self.calculating(): continue def getAligned(self, player): longest_alignment = 1 for token in player.tokens: l_shape_first_direction = 0 for direction in SURROUNDING: current_alignment = 1 alignment_contain_zero = False zero_is_last = False followed_by_two_0 = False current_cell = token module_current_cell = current_cell % 5 next_cell = token + direction IN_GRID = 0 <= next_cell < 25 and ( (module_current_cell != 0 and module_current_cell != 4) or (current_cell + next_cell) % 5 != 4) while IN_GRID: next_cell_value = self.grid[next_cell] if next_cell_value == 0: if alignment_contain_zero: followed_by_two_0 = True break else: alignment_contain_zero = True zero_is_last = True elif next_cell_value == player.idt: zero_is_last = False current_alignment += 1 else: break current_cell = next_cell module_current_cell = current_cell % 5 next_cell = current_cell + direction IN_GRID = 0 <= next_cell < 25 and ( (module_current_cell != 0 and module_current_cell != 4) or (current_cell + next_cell) % 5 != 4) if current_alignment == 4: if alignment_contain_zero and not zero_is_last: current_alignment = 3 else: current_alignment = 4 elif current_alignment == 3: if not alignment_contain_zero: current_cell = token module_current_cell = current_cell % 5 next_cell = current_cell - direction IN_GRID = 0 <= next_cell < 25 and ( (module_current_cell != 0 and module_current_cell != 4) or (current_cell + next_cell) % 5 != 4) if IN_GRID and self.grid[next_cell] == 0: current_alignment = 3 else: current_alignment = 1 square_alignment, l_shape_first_direction = self.squareTest( l_shape_first_direction, direction, token, player.idt) current_alignment = max(current_alignment, square_alignment) else: current_alignment = 3 elif current_alignment == 2: if not alignment_contain_zero or zero_is_last: square_alignment, l_shape_first_direction = self.squareTest( l_shape_first_direction, direction, token, player.idt) current_alignment = max(current_alignment, square_alignment) if current_alignment == 2: if not followed_by_two_0: current_cell = token module_current_cell = current_cell % 5 next_cell = current_cell - direction IN_GRID = 0 <= next_cell < 25 and ( (module_current_cell != 0 and module_current_cell != 4) or (current_cell + next_cell) % 5 != 4) if IN_GRID and self.grid[next_cell] == 0: if not zero_is_last: current_cell = next_cell module_current_cell = current_cell % 5 next_cell = current_cell - direction IN_GRID = 0 <= next_cell < 25 and ( (module_current_cell != 0 and module_current_cell != 4) or (current_cell + next_cell) % 5 != 4 ) if not IN_GRID or self.grid[ next_cell] != 0: current_alignment = 1 else: current_alignment = 1 elif alignment_contain_zero: current_alignment = 1 if current_alignment > 2: return current_alignment if current_alignment > longest_alignment: longest_alignment = current_alignment return longest_alignment def squareTest(self, l_shape_first_direction, direction, token, idt): current_alignment = 3 if l_shape_first_direction == 0: current_alignment = 1 if direction != 6: l_shape_first_direction = direction elif LSTTT[l_shape_first_direction] != direction: current_alignment = 1 if direction == 5 and l_shape_first_direction == 1: fourth_cell_value = self.grid[token + 6] if fourth_cell_value == idt: current_alignment = 4 elif fourth_cell_value == 0: current_alignment = 3 else: fourth_cell_value = self.grid[token + LSFTT[l_shape_first_direction]] if fourth_cell_value != 0: current_alignment = 1 return current_alignment, l_shape_first_direction def addToken(self, player, pos): self.grid[pos] = player.idt bisect.insort(player.tokens, pos) def removeToken(self, player, pos): self.grid[pos] = 0 for token in player.tokens: if token == pos: player.tokens.remove(token) break def moveToken(self, player, pos, direction): self.grid[pos] = 0 self.grid[pos + direction] = player.idt for i, token in enumerate(player.tokens): if token == pos: player.tokens[i] += direction player.tokens.sort() break def getPossibleMove(self, token): token_moves = [] module_pos = token % 5 SAFE_ZONE = (module_pos != 0 and module_pos != 4) for shift in DIRECTIONS: token_plus = token + shift if 0 <= token_plus < 25 and self.grid[token_plus] == 0 and ( SAFE_ZONE or (token_plus + token) % 5 != 4): token_moves.append([1, token, shift]) return token_moves def getAllMoves(self, player): if len(player.tokens) < 4: moves = [[0, pos, 0] for pos in self.getAllEmpty()] else: moves = [] for token in player.tokens: moves.extend(self.getPossibleMove(token)) return moves def getAllEmpty(self): return np.where(self.grid == 0)[0] def over(self, align_score=None): if align_score is None: align_score = [self.getAligned(player) for player in self.players] return max(align_score) >= 4 def getScore(self, align_score=None, depth=0): if align_score is None: align_score = [self.getAligned(player) for player in self.players] p1, p2 = align_score if p1 >= 4: return 20 * (depth + 1) elif p2 >= 4: return -20 * (depth + 1) else: w1, w2 = (1.50, 1.75) if self.turn_to.idt == 1 else (1.75, 1.50) return round((p1**w1) - (p2**w2), 4) # move = (0, pos token à placer, 0) ou (1, pos token à deplacer, direction) def minMax(self, depth, alpha, beta, player): if self.kill_thread: self.minmax_thread = None raise SystemExit() DEPTH_IS_ZERO = depth == 0 DEPTH_IS_MAX = depth == MAX_DEPTH[self.index_difficulty[ self.turn_to.idt - 1]] align_score = [self.getAligned(p) for p in self.players] if DEPTH_IS_ZERO or self.over(align_score): return self.getScore(align_score, depth) if player.idt == 1: max_score = -np.inf max_score_move = None for move in self.getAllMoves(player): move_index, pos, direction = move if move_index == 0: self.addToken(player, pos) else: self.moveToken(player, pos, direction) score = self.minMax(depth - 1, alpha, beta, self.players[abs(player.idt - 2)]) if move_index == 0: self.removeToken(player, pos) else: self.moveToken(player, pos + direction, -direction) if score > max_score: max_score = score max_score_move = move alpha = max(alpha, score) if beta <= alpha: break if not DEPTH_IS_MAX: return max_score else: return max_score, max_score_move else: min_score = np.inf min_score_move = None for move in self.getAllMoves(player): move_index, pos, direction = move if move_index == 0: self.addToken(player, pos) else: self.moveToken(player, pos, direction) score = self.minMax(depth - 1, alpha, beta, self.players[abs(player.idt - 2)]) if move_index == 0: self.removeToken(player, pos) else: self.moveToken(player, pos + direction, -direction) if score < min_score: min_score = score min_score_move = move beta = min(beta, score) if beta <= alpha: break if not DEPTH_IS_MAX: return min_score else: return min_score, min_score_move def makeMove(self, move): if move[0] == 0: _, position, _ = move self.addToken(self.turn_to, position) if self.render_enabled: AI_tokens = [ token for token in self.players_tokens if token[0] == self.turn_to.idt ] for drop_zones in self.plate.playable_zones: div, mod = divmod(position, 5) if drop_zones.abscisse == mod and drop_zones.ordonne == div: drop_zones.available = False token_view = AI_tokens[len(self.turn_to.tokens) - 1][2] token_view.placeToken((drop_zones.x, drop_zones.y)) self.last_played[self.turn_to.idt - 1] = token_view break else: _, token, direction = move self.moveToken(self.turn_to, token, direction) if self.render_enabled: AI_tokens = [ token for token in self.players_tokens if token[0] == self.turn_to.idt ] current_drop_zone, future_drop_zone, i = None, None, 0 while current_drop_zone is None or future_drop_zone is None: drop_zone = self.plate.playable_zones[i] div, mod = divmod(token, 5) if current_drop_zone is None and drop_zone.abscisse == mod and drop_zone.ordonne == div: current_drop_zone = drop_zone div, mod = divmod(token + direction, 5) if future_drop_zone is None and drop_zone.abscisse == mod and drop_zone.ordonne == div: future_drop_zone = drop_zone i += 1 current_drop_zone.available = True for token in AI_tokens: if token[2].initial_x == current_drop_zone.x and token[ 2].initial_y == current_drop_zone.y: token[2].placeToken( (future_drop_zone.x, future_drop_zone.y)) self.last_played[self.turn_to.idt - 1] = token[2] future_drop_zone.available = False break self.turn_to.has_played = True self.minmax_thread = None def AI_handler(self): # print('\nGrid before : \n', self.rectGrid()) score, move = self.minMax( MAX_DEPTH[self.index_difficulty[self.turn_to.idt - 1]], -np.inf, np.inf, self.turn_to) # print('Score : ', score, ' | Selected move : ', move) self.makeMove(move) def update(self): if not self.game_ended: if not self.turn_to.has_played: if self.turn_to.ptype == 1 and not self.calculating(): if self.turn != 0: self.minmax_thread = threading.Thread( target=self.AI_handler) self.minmax_thread.start() else: difficulty = self.index_difficulty[self.turn_to.idt - 1] if difficulty == 0 or (difficulty == 1 and np.random.random() > .5): self.makeMove(self.getRandomMove()) else: self.makeMove([ 0, np.random.choice( [6, 7, 8, 11, 12, 13, 16, 17, 18]), 0 ]) else: self.turn += 1 # print(f'P{self.turn_to.idt} align : {self.getAligned(self.turn_to)}, tokens : {self.turn_to.tokens}') if self.over(): self.won() self.turn_to.has_played = False self.turn_to = self.players[abs(self.turn_to.idt - 2)] def getRandomMove(self): return randomChoice(self.getAllMoves(self.turn_to)) def render(self): mouse_pos = pygame.mouse.get_pos() self.surf.fill(BACKGROUND) self.surf.blit(self.player_one, self.player_one_rect) self.surf.blit(self.player_two, self.player_two_rect) if self.error_trigger_code == 1: self.surf.blit(self.error_txt_1, self.error_txt_1_rect) elif self.error_trigger_code == 2: self.surf.blit(self.error_txt_2, self.error_txt_2_rect) if self.turn_to.idt == 1: self.angle = (self.angle + 2) % 360 rotated_surf = pygame.transform.rotate(self.currentlyplaying, self.angle) self.currentlyplaying_rect = rotated_surf.get_rect( center=HOURGLASS_CENTER_ONE) else: self.angle = (self.angle + 2) % 360 rotated_surf = pygame.transform.rotate(self.currentlyplaying, self.angle) self.currentlyplaying_rect = rotated_surf.get_rect( center=HOURGLASS_CENTER_TWO) self.surf.blit(rotated_surf, self.currentlyplaying_rect) if self.back_btn.get_rect().collidepoint(pygame.mouse.get_pos()): self.back_btn.hover(self.surf) else: self.back_btn.drawRect(self.surf) self.plate.render() for token_view in self.players_tokens: token_view[2].render(highlighted=token_view[2] in self.last_played) if self.game_ended: background_end = pygame.Surface(SCREEN_SIZE) background_end.fill(GRAY) background_end.set_alpha(150) self.surf.blit(background_end, (0, 0)) bandeau = pygame.Surface((SCREEN_SIZE[0], 400)) bandeau.fill(BACKGROUND) bandeau.set_alpha(210) self.surf.blit(bandeau, BANDEAU_POSITION) if self.retry_btn.get_rect().collidepoint(mouse_pos): self.retry_btn.hover(self.surf) else: self.retry_btn.drawRect(self.surf) if self.goto_menu.get_rect().collidepoint(mouse_pos): self.goto_menu.hover(self.surf) else: self.goto_menu.drawRect(self.surf) if self.leave_game.get_rect().collidepoint(mouse_pos): self.leave_game.hover(self.surf) else: self.leave_game.drawRect(self.surf) self.surf.blit(self.winner_annouced, self.winner_annouced_rect) def parseEvent(self, event): mouse_pos = pygame.mouse.get_pos() if event.type == pygame.MOUSEBUTTONDOWN: if self.back_btn.onButton(mouse_pos): return CODE_TO_MENU if self.game_ended: if self.goto_menu.onButton(mouse_pos): self.game_ended = False return CODE_TO_MENU if self.leave_game.onButton(mouse_pos): pygame.quit() self.killMinMax() quit() if self.retry_btn.onButton(mouse_pos): self.game_ended = False return CODE_TO_GAME for token_view in self.players_tokens: if token_view[2].onToken(mouse_pos) and not token_view[ 3] and self.turn_to.idt == token_view[0]: self.selected_token = token_view[2] self.selection_offset_x = token_view[2].x - mouse_pos[0] self.selection_offset_y = token_view[2].y - mouse_pos[1] if event.type == pygame.MOUSEBUTTONUP: if self.selected_token is not None: for drop_zone in self.plate.playable_zones: if drop_zone.isAvailable() and drop_zone.onPropzone(mouse_pos) and \ drop_zone.legitMove(self.selected_token, len(self.turn_to.tokens)): current_drop_zone, i = None, 0 while i < len(self.plate.playable_zones ) and current_drop_zone is None: iter_drop_zone = self.plate.playable_zones[i] if self.selected_token.initial_x == iter_drop_zone.x and \ self.selected_token.initial_y == iter_drop_zone.y: current_drop_zone = iter_drop_zone i += 1 if current_drop_zone is not None: if len(self.turn_to.tokens) < 4: self.error_trigger_code = 2 break self.removeToken( self.turn_to, 5 * current_drop_zone.ordonne + current_drop_zone.abscisse) current_drop_zone.available = True self.addToken( self.turn_to, 5 * drop_zone.ordonne + drop_zone.abscisse) self.selected_token.placeToken( (drop_zone.x, drop_zone.y)) self.last_played[self.turn_to.idt - 1] = self.selected_token drop_zone.available = False self.selected_token = None self.turn_to.has_played = True self.error_trigger_code = 0 break if self.selected_token is not None: self.selected_token.placeToken( (self.selected_token.initial_x, self.selected_token.initial_y)) self.selected_token = None self.error_trigger_code = max(self.error_trigger_code, 1) if event.type == pygame.MOUSEMOTION: if self.selected_token is not None: self.selected_token.drag( (self.selection_offset_x + mouse_pos[0], self.selection_offset_y + mouse_pos[1])) def rectGrid(self): return self.grid.reshape((GRID_SIZE, GRID_SIZE)) def print(self): print(self.rectGrid())
class Teeko: def __init__(self, surf=None): self.grid = None self.index_difficulty = None self.players = None self.turn_to = None # indicate which player needs to play self.minmax_thread = None self.kill_thread = False self.turn = None self.render_enabled = surf is not None self.surf = surf self.players_tokens = None self.last_played = None self.font = None self.player_one = None self.player_one_rect = None self.player_two = None self.player_two_rect = None self.back_btn = None self.plate = None self.error_txt_1 = None self.error_txt_1_rect = None self.error_txt_2 = None self.error_txt_2_rect = None self.currentlyplaying = None self.currentlyplaying_rect = None self.angle = None self.winner_annouced = None self.winner_annouced_rect = None self.retry_btn = None self.goto_menu = None self.leave_game = None self.selected_token = None self.selection_offset_y = None self.selection_offset_x = None self.game_ended = False self.error_trigger_code = False # start a new game def reset(self, players=None, index_difficulty=(1, 1)): self.grid = np.zeros(GRID_SIZE * GRID_SIZE, dtype=np.int) # grid has only one dimension for better performances self.index_difficulty = index_difficulty if players is not None: self.players = players for player in self.players: player.tokens.clear() else: self.players = [Player(i, 1, i - 1) for i in [1, 2]] self.turn_to = randomChoice(self.players) self.minmax_thread = None self.kill_thread = False self.turn = 0 if self.render_enabled: self.initRender() def initRender(self): self.players_tokens = [] for k in range(2): x = (SCREEN_SIZE[0] - SQUARE_WIDTH * GRID_SIZE) // 4 + (k * int( SQUARE_WIDTH * GRID_SIZE + (SCREEN_SIZE[0] - SQUARE_WIDTH * GRID_SIZE) / 2 )) self.players_tokens.extend( (k + 1, m + 1, TokenView(self.surf, x, m * (TOKEN_RADIUS * 2 + 30) + 250, COLORS[self.players[k].color_index]), self.players[k].ptype != 0) for m in range(4) ) self.last_played = [None, None] self.font = pygame.font.Font('resources/Amatic-Bold.ttf', 50) self.player_one = self.font.render('Player 1', True, BLACK) self.player_one_rect = self.player_one.get_rect() self.player_one_rect.center = GAME_PLAYER_ONE_CENTER self.player_two = self.font.render('Player 2', True, BLACK) self.player_two_rect = self.player_two.get_rect() self.player_two_rect.center = GAME_PLAYER_TWO_CENTER self.currentlyplaying = pygame.image.load("resources/hourglass.png") self.angle = 0 self.back_btn = Button(BACK_BTN_POS, BACK_BTN_SIZE, '< Back', BACKGROUND) self.plate = Plate(self.surf, PLATE_POS, PLATE_W) self.error_txt_1 = self.font.render('You can\'t place your token here :/', True, BLACK) self.error_txt_1_rect = self.error_txt_1.get_rect() self.error_txt_1_rect.center = ERROR_TXT_CENTER self.error_txt_2 = self.font.render('You can\'t move your tokens yet :/', True, BLACK) self.error_txt_2_rect = self.error_txt_2.get_rect() self.error_txt_2_rect.center = ERROR_TXT_CENTER self.font = pygame.font.Font('resources/Amatic-Bold.ttf', 80) self.winner_annouced = self.font.render(f'Player {self.turn_to.idt} won !', True, BLACK) self.winner_annouced_rect = self.winner_annouced.get_rect() self.winner_annouced_rect.center = WINNER_CENTER self.retry_btn = Button(RETRY_BTN_POS, RETRY_BTN_SIZE, 'Retry', BACKGROUND) self.goto_menu = Button(MENU_BTN_POS, MENU_BTN_SIZE, 'Go to Menu', BACKGROUND) self.leave_game = Button(QUIT_BTN_POS, QUIT_BTN_SIZE, 'Quit', BACKGROUND) self.selected_token = None self.selection_offset_y = 0 self.selection_offset_x = 0 # end the game def won(self): self.game_ended = True self.winner_annouced = self.font.render(f'Player {self.turn_to.idt} won !', True, BLACK) # print(f'Game finished. Player {self.turn_to.idt} won\n', self.rectGrid()) def calculating(self): return self.minmax_thread is not None def killMinMax(self): if self.calculating(): self.kill_thread = True # indicate that the thread must exit while self.calculating(): # waits until it exited continue # Compute the longest correct alignment belonging to the specified player def getAligned(self, player): longest_alignment = 1 for token in player.tokens: l_shape_first_direction = 0 # L shape indicates an alignment which is one away from a 2 by 2 square for direction in SURROUNDING: # Alignments are checked from to top right corner to the bottom left corner for better performances current_alignment = 1 alignment_contain_zero = False zero_is_last = False followed_by_two_0 = False current_cell = token module_current_cell = current_cell % 5 next_cell = token + direction IN_GRID = 0 <= next_cell < 25 and ((module_current_cell != 0 and module_current_cell != 4) or (current_cell + next_cell) % 5 != 4) while IN_GRID: # We're checking cells in the given direction while it is meaningful next_cell_value = self.grid[next_cell] if next_cell_value == 0: if alignment_contain_zero: # An alignment containing two consecutive 0 is not considered such. e.g. : 11001 will return 2 followed_by_two_0 = True break else: # This check allows 1011 to be considered as an alignment of length 3 alignment_contain_zero = True zero_is_last = True elif next_cell_value == player.idt: zero_is_last = False current_alignment += 1 else: # in this case the other player is blocking the current alignment break current_cell = next_cell module_current_cell = current_cell % 5 next_cell = current_cell + direction # The following check indicates if the current token is in the grid. Keep in mind or 5 by 5 grid is store in an array of size 25 IN_GRID = 0 <= next_cell < 25 and ((module_current_cell != 0 and module_current_cell != 4) or (current_cell + next_cell) % 5 != 4) if current_alignment == 4: if alignment_contain_zero and not zero_is_last: # This insures that 11011 is considered an alignment of length 3 current_alignment = 3 elif current_alignment == 3: if not alignment_contain_zero: # Here the alignment cannot extends further, so we're checking if there an empty spot before the first token current_cell = token module_current_cell = current_cell % 5 next_cell = current_cell - direction IN_GRID = 0 <= next_cell < 25 and ((module_current_cell != 0 and module_current_cell != 4) or (current_cell + next_cell) % 5 != 4) if IN_GRID and self.grid[next_cell] == 0: current_alignment = 3 else: # If there is not empty spot before the first token, the alignment can never win the game # As such, it is meaningless current_alignment = 1 square_alignment, l_shape_first_direction = self.squareTest(l_shape_first_direction, direction, token, player.idt) current_alignment = max(current_alignment, square_alignment) else: current_alignment = 3 elif current_alignment == 2: if not alignment_contain_zero or zero_is_last: # If the current alignment isn't 101 square_alignment, l_shape_first_direction = self.squareTest(l_shape_first_direction, direction, token, player.idt) current_alignment = max(current_alignment, square_alignment) if current_alignment == 2: # If the alignment isn't an L shape if not followed_by_two_0: # If the alignment end with two 0, the following is implicit # Similarly to the alignments of length 3, we're checking if the alignment can grow to 4 current_cell = token module_current_cell = current_cell % 5 next_cell = current_cell - direction IN_GRID = 0 <= next_cell < 25 and ( (module_current_cell != 0 and module_current_cell != 4) or (current_cell + next_cell) % 5 != 4) if IN_GRID and self.grid[next_cell] == 0: if not zero_is_last: current_cell = next_cell module_current_cell = current_cell % 5 next_cell = current_cell - direction IN_GRID = 0 <= next_cell < 25 and ( (module_current_cell != 0 and module_current_cell != 4) or (current_cell + next_cell) % 5 != 4) if not IN_GRID or self.grid[next_cell] != 0: current_alignment = 1 else: current_alignment = 1 elif alignment_contain_zero: # 101 is consider to be length 1 current_alignment = 1 if current_alignment > 2: # There can't be 2 alignments of length greater than 2 return current_alignment if current_alignment > longest_alignment: longest_alignment = current_alignment return longest_alignment # Determines if there is an L shape alignment. An L shape is made up of two size 2 alignments def squareTest(self, l_shape_first_direction, direction, token, idt): current_alignment = 3 if l_shape_first_direction == 0: # If the alignment of size 2 is the first one for this token current_alignment = 1 if direction != 6: # There is no direction past 6, so there can't be an L shape alignment starting with 6 l_shape_first_direction = direction elif LSTTT[l_shape_first_direction] != direction: # indicates whether the current alignment and the first one found describe an L shape alignment current_alignment = 1 if direction == 5 and l_shape_first_direction == 1: # direction 1 has two possibilities, this check the one that isn't in LSTTT fourth_cell_value = self.grid[token + 6] # 1 5 is the only combination that can mean a 2 by 2 alignment if fourth_cell_value == idt: current_alignment = 4 elif fourth_cell_value == 0: current_alignment = 3 else: # If it is an L shape, we check whether the other player is blocking it fourth_cell_value = self.grid[token + LSFTT[l_shape_first_direction]] if fourth_cell_value != 0: current_alignment = 1 return current_alignment, l_shape_first_direction # Place a token def addToken(self, player, pos): self.grid[pos] = player.idt bisect.insort(player.tokens, pos) # Remove a token def removeToken(self, player, pos): self.grid[pos] = 0 for token in player.tokens: if token == pos: player.tokens.remove(token) break # Move a token def moveToken(self, player, pos, direction): self.grid[pos] = 0 self.grid[pos + direction] = player.idt for i, token in enumerate(player.tokens): if token == pos: player.tokens[i] += direction player.tokens.sort() break # Return every move possible for a given token def getPossibleMove(self, token): token_moves = [] module_pos = token % 5 SAFE_ZONE = (module_pos != 0 and module_pos != 4) # This area doesn't need an out of bounds horizontal check for shift in DIRECTIONS: token_plus = token + shift if 0 <= token_plus < 25 and self.grid[token_plus] == 0 and (SAFE_ZONE or (token_plus + token) % 5 != 4): token_moves.append([1, token, shift]) return token_moves # Return every move possible for a given player def getAllMoves(self, player): if len(player.tokens) < 4: # If the player has not place all of his tokens yet moves = [[0, pos, 0] for pos in self.getAllEmpty()] else: moves = [] for token in player.tokens: moves.extend(self.getPossibleMove(token)) return moves # move = [type, position, direction], type = 0 if the token is placed, 1 if it is moved # Return every spot in the grid that doesn't contain a token def getAllEmpty(self): return np.where(self.grid == 0)[0] # Determine if the game is over def over(self, align_score=None): if align_score is None: align_score = [self.getAligned(player) for player in self.players] return max(align_score) >= 4 # Return the score for a given state and depth def getScore(self, align_score=None, depth=0): if align_score is None: align_score = [self.getAligned(player) for player in self.players] p1, p2 = align_score if p1 >= 4: return 20 * (depth + 1) elif p2 >= 4: return -20 * (depth + 1) else: w1, w2 = (1.50, 1.75) if self.turn_to.idt == 1 else (1.75, 1.50) return round((p1 ** w1) - (p2 ** w2), 4) def minMax(self, depth, alpha, beta, player): if self.kill_thread: # terminated by the main thread self.minmax_thread = None raise SystemExit() DEPTH_IS_ZERO = depth == 0 DEPTH_IS_MAX = depth == MAX_DEPTH[self.index_difficulty[self.turn_to.idt - 1]] align_score = [self.getAligned(p) for p in self.players] if DEPTH_IS_ZERO or self.over(align_score): return self.getScore(align_score, depth) moves = self.getAllMoves(player) if player.idt == 1: max_score = -np.inf max_score_move = None for move in tqdm(moves) if DEPTH_IS_MAX else moves: move_index, pos, direction = move # Try if move_index == 0: self.addToken(player, pos) else: self.moveToken(player, pos, direction) # Assess score = self.minMax(depth - 1, alpha, beta, self.players[abs(player.idt - 2)]) # Revert if move_index == 0: self.removeToken(player, pos) else: self.moveToken(player, pos + direction, -direction) if score > max_score: max_score = score max_score_move = move alpha = max(alpha, score) # update alpha if beta <= alpha: break if not DEPTH_IS_MAX: return max_score else: return max_score, max_score_move else: min_score = np.inf min_score_move = None for move in tqdm(moves) if DEPTH_IS_MAX else moves: move_index, pos, direction = move # Try if move_index == 0: self.addToken(player, pos) else: self.moveToken(player, pos, direction) # Assess score = self.minMax(depth - 1, alpha, beta, self.players[abs(player.idt - 2)]) # Revert if move_index == 0: self.removeToken(player, pos) else: self.moveToken(player, pos + direction, -direction) if score < min_score: min_score = score min_score_move = move beta = min(beta, score) # update beta if beta <= alpha: break if not DEPTH_IS_MAX: return min_score else: return min_score, min_score_move # Apply a move to the environment and interface def makeMove(self, move): if move[0] == 0: _, position, _ = move self.addToken(self.turn_to, position) if self.render_enabled: AI_tokens = [token for token in self.players_tokens if token[0] == self.turn_to.idt] # place a token on a drop zone for drop_zones in self.plate.playable_zones: div, mod = divmod(position, 5) if drop_zones.abscisse == mod and drop_zones.ordonne == div: drop_zones.available = False token_view = AI_tokens[len(self.turn_to.tokens) - 1][2] token_view.placeToken((drop_zones.x, drop_zones.y)) self.last_played[self.turn_to.idt - 1] = token_view break else: _, token, direction = move self.moveToken(self.turn_to, token, direction) if self.render_enabled: AI_tokens = [token for token in self.players_tokens if token[0] == self.turn_to.idt] # move the selected token on it's new drop zone current_drop_zone, future_drop_zone, i = None, None, 0 while current_drop_zone is None or future_drop_zone is None: drop_zone = self.plate.playable_zones[i] div, mod = divmod(token, 5) if current_drop_zone is None and drop_zone.abscisse == mod and drop_zone.ordonne == div: current_drop_zone = drop_zone div, mod = divmod(token + direction, 5) if future_drop_zone is None and drop_zone.abscisse == mod and drop_zone.ordonne == div: future_drop_zone = drop_zone i += 1 current_drop_zone.available = True for token in AI_tokens: if token[2].initial_x == current_drop_zone.x and token[2].initial_y == current_drop_zone.y: token[2].placeToken((future_drop_zone.x, future_drop_zone.y)) self.last_played[self.turn_to.idt - 1] = token[2] future_drop_zone.available = False break self.turn_to.has_played = True self.minmax_thread = None # MinMax thread handler def AI_handler(self): # print('\nGrid before : \n', self.rectGrid()) score, move = self.minMax(MAX_DEPTH[self.index_difficulty[self.turn_to.idt - 1]], -np.inf, np.inf, self.turn_to) # print('Score : ', score, ' | Selected move : ', move) self.makeMove(move) def update(self): if not self.game_ended: if not self.turn_to.has_played: if self.turn_to.ptype == 1 and not self.calculating(): if self.turn != 0: self.minmax_thread = threading.Thread(target=self.AI_handler) self.minmax_thread.start() # starts MinMax else: difficulty = self.index_difficulty[self.turn_to.idt - 1] if difficulty == 0 or (difficulty == 1 and np.random.random() > .5): self.makeMove(self.getRandomMove()) else: self.makeMove([0, np.random.choice([6, 7, 8, 11, 12, 13, 16, 17, 18]), 0]) else: self.turn += 1 # print(f'P{self.turn_to.idt} align : {self.getAligned(self.turn_to)}, tokens : {self.turn_to.tokens}') if self.over(): self.won() self.turn_to.has_played = False self.turn_to = self.players[abs(self.turn_to.idt - 2)] # other player's turn def getRandomMove(self): return randomChoice(self.getAllMoves(self.turn_to)) def render(self): mouse_pos = pygame.mouse.get_pos() self.surf.fill(BACKGROUND) self.surf.blit(self.player_one, self.player_one_rect) self.surf.blit(self.player_two, self.player_two_rect) if self.error_trigger_code == 1: self.surf.blit(self.error_txt_1, self.error_txt_1_rect) elif self.error_trigger_code == 2: self.surf.blit(self.error_txt_2, self.error_txt_2_rect) if self.turn_to.idt == 1: self.angle = (self.angle + 2) % 360 rotated_surf = pygame.transform.rotate(self.currentlyplaying, self.angle) self.currentlyplaying_rect = rotated_surf.get_rect(center=HOURGLASS_CENTER_ONE) else: self.angle = (self.angle + 2) % 360 rotated_surf = pygame.transform.rotate(self.currentlyplaying, self.angle) self.currentlyplaying_rect = rotated_surf.get_rect(center=HOURGLASS_CENTER_TWO) self.surf.blit(rotated_surf, self.currentlyplaying_rect) if self.back_btn.get_rect().collidepoint(pygame.mouse.get_pos()): self.back_btn.hover(self.surf) else: self.back_btn.drawRect(self.surf) self.plate.render() for token_view in self.players_tokens: token_view[2].render(highlighted=token_view[2] in self.last_played) if self.game_ended: # displays end game screen background_end = pygame.Surface(SCREEN_SIZE) background_end.fill(GRAY) background_end.set_alpha(150) self.surf.blit(background_end, (0, 0)) bandeau = pygame.Surface((SCREEN_SIZE[0], 400)) bandeau.fill(BACKGROUND) bandeau.set_alpha(210) self.surf.blit(bandeau, BANDEAU_POSITION) if self.retry_btn.get_rect().collidepoint(mouse_pos): self.retry_btn.hover(self.surf) else: self.retry_btn.drawRect(self.surf) if self.goto_menu.get_rect().collidepoint(mouse_pos): self.goto_menu.hover(self.surf) else: self.goto_menu.drawRect(self.surf) if self.leave_game.get_rect().collidepoint(mouse_pos): self.leave_game.hover(self.surf) else: self.leave_game.drawRect(self.surf) self.surf.blit(self.winner_annouced, self.winner_annouced_rect) # Parse user inputs def parseEvent(self, event): mouse_pos = pygame.mouse.get_pos() if event.type == pygame.MOUSEBUTTONDOWN: if self.back_btn.onButton(mouse_pos): return CODE_TO_MENU if self.game_ended: if self.goto_menu.onButton(mouse_pos): self.game_ended = False return CODE_TO_MENU if self.leave_game.onButton(mouse_pos): pygame.quit() self.killMinMax() quit() if self.retry_btn.onButton(mouse_pos): self.game_ended = False return CODE_TO_GAME for token_view in self.players_tokens: if token_view[2].onToken(mouse_pos) and not token_view[3] and self.turn_to.idt == token_view[0]: self.selected_token = token_view[2] self.selection_offset_x = token_view[2].x - mouse_pos[0] self.selection_offset_y = token_view[2].y - mouse_pos[1] if event.type == pygame.MOUSEBUTTONUP: if self.selected_token is not None: for drop_zone in self.plate.playable_zones: if drop_zone.isAvailable() and drop_zone.onPropzone(mouse_pos) and \ drop_zone.legitMove(self.selected_token, len(self.turn_to.tokens)): current_drop_zone, i = None, 0 while i < len(self.plate.playable_zones) and current_drop_zone is None: iter_drop_zone = self.plate.playable_zones[i] if self.selected_token.initial_x == iter_drop_zone.x and \ self.selected_token.initial_y == iter_drop_zone.y: current_drop_zone = iter_drop_zone i += 1 if current_drop_zone is not None: if len(self.turn_to.tokens) < 4: self.error_trigger_code = 2 break self.removeToken(self.turn_to, 5 * current_drop_zone.ordonne + current_drop_zone.abscisse) current_drop_zone.available = True self.addToken(self.turn_to, 5 * drop_zone.ordonne + drop_zone.abscisse) self.selected_token.placeToken((drop_zone.x, drop_zone.y)) self.last_played[self.turn_to.idt - 1] = self.selected_token drop_zone.available = False self.selected_token = None self.turn_to.has_played = True self.error_trigger_code = 0 break if self.selected_token is not None: self.selected_token.placeToken((self.selected_token.initial_x, self.selected_token.initial_y)) self.selected_token = None self.error_trigger_code = max(self.error_trigger_code, 1) if event.type == pygame.MOUSEMOTION: if self.selected_token is not None: self.selected_token.drag((self.selection_offset_x + mouse_pos[0], self.selection_offset_y + mouse_pos[1])) def rectGrid(self): return self.grid.reshape((GRID_SIZE, GRID_SIZE)) def print(self): print(self.rectGrid())
def __init__(self, surf): self.surf = surf self.font = pygame.font.Font('resources/Amatic-Bold.ttf', 180) self.title = self.font.render('Teeko AI', True, BLACK) self.title_rect = self.title.get_rect() self.title_rect.center = MENU_TITLE_POS self.index_difficulty_one = 1 self.index_difficulty_two = 1 self.playerone = Player(1, 0, 0) self.playertwo = Player(2, 1, 1) self.color_btn_one = ColorChanger(self.surf, COLOR_BTN_ONE_POS, 30, COLORS[self.playerone.color_index]) self.color_btn_two = ColorChanger(self.surf, COLOR_BTN_TWO_POS, 30, COLORS[self.playertwo.color_index]) self.tick_zone_one = Button(TICK_ZONE_ONE_POS, TICK_ZONE_ONE_SIZE, PLAYERTYPE[self.playerone.ptype], BACKGROUND) self.tick_zone_two = Button(TICK_ZONE_TWO_POS, TICK_ZONE_TWO_SIZE, PLAYERTYPE[self.playertwo.ptype], BACKGROUND) self.rulesbtn = Button(RULES_BTN_POS, RULES_BTN_SIZE, "Rules", BACKGROUND) self.displayrules = False self.font = pygame.font.Font('resources/Amatic-Bold.ttf', 50) self.player_one = self.font.render('Player 1', True, BLACK) self.player_one_rect = self.player_one.get_rect() self.player_one_rect.center = MENU_PLAYER_ONE_CENTER self.player_two = self.font.render('Player 2', True, BLACK) self.player_two_rect = self.player_two.get_rect() self.player_two_rect.center = MENU_PLAYER_TWO_CENTER self.start_btn = Button(START_BTN_POS, START_BTN_SIZE, 'Start', BACKGROUND) self.AI_diff_one = Button( IA_DIFF_ONE_POS, IA_DIFF_ONE_SIZE, 'AI Difficulty : ' + DIFFICULTY[self.index_difficulty_one], BACKGROUND) self.AI_diff_one.disable() self.AI_diff_two = Button( IA_DIFF_TWO_POS, IA_DIFF_TWO_SIZE, 'AI Difficulty : ' + DIFFICULTY[self.index_difficulty_two], BACKGROUND) self.leave_btn = Button(LEAVE_BTN_POS, LEAVE_BTN_SIZE, 'Leave', BACKGROUND)
class Menu: def __init__(self, surf): self.surf = surf self.font = pygame.font.Font('resources/Amatic-Bold.ttf', 180) self.title = self.font.render('Teeko AI', True, BLACK) self.title_rect = self.title.get_rect() self.title_rect.center = MENU_TITLE_POS self.index_difficulty_one = 1 self.index_difficulty_two = 1 self.playerone = Player(1, 0, 0) self.playertwo = Player(2, 1, 1) self.color_btn_one = ColorChanger(self.surf, COLOR_BTN_ONE_POS, 30, COLORS[self.playerone.color_index]) self.color_btn_two = ColorChanger(self.surf, COLOR_BTN_TWO_POS, 30, COLORS[self.playertwo.color_index]) self.tick_zone_one = Button(TICK_ZONE_ONE_POS, TICK_ZONE_ONE_SIZE, PLAYERTYPE[self.playerone.ptype], BACKGROUND) self.tick_zone_two = Button(TICK_ZONE_TWO_POS, TICK_ZONE_TWO_SIZE, PLAYERTYPE[self.playertwo.ptype], BACKGROUND) self.rulesbtn = Button(RULES_BTN_POS, RULES_BTN_SIZE, "Rules", BACKGROUND) self.displayrules = False self.font = pygame.font.Font('resources/Amatic-Bold.ttf', 50) self.player_one = self.font.render('Player 1', True, BLACK) self.player_one_rect = self.player_one.get_rect() self.player_one_rect.center = MENU_PLAYER_ONE_CENTER self.player_two = self.font.render('Player 2', True, BLACK) self.player_two_rect = self.player_two.get_rect() self.player_two_rect.center = MENU_PLAYER_TWO_CENTER self.start_btn = Button(START_BTN_POS, START_BTN_SIZE, 'Start', BACKGROUND) self.AI_diff_one = Button( IA_DIFF_ONE_POS, IA_DIFF_ONE_SIZE, 'AI Difficulty : ' + DIFFICULTY[self.index_difficulty_one], BACKGROUND) self.AI_diff_one.disable() self.AI_diff_two = Button( IA_DIFF_TWO_POS, IA_DIFF_TWO_SIZE, 'AI Difficulty : ' + DIFFICULTY[self.index_difficulty_two], BACKGROUND) self.leave_btn = Button(LEAVE_BTN_POS, LEAVE_BTN_SIZE, 'Leave', BACKGROUND) def displayrules(self): background_rules = pygame.Surface(SCREEN_SIZE) background_rules.fill(GRAY) background_rules.set_alpha(150) self.surf.blit(background_rules, (0, 0)) bandeau = pygame.Surface((SCREEN_SIZE[0] - 200, SCREEN_SIZE[1] - 200)) bandeau.fill(BACKGROUND) self.surf.blit(bandeau, (100, 100)) def parseEvent(self, event): if event.type == pygame.MOUSEBUTTONDOWN: pos = pygame.mouse.get_pos() if self.start_btn.onButton(pos): return CODE_TO_GAME if self.rulesbtn.onButton(pos): return CODE_TO_RULES if self.AI_diff_one.onButton( pos) and not self.AI_diff_one.isDisabled(): if self.index_difficulty_one < 2: self.index_difficulty_one += 1 else: self.index_difficulty_one = 0 self.AI_diff_one.text = 'AI Difficulty : ' + DIFFICULTY[ self.index_difficulty_one] if self.AI_diff_two.onButton( pos) and not self.AI_diff_two.isDisabled(): if self.index_difficulty_two < 2: self.index_difficulty_two += 1 else: self.index_difficulty_two = 0 self.AI_diff_two.text = 'AI Difficulty : ' + DIFFICULTY[ self.index_difficulty_two] if self.tick_zone_one.onButton(pos): if self.playerone.ptype == 0: self.playerone.ptype = 1 self.AI_diff_one.enable() else: self.playerone.ptype = 0 self.AI_diff_one.disable() self.tick_zone_one.text = PLAYERTYPE[self.playerone.ptype] if self.tick_zone_two.onButton(pos): if self.playertwo.ptype == 0: self.playertwo.ptype = 1 self.AI_diff_two.enable() else: self.playertwo.ptype = 0 self.AI_diff_two.disable() self.tick_zone_two.text = PLAYERTYPE[self.playertwo.ptype] if self.leave_btn.onButton(pos): pygame.quit() quit() if self.color_btn_one.is_inside(pos): if self.playerone.color_index + 1 < len(COLORS): if self.playerone.color_index + 1 != self.playertwo.color_index: self.playerone.color_index += 1 else: self.playerone.color_index += 2 else: if self.playertwo.color_index == 0: self.playerone.color_index = 1 else: self.playerone.color_index = 0 if self.color_btn_two.is_inside(pos): if self.playertwo.color_index + 1 < len(COLORS): if self.playertwo.color_index + 1 != self.playerone.color_index: self.playertwo.color_index += 1 else: self.playertwo.color_index += 2 else: if self.playerone.color_index == 0: self.playertwo.color_index = 1 else: self.playertwo.color_index = 0 def render(self): self.surf.fill(BACKGROUND) if self.start_btn.get_rect().collidepoint(pygame.mouse.get_pos()): self.start_btn.hover(self.surf) else: self.start_btn.drawRect(self.surf) if self.tick_zone_one.get_rect().collidepoint(pygame.mouse.get_pos()): self.tick_zone_one.hover(self.surf) else: self.tick_zone_one.drawRect(self.surf) if self.tick_zone_two.get_rect().collidepoint(pygame.mouse.get_pos()): self.tick_zone_two.hover(self.surf) else: self.tick_zone_two.drawRect(self.surf) if self.AI_diff_one.get_rect().collidepoint( pygame.mouse.get_pos()) and not self.AI_diff_one.isDisabled(): self.AI_diff_one.hover(self.surf) else: self.AI_diff_one.drawRect(self.surf) if self.AI_diff_two.get_rect().collidepoint( pygame.mouse.get_pos()) and not self.AI_diff_two.isDisabled(): self.AI_diff_two.hover(self.surf) else: self.AI_diff_two.drawRect(self.surf) if self.leave_btn.get_rect().collidepoint(pygame.mouse.get_pos()): self.leave_btn.hover(self.surf) else: self.leave_btn.drawRect(self.surf) if self.rulesbtn.get_rect().collidepoint(pygame.mouse.get_pos()): self.rulesbtn.hover(self.surf) else: self.rulesbtn.drawRect(self.surf) if self.color_btn_one.is_inside(pygame.mouse.get_pos()): self.color_btn_one.hover(COLORS[self.playerone.color_index]) else: self.color_btn_one.drawCircle(COLORS[self.playerone.color_index]) if self.color_btn_two.is_inside(pygame.mouse.get_pos()): self.color_btn_two.hover(COLORS[self.playertwo.color_index]) else: self.color_btn_two.drawCircle(COLORS[self.playertwo.color_index]) self.surf.blit(self.title, self.title_rect) self.surf.blit(self.player_one, self.player_one_rect) self.surf.blit(self.player_two, self.player_two_rect)