python五子棋全代码

python五子棋全代码

前言

  自2020年10月14日我首次公开完整的Java五子棋代码以来,时光已悄然流逝了近五个春秋。这期间,我从一名青涩的大学生蜕变成为拥有三年工作经验的全栈开发者,编程思维与技术水平均实现一定的提升。当我再次审视昔日亲手编写的Java五子棋代码时,不禁发现了诸多“屎山代码”,这些陈旧的代码宛如难以下咽的磐石,重写的难度无异于自我设障。于是,我一拍大腿,决定另起炉灶,这也正是本文诞生的契机。

  近期,我的笔触多聚焦于Python编程领域,尽管我的职业生涯主要围绕Java与Vue等技术栈展开。长期使用Java后,我在业余时间渴望寻求一种更为轻松愉悦的编程体验,Python便自然而然地成为了我的兴趣之选。其简洁高效的特点,让我能够迅速将创意转化为现实,无需过多繁琐的编码过程。

  接下来,就让我们一同见证我用Python重新打造的五子棋游戏吧。

效果图和功能

  部分效果图,其余功能大家自行探索。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  1. 单机模式:两人面对面游玩。
  2. 人机模式:挑战若智人机。
  3. 联机模式:局域网联机。
  4. 重开、悔棋、认输等基础操作。

项目结构

  • main.py —— 启动程序
  • MainWindow.py —— 主窗口
  • UserInterface.py —— 用户界面绘制
  • ButtonFunction.py —— 按钮相关函数
  • MouseFunction.py —— 鼠标相关函数
  • ProduceResult.py —— 判断对局结果
  • AIAlgorithm.py —— 人机对战
  • OnlineFunction.py —— 联机对战

源代码

main.py —— 启动程序
from PyQt5.QtWidgets import QApplication

from MainWindow import MainWindow


def main():
    app = QApplication([])
    # 创建主窗口
    MainWindow()
    # 进入事件循环
    app.exec_()


if __name__ == "__main__":
    main()

MainWindow.py —— 主窗口
import socket

from PyQt5.QtWidgets import QMainWindow, QWidget, QApplication

from ButtonFunction import initButton, selectMode
from MouseFunction import containerMouseClicked, containerMouseMove
from UserInterface import initUI, drawChessboard


# 主窗口
class MainWindow(QMainWindow):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)
        # 获取桌面尺寸
        self.desktop = QApplication.desktop()
        self.screen_rect = self.desktop.screenGeometry()
        # 设置主窗口比例
        self.main_width = int(self.screen_rect.width() * 0.45)
        self.main_height = int(self.screen_rect.height() * 0.55)
        # 设置主窗口位置
        self.main_move_x = (self.screen_rect.width() - self.main_width) // 2
        self.main_move_y = (self.screen_rect.height() - self.main_width) // 2
        # 状态栏
        self.status = self.statusBar()

        # 整体容器
        self.container = QWidget(self)
        # 重写鼠标点击事件
        self.container.mousePressEvent = self.containerMousePressEvent
        # 开启鼠标跟踪
        self.container.setMouseTracking(True)
        # 重新鼠标移入事件
        self.container.mouseMoveEvent = self.containerMouseMoveEvent

        # 正方形棋盘位置大小
        self.chessboard = 15
        self.chessboard_size = int(self.main_height * 0.9)
        self.chessboard_move = int(self.chessboard_size - self.chessboard_size * 0.95)
        self.chessboard_size_move = self.chessboard_size + self.chessboard_move
        # 正方形网格间距
        self.grid_size = self.chessboard_size / (self.chessboard - 1)

        # 按钮大小
        self.button_width = int(self.main_width * 0.2)
        self.button_height = int(self.main_height * 0.05)
        # 按钮位置
        self.button_x = self.chessboard_size_move + (
                (self.main_width - self.chessboard_size_move) / 2 - self.button_width / 2)
        self.button_y = self.main_height / 3

        # 棋子坐标
        self.chess_coord = []
        # 棋子颜色
        self.chess_color = True
        # 棋子大小
        self.chess_size = self.grid_size / 1.2
        # 预要落子的坐标
        self.advance_chess_coord = {}

        # 游戏模式 0:单机 1:人机 2:联机
        self.game_mode = 0

        # 服务端IP
        self.server_ip = ''
        # 服务端口
        self.server_port = 8080
        # TCP/IP服务器
        self.tcp_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # TCP/IP的socket
        self.tcp_socket = None

        # 在线对战 棋手 颜色
        self.chess_color_online = None
        self.player_ready = False

        # 初始化UI
        initUI(self)
        # 初始化按钮
        initButton(self)
        self.show()
        # 选择游戏模式
        selectMode(self)

    # 重写绘图事件
    def paintEvent(self, event):
        drawChessboard(self)

    # 重写container的鼠标点击事件
    def containerMousePressEvent(self, event):
        containerMouseClicked(self, event)

    # 重写container的鼠标移入事件
    def containerMouseMoveEvent(self, event):
        containerMouseMove(self, event)

