Python實(shí)現(xiàn)五子棋聯(lián)機(jī)對戰(zhàn)小游戲
效果演示

開發(fā)工具
Python版本: 3.6.4
相關(guān)模塊:
pygame模塊;
PyQt5模塊;
以及一些Python自帶的模塊。
環(huán)境搭建
安裝Python并添加到環(huán)境變量,pip安裝需要的相關(guān)模塊即可。
主要代碼
這里簡單介紹下原理吧,代碼主要用PyQt5寫的,pygame只用來播放一些音效。首先,設(shè)計(jì)并實(shí)現(xiàn)個(gè)游戲主界面:

代碼實(shí)現(xiàn)如下
'''游戲開始界面'''
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)
# 按鈕
# --人機(jī)對戰(zhàn)
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)
# --聯(lián)機(jī)對戰(zhàn)
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)
'''人機(jī)對戰(zhàn)'''
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()
'''聯(lián)機(jī)對戰(zhàn)'''
def playOnline(self):
self.close()
self.gaming_ui = playOnlineUI(cfg, self)
self.gaming_ui.show()
會pyqt5的應(yīng)該都可以寫出這樣的界面,沒啥特別的,記得把人機(jī)對戰(zhàn)和聯(lián)機(jī)對戰(zhàn)兩個(gè)按鈕觸發(fā)后的信號分別綁定到人機(jī)對戰(zhàn)和聯(lián)機(jī)對戰(zhàn)的函數(shù)上就行。
效果大概是這樣的:

主要的代碼實(shí)現(xiàn)如下:
'''人機(jī)對戰(zhàn)'''
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)
# 落子標(biāo)志
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
# 實(shí)例化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'))
'''鼠標(biāo)左鍵點(diǎn)擊事件-玩家回合'''
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
# 保證只在棋盤范圍內(nèi)響應(yīng)
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
# 實(shí)例化一個(gè)棋子并顯示
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()
# 最后落子位置標(biāo)志對落子位置進(jìn)行跟隨
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
# 切換回合方(其實(shí)就是改顏色)
self.nextRound()
'''鼠標(biāo)左鍵釋放操作-調(diào)用電腦回合'''
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)
# 實(shí)例化一個(gè)棋子并顯示
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()
# 最后落子位置標(biāo)志對落子位置進(jìn)行跟隨
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
# 切換回合方(其實(shí)就是改顏色)
self.nextRound()
'''改變落子方'''
def nextRound(self):
self.whoseround = self.player_color if self.whoseround == self.ai_color else self.ai_color
'''顯示游戲結(jié)束結(jié)果'''
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()
'''認(rèn)輸'''
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()
'''悔棋-只有我方回合的時(shí)候可以悔棋'''
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()
'''開始游戲-之前的對弈必須已經(jīng)結(jié)束才行'''
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()
'''關(guān)閉窗口事件'''
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()
整個(gè)邏輯是這樣的:
設(shè)計(jì)并實(shí)現(xiàn)游戲的基本界面之后,先默認(rèn)永遠(yuǎn)是玩家先手(白子),電腦后手(黑子)。然后,當(dāng)監(jiān)聽到玩家鼠標(biāo)左鍵點(diǎn)擊到棋盤網(wǎng)格所在的范圍內(nèi)的時(shí)候,捕獲該位置,若該位置之前沒有人落子過,則玩家成功落子,否則重新等待玩家鼠標(biāo)左鍵點(diǎn)擊事件。玩家成功落子后,判斷是否因?yàn)橥婕衣渥佣鴮?dǎo)致游戲結(jié)束(即棋盤上有5顆同色子相連了),若游戲結(jié)束,則顯示游戲結(jié)束界面,否則輪到AI落子。AI落子和玩家落子的邏輯類似,然后又輪到玩家落子,以此類推。
需要注意的是:為保證響應(yīng)的實(shí)時(shí)性,AI落子算法應(yīng)當(dāng)寫到鼠標(biāo)左鍵點(diǎn)擊后釋放事件的響應(yīng)中(感興趣的小伙伴可以試試寫到鼠標(biāo)點(diǎn)擊事件的響應(yīng)中,這樣會導(dǎo)致必須在AI計(jì)算結(jié)束并落子后,才能顯示玩家上一次的落子和AI此次的落子結(jié)果)。
開始按鈕就是重置游戲,沒啥可說的,這里為了避免有些人喜歡耍賴,我實(shí)現(xiàn)的時(shí)候代碼寫的是必須完成當(dāng)前對弈才能重置游戲。
因?yàn)槭呛虯I下,所以悔棋按鈕直接悔兩步,從歷史記錄列表里pop最后兩次落子然后從棋盤對應(yīng)位置取下這兩次落子就OK了,并且保證只有我方回合可以悔棋以避免出現(xiàn)意料之外的邏輯出錯(cuò)。
認(rèn)輸按鈕也沒啥可說的,就是認(rèn)輸然后提前結(jié)束游戲。
接下來我們來實(shí)現(xiàn)一下聯(lián)機(jī)對戰(zhàn),這里我們選擇使用TCP/IP協(xié)議進(jìn)行聯(lián)機(jī)通信從而實(shí)現(xiàn)聯(lián)機(jī)對戰(zhàn)。先啟動游戲的一方作為服務(wù)器端:

通過新開一個(gè)線程來實(shí)現(xiàn)監(jiān)聽:
threading.Thread(target=self.startListen).start()
'''開始監(jiān)聽客戶端的連接'''
def startListen(self):
while True:
self.setWindowTitle('五子棋-???: ilove-python ——> 服務(wù)器端啟動成功, 等待客戶端連接中')
self.tcp_socket, self.client_ipport = self.tcp_server.accept()
self.setWindowTitle('五子棋-???: ilove-python ——> 客戶端已連接, 點(diǎn)擊開始按鈕進(jìn)行游戲')
后啟動方作為客戶端連接服務(wù)器端并發(fā)送客戶端玩家的基本信息:
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 ——> 已經(jīng)成功連接服務(wù)器, 點(diǎn)擊開始按鈕進(jìn)行游戲')
當(dāng)客戶端連接到服務(wù)器端時(shí),服務(wù)器端也發(fā)送服務(wù)器端的玩家基本信息給客戶端:
data = {'type': 'nickname', 'data': self.nickname}
self.tcp_socket.sendall(packSocketData(data))
然后客戶端和服務(wù)器端都利用新開的線程來實(shí)現(xiàn)網(wǎng)絡(luò)數(shù)據(jù)監(jiān)聽接收:
'''接收客戶端數(shù)據(jù)'''
def receiveClientData(self):
while True:
data = receiveAndReadSocketData(self.tcp_socket)
self.receive_signal.emit(data)
'''接收服務(wù)器端數(shù)據(jù)'''
def receiveServerData(self):
while True:
data = receiveAndReadSocketData(self.tcp_socket)
self.receive_signal.emit(data)
并根據(jù)接收到的不同數(shù)據(jù)在主進(jìn)程中做成對應(yīng)的響應(yīng):
'''響應(yīng)接收到的數(shù)據(jù)'''
def responseForReceiveData(self, data):
if data['type'] == 'action' and data['detail'] == 'exit':
QMessageBox.information(self, '提示', '您的對手已退出游戲, 游戲?qū)⒆詣臃祷刂鹘缑?)
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']
# 實(shí)例化一個(gè)棋子并顯示
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()
# 最后落子位置標(biāo)志對落子位置進(jìn)行跟隨
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
# 切換回合方(其實(shí)就是改顏色)
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, 執(zhí)白者先行.' % {'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']
修改的地方
必須點(diǎn)擊開始按鈕,并經(jīng)過對方同意之后,才能正式開始對弈,悔棋按鈕只有在對方回合才能按,對方同意悔棋后需要記得把落子方切換回自己。然后加了一個(gè)催促按鈕,同樣必須在對方回合才能按。以上就是全部代碼修改的全部地方了。?
以上就是Python實(shí)現(xiàn)五子棋聯(lián)機(jī)對戰(zhàn)小游戲的詳細(xì)內(nèi)容,更多關(guān)于Python 五子棋聯(lián)機(jī)對戰(zhàn)的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
python遞歸&迭代方法實(shí)現(xiàn)鏈表反轉(zhuǎn)
這篇文章主要介紹了python遞歸&迭代方法實(shí)現(xiàn)鏈表反轉(zhuǎn),文章分享一段詳細(xì)實(shí)現(xiàn)代碼,需要的小伙伴可以參考一下,希望對你的學(xué)習(xí)或工作有所幫助2022-02-02
Python?Poetrya項(xiàng)目依賴管理安裝使用詳解
這篇文章主要為大家介紹了Python?Poetrya項(xiàng)目依賴管理安裝使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-11-11
基于CentOS搭建Python Django環(huán)境過程解析
這篇文章主要介紹了基于CentOS搭建Python Django環(huán)境過程解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-08-08
matplotlib之多邊形選區(qū)(PolygonSelector)的使用
這篇文章主要介紹了matplotlib之多邊形選區(qū)(PolygonSelector)的使用,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-02-02

