Python游戏开发,pygame模块,Python实现五子棋联机对战小游戏

  • 时间:2022-03-15 14:30 作者:扒皮狼 来源: 阅读:500
  • 扫一扫,手机访问
摘要:往期回顾Python实现过迷宫小游戏Python实现“小兔子和Bun”游戏Python实现八音符小游戏Python实现拼图小游戏Python实现滑雪小游戏Python实现经典90坦克大战Python实现FlappyBird小游戏Python实现恐龙跳一跳小游戏Python实现塔防小游戏Python实

往期回顾

Python实现过迷宫小游戏

Python实现“小兔子和Bun”游戏

Python实现八音符小游戏

Python实现拼图小游戏

Python实现滑雪小游戏

Python实现经典90坦克大战

Python实现FlappyBird小游戏

Python实现恐龙跳一跳小游戏

Python实现塔防小游戏

Python实现接水果和金币小游戏

Python实现简易版飞机大战小游戏

Python实现俄罗斯方块小游戏

Python实现推箱子小游戏

Python实现外星人入侵小游戏

Python实现经典吃豆豆小游戏

Python实现消消乐小游戏

Python实现24点小游戏

Python实现乒乓球小游戏

Python实现打砖块小游戏

Python实现过打地鼠小游戏

前言

这次我们来写个简单支持联机对战的游戏,支持局域网联机对战的五子棋小游戏。废话不多说,让我们愉快地开始吧~

效果演示

请增加图片形容

开发工具

Python版本: 3.6.4

相关模块:

pygame模块;

PyQt5模块;

以及少量Python自带的模块。

环境调试

安装Python并增加到环境变量,pip安装需要的相关模块就可。

原理简介

这里简单详情下原理吧,代码主要用PyQt5写的,pygame只用来播放少量音效。首先,设计并实现个游戏主界面:


五子棋

代码实现如下:

'''游戏开始界面'''class gameStartUI(QWidget):  def __init__(self, parent=None, **kwargs):    super(gameStartUI, self).__init__(parent)    self.setFixedSize(760, 650)    self.setWindowTitle('五子棋-🛰️: ilove-python')    self.setWindowIcon(QIcon(cfg.ICON_FILEPATH))    # 背景图片    palette = QPalette()    palette.setBrush(self.backgroundRole(), QBrush(QPixmap(cfg.BACKGROUND_IMAGEPATHS.get('bg_start'))))    self.setPalette(palette)    # 按钮    # --人机对战    self.ai_button = PushButton(cfg.BUTTON_IMAGEPATHS.get('ai'), self)    self.ai_button.move(250, 200)    self.ai_button.show()    self.ai_button.click_signal.connect(self.playWithAI)    # --联机对战    self.online_button = PushButton(cfg.BUTTON_IMAGEPATHS.get('online'), self)    self.online_button.move(250, 350)    self.online_button.show()    self.online_button.click_signal.connect(self.playOnline)  '''人机对战'''  def playWithAI(self):    self.close()    self.gaming_ui = playWithAIUI(cfg)    self.gaming_ui.exit_signal.connect(lambda: sys.exit())    self.gaming_ui.back_signal.connect(self.show)    self.gaming_ui.show()  '''联机对战'''  def playOnline(self):    self.close()    self.gaming_ui = playOnlineUI(cfg, self)    self.gaming_ui.show()

会pyqt5的应该都可以写出这样的界面,没啥特别的,记得把人机对战和联机对战两个按钮触发后的信号分别绑定到人机对战和联机对战的函数上就行。

效果大概是这样的:


五子棋

主要的代码实现如下:

'''人机对战'''class playWithAIUI(QWidget):    back_signal = pyqtSignal()    exit_signal = pyqtSignal()    send_back_signal = False    def __init__(self, cfg, parent=None, **kwargs):        super(playWithAIUI, self).__init__(parent)        self.cfg = cfg        self.setFixedSize(760, 650)        self.setWindowTitle('五子棋-🛰️: ilove-python')        self.setWindowIcon(QIcon(cfg.ICON_FILEPATH))        # 背景图片        palette = QPalette()        palette.setBrush(self.backgroundRole(), QBrush(QPixmap(cfg.BACKGROUND_IMAGEPATHS.get('bg_game'))))        self.setPalette(palette)        # 按钮        self.home_button = PushButton(cfg.BUTTON_IMAGEPATHS.get('home'), self)        self.home_button.click_signal.connect(self.goHome)        self.home_button.move(680, 10)        self.startgame_button = PushButton(cfg.BUTTON_IMAGEPATHS.get('startgame'), self)        self.startgame_button.click_signal.connect(self.startgame)        self.startgame_button.move(640, 240)        self.regret_button = PushButton(cfg.BUTTON_IMAGEPATHS.get('regret'), self)        self.regret_button.click_signal.connect(self.regret)        self.regret_button.move(640, 310)        self.givein_button = PushButton(cfg.BUTTON_IMAGEPATHS.get('givein'), self)        self.givein_button.click_signal.connect(self.givein)        self.givein_button.move(640, 380)        # 落子标志        self.chessman_sign = QLabel(self)        sign = QPixmap(cfg.CHESSMAN_IMAGEPATHS.get('sign'))        self.chessman_sign.setPixmap(sign)        self.chessman_sign.setFixedSize(sign.size())        self.chessman_sign.show()        self.chessman_sign.hide()        # 棋盘(19*19矩阵)        self.chessboard = [[None for i in range(19)] for _ in range(19)]        # 历史记录(悔棋用)        self.history_record = []        # 能否在游戏中        self.is_gaming = True        # 胜利方        self.winner = None        self.winner_info_label = None        # 颜色分配and目前轮到谁落子        self.player_color = 'white'        self.ai_color = 'black'        self.whoseround = self.player_color        # 实例化ai        self.ai_player = aiGobang(self.ai_color, self.player_color)        # 落子声音加载        pygame.mixer.init()        self.drop_sound = pygame.mixer.Sound(cfg.SOUNDS_PATHS.get('drop'))    '''鼠标左键点击事件-玩家回合'''    def mousePressEvent(self, event):        if (event.buttons() != QtCore.Qt.LeftButton) or (self.winner is not None) or (self.whoseround != self.player_color) or (not self.is_gaming):            return        # 保证只在棋盘范围内响应        if event.x() >= 50 and event.x() <= 50 + 30 * 18 + 14 and event.y() >= 50 and event.y() <= 50 + 30 * 18 + 14:            pos = Pixel2Chesspos(event)            # 保证落子的地方原本没有人落子            if self.chessboard[pos[0]][pos[1]]:                return            # 实例化一个棋子并显示            c = Chessman(self.cfg.CHESSMAN_IMAGEPATHS.get(self.whoseround), self)            c.move(event.pos())            c.show()            self.chessboard[pos[0]][pos[1]] = c            # 落子声音响起            self.drop_sound.play()            # 最后落子位置标志对落子位置进行跟随            self.chessman_sign.show()            self.chessman_sign.move(c.pos())            self.chessman_sign.raise_()            # 记录这次落子            self.history_record.append([*pos, self.whoseround])            # 能否胜利了            self.winner = checkWin(self.chessboard)            if self.winner:                self.showGameEndInfo()                return            # 切换回合方(其实就是改颜色)            self.nextRound()    '''鼠标左键释放操作-调用电脑回合'''    def mouseReleaseEvent(self, event):        if (self.winner is not None) or (self.whoseround != self.ai_color) or (not self.is_gaming):            return        self.aiAct()    '''电脑自动下-AI回合'''    def aiAct(self):        if (self.winner is not None) or (self.whoseround == self.player_color) or (not self.is_gaming):            return        next_pos = self.ai_player.act(self.history_record)        # 实例化一个棋子并显示        c = Chessman(self.cfg.CHESSMAN_IMAGEPATHS.get(self.whoseround), self)        c.move(QPoint(*Chesspos2Pixel(next_pos)))        c.show()        self.chessboard[next_pos[0]][next_pos[1]] = c        # 落子声音响起        self.drop_sound.play()        # 最后落子位置标志对落子位置进行跟随        self.chessman_sign.show()        self.chessman_sign.move(c.pos())        self.chessman_sign.raise_()        # 记录这次落子        self.history_record.append([*next_pos, self.whoseround])        # 能否胜利了        self.winner = checkWin(self.chessboard)        if self.winner:            self.showGameEndInfo()            return        # 切换回合方(其实就是改颜色)        self.nextRound()    '''改变落子方'''    def nextRound(self):        self.whoseround = self.player_color if self.whoseround == self.ai_color else self.ai_color    '''显示游戏结束结果'''    def showGameEndInfo(self):        self.is_gaming = False        info_img = QPixmap(self.cfg.WIN_IMAGEPATHS.get(self.winner))        self.winner_info_label = QLabel(self)        self.winner_info_label.setPixmap(info_img)        self.winner_info_label.resize(info_img.size())        self.winner_info_label.move(50, 50)        self.winner_info_label.show()    '''认输'''    def givein(self):        if self.is_gaming and (self.winner is None) and (self.whoseround == self.player_color):            self.winner = self.ai_color            self.showGameEndInfo()    '''悔棋-只有我方回合的时候可以悔棋'''    def regret(self):        if (self.winner is not None) or (len(self.history_record) == 0) or (not self.is_gaming) and (self.whoseround != self.player_color):            return        for _ in range(2):            pre_round = self.history_record.pop(-1)            self.chessboard[pre_round[0]][pre_round[1]].close()            self.chessboard[pre_round[0]][pre_round[1]] = None        self.chessman_sign.hide()    '''开始游戏-之前的对弈必需已经结束才行'''    def startgame(self):        if self.is_gaming:            return        self.is_gaming = True        self.whoseround = self.player_color        for i, j in product(range(19), range(19)):            if self.chessboard[i][j]:                self.chessboard[i][j].close()                self.chessboard[i][j] = None        self.winner = None        self.winner_info_label.close()        self.winner_info_label = None        self.history_record.clear()        self.chessman_sign.hide()    '''关闭窗口事件'''    def closeEvent(self, event):        if not self.send_back_signal:            self.exit_signal.emit()    '''返回游戏主页面'''    def goHome(self):        self.send_back_signal = True        self.close()        self.back_signal.emit()