UserInterface.py —— 用户界面绘制
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QPen, QColor, QBrush, QPainter


# 初始化UI
def initUI(self):
    # 主窗口大小
    self.resize(self.main_width, self.main_height)
    # 固定主窗口大小
    self.setFixedSize(self.width(), self.height())
    # 主窗口居中
    self.move(self.main_move_x, self.main_move_y)
    # 状态栏和标题
    self.status.showMessage('请开始游戏')
    self.setWindowTitle('五子棋')
    # 创建容器
    self.setCentralWidget(self.container)


# 绘制棋盘
def drawChessboard(self):
    painter = QPainter(self)
    # 使用抗锯齿
    painter.setRenderHint(QPainter.Antialiasing)
    painter.setRenderHint(QPainter.HighQualityAntialiasing)
    # 设置画笔
    painter.setPen(QPen(QColor("black")))
    # 设置画刷
    painter.setBrush(QBrush(QColor("black")))

    # 绘制方格线
    for i in range(15):
        # 设置线的粗细
        pen = QPen(QColor("black"), 2) if i == 3 or i == 11 else QPen(QColor("black"), 1)
        painter.setPen(pen)
        # 纵向线
        painter.drawLine(self.grid_size * i + self.chessboard_move,
                         self.chessboard_move,
                         self.grid_size * i + self.chessboard_move,
                         self.chessboard_size_move)
        # 横向线
        painter.drawLine(self.chessboard_move,
                         self.grid_size * i + self.chessboard_move,
                         self.chessboard_size_move,
                         self.grid_size * i + self.chessboard_move)

    # 绘制四个点
    dot_move1 = self.grid_size * 3
    dot_move2 = self.grid_size * 11
    dot_move3 = self.grid_size * 7
    dot_size = 10
    points = [(dot_move1, dot_move1),
              (dot_move2, dot_move1),
              (dot_move2, dot_move2),
              (dot_move1, dot_move2),
              (dot_move3, dot_move3)]
    for x, y in points:
        x += self.chessboard_move - dot_size / 2
        y += self.chessboard_move - dot_size / 2
        painter.drawEllipse(x, y, dot_size, dot_size)

    # 绘制预落子
    if self.advance_chess_coord:
        drawChess(self, painter, self.advance_chess_coord)

    # 绘制棋子
    for coord in self.chess_coord:
        drawChess(self, painter, coord)


# 绘制棋子
def drawChess(self, painter, coord):
    # 普通棋子
    brush = QBrush(QColor("black")) if coord.get('color', True) else QBrush(QColor("white"))
    # 胜利后棋子
    pen = QPen(QColor("yellow"), 2) if coord.get('outline', False) else QPen(QColor("black"), 1)
    # 预落下棋子
    if coord.get('calculate', False):
        brush = QBrush(QColor(0, 0, 0, 20))
        pen.setStyle(Qt.DashLine)

    painter.setBrush(brush)
    painter.setPen(pen)

    painter.drawEllipse(coord['x'] * self.grid_size + self.chessboard_move - self.chess_size / 2,
                        coord['y'] * self.grid_size + self.chessboard_move - self.chess_size / 2,
                        self.chess_size,
                        self.chess_size)

ButtonFunction.py —— 按钮相关函数
from functools import partial

from PyQt5.QtWidgets import QPushButton, QMessageBox

from AIAlgorithm import selectSquads
from OnlineFunction import selectSide


# 初始化按钮
def initButton(self):
    buttons = [
        {
            'text': '选择模式',
            'clicked': partial(selectMode, self)
        },
        {
            'text': '重新开始',
            'clicked': partial(startGame, self)
        },
        {
            'text': '悔棋',
            'clicked': partial(regret, self)
        },
        {
            'text': '认输',
            'clicked': partial(giveUp, self)
        }]

    for i, info in enumerate(buttons):
        button = QPushButton(self.container)
        button.setText(info['text'])
        button.setGeometry(self.button_x, self.button_y + self.button_height * i, self.button_width,
                           self.button_height)
        button.clicked.connect(partial(info['clicked']))


