preface

This time we will write a simple support online games, support LAN online gobang games. Without further ado, let’s begin happily ~

Results demonstrate

The development tools

Python version: 3.6.4

Related modules:

Pygame module;

PyQt5 module;

And some modules that come with Python.

Environment set up

Install Python and add it to the environment variables. PIP installs the required modules.

Introduction of the principle

The code is mostly written in PyQt5, and PyGame is only used to play some sound effects. First, design and implement the main interface of the game:

Code implementation is as follows:

"Game Start screen"
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 Image
    palette = QPalette()
    palette.setBrush(self.backgroundRole(), QBrush(QPixmap(cfg.BACKGROUND_IMAGEPATHS.get('bg_start'))))
    self.setPalette(palette)
    # button
    # -- Man versus machine
    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)
    # -- Play online
    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 Vs. Man"
  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()
  Play Online
  def playOnline(self) :
    self.close()
    self.gaming_ui = playOnlineUI(cfg, self)
    self.gaming_ui.show()
Copy the code

Pyqt5 should be able to write such an interface, nothing special, remember to the man-machine and play online two buttons triggered after the signal bound to the man-machine and play functions.

The effect looks something like this:

The main code implementation is as follows:

"Man Vs. Man"
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 Image
        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 flag
        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()
        # Checkerboard (19 by 19 matrix)
        self.chessboard = [[None for i in range(19)] for _ in range(19)]
        # History (for chess)
        self.history_record = []
        # In the game or not
        self.is_gaming = True
        # victory party
        self.winner = None
        self.winner_info_label = None
        Whose turn is it to place the ball
        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)
        # Drop child 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 responses are 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)
            # Ensure that the place where the pin is placed has not been placed
            if self.chessboard[pos[0]][pos[1]] :return
            # Instantiate a chess piece and display
            c = Chessman(self.cfg.CHESSMAN_IMAGEPATHS.get(self.whoseround), self)
            c.move(event.pos())
            c.show()
            self.chessboard[pos[0]][pos[1]] = c
            # The drop sounds
            self.drop_sound.play()
            The # final drop position flag follows the drop position
            self.chessman_sign.show()
            self.chessman_sign.move(c.pos())
            self.chessman_sign.raise_()
            Record this drop
            self.history_record.append([*pos, self.whoseround])
            # Victory or not
            self.winner = checkWin(self.chessboard)
            if self.winner:
                self.showGameEndInfo()
                return
            # Change the color of the turn
            self.nextRound()
    Left mouse Button release operation - Call computer turn
    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 automatic down -AI turn"
    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
        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 drop sounds
        self.drop_sound.play()
        The # final drop position flag follows the drop position
        self.chessman_sign.show()
        self.chessman_sign.move(c.pos())
        self.chessman_sign.raise_()
        Record this drop
        self.history_record.append([*next_pos, self.whoseround])
        # Victory or not
        self.winner = checkWin(self.chessboard)
        if self.winner:
            self.showGameEndInfo()
            return
        # Change the color of the turn
        self.nextRound()
    Change drop square
    def nextRound(self) :
        self.whoseround = self.player_color if self.whoseround == self.ai_color else self.ai_color
    Display end of game result
    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()
    "' give up ' ' '
    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()
    "Contrite - Only on our turn can we contrite."
    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 be over.
    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 main game page.
    def goHome(self) :
        self.send_back_signal = True
        self.close()
        self.back_signal.emit()
Copy the code

The whole logic goes like this:

After designing and implementing the basic interface of the game, the default is always the player first (white) and the computer second (black). Then, when the listener detects that the player’s left mouse button is within the radius of the grid, the position is captured. If the position has not been placed before, the player succeeds in landing, otherwise the player waits for the left mouse button to click again. If the game is over, the end of the game interface will be displayed. Otherwise, it will be the turn of THE AI. The LOGIC of the AI drop is similar to the player drop, and then the player drop, and so on.

Need to be aware that in order to ensure the real-time response, AI move later algorithm should be wrote after the mouse the left key click release events in the response (interested friends can try wrote the mouse to click in the response of events, which can cause must be at the end of the AI calculation and move later, can show the player of the last move later and AI the move later results).

