Python game development, pygame module, python implementation of Gobang online game

Posted by laduch on Wed, 29 Dec 2021 04:37:37 +0100

preface

This time, let's write a simple game that supports online combat and a Gobang game that supports LAN online combat. No more nonsense. Let's start happily~

Effect demonstration

development tool

Python version: 3.6 four

Related modules:

pygame module;

PyQt5 module;

And some Python built-in modules.

Environment construction

Install Python and add it to the environment variable. pip can install the relevant modules required.

Principle introduction

Here is a brief introduction to the principle. The code is mainly written in PyQt5. pygame is only used to play some sound effects. First, design and implement a game main interface:

The code implementation is as follows:

'''Game start interface'''
class gameStartUI(QWidget):
  def __init__(self, parent=None, **kwargs):
    super(gameStartUI, self).__init__(parent)
    self.setFixedSize(760, 650)
    self.setWindowTitle('Gobang-🛰️: ilove-python')
    self.setWindowIcon(QIcon(cfg.ICON_FILEPATH))
    # Background picture
    palette = QPalette()
    palette.setBrush(self.backgroundRole(), QBrush(QPixmap(cfg.BACKGROUND_IMAGEPATHS.get('bg_start'))))
    self.setPalette(palette)
    # Button
    # --Man machine combat
    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)
    # --Online battle
    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)
  '''Man machine combat'''
  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()
  '''Online battle'''
  def playOnline(self):
    self.close()
    self.gaming_ui = playOnlineUI(cfg, self)
    self.gaming_ui.show()

Yes pyqt5, you can write such an interface. It's nothing special. Remember to bind the signals triggered by the man-machine battle and online battle buttons to the functions of man-machine battle and online battle respectively.

The effect is like this:

The main code implementation is as follows:

'''Man machine combat'''
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('Gobang-🛰️: ilove-python')
        self.setWindowIcon(QIcon(cfg.ICON_FILEPATH))
        # Background picture
        palette = QPalette()
        palette.setBrush(self.backgroundRole(), QBrush(QPixmap(cfg.BACKGROUND_IMAGEPATHS.get('bg_game'))))
        self.setPalette(palette)
        # Button
        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)
        # Drop sign
        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()
        # Chessboard (19 * 19 matrix)
        self.chessboard = [[None for i in range(19)] for _ in range(19)]
        # History (for repentance)
        self.history_record = []
        # Is it in the game
        self.is_gaming = True
        # Victorious side
        self.winner = None
        self.winner_info_label = None
        # Whose turn is it to assign colors
        self.player_color = 'white'
        self.ai_color = 'black'
        self.whoseround = self.player_color
        # Instantiate ai
        self.ai_player = aiGobang(self.ai_color, self.player_color)
        # Falling sound loading
        pygame.mixer.init()
        self.drop_sound = pygame.mixer.Sound(cfg.SOUNDS_PATHS.get('drop'))
    '''Left mouse click event-Player turn'''
    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
        # Ensure that the response is only within the chessboard range
        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)
            # There was no one in the place where they were guaranteed to fall
            if self.chessboard[pos[0]][pos[1]]:
                return
            # Instantiate a chess piece and display it
            c = Chessman(self.cfg.CHESSMAN_IMAGEPATHS.get(self.whoseround), self)
            c.move(event.pos())
            c.show()
            self.chessboard[pos[0]][pos[1]] = c
            # The falling sound sounded
            self.drop_sound.play()
            # Finally, the falling position mark follows the falling position
            self.chessman_sign.show()
            self.chessman_sign.move(c.pos())
            self.chessman_sign.raise_()
            # Record this fall
            self.history_record.append([*pos, self.whoseround])
            # Did you win
            self.winner = checkWin(self.chessboard)
            if self.winner:
                self.showGameEndInfo()
                return
            # Switch round side (actually change color)
            self.nextRound()
    '''Left mouse button release operation-Call computer round'''
    def mouseReleaseEvent(self, event):
        if (self.winner is not None) or (self.whoseround != self.ai_color) or (not self.is_gaming):
            return
        self.aiAct()
    '''Computer automatically-AI round'''
    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)
        # Instantiate a chess piece and display it
        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
        # The falling sound sounded
        self.drop_sound.play()
        # Finally, the falling position mark follows the falling position
        self.chessman_sign.show()
        self.chessman_sign.move(c.pos())
        self.chessman_sign.raise_()
        # Record this fall
        self.history_record.append([*next_pos, self.whoseround])
        # Did you win
        self.winner = checkWin(self.chessboard)
        if self.winner:
            self.showGameEndInfo()
            return
        # Switch round side (actually change color)
        self.nextRound()
    '''Change Luozi square'''
    def nextRound(self):
        self.whoseround = self.player_color if self.whoseround == self.ai_color else self.ai_color
    '''Show game end results'''
    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()
    '''Admit defeat'''
    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()
    '''Repentance chess-Only our turn can repent'''
    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()
    '''Start the game-The previous game must have ended'''
    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()
    '''Close window event'''
    def closeEvent(self, event):
        if not self.send_back_signal:
            self.exit_signal.emit()
    '''Return to the game main page'''
    def goHome(self):
        self.send_back_signal = True
        self.close()
        self.back_signal.emit()

The whole logic is as follows:

After designing and implementing the basic interface of the game, the first default is always the player's first hand (white son) and the computer's second hand (black son). Then, when the supervisor hears that the player clicks the left mouse button to the range where the chessboard grid is located, capture the position. If no one has dropped the position before, the player will successfully drop the position. Otherwise, wait for the player's left mouse button click event again. After the player successfully lands, judge whether the game ends because of the player's landing (that is, there are 5 dice connected with the same color on the chessboard). If the game ends, the game end interface will be displayed, otherwise it's AI's turn to land. The logic of AI falling is similar to that of player falling, and then it's the player's turn to fall, and so on.

It should be noted that in order to ensure the real-time response, the AI drop algorithm should be written into the response to the release event after the left mouse click (interested partners can try to write into the response to the mouse click event, which will cause the player's last drop and the AI's current drop results to be displayed only after the AI calculation is completed and dropped).

The start button is to reset the game. There's nothing to say. Here, in order to avoid some people like to cheat, I wrote the code that I must complete the current game to reset the game.

Because it is against AI, the repentance button directly repents two steps. pop the last two drops from the history list, and then remove the two drops from the corresponding position of the chessboard. It is OK to ensure that only our turn can repent to avoid unexpected logic errors.

There's nothing to say about the admit defeat button, that is, admit defeat and end the game ahead of time.

Next, let's realize the online battle. Here, we choose to use TCP/IP protocol for online communication to realize the online battle. Start the side of the game as the server side first:

Listen by opening a new thread:
threading.Thread(target=self.startListen).start()
'''Start listening for client connections'''
def startListen(self):
    while True:
       self.setWindowTitle('Gobang-🛰️: ilove-python -> The server is started successfully, Waiting for client connection')
       self.tcp_socket, self.client_ipport = self.tcp_server.accept()
       self.setWindowTitle('Gobang-🛰️: ilove-python -> Client connected, Click the start button to play the game')

The post initiator connects to the server as the client and sends the basic information of the client player:

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('Gobang-🛰️: ilove-python -> The server has been successfully connected, Click the start button to play the game')

When the client connects to the server, the server also sends the basic player information of the server to the client:

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

Then, both the client and server use the newly opened thread to realize network data listening and receiving:

'''Receive client data'''
def receiveClientData(self):
    while True:
        data = receiveAndReadSocketData(self.tcp_socket)
        self.receive_signal.emit(data)
'''Receiving server-side data'''
def receiveServerData(self):
    while True:
        data = receiveAndReadSocketData(self.tcp_socket)
        self.receive_signal.emit(data)

And make corresponding responses in the main process according to different data received:

'''Response to received data'''
def responseForReceiveData(self, data):
    if data['type'] == 'action' and data['detail'] == 'exit':
        QMessageBox.information(self, 'Tips', 'Your opponent has quit the game, The game will automatically return to the main interface')
        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, 'Tips', 'Other party's request(again)Start the game, You are%s, Do you agree?' % {'white': 'Baizi', 'black': 'sunspot'}.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('Gobang-🛰️: ilove-python -> %s Go chess' % 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']
        # Instantiate a chess piece and display it
        c = Chessman(self.cfg.CHESSMAN_IMAGEPATHS.get(self.whoseround), self)
        c.move(QPoint(*Chesspos2Pixel(pos)))
        c.show()
        self.chessboard[pos[0]][pos[1]] = c
        # The falling sound sounded
        self.drop_sound.play()
        # Finally, the falling position mark follows the falling position
        self.chessman_sign.show()
        self.chessman_sign.move(c.pos())
        self.chessman_sign.raise_()
        # Record this fall
        self.history_record.append([*pos, self.whoseround])
        # Did you win
        self.winner = checkWin(self.chessboard)
        if self.winner:
            self.showGameEndInfo()
            return
        # Switch round side (actually change color)
        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, 'Tips', 'The other party asks for repentance, Do you agree?', 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('Gobang-🛰️: ilove-python -> %s Go chess' % 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, 'Tips', 'The other party agrees to start the game request, You are%s, The speaker goes first.' % {'white': 'Baizi', 'black': 'sunspot'}.get(self.player_color))
        else:
            QMessageBox.information(self, 'Tips', 'The other party rejected your request to start the game.')
    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, 'Tips', 'The other party agreed to your request.')
        else:
            QMessageBox.information(self, 'Tips', 'Your request for repentance has been rejected.')
    elif data['type'] == 'nickname':
        self.opponent_nickname = data['data']

Place of modification

You must click the start button and get the other party's consent before you can officially start the game. The repentance button can only be pressed in the other party's turn. After the other party agrees to repent, you need to remember to switch the falling party back to yourself. Then a push button is added, which can only be pressed after the opponent's turn. The above is all the code changes.

This is the end of the article. Thank you for watching, Python 24 games series , the next article shares 2048 games

In order to thank readers, I would like to share some of my recent collection of programming dry goods with you and give back to every reader. I hope I can help you.

All done ~ get the complete source code by private letter..

Previous review

Python implementation of bomber games

Python implementation of classic bean eating games

Python Xiaole games

Python real dinosaur jump games

Python implementation of simple version of aircraft war games

Tetris games implemented in Python

Python alien invasion games

Python implements "Bunny and Bun" game

Python implementation of eight tone symbol games

Python puzzle games

Python skiing games

Python implements the classic 90 tank war

Python FlappyBird games

Python tower defense games

Python implementation of fruit and gold coin games

Python push box games

Python 24 point games

Python implementation of table tennis games

Python implementation of brick games

Python has implemented maze games

Python implementation of hamster games

Topics: Python Game Development pygame