# 选择模式
def selectMode(self):
    msg = QMessageBox(QMessageBox.Question, "选择", "请选择游戏模式")
    msg.addButton(self.tr("单机"), QMessageBox.AcceptRole)
    msg.addButton(self.tr("人机"), QMessageBox.AcceptRole)
    msg.addButton(self.tr("联机"), QMessageBox.AcceptRole)
    exit_button = msg.addButton(self.tr("退出"), QMessageBox.AcceptRole)
    self.game_mode = msg.exec_()
    if msg.clickedButton() == exit_button:
        exit()
    startGame(self)
    if self.game_mode == 2:
        selectSide(self)


# 开始游戏
def startGame(self):
    self.chess_coord = []
    self.chess_color = True
    self.status.showMessage("等待黑方落子")
    self.update()
    if self.game_mode == 1:
        selectSquads(self)


# 悔棋
def regret(self):
    if len(self.chess_coord) > 0:
        self.chess_coord.pop()
        if self.game_mode == 1 and len(self.chess_coord) > 0:
            self.chess_coord.pop()
        self.update()


# 认输
def giveUp(self):
    QMessageBox.information(self, "游戏结束",
                            f"{'黑方' if self.chess_color else '白方'}认输,恭喜{'黑方' if not self.chess_color else '白方'}胜利!",
                            QMessageBox.Yes, QMessageBox.Yes)
    startGame(self)

MouseFunction.py —— 鼠标相关函数
import json

import numpy as np

from AIAlgorithm import aiGame
from ProduceResult import checkWin


# 计算棋子坐标
def calculateCoord(self, event):
    x = event.pos().x()
    y = event.pos().y()
    # 限制点击区域
    if x < self.chessboard_move - 10 or \
            x > self.chessboard_size_move + 10 or \
            y < self.chessboard_move - 10 \
            or y > self.chessboard_size_move + 10:
        return False
    # 计算落子位置
    x = np.round((x - self.chessboard_move) / self.grid_size)
    y = np.round((y - self.chessboard_move) / self.grid_size)

    # 判断位置上是否已有棋子
    if any(coord['x'] == x and coord['y'] == y for coord in self.chess_coord):
        return False

    return {'x': x, 'y': y, 'color': self.chess_color}


# 重写后的container鼠标点击事件
def containerMouseClicked(self, event):
    if self.game_mode == 2 and not self.player_ready:
        return
    if self.chess_color != self.chess_color_online and self.chess_color_online is not None:
        return
    coord = calculateCoord(self, event)
    if coord:
        # 将棋子坐标添加到列表中
        self.chess_coord.append(coord)
        if self.game_mode == 2:
            self.tcp_socket.sendall(json.dumps(coord).encode('utf-8'))
        render(self)


# 渲染当前棋子布局
def render(self):
    # 重绘
    self.update()
    # 判断是否胜利
    if checkWin(self):
        return
    # 切换颜色
    self.chess_color = not self.chess_color
    self.status.showMessage(f"等待{'黑方' if self.chess_color else '白方'}落子")
    # 判断是否是人机模式
    if self.game_mode == 1:
        # AI行动
        aiGame(self)
        # 判断是否胜利
        if checkWin(self):
            return
    # 重绘
    self.update()


# 重写后的container鼠标移动事件
def containerMouseMove(self, event):
    if self.game_mode == 2 and not self.player_ready:
        return
    if self.chess_color != self.chess_color_online and self.chess_color_online is not None:
        self.advance_chess_coord = None
        self.update()
        return
    coord = calculateCoord(self, event)
    if coord:
        coord['calculate'] = True
        # 将棋子坐标添加到列表中
        self.advance_chess_coord = coord
        self.update()

ProduceResult.py —— 判断对局结果
from itertools import product
from PyQt5.QtWidgets import QMessageBox

from ButtonFunction import startGame


# 判断是否胜利
def checkWin(self):
    # 判断是否五子连珠
    linking_coords = checkLink(self)
    if linking_coords:
        color = linking_coords[-1]['color']
        QMessageBox.information(self, "游戏结束", f"恭喜{'黑方' if color else '白方'}胜利!",
                                QMessageBox.Yes, QMessageBox.Yes)
        startGame(self)
        return True
    # 判断棋盘空间是否已满
    if len(self.chess_coord) == 225:
        QMessageBox.information(self, "游戏结束", f"棋盘已满,平局结束!",
                                QMessageBox.Yes, QMessageBox.Yes)
        startGame(self)
        return True
    return False


