pip install pygameimport pygame import sys from pygame import gfxdraw class GomokuGame: def __init__(self): pygame.init() # 游戏配置 self.BOARD_SIZE 15 self.CELL_SIZE 35 self.MARGIN 40 self.BOARD_PIXEL_SIZE (self.BOARD_SIZE - 1) * self.CELL_SIZE self.WINDOW_WIDTH self.BOARD_PIXEL_SIZE 2 * self.MARGIN self.WINDOW_HEIGHT self.BOARD_PIXEL_SIZE 2 * self.MARGIN 30 # 颜色配置 self.COLORS { background: (250, 248, 240), # 浅米色背景 board: (220, 190, 140), # 浅木纹色棋盘 line: (100, 80, 60), # 棋盘线颜色 black_piece: (30, 30, 30), # 黑棋颜色 white_piece: (245, 245, 245), # 白棋颜色 highlight: (255, 215, 0), # 高亮颜色 button_normal: (100, 140, 180), # 按钮正常颜色 button_hover: (70, 110, 150), # 按钮悬停颜色 button_text: (255, 255, 255), # 按钮文字颜色 text: (50, 50, 50), win_line: (255, 69, 0) # 胜利线颜色 } # 创建窗口 self.screen pygame.display.set_mode((self.WINDOW_WIDTH, self.WINDOW_HEIGHT)) pygame.display.set_caption(五子棋 - Gomoku) # 游戏状态 self.board [[0 for _ in range(self.BOARD_SIZE)] for _ in range(self.BOARD_SIZE)] self.current_player 1 # 1: 黑棋, 2: 白棋 self.game_over False self.winner None self.win_line [] self.last_move None # AI 模式 self.ai_mode False # 历史记录用于悔棋 self.move_history [] # 初始化字体支持中文 self.init_fonts() # 按钮 button_y self.BOARD_PIXEL_SIZE self.MARGIN 20 button_width 100 button_height 35 button_spacing 15 total_button_width 4 * button_width 3 * button_spacing start_x (self.WINDOW_WIDTH - total_button_width) // 2 self.buttons [ {text: 重新开始, rect: pygame.Rect(start_x, button_y, button_width, button_height), action: restart}, {text: AI: 关, rect: pygame.Rect(start_x button_width button_spacing, button_y, button_width, button_height), action: toggle_ai}, {text: 悔棋, rect: pygame.Rect(start_x 2 * (button_width button_spacing), button_y, button_width, button_height), action: undo}, {text: 退出, rect: pygame.Rect(start_x 3 * (button_width button_spacing), button_y, button_width, button_height), action: quit} ] def init_fonts(self): 初始化支持中文的字体 # 尝试多种中文字体 font_names [ microsoftyahei, # 微软雅黑 simsun, # 宋体 simhei, # 黑体 kaiti, # 楷体 Arial Unicode MS, None # 默认字体 ] self.font_large None self.font_medium None self.font_small None for font_name in font_names: try: if font_name: self.font_large pygame.font.SysFont(font_name, 32) self.font_medium pygame.font.SysFont(font_name, 24) self.font_small pygame.font.SysFont(font_name, 18) else: self.font_large pygame.font.Font(None, 32) self.font_medium pygame.font.Font(None, 24) self.font_small pygame.font.Font(None, 18) # 测试字体是否能渲染中文 test_surface self.font_medium.render(测试, True, (0, 0, 0)) if test_surface.get_width() 10: # 如果宽度合理说明字体支持中文 break except: continue # 如果所有字体都失败使用默认字体 if self.font_large is None: self.font_large pygame.font.Font(None, 32) self.font_medium pygame.font.Font(None, 24) self.font_small pygame.font.Font(None, 18) def draw_board(self): 绘制棋盘 # 绘制背景 self.screen.fill(self.COLORS[background]) # 绘制棋盘底座带圆角和阴影 board_rect pygame.Rect( self.MARGIN - 15, self.MARGIN - 15, self.BOARD_PIXEL_SIZE 30, self.BOARD_PIXEL_SIZE 30 ) # 绘制阴影 shadow_rect board_rect.copy() shadow_rect.x 4 shadow_rect.y 4 pygame.draw.rect(self.screen, (180, 160, 120), shadow_rect, border_radius8) # 绘制棋盘主体 pygame.draw.rect(self.screen, self.COLORS[board], board_rect, border_radius8) # 绘制网格线 for i in range(self.BOARD_SIZE): # 横线 start_pos (self.MARGIN, self.MARGIN i * self.CELL_SIZE) end_pos (self.MARGIN self.BOARD_PIXEL_SIZE, self.MARGIN i * self.CELL_SIZE) pygame.draw.line(self.screen, self.COLORS[line], start_pos, end_pos, 1) # 竖线 start_pos (self.MARGIN i * self.CELL_SIZE, self.MARGIN) end_pos (self.MARGIN i * self.CELL_SIZE, self.MARGIN self.BOARD_PIXEL_SIZE) pygame.draw.line(self.screen, self.COLORS[line], start_pos, end_pos, 1) # 绘制天元和星位 star_positions [(3, 3), (11, 3), (7, 7), (3, 11), (11, 11)] for x, y in star_positions: center_x self.MARGIN x * self.CELL_SIZE center_y self.MARGIN y * self.CELL_SIZE pygame.draw.circle(self.screen, self.COLORS[line], (center_x, center_y), 4) def draw_pieces(self): 绘制棋子 for i in range(self.BOARD_SIZE): for j in range(self.BOARD_SIZE): if self.board[i][j] ! 0: x self.MARGIN j * self.CELL_SIZE y self.MARGIN i * self.CELL_SIZE # 棋子颜色 if self.board[i][j] 1: piece_color self.COLORS[black_piece] outline_color (60, 60, 60) else: piece_color self.COLORS[white_piece] outline_color (200, 200, 200) # 绘制棋子带阴影 shadow_offset 2 shadow_pos (x shadow_offset, y shadow_offset) pygame.draw.circle(self.screen, (120, 110, 90), shadow_pos, self.CELL_SIZE // 2 - 3) # 绘制棋子 pygame.draw.circle(self.screen, piece_color, (x, y), self.CELL_SIZE // 2 - 2) pygame.draw.circle(self.screen, outline_color, (x, y), self.CELL_SIZE // 2 - 2, 2) # 绘制高光效果 if self.board[i][j] 1: # 黑棋高光 highlight_pos (x - self.CELL_SIZE // 8, y - self.CELL_SIZE // 8) pygame.draw.circle(self.screen, (90, 90, 90), highlight_pos, self.CELL_SIZE // 8) else: # 白棋高光 highlight_pos (x - self.CELL_SIZE // 8, y - self.CELL_SIZE // 8) pygame.draw.circle(self.screen, (255, 255, 255), highlight_pos, self.CELL_SIZE // 8) # 高亮最后一步 if self.last_move: x self.MARGIN self.last_move[1] * self.CELL_SIZE y self.MARGIN self.last_move[0] * self.CELL_SIZE pygame.draw.circle(self.screen, self.COLORS[highlight], (x, y), 4) def draw_win_line(self): 绘制胜利连线 if self.win_line: for i in range(len(self.win_line) - 1): start self.win_line[i] end self.win_line[i 1] start_x self.MARGIN start[1] * self.CELL_SIZE start_y self.MARGIN start[0] * self.CELL_SIZE end_x self.MARGIN end[1] * self.CELL_SIZE end_y self.MARGIN end[0] * self.CELL_SIZE pygame.draw.line(self.screen, self.COLORS[win_line], (start_x, start_y), (end_x, end_y), 5) def draw_ui(self): 绘制用户界面 # 绘制当前玩家提示 if not self.game_over: player_text 黑棋 if self.current_player 1 else 白棋 player_color self.COLORS[black_piece] if self.current_player 1 else self.COLORS[white_piece] text f当前玩家: {player_text} text_surface self.font_medium.render(text, True, player_color) self.screen.blit(text_surface, (self.WINDOW_WIDTH // 2 - text_surface.get_width() // 2, 10)) else: if self.winner: winner_text 黑棋 if self.winner 1 else 白棋 text f{winner_text}获胜! text_surface self.font_large.render(text, True, self.COLORS[win_line]) else: text 平局! text_surface self.font_large.render(text, True, self.COLORS[text]) self.screen.blit(text_surface, (self.WINDOW_WIDTH // 2 - text_surface.get_width() // 2, 10)) # 绘制按钮 mouse_pos pygame.mouse.get_pos() for button in self.buttons: rect button[rect] if rect.collidepoint(mouse_pos): color self.COLORS[button_hover] else: color self.COLORS[button_normal] # 绘制按钮阴影 shadow_rect rect.copy() shadow_rect.x 2 shadow_rect.y 2 pygame.draw.rect(self.screen, (100, 100, 100), shadow_rect, border_radius6) # 绘制按钮主体 pygame.draw.rect(self.screen, color, rect, border_radius6) # 绘制按钮文字 text_surface self.font_small.render(button[text], True, self.COLORS[button_text]) text_rect text_surface.get_rect(centerrect.center) self.screen.blit(text_surface, text_rect) def is_valid_move(self, row, col): 检查移动是否有效 return 0 row self.BOARD_SIZE and 0 col self.BOARD_SIZE and self.board[row][col] 0 def make_move(self, row, col): 落子 if self.is_valid_move(row, col): self.board[row][col] self.current_player self.last_move (row, col) self.move_history.append((row, col, self.current_player)) # 记录历史 # 检查胜利 if self.check_win(row, col): self.game_over True self.winner self.current_player elif self.is_board_full(): self.game_over True self.winner None else: # 切换玩家 self.current_player 3 - self.current_player # AI 模式下如果轮到白棋则AI下棋 if self.ai_mode and self.current_player 2 and not self.game_over: pygame.display.flip() pygame.time.wait(300) self.ai_move() return True return False def check_win(self, row, col): 检查是否胜利 player self.board[row][col] directions [ [(0, 1), (0, -1)], # 水平 [(1, 0), (-1, 0)], # 垂直 [(1, 1), (-1, -1)], # 对角线 [(1, -1), (-1, 1)] # 反对角线 ] for direction in directions: count 1 line_positions [(row, col)] for d in direction: dr, dc d new_row, new_col row dr, col dc while (0 new_row self.BOARD_SIZE and 0 new_col self.BOARD_SIZE and self.board[new_row][new_col] player): count 1 line_positions.append((new_row, new_col)) new_row dr new_col dc if count 5: self.win_line line_positions return True return False def is_board_full(self): 检查棋盘是否已满 return all(self.board[i][j] ! 0 for i in range(self.BOARD_SIZE) for j in range(self.BOARD_SIZE)) def get_valid_moves(self): 获取所有有效移动 valid_moves [] for i in range(self.BOARD_SIZE): for j in range(self.BOARD_SIZE): if self.board[i][j] 0: # 只考虑有邻居的位置 if self.has_neighbor(i, j): valid_moves.append((i, j)) return valid_moves def has_neighbor(self, row, col): 检查是否有邻居棋子 if self.is_board_empty(): return True for dr in [-2, -1, 0, 1, 2]: for dc in [-2, -1, 0, 1, 2]: if dr 0 and dc 0: continue nr, nc row dr, col dc if (0 nr self.BOARD_SIZE and 0 nc self.BOARD_SIZE and self.board[nr][nc] ! 0): return True return False def is_board_empty(self): 检查棋盘是否为空 return all(self.board[i][j] 0 for i in range(self.BOARD_SIZE) for j in range(self.BOARD_SIZE)) def evaluate_position(self, row, col, player): 评估某个位置的分数 score 0 directions [ (0, 1), # 水平 (1, 0), # 垂直 (1, 1), # 对角线 (1, -1) # 反对角线 ] for dr, dc in directions: # 连续棋子数 count 0 open_ends 0 # 正方向 for step in range(1, 5): nr, nc row dr * step, col dc * step if not (0 nr self.BOARD_SIZE and 0 nc self.BOARD_SIZE): break if self.board[nr][nc] player: count 1 elif self.board[nr][nc] 0: open_ends 1 break else: break # 反方向 for step in range(1, 5): nr, nc row - dr * step, col - dc * step if not (0 nr self.BOARD_SIZE and 0 nc self.BOARD_SIZE): break if self.board[nr][nc] player: count 1 elif self.board[nr][nc] 0: open_ends 1 break else: break # 评分 if count 4: score 100000 # 必胜 elif count 3: if open_ends 2: score 10000 # 活四 elif open_ends 1: score 1000 # 冲四 elif count 2: if open_ends 2: score 500 # 活三 elif open_ends 1: score 100 # 冲三 elif count 1 and open_ends 2: score 50 return score def ai_move(self): AI下棋 valid_moves self.get_valid_moves() if not valid_moves: return # 棋盘为空时下在中心 if self.is_board_empty(): center self.BOARD_SIZE // 2 self.make_move(center, center) return best_move None best_score -float(inf) for row, col in valid_moves: # 评估进攻分数 attack_score self.evaluate_position(row, col, 2) # 评估防守分数 defense_score self.evaluate_position(row, col, 1) # 综合评分 total_score attack_score defense_score * 0.9 # 略微偏向中心 center_distance abs(row - self.BOARD_SIZE // 2) abs(col - self.BOARD_SIZE // 2) total_score - center_distance * 5 if total_score best_score: best_score total_score best_move (row, col) if best_move: self.make_move(best_move[0], best_move[1]) def undo_move(self): 悔棋 if not self.move_history or self.game_over: return # 如果是AI模式需要回退两步AI的一步和玩家的一步 if self.ai_mode: if len(self.move_history) 2: # 回退AI的棋 row, col, player self.move_history.pop() self.board[row][col] 0 # 回退玩家的棋 row, col, player self.move_history.pop() self.board[row][col] 0 # 更新最后一步 if self.move_history: self.last_move (self.move_history[-1][0], self.move_history[-1][1]) else: self.last_move None # 切换回玩家 self.current_player 1 elif len(self.move_history) 1: # 只有一步棋玩家第一步回退 row, col, player self.move_history.pop() self.board[row][col] 0 self.last_move None self.current_player 1 else: # 双人模式只回退一步 row, col, player self.move_history.pop() self.board[row][col] 0 # 更新最后一步 if self.move_history: self.last_move (self.move_history[-1][0], self.move_history[-1][1]) else: self.last_move None # 切换回上一个玩家 self.current_player player def restart_game(self): 重新开始游戏 self.board [[0 for _ in range(self.BOARD_SIZE)] for _ in range(self.BOARD_SIZE)] self.current_player 1 self.game_over False self.winner None self.win_line [] self.last_move None self.move_history [] # 清空历史记录 def toggle_ai_mode(self): 切换AI模式 self.ai_mode not self.ai_mode for button in self.buttons: if button[action] toggle_ai: button[text] fAI: {开 if self.ai_mode else 关} break def handle_click(self, pos): 处理鼠标点击 # 检查按钮点击 for button in self.buttons: if button[rect].collidepoint(pos): if button[action] restart: self.restart_game() elif button[action] toggle_ai: self.toggle_ai_mode() elif button[action] undo: self.undo_move() elif button[action] quit: pygame.quit() sys.exit() return # 检查棋盘点击 if not self.game_over and not (self.ai_mode and self.current_player 2): x, y pos if y self.BOARD_PIXEL_SIZE self.MARGIN: # 转换为网格坐标 col round((x - self.MARGIN) / self.CELL_SIZE) row round((y - self.MARGIN) / self.CELL_SIZE) if self.is_valid_move(row, col): self.make_move(row, col) def run(self): 运行游戏主循环 clock pygame.time.Clock() while True: for event in pygame.event.get(): if event.type pygame.QUIT: pygame.quit() sys.exit() elif event.type pygame.MOUSEBUTTONDOWN: if event.button 1: # 左键 self.handle_click(event.pos) # 绘制 self.draw_board() self.draw_pieces() self.draw_win_line() self.draw_ui() pygame.display.flip() clock.tick(60) def main(): 主函数 game GomokuGame() game.run() if __name__ __main__: main()