The start button is just to reset the game, so there’s nothing to say about it, but in case some of you want to cheat, the code I implemented says you have to finish the current game to reset the game.

Since it’s against the AI, the retract button directly regrets two moves, pops the last two moves from the history list and then removes them from the corresponding position on the board, and ensures that only our own turn can retract to avoid unexpected logical errors.

The defeat button doesn’t have anything to say, it just throws in the towel and ends the game early.

Next we will implement the online play, here we choose to use TCP/IP protocol for online communication to achieve the online play. Start the game first on one side as the server side:

Thread(target=self.startListen).start()Start listening for client connections
def startListen(self) :
    while True:
       self.setWindowTitle('Gobang -🛰️: ilove-python -- > Server started successfully, waiting for client to connect')
       self.tcp_socket, self.client_ipport = self.tcp_server.accept()
       self.setWindowTitle('Gobang -🛰️: ilove- Python -- > Client connected, click start button to play')
Copy the code

The later initiator connects to the server as a 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 -- > Connect server successfully, click start button to play')
Copy the code

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

data = {'type': 'nickname'.'data': self.nickname}
self.tcp_socket.sendall(packSocketData(data))
Copy the code

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

Receiving 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)
Copy the code

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

Response to received data.
def responseForReceiveData(self, data) :
    if data['type'] = ='action' and data['detail'] = ='exit':
        QMessageBox.information(self, 'tip'.'Your opponent has quit the game, the game will automatically return to the main screen')
        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, 'tip'.'The other party requests to (re) start the game, you are %s, do you agree? ' % {'white': '白子'.'black': 'the spots'}.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('Gobackgammon -🛰️: ilove-python -- > %s moves' % 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
        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 drop sounds
        self.drop_sound.play()
        The # final drop position flag follows the drop position
        self.chessman_sign.show()
        self.chessman_sign.move(c.pos())
        self.chessman_sign.raise_()
        Record this drop
        self.history_record.append([*pos, self.whoseround])
        # Victory or not
        self.winner = checkWin(self.chessboard)
        if self.winner:
            self.showGameEndInfo()
            return
        # Change the color of the turn
        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, 'tip'.'The other side requests to take back the chess, 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('Gobackgammon -🛰️: ilove-python -- > %s moves' % 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, 'tip'.'The other party agrees to start the game request, you are %s, the white person first.' % {'white': '白子'.'black': 'the spots'}.get(self.player_color))
        else:
            QMessageBox.information(self, 'tip'.'Your request to start the game has been rejected.')
    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, 'tip'.'Your request for a reversal has been granted.')
        else:
            QMessageBox.information(self, 'tip'.'The opponent has rejected your request for a return.')
    elif data['type'] = ='nickname':
        self.opponent_nickname = data['data']
Copy the code

Modifications

Must click the “start” button, and after the other side agreed, can officially start the game, the reverse button can only be pressed in the other side’s turn, after the other side agreed to reverse, need to remember to switch back to his position. Then I added an urge button, which also had to be pressed during the opponent’s turn. So that’s all the code changes.

This is the end of the article, thank you for watching Python24 small games series, the next article to share 2048 small games

To thank the readers, I’d like to share some of my recent programming gems to give back to each and every one of you.

All done~ private message to get complete source code.

Review past

Python to achieve bomb people small game

Python to achieve classic pac-man games

Python to achieve elimination of music games

Python real dinosaur jump a jump small game is

Python simple version of the aircraft war games

Python tetris small game implementation

Python alien invasion to achieve small games

Python to achieve “little rabbit and Bun” game

Python eight notes to achieve small games

Python to achieve the puzzle game

Python to achieve ski games

Python for classic 90 tank wars

Python FlappyBird implementation of small games

Python to achieve tower defense games

Python to achieve fruit and gold coins small games

Python to achieve a small game push box

Python to achieve 24 small games

Python to achieve table tennis games

Python brick to achieve small games

Python to achieve a maze of small games

Python to achieve whack-a-mole games