# 计算连接数
def checkLink(self):
    if not self.chess_coord:
        return False
    last_coord = self.chess_coord[-1]
    directions = [
        (1, 0),  # 垂直方向
        (0, 1),  # 水平方向
        (1, 1),  # 右对角线
        (-1, 1)  # 左对角线
    ]

    for direction, method in product(directions, range(5)):
        linking_coords = []
        for i in range(5):
            new_coord = {
                'x': last_coord['x'] + direction[0] * (i - method),
                'y': last_coord['y'] + direction[1] * (i - method),
                'color': last_coord['color']
            }
            if new_coord in self.chess_coord:
                new_coord['outline'] = True
                linking_coords.append(new_coord)

        if len(linking_coords) == 5:
            self.chess_coord.extend(linking_coords)
            self.update()
            return linking_coords

    return False

AIAlgorithm.py —— 人机对战
from random import choice

import numpy as np
from PyQt5.QtWidgets import QMessageBox


# 选择阵容
def selectSquads(self):
    msg_squads = QMessageBox(QMessageBox.Question, "选择", "请选择您的阵容")
    msg_squads.addButton(self.tr("黑子"), QMessageBox.AcceptRole)
    white = msg_squads.addButton(self.tr("白子"), QMessageBox.AcceptRole)
    msg_squads.exec_()
    # 玩家选择白方,则人机执黑先行
    if msg_squads.clickedButton() == white:
        aiGame(self)


# 人机对战AI算法
def aiGame(self):
    ai_coord = strategy(self)
    self.chess_coord.append({'x': ai_coord[0], 'y': ai_coord[1], 'color': self.chess_color})
    # 切换颜色
    self.chess_color = not self.chess_color
    self.status.showMessage(f"等待{'黑方' if self.chess_color else '白方'}落子")


# 开始计算
def strategy(self):
    # 将黑白坐标拆分 ([白方坐标],[黑方坐标])
    board = ([], [])
    for coord in self.chess_coord:
        if coord['color']:
            board[0].append((coord['x'], coord['y']))
        else:
            board[1].append((coord['x'], coord['y']))

    # 将坐标转换成二维数组 1: 我方 -1: 敌方 0: 空位
    table = np.zeros([self.chessboard, self.chessboard])
    for i in range(self.chessboard):
        for j in range(self.chessboard):
            if self.chess_color:
                if (i, j) in board[0]:
                    table[i, j] = -1
                elif (i, j) in board[1]:
                    table[i, j] = 1
            else:
                if (i, j) in board[0]:
                    table[i, j] = 1
                elif (i, j) in board[1]:
                    table[i, j] = -1

    # 判断自己是否先手
    if len(board[0]) == 0 and len(board[1]) == 0:
        return 7, 7
    else:
        score_table = {}
        for i in range(self.chessboard):
            for j in range(self.chessboard):
                if table[i, j] == 0:
                    table[i, j] = 1
                    score_table[(i, j)] = heuristic(self, table)
                    table[i, j] = 0
        self_position = randomChoose(score_table)
        return self_position[0], self_position[1]


# 计分规则
def score(five_tuple):
    if len(five_tuple) != 5:
        print("ERROR")
        return None
    if 1 in five_tuple and -1 in five_tuple:
        return 0
    elif sum(five_tuple) == 0:
        return 7
    elif sum(five_tuple) == -1:
        return -35
    elif sum(five_tuple) == -2:
        return -800
    elif sum(five_tuple) == -3:
        return -15000
    elif sum(five_tuple) == -4:
        return -800000
    elif sum(five_tuple) == -5:
        return -10000000
    elif sum(five_tuple) == 5:
        return 10000000
    elif sum(five_tuple) == 1:
        return 15
    elif sum(five_tuple) == 2:
        return 400
    elif sum(five_tuple) == 3:
        return 1800
    elif sum(five_tuple) == 4:
        return 100000


# 计分
def heuristic(self, table):
    sum_score = 0
    for i in range(self.chessboard):
        for j in range(self.chessboard):
            if j + 4 < self.chessboard:
                sum_score += score(tuple(table[i, j:j + 5]))
            if i + 4 < self.chessboard:
                sum_score += score(tuple(table[i:i + 5, j]))
            if i + 4 < self.chessboard and j + 4 < self.chessboard:
                five_tuple = []
                for k in range(5):
                    five_tuple.append(table[i + k, j + k])
                sum_score += score(tuple(five_tuple))
            if i + 4 < self.chessboard and j - 4 >= 0:
                five_tuple = []
                for k in range(5):
                    five_tuple.append(table[i + k, j - k])
                sum_score += score(tuple(five_tuple))
    return sum_score


