一、游戏简介
五子棋是一种广受欢迎的棋类游戏,目标是在棋盘上先形成连续的五个棋子(横、竖或斜方向)。这款 AI-Gomoku 游戏采用了标准的 15×15 棋盘,玩家通过鼠标点击棋盘放置白棋,而电脑则自动下黑棋。游戏的胜负判定机制能够自动检测五子连珠的情况,一旦出现,即可判定获胜方。这款名为 AI-Gomoku-with-Prohibition-Moves 的五子棋游戏是一个基于 Python 和 Pygame 开发的人机对战游戏项目,由南京邮电大学智能科学与技术专业的庹忠曜、鲁健和张晨斌三位同学共同开发。它不仅具有经典的五子棋玩法,还融入了智能 AI 对手和多种实用功能,为玩家提供了一个既有趣又具有挑战性的游戏体验。
项目链接:https://github.com/cheinralational/AI-Gomoku-with-Prohibition-Moves
二、项目介绍
1.项目特点
该游戏的开发团队在设计时注重用户体验和功能的完整性。首先,游戏界面简洁美观,视觉设计上采用了直观的鼠标操作方式,玩家可以轻松地通过点击鼠标在棋盘上放置棋子。其次,游戏内置了智能 AI 对手,虽然采用了简单的评估算法,但已经能够为玩家提供具有一定挑战性的对局。此外,游戏还提供了“重新开始”按钮,方便玩家在游戏结束后快速重置游戏,重新开始新的对局。
2.项目结构
整个项目的代码结构清晰合理,便于开发者进行阅读和维护。项目目录下包含多个模块文件,其中 main.py
是主程序入口,负责启动游戏并初始化各个模块。init.py
用于游戏的初始化和常量配置,draw.py
负责游戏界面的绘制,logic.py
则包含了游戏的核心逻辑和 AI 算法。此外,requirements.txt
文件列出了项目所需的依赖库,而 README.md
文件则提供了详细的项目文档和操作指南。
3.技术栈
该项目的技术栈主要基于 Python 3.9+ 和 Pygame 2.6.1,同时还使用了 NumPy 库。Python 是一种广泛使用的高级编程语言,具有简洁易读的语法和强大的库支持,非常适合用于开发此类游戏。Pygame 是一个开源的 Python 库,专门用于开发游戏,提供了丰富的图形和音频处理功能,能够帮助开发者快速实现游戏的交互界面和逻辑功能。NumPy 则是一个强大的科学计算库,可以用于处理数组和矩阵运算,为游戏中的 AI 算法提供了高效的计算支持。
4.开发团队
开发团队由三位来自南京邮电大学智能科学与技术专业的学生组成,他们分别是庹忠曜、鲁健和张晨斌。他们在项目中展现了扎实的专业知识和出色的团队协作能力。庹忠曜主要负责项目的整体架构设计和核心逻辑的实现,鲁健专注于游戏界面的绘制和用户体验的优化,而张晨斌则致力于 AI 算法的研究和开发。通过分工合作,他们成功地完成了这个项目,并将其开源分享给更多的人。
5.开源协议与贡献指南
该项目采用了 MIT License 开源协议,这意味着任何人都可以自由地使用、修改和分发该项目的代码。开发团队在 README.md
文件中提供了详细的贡献指南,欢迎其他开发者提交 Issue 和 Pull Request,共同参与到项目的开发和改进中。无论是修复代码中的问题、优化 AI 算法,还是增加新的功能,开发团队都欢迎并鼓励大家积极参与。
三、代码分解
1.draw.py
在绘制棋盘之前,代码首先通过 screen.fill
方法将整个游戏窗口的背景填充为指定的棋盘颜色(BOARD_COLOR
)。这个颜色通常是一个温和的米色或其他类似的颜色,以模拟真实的棋盘材质,为玩家提供一个舒适且熟悉的视觉体验。通过填充整个窗口区域(从坐标 (0, 0)
开始,宽度和高度均为 WINDOW_SIZE
),为后续的棋盘绘制提供了一个整洁的画布。为了构建棋盘的基本框架,代码使用了两层嵌套的 for
循环来绘制棋盘的网格线。这些网格线是棋盘上棋子放置的引导线,它们将棋盘划分为一个个格子。通过 pygame.draw.line
方法,代码在棋盘上绘制了水平线和垂直线。每条线的起点和终点坐标是通过计算得出的,考虑到了棋盘边缘的留白(MARGIN
)以及每个格子的大小(GRID_SIZE
)。通过这种方式,代码确保了网格线能够均匀地分布在棋盘上,并且与棋盘边缘保持适当的距离,从而形成了一个标准的 15×15 的棋盘网格。
import pygame
from init import *
def draw_board(game_state):
"""绘制棋盘、棋子和按钮"""
screen = game_state['screen']
board = game_state['board']
last_move = game_state['last_move']
font = game_state['font']
button_font = game_state['button_font']
restart_button = game_state['restart_button']
# 绘制棋盘背景
screen.fill(BOARD_COLOR, (0, 0, WINDOW_SIZE, WINDOW_SIZE))
# 绘制网格线
for i in range(BOARD_SIZE):
pygame.draw.line(screen, BLACK,
(MARGIN, MARGIN + i * GRID_SIZE),
(WINDOW_SIZE - MARGIN, MARGIN + i * GRID_SIZE), 1)
pygame.draw.line(screen, BLACK,
(MARGIN + i * GRID_SIZE, MARGIN),
(MARGIN + i * GRID_SIZE, WINDOW_SIZE - MARGIN), 1)
# 绘制天元和星位
star_points = [3, 7, 11]
for x in star_points:
for y in star_points:
pygame.draw.circle(screen, BLACK,
(MARGIN + x * GRID_SIZE, MARGIN + y * GRID_SIZE), 5)
# 绘制棋子
for y in range(BOARD_SIZE):
for x in range(BOARD_SIZE):
if board[y][x] == 1: # 黑棋
pygame.draw.circle(screen, BLACK,
(MARGIN + x * GRID_SIZE, MARGIN + y * GRID_SIZE), PIECE_RADIUS)
elif board[y][x] == 2: # 白棋
pygame.draw.circle(screen, WHITE,
(MARGIN + x * GRID_SIZE, MARGIN + y * GRID_SIZE), PIECE_RADIUS)
# 标记最后一步
if last_move:
x, y = last_move
color = RED if board[y][x] == 1 else GREEN
pygame.draw.circle(screen, color,
(MARGIN + x * GRID_SIZE, MARGIN + y * GRID_SIZE), 5)
# 显示获胜信息
if game_state['game_over'] and game_state['winner']:
text = f"{game_state['winner']} 获胜!"
text_surface = font.render(text, True, BLUE)
text_rect = text_surface.get_rect(center=(WINDOW_SIZE // 2, WINDOW_SIZE // 2 - 50))
screen.blit(text_surface, text_rect)
# 绘制重新开始按钮
mouse_pos = pygame.mouse.get_pos()
button_color = BUTTON_HOVER_COLOR if restart_button.collidepoint(mouse_pos) else BUTTON_COLOR
pygame.draw.rect(screen, button_color, restart_button, border_radius=5)
pygame.draw.rect(screen, BLACK, restart_button, 2, border_radius=5) # 边框
text_surface = button_font.render("重新开始", True, WHITE)
text_rect = text_surface.get_rect(center=restart_button.center)
screen.blit(text_surface, text_rect)
棋子是五子棋游戏的核心元素,玩家通过在棋盘上放置棋子来进行对战。代码通过两层嵌套的 for
循环遍历棋盘的每一个位置,并根据棋盘数据(board
)中的值来判断该位置是否有棋子以及棋子的颜色。如果某个位置的值为 1
,则表示该位置放置了黑棋;如果值为 2
,则表示放置了白棋。对于每个有棋子的位置,代码使用 pygame.draw.circle
方法绘制一个圆形来表示棋子。棋子的颜色(黑色或白色)和大小(由 PIECE_RADIUS
定义)都是根据棋盘数据动态确定的,从而确保棋子能够准确地放置在棋盘的对应位置上。
2.logic.py
在五子棋游戏中,玩家或电脑的每一次落子都需要满足特定的条件才能被认为是合法的。is_valid_move
函数的作用就是验证这些条件是否得到满足。具体来说,该函数接收三个参数:棋盘状态 board
,以及落子的横坐标 x
和纵坐标 y
。它首先检查落子的坐标是否在棋盘的有效范围内,即 x
和 y
的值必须大于等于 0 且小于棋盘的大小 BOARD_SIZE
。这是最基本的前提条件,因为任何超出棋盘范围的落子都是无效的。其次,该函数还会检查目标位置是否已经被占据。在棋盘数组 board
中,如果某个位置的值不为 0,那就意味着该位置已经有棋子存在,因此不能再次落子。只有当目标位置的值为 0 时,才表示该位置是空闲的,可以放置棋子。通过这两个条件的严格检查,is_valid_move
函数能够确保每一次落子操作都在合法的范围内进行,从而维护了游戏的基本规则和秩序。
import numpy as np
import random
from init import *
def is_valid_move(board, x, y):
"""检查落子是否有效"""
return 0 <= x < BOARD_SIZE and 0 <= y < BOARD_SIZE and board[y][x] == 0
def check_win(board, x, y, player):
"""检查是否获胜"""
directions = [(1, 0), (0, 1), (1, 1), (1, -1)]
for dx, dy in directions:
count = 1 # 当前落子
# 向一个方向检查
nx, ny = x + dx, y + dy
while 0 <= nx < BOARD_SIZE and 0 <= ny < BOARD_SIZE and board[ny][nx] == player:
count += 1
nx += dx
ny += dy
# 向相反方向检查
nx, ny = x - dx, y - dy
while 0 <= nx < BOARD_SIZE and 0 <= ny < BOARD_SIZE and board[ny][nx] == player:
count += 1
nx -= dx
ny -= dy
if count >= 5:
return True
return False
def evaluate_position(board, x, y, player):
"""评估位置得分"""
score = 0
directions = [(1, 0), (0, 1), (1, 1), (1, -1)]
for dx, dy in directions:
line = 1 # 当前棋子
# 正向检查
nx, ny = x + dx, y + dy
while 0 <= nx < BOARD_SIZE and 0 <= ny < BOARD_SIZE and board[ny][nx] == player:
line += 1
nx += dx
ny += dy
# 反向检查
nx, ny = x - dx, y - dy
while 0 <= nx < BOARD_SIZE and 0 <= ny < BOARD_SIZE and board[ny][nx] == player:
line += 1
nx -= dx
ny -= dy
if line >= 5: return 10000 # 胜利
if line == 4: score += 1000
elif line == 3: score += 100
elif line == 2: score += 10
return score
def find_best_move(board):
"""AI寻找最佳落子位置"""
if np.sum(board) == 0: # 第一步下天元
return (BOARD_SIZE // 2, BOARD_SIZE // 2)
best_score = -1
best_move = None
center = BOARD_SIZE // 2
for y in range(max(0, center - 3), min(BOARD_SIZE, center + 4)):
for x in range(max(0, center - 3), min(BOARD_SIZE, center + 4)):
if is_valid_move(board, x, y):
score = evaluate_position(board, x, y, 1) + evaluate_position(board, x, y, 2) * 0.8
if score > best_score:
best_score = score
best_move = (x, y)
if best_move is None: # 随机选择
valid_moves = [(x, y) for x in range(BOARD_SIZE) for y in range(BOARD_SIZE) if is_valid_move(board, x, y)]
if valid_moves:
return random.choice(valid_moves)
return best_move
def computer_move(game_state):
"""电脑走棋"""
move = find_best_move(game_state['board'])
if move:
x, y = move
game_state['board'][y][x] = 1
game_state['last_move'] = (x, y)
if check_win(game_state['board'], x, y, 1):
game_state['game_over'] = True
game_state['winner'] = "黑棋(电脑)"
else:
game_state['current_player'] = 2
def player_move(game_state, x, y):
"""玩家走棋"""
if is_valid_move(game_state['board'], x, y):
game_state['board'][y][x] = 2
game_state['last_move'] = (x, y)
if check_win(game_state['board'], x, y, 2):
game_state['game_over'] = True
game_state['winner'] = "白棋(玩家)"
else:
game_state['current_player'] = 1
computer_move(game_state)
def reset_game(game_state):
"""重置游戏"""
game_state['board'] = np.zeros((BOARD_SIZE, BOARD_SIZE), dtype=int)
game_state['current_player'] = 1
game_state['game_over'] = False
game_state['last_move'] = None
game_state['winner'] = None
computer_move(game_state)
五子棋游戏的目标是在棋盘上形成连续的五个棋子,无论是横向、纵向还是斜向。check_win
函数的作用就是判断玩家或电脑是否已经实现了这一目标。它接收四个参数:棋盘状态 board
,落子的横坐标 x
和纵坐标 y
,以及当前玩家的标识 player
。函数内部定义了一个名为 directions
的列表,其中包含了四个方向的增量 (1, 0)
、(0, 1)
、(1, 1)
和 (1, -1)
,分别对应横向、纵向、正对角线和反对角线方向。对于每个方向,函数会沿着该方向和其相反方向进行检查。它从落子位置开始,沿着指定方向逐个检查相邻的棋子,如果相邻棋子与当前玩家的棋子颜色相同(即 board[ny][nx] == player
),则将计数器 count
加 1,并继续沿着该方向检查下一个棋子。当遇到不同颜色的棋子或到达棋盘边界时,停止检查该方向。通过这种方式,函数能够统计出在每个方向上与落子位置相连的同色棋子的数量。如果在任意一个方向上,同色棋子的数量达到或超过 5 个,那么就认为当前玩家获胜,函数返回 True
;否则,返回 False
。这个函数是判断游戏胜负的关键,它确保了游戏能够在正确的时刻结束,并且能够准确地识别出获胜方。
3.main.py
在主函数中,最核心的部分是一个无限循环,它不断监听和处理各种用户输入事件。这个循环通过 pygame.event.get()
方法获取当前发生的所有事件,然后逐一检查这些事件的类型。如果事件类型是 QUIT
(即用户尝试关闭游戏窗口),程序会调用 pygame.quit()
来关闭 Pygame 库,并通过 sys.exit()
来退出整个程序。这是游戏正常退出的机制,确保了在用户关闭窗口时,程序能够优雅地结束运行。
import pygame
import sys
from pygame.locals import *
from init import *
from draw import draw_board
from logic import player_move, reset_game
def main():
"""主游戏循环"""
game_state = init_game()
reset_game(game_state)
while True:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
elif event.type == MOUSEBUTTONDOWN:
mouse_x, mouse_y = event.pos
# 检查重新开始按钮
if game_state['restart_button'].collidepoint(mouse_x, mouse_y):
reset_game(game_state)
# 检查棋盘点击
elif not game_state['game_over'] and game_state['current_player'] == 2 and mouse_y < WINDOW_SIZE:
x = round((mouse_x - MARGIN) / GRID_SIZE)
y = round((mouse_y - MARGIN) / GRID_SIZE)
if 0 <= x < BOARD_SIZE and 0 <= y < BOARD_SIZE:
player_move(game_state, x, y)
draw_board(game_state)
pygame.display.update()
if __name__ == "__main__":
main()
如果事件类型是 MOUSEBUTTONDOWN
(即用户点击了鼠标),程序会进一步处理这个事件。首先,通过 event.pos
获取鼠标点击的位置坐标 (mouse_x, mouse_y)
。然后,检查鼠标点击的位置是否在“重新开始”按钮的区域内。这是通过调用 game_state['restart_button'].collidepoint(mouse_x, mouse_y)
来实现的,如果鼠标点击的位置确实位于按钮区域内,程序会调用 reset_game
函数来重置游戏状态,从而允许玩家重新开始一局游戏。