整个逻辑是这样的:

设计并实现游戏的基本界面之后,先默认永远是玩家先手(白子),电脑后手(黑子)。而后,当监听到玩家鼠标左键点击到棋盘网格所在的范围内的时候,捕获该位置,若该位置之前没有人落子过,则玩家成功落子,否则重新等待玩家鼠标左键点击事件。玩家成功落子后,判断能否由于玩家落子而导致游戏结束(即棋盘上有5颗同色子相连了),若游戏结束,则显示游戏结束界面,否则轮到AI落子。AI落子和玩家落子的逻辑相似,而后又轮到玩家落子,以此类推。

需要注意的是:为保证响应的实时性,AI落子算法应当写到鼠标左键点击后释放事件的响应中(感兴趣的小伙伴可以试试写到鼠标点击事件的响应中,这样会导致必需在AI计算结束并落子后,才能显示玩家上一次的落子和AI此次的落子结果)。

开始按钮就是重置游戏,没啥可说的,这里为了避免有些人喜欢耍赖,我实现的时候代码写的是必需完成当前对弈才能重置游戏。

由于是和AI下,所以悔棋按钮直接悔两步,从历史记录列表里pop最后两次落子而后从棋盘对应位置取下这两次落子就OK了,并且保证只有我方回合可以悔棋以避免出现预料之外的逻辑出错。

认输按钮也没啥可说的,就是认输而后提前结束游戏。

接下来我们来实现一下联机对战,这里我们选择使用TCP/IP协议进行联机通信从而实现联机对战。先启动游戏的一方作为服务器端:

五子棋.png
通过新开一个线程来实现监听:threading.Thread(target=self.startListen).start()'''开始监听用户端的连接'''def startListen(self):    while True:       self.setWindowTitle('五子棋-🛰️: ilove-python ——> 服务器端启动成功, 等待用户端连接中')       self.tcp_socket, self.client_ipport = self.tcp_server.accept()       self.setWindowTitle('五子棋-🛰️: ilove-python ——> 用户端已连接, 点击开始按钮进行游戏')

后启动方作为用户端连接服务器端并发送用户端玩家的基本信息:

self.tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)self.tcp_socket.connect(self.server_ipport)data = {'type': 'nickname', 'data': self.nickname}self.tcp_socket.sendall(packSocketData(data))self.setWindowTitle('五子棋-🛰️: ilove-python ——> 已经成功连接服务器, 点击开始按钮进行游戏')

当用户端连接到服务器端时,服务器端也发送服务器端的玩家基本信息给用户端:

data = {'type': 'nickname', 'data': self.nickname}self.tcp_socket.sendall(packSocketData(data))

而后用户端和服务器端都利用新开的线程来实现网络数据监听接收:

'''接收用户端数据'''def receiveClientData(self):    while True:        data = receiveAndReadSocketData(self.tcp_socket)        self.receive_signal.emit(data)'''接收服务器端数据'''def receiveServerData(self):    while True:        data = receiveAndReadSocketData(self.tcp_socket)        self.receive_signal.emit(data)

并根据接收到的不同数据在主进程中做成对应的响应:

'''响应接收到的数据'''def responseForReceiveData(self, data):    if data['type'] == 'action' and data['detail'] == 'exit':        QMessageBox.information(self, '提醒', '您的对手已退出游戏, 游戏将自动返回主界面')        self.goHome()    elif data['type'] == 'action' and data['detail'] == 'startgame':        self.opponent_player_color, self.player_color = data['data']        self.whoseround = 'white'        self.whoseround2nickname_dict = {self.player_color: self.nickname, self.opponent_player_color: self.opponent_nickname}        res = QMessageBox.information(self, '提醒', '对方请求(重新)开始游戏, 您为%s, 您能否同意?' % {'white': '白子', 'black': '黑子'}.get(self.player_color), QMessageBox.Yes | QMessageBox.No)        if res == QMessageBox.Yes:            data = {'type': 'reply', 'detail': 'startgame', 'data': True}            self.tcp_socket.sendall(packSocketData(data))            self.is_gaming = True            self.setWindowTitle('五子棋-🛰️: ilove-python ——> %s走棋' % self.whoseround2nickname_dict.get(self.whoseround))            for i, j in product(range(19), range(19)):                if self.chessboard[i][j]:                    self.chessboard[i][j].close()                    self.chessboard[i][j] = None            self.history_record.clear()            self.winner = None            if self.winner_info_label:                self.winner_info_label.close()            self.winner_info_label = None            self.chessman_sign.hide()        else:            data = {'type': 'reply', 'detail': 'startgame', 'data': False}            self.tcp_socket.sendall(packSocketData(data))    elif data['type'] == 'action' and data['detail'] == 'drop':        pos = data['data']        # 实例化一个棋子并显示        c = Chessman(self.cfg.CHESSMAN_IMAGEPATHS.get(self.whoseround), self)        c.move(QPoint(*Chesspos2Pixel(pos)))        c.show()        self.chessboard[pos[0]][pos[1]] = c        # 落子声音响起        self.drop_sound.play()        # 最后落子位置标志对落子位置进行跟随        self.chessman_sign.show()        self.chessman_sign.move(c.pos())        self.chessman_sign.raise_()        # 记录这次落子        self.history_record.append([*pos, self.whoseround])        # 能否胜利了        self.winner = checkWin(self.chessboard)        if self.winner:            self.showGameEndInfo()            return        # 切换回合方(其实就是改颜色)        self.nextRound()    elif data['type'] == 'action' and data['detail'] == 'givein':        self.winner = self.player_color        self.showGameEndInfo()    elif data['type'] == 'action' and data['detail'] == 'urge':        self.urge_sound.play()    elif data['type'] == 'action' and data['detail'] == 'regret':        res = QMessageBox.information(self, '提醒', '对方请求悔棋, 您能否同意?', QMessageBox.Yes | QMessageBox.No)        if res == QMessageBox.Yes:            pre_round = self.history_record.pop(-1)            self.chessboard[pre_round[0]][pre_round[1]].close()            self.chessboard[pre_round[0]][pre_round[1]] = None            self.chessman_sign.hide()            self.nextRound()            data = {'type': 'reply', 'detail': 'regret', 'data': True}            self.tcp_socket.sendall(packSocketData(data))        else:            data = {'type': 'reply', 'detail': 'regret', 'data': False}            self.tcp_socket.sendall(packSocketData(data))    elif data['type'] == 'reply' and data['detail'] == 'startgame':        if data['data']:            self.is_gaming = True            self.setWindowTitle('五子棋-🛰️: ilove-python ——> %s走棋' % self.whoseround2nickname_dict.get(self.whoseround))            for i, j in product(range(19), range(19)):                if self.chessboard[i][j]:                    self.chessboard[i][j].close()                    self.chessboard[i][j] = None            self.history_record.clear()            self.winner = None            if self.winner_info_label:                self.winner_info_label.close()            self.winner_info_label = None            self.chessman_sign.hide()            QMessageBox.information(self, '提醒', '对方同意开始游戏请求, 您为%s, 执白者先行.' % {'white': '白子', 'black': '黑子'}.get(self.player_color))        else:            QMessageBox.information(self, '提醒', '对方拒绝了您开始游戏的请求.')    elif data['type'] == 'reply' and data['detail'] == 'regret':        if data['data']:            pre_round = self.history_record.pop(-1)            self.chessboard[pre_round[0]][pre_round[1]].close()            self.chessboard[pre_round[0]][pre_round[1]] = None            self.nextRound()            QMessageBox.information(self, '提醒', '对方同意了您的悔棋请求.')        else:            QMessageBox.information(self, '提醒', '对方拒绝了您的悔棋请求.')    elif data['type'] == 'nickname':        self.opponent_nickname = data['data']