# 选择最高分位置
def randomChoose(score_table):
    max_value = max(score_table.items(), key=lambda x: x[1])[1]
    positions = []
    for item in score_table.items():
        if item[1] == max_value:
            positions.append(item[0])
    return choice(positions)

OnlineFunction.py —— 联机对战
import json
import socket
import threading
from functools import partial

import pyperclip
from PyQt5.QtWidgets import QMessageBox, QInputDialog


# 选择服务端或客户端
def selectSide(self):
    msg_side = QMessageBox(QMessageBox.Question, "选择", "请选择创建或进入房间")
    server_button = msg_side.addButton(self.tr("创建房间"), QMessageBox.AcceptRole)
    client_button = msg_side.addButton(self.tr("进入房间"), QMessageBox.AcceptRole)
    msg_side.exec_()
    if msg_side.clickedButton() == server_button:
        checkNetwork(self)
        extractIp(self)
        server(self)
    elif msg_side.clickedButton() == client_button:
        checkNetwork(self)
        client(self)


# 检查网络连接
def checkNetwork(self):
    try:
        # 超时时间
        socket.socket(socket.AF_INET, socket.SOCK_STREAM).connect(('www.baidu.com', 80))
    except socket.error as ex:
        QMessageBox.critical(self, "网络连接异常", "当前网络不可用,请检查您的网络状态\n" + str(ex))
        selectSide(self)


# 获取IP地址
def extractIp(self):
    st = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    st.connect(('10.255.255.255', 1))
    self.server_ip = st.getsockname()[0]
    pyperclip.copy(self.server_ip)
    QMessageBox.information(self, "房间信息", "IP:" + self.server_ip + ",已复制到剪贴板")


# 服务端
def server(self):
    # 绑定IP端口
    self.tcp_server.bind((self.server_ip, self.server_port))
    # 可设置最大连接数
    self.tcp_server.listen()
    threading.Thread(target=partial(startServerListen, self)).start()


# 服务端监听线程
def startServerListen(self):
    while True:
        try:
            self.status.showMessage('房间创建成功, 等待对方加入...')
            self.tcp_socket, address = self.tcp_server.accept()
            threading.Thread(target=partial(receiveData, self)).start()
            self.player_ready = True
            self.chess_color_online = True
            self.status.showMessage('对方已加入, 可以开始游戏')
        except:
            break


# 客户端
def client(self):
    self.tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    text, ok = QInputDialog.getText(None, "输入框", "请输入房间IP:")
    if ok and text != '':
        print("你输入的内容是:", text)
        self.server_ip = text
    else:
        print("用户取消了输入或没有输入内容")
        return selectSide(self)

    try:
        self.tcp_socket.connect((self.server_ip, self.server_port))
    except socket.error as e:
        QMessageBox.critical(self, "网络连接异常", "无法连接到服务器,请检查IP地址是否正确\n" + str(e))
        selectSide(self)

    threading.Thread(target=partial(receiveData, self)).start()
    self.player_ready = True
    self.chess_color_online = False
    self.status.showMessage('您已经加入房间, 请等待房主开始游戏')


# 接收数据
def receiveData(self):
    from MouseFunction import render
    while True:
        try:
            data = self.tcp_socket.recv(1024)
            if data:
                self.chess_coord.append(json.loads(data.decode('utf-8')))
            else:
                break
        except socket.timeout:
            print("接收数据超时,正在重试...")
            continue
        except Exception as e:
            print(f"接收数据时发生错误:{e}")
            break
        render(self)

结束语

  随着这篇文章的收尾,我的Python版五子棋项目也暂告一段落。虽然项目不大,但它不仅是对我编程技能的一次小小挑战,更是对过往编程经历的一次深刻反思与成长。从Java到Python,不仅是编程语言的转换,更是编程思维与习惯的革新。未来,我将继续在这条编程之路上探索前行,不断学习新知识,挑战新高度,用代码编织出更加丰富多彩的数字世界。感谢每一位读者的陪伴与支持,期待与你们在编程的海洋里再次相遇,共同书写属于我们的精彩篇章。

  同学们按照目录复制到对应的文件中,运行main即可启动,如果不想复制,或复制后无法运行,可以私聊我获取项目仓库地址,以拉取完整项目。

  联机模式目前只完成了棋子的同步,后续功能将更新在仓库中。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

晓时谷雨

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值