Python Socket制作简单的五子棋联机对战游戏

一、项目概述

本项目实现了一个简单的五子棋双人联机对战游戏,使用 Python 编程语言,结合 socket 模块实现网络通信,pygame 模块实现图形界面。玩家可以通过局域网连接,进行实时对战。

(应该是可以局域网的,不过我在将本地localhost改为主机ip地址时,拒绝连接,应该是防火墙的问题,觉得麻烦就没有测试了,如果有问题,可以提出,本文仅作为参考)

最后有源代码, 源码都在一个文件内, 导入相关package资源后,从终端先运行服务端,然后两个客户端就可以了

联机功能只实现得比较基础

此外AI对战有简单实现,只实现了附近落子和连成3个的时候会堵截,很容易赢,就不献丑了

1. 文本模式可以参考我之前的文章,比较简单,适合新手python五子棋项目(新手入门 文本模式入手)-CSDN博客

2. 图像模式,也可以参考另一篇博客,我也是参照这片文章写出来的图形界面,不过缺少AI对战和联机..Python Pygame制作简单五子棋游戏(详细代码+解释)_scratch五子棋游戏制作教程-CSDN博客

二、功能特点

  1. 双人联机对战:支持两个玩家通过网络进行对战。

  2. 图形化界面:使用 pygame 绘制棋盘和棋子,提供友好的交互体验。

  3. 实时通信:通过 TCP 套接字实现玩家之间的实时消息传递。

  4. 简单易用:代码结构清晰,易于理解和扩展。

三、代码结构与实现

1. 服务端实现

服务端负责接收客户端的连接请求,并转发玩家之间的操作消息。

import socket
import threading

clients = []  # 存储客户端连接

def handle_client(conn):
    """处理客户端连接"""
    while True:
        try:
            data = conn.recv(1024).decode("utf-8")  # 接收数据
            if data:
                print("收到数据:", data)
            for client in clients:  # 转发数据给其他客户端
                if client != conn:
                    print("转发数据给其它客户端:", data)
                    client.send(data.encode("utf-8"))
        except:
            clients.remove(conn)
            break

def start_server():
    """启动服务端"""
    server = socket.socket()
    server.bind(('0.0.0.0', 8800))  # 绑定地址和端口
    server.listen(2)  # 最大连接数

    while len(clients) < 2:  # 等待两个客户端连接
        print("等待玩家连接...")
        conn, addr = server.accept()
        print(f"新链接来自:{addr}")
        clients.append(conn)
        threading.Thread(target=handle_client, args=(conn,)).start()

2. 客户端实现

客户端负责与服务端通信,并通过图形界面展示游戏状态。

import socket
import threading
import queue

class NetworkClient:
    """网络客户端"""
    def __init__(self, game_windows):
        self.sock = socket.socket()
        self.sock.connect(('localhost', 8800))  # 连接服务端
        self.recv_thread = threading.Thread(target=self.recv_loop, daemon=True)
        self.network_queue = queue.Queue()
        self.recv_thread.start()
        self.game_windows = game_windows

    def send(self, data):
        """发送数据到服务端"""
        print("发送数据:", data)
        self.sock.send(data.encode("utf-8"))

    def recv_loop(self):
        """接收服务端数据"""
        while True:
            try:
                data = self.sock.recv(1024).decode("utf-8")
                if data:
                    self.network_queue.put(data)
            except ConnectionResetError:
                break

    def process_message(self):
        """处理接收到的消息"""
        while not self.network_queue.empty():
            msg = self.network_queue.get()
            row, col = map(int, msg.split(','))
            self.game_windows.handle_network_move(row, col)

3. 图形界面实现

使用 pygame 绘制棋盘和棋子,并处理玩家的交互操作。

import pygame
import sys

class GameWindow:
    """游戏窗口"""
    CELL_SIZE = 48  # 格子大小
    COLORS = {
        'board': (238, 170, 0),  # 棋盘颜色
        'black': (0, 0, 0),
        'white': (255, 255, 255)
    }

    def __init__(self, player_num):
        pygame.init()
        self.screen = pygame.display.set_mode((self.CELL_SIZE * 15, self.CELL_SIZE * 15))
        pygame.display.set_caption(f"玩家{player_num}")
        self.network = NetworkClient(self)
        self.board = [['·' for _ in range(15)] for _ in range(15)]
        self.current_player = "●"

    def draw_board(self):
        """绘制棋盘"""
        self.screen.fill(self.COLORS['board'])
        for i in range(15):
            pygame.draw.line(self.screen, self.COLORS['black'], (i * self.CELL_SIZE, 0), (i * self.CELL_SIZE, self.CELL_SIZE * 15))
            pygame.draw.line(self.screen, self.COLORS['black'], (0, i * self.CELL_SIZE), (self.CELL_SIZE * 15, i * self.CELL_SIZE))

        for i in range(15):
            for j in range(15):
                if self.board[i][j] == '●':
                    color = self.COLORS['black']
                elif self.board[i][j] == '○':
                    color = self.COLORS['white']
                else:
                    continue
                center = (j * self.CELL_SIZE + self.CELL_SIZE // 2, i * self.CELL_SIZE + self.CELL_SIZE // 2)
                pygame.draw.circle(self.screen, color, center, self.CELL_SIZE // 2 - 2)
        pygame.display.update()

    def handle_click(self, x, y):
        """处理鼠标点击"""
        col = x // self.CELL_SIZE
        row = y // self.CELL_SIZE
        if 0 <= col < 15 and 0 <= row < 15 and self.board[col][row] == '·':
            self.board[row][col] = self.current_player
            if self.network:
                self.network.send(f"{row},{col}")
            self.current_player = '○' if self.current_player == '●' else '●'

    def run(self):
        """运行游戏"""
        clock = pygame.time.Clock()
        while True:
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    pygame.quit()
                    sys.exit()
                if event.type == pygame.MOUSEBUTTONDOWN:
                    x, y = pygame.mouse.get_pos()
                    self.handle_click(x, y)
            if self.network:
                self.network.process_message()
            self.draw_board()
            clock.tick(30)

 4. 命令行启动方式

通过命令行参数启动服务端或客户端:python + 文件地址 + 

#启动
if __name__=='__main__':
    if len(sys.argv)>1:#根据命令行参数启动服务器或客户端
        if sys.argv[1]=="server":
            start_server()
        elif sys.argv[1]=="client1":
            game=GameWindow(1)
            game.run()
        elif sys.argv[1]=="client2":
            game=GameWindow(2)
            game.run()

 5. 命令行启动

通过命令行参数启动服务端或客户端:python + 文件地址 + 服务端(或用户端1或用户端2) 例: 

# 启动服务端
python 五子棋联机对战.py server

# 启动客户端1
python 五子棋联机对战.py client1

# 启动客户端2
python 五子棋联机对战.py client2

四、运行效果

  1. 服务端:等待玩家连接,转发玩家操作。

  2. 客户端:显示棋盘,玩家可以通过鼠标点击下棋,实时同步对方操作。

  3. Python Socket制作简单的五子棋联机对战游戏_第1张图片

五、源码

本项目实现了一个简单的五子棋双人联机对战游戏,适合初学者学习网络编程和图形界面开发。你可以在此基础上扩展更多功能,例如增加胜负判断、聊天功能等。

#!/usr/bin/python
# -*- coding:utf-8 -*-
import sys

import pygame
import socket
import threading
import queue

clients = []  # 设置客户端初始连接为空


def handle_client(conn):
    while True:
        try:
            data = conn.recv(1024).decode("utf-8")  # 设置接收数据的大小最大为1024字节
            # recv()方法接收TCP消息,返回一个包含接收数据的缓冲区。
            if data:
                print("收到数据:", data)
            for client in clients:  # 遍历客户端
                if client != conn:  # 如果客户端不为自己本身 就发送数据给其它客户端
                    print("转发数据给其它客户端:", data)
                    client.send(data.encode("utf-8"))
        except:
            clients.remove(conn)
            break

# 服务端
def start_server():
    server = socket.socket()
    server.bind(('0.0.0.0', 8800))  # 地址和端口号 第一个括号是bind的参数,第二个括号为元组
    server.listen(2)  # 最大连接数

    while len(clients) < 2:  # 如果客户端连接数小于2
        print("等待玩家连接...")
        conn, addr = server.accept()  # accept方法接受连接并返回(conn, address),
        # 其中conn是新的套接字对象,可以用来发送和接收数据,address是连接的客户端地址。
        print(f"新链接来自:{addr}")
        clients.append(conn)  # 向客户端列表添加连接
        print("当前连接数:", len(clients))
        threading.Thread(target=handle_client, args=(conn,)).start()
        # 为每个客户端连接创建一个线程,用于接收消息
        # 解释target:要调用的可调用对象 args:传递给目标的参数元组 start:线程对象开始执行

# 客户端
class NetworkClient:
    def __init__(self, game_windows):  # self参数用于表示类的实例本身 允许访问操作对象属性和方法
        # __init__方法是一个特殊的方法(init是单词初始化initialization的省略形式),
        # 在使用类创建对象之后被执行,用于给新创建的对象初始化属性用。
        self.sock = socket.socket()  # 创建一个socket对象
        self.sock.connect(('localhost', 8800))  # connect()方法:连接到服务器
        self.recv_thread = threading.Thread(target=self.recv_loop, daemon=True)
        # daemon参数的作用: 设置为True时,主线程结束时,子线程也会结束
        self.network_queue = queue.Queue()  # 消息队列
        self.recv_thread.start()  # 启动线程
        self.game_windows = game_windows  # 将游戏窗口传入,方便更新界面

    def send(self, data):
        print("发送数据:", data)
        self.sock.send(data.encode("utf-8"))  # 发送数据

    def recv_loop(self):  # 接收数据
        while True:
            try:
                data = self.sock.recv(1024).decode("utf-8")  # 接收数据
                if data:
                    self.network_queue.put(data)  # 将数据放入消息队列
            except ConnectionResetError:  # 连接重置错误
                break

    def process_message(self):  # 处理消息
        while not self.network_queue.empty():  # 如果消息队列不为空
            msg = self.network_queue.get()  # 获取消息
            row, col = map(int, msg.split(','))
            self.game_windows.handle_network_move(row, col)  # 更新棋盘状态


# 图形界面
class GameWindow:
    CELL_SIZE = 48  # 一个格子的大小
    COLORS = {  # 颜色定义
        'board': (238, 170, 0),  # 棋盘颜色
        'black': (0, 0, 0),
        'white': (255, 255, 255)
    }

    def __init__(self, player_num):  # player_num是玩家编号,用于设置窗口标题
        pygame.init()  # 初始化
        self.screen = pygame.display.set_mode((self.CELL_SIZE * 15, self.CELL_SIZE * 15))  # 设置窗口大小
        pygame.display.set_caption(f"玩家{player_num}")
        self.network = NetworkClient(self)  # 创建网络客户端
        self.board = [['·' for _ in range(15)] for _ in range(15)]  # 初始化棋盘
        self.current_player = "●"  # 当前玩家

    def draw_board(self):
        # 填充屏幕背景 用之前电定义的COLOR的字典
        self.screen.fill(self.COLORS['board'])  # 设置为棋盘颜色
        # 画棋盘
        for i in range(15):
            pygame.draw.line(self.screen, self.COLORS['black'],
                             (i * self.CELL_SIZE, 0),
                             (i * self.CELL_SIZE, self.CELL_SIZE * 15))

            pygame.draw.line(self.screen, self.COLORS['black'],
                             (0, i * self.CELL_SIZE),
                             (self.CELL_SIZE * 15, i * self.CELL_SIZE))

        for i in range(15):
            for j in range(15):
                if self.board[i][j] == '●':
                    color = self.COLORS['black']
                elif self.board[i][j] == '○':
                    color = self.COLORS['white']
                else:
                    continue
                # 计算中心坐标 绘制圆圈(棋子)
                center = (j * self.CELL_SIZE + self.CELL_SIZE // 2,  # j为列数 i为行数
                          i * self.CELL_SIZE + self.CELL_SIZE // 2)
                pygame.draw.circle(self.screen, color, center, self.CELL_SIZE // 2 - 2)

        pygame.display.update()  # 更新屏幕


    def handle_click(self, x, y):  # 处理鼠标点击事件
        col = x // self.CELL_SIZE
        row = y // self.CELL_SIZE
        if 0 <= col < 15 and 0 <= row < 15 and self.board[col][row] == '·':
            self.board[row][col]=self.current_player
            if self.network:
                self.network.send(f"{row},{col}")
            self.current_player = '○' if self.current_player == '●' else '●'#(三元运算符) 切换玩家

    def handle_network_move(self,row,col):#处理网络消息
        if self.board[row][col]=='·':
            self.board[row][col]=self.current_player
            self.current_player='○' if self.current_player=='●' else '●'
        self.draw_board()  # 重新绘制棋盘

    def run(self):
        clock=pygame.time.Clock()  # 创建一个时钟对象
        while True:
            for event in pygame.event.get():#获取事件
                if event.type==pygame.QUIT:
                    pygame.quit()#退出
                    sys.exit()#退出
                if event.type==pygame.MOUSEBUTTONDOWN:#处理鼠标点击事件
                    x,y=pygame.mouse.get_pos()
                    self.handle_click(x,y)
            if self.network:
                self.network.process_message()
            self.draw_board()
            clock.tick(30)#限制帧率

#启动
if __name__=='__main__':
    if len(sys.argv)>1:#根据命令行参数启动服务器或客户端
        if sys.argv[1]=="server":
            start_server()
        elif sys.argv[1]=="client1":
            game=GameWindow(1)
            game.run()
        elif sys.argv[1]=="client2":
            game=GameWindow(2)
            game.run()

#启动方式
#终端输入以下命令 python 项目文件地址 服务端(或客户端1或客户端2) 例如:
#python 五子棋联机对战.py server
#python 五子棋联机对战.py client1
#python 五子棋联机对战.py client2

你可能感兴趣的:(游戏,pygame,python)