修改的地方

必需点击开始按钮,并经过对方同意之后,才能正式开始对弈,悔棋按钮只有在对方回合才能按,对方同意悔棋后需要记得把落子方切换回自己。而后加了一个催促按钮,同样必需在对方回合才能按。以上就是一律代码修改的一律地方了。

干货主要有:

① 2000多本Python电子书(主流和经典的书籍应该都有了)

② Python标准库资料(最全中文版)

③ 项目源码(四五十个有趣且经典的练手项目及源码)

④ Python基础入门、爬虫、web开发、大数据分析方面的视频(适合小白学习)

⑤ Python学习路线图(告别不入流的学习)

⑥ 两天的Python爬虫训练营直播权限

All done~详见个人主页简介或者私信获取完整源代码。。

  • 全部评论(0)
最新发布的资讯信息
【系统环境|】2FA验证器 验证码如何登录(2024-04-01 20:18)
【系统环境|】怎么做才能建设好外贸网站?(2023-12-20 10:05)
【系统环境|数据库】 潮玩宇宙游戏道具收集方法(2023-12-12 16:13)
【系统环境|】遥遥领先!青否数字人直播系统5.0发布,支持真人接管实时驱动!(2023-10-12 17:31)
【系统环境|服务器应用】克隆自己的数字人形象需要几步?(2023-09-20 17:13)
【系统环境|】Tiktok登录教程(2023-02-13 14:17)
【系统环境|】ZORRO佐罗软件安装教程及一键新机使用方法详细简介(2023-02-10 21:56)
【系统环境|】阿里云 centos 云盘扩容命令(2023-01-10 16:35)
【系统环境|】补单系统搭建补单源码搭建(2022-05-18 11:35)
【系统环境|服务器应用】高端显卡再度登上热搜,竟然是因为“断崖式”的降价(2022-04-12 19:47)
手机二维码手机访问领取大礼包
返回顶部