PythonとPygameを使用したネットワーク対戦五目並べの実装

ネットワーク対戦機能付き五目並べの実装方法

前回のSocketプログラミングの基礎知識を基に、PythonとPygameを使用してネットワーク対戦機能付きの五目並べゲームを実装します。

実装手順

  1. サーバー側が接続を待機
  2. TCP接続でクライアントがサーバーに接続して対局開始
  3. 勝敗判定後、一方が再対戦を提案
  4. 相手が承諾すれば新規対局を開始

コード構造

サーバーとクライアントの違いはmain.pyのみにあります。

3.1 サーバー側main.py

import pygame
from pygame.locals import *
import sys
import os
from socket import *
import select
import piece
from tkinter import *
from params import Params
from utils import *

pygame.init()
pygame.mixer.init()

# カラーや盤面の初期設定
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)

rows = int(Params.get('ROWS'))
blocksize = int(Params.get('blockSize'))
width = int(Params.get('WIDTH'))
height = int(Params.get('HEIGHT'))

game_bgsize = width, height

# 盤面の座標計算
x = []
y = []
for i in range(0, rows):
    x.append(28 + i * blocksize)
    y.append(28 + i * blocksize)

# 棋石管理用マップ
chess_map = {}

# フォント設定
fontpath = os.path.join(Params.get('resourcePath'), Params.get('fontFolder'), 'simkai.ttf')
myfont = pygame.font.Font(fontpath, 38)

# 各種テキスト描画用の設定
txtplayerandplayer = myfont.render("プレイヤー対プレイヤー", True, WHITE)
txtplayerandplayer_rect = txtplayerandplayer.get_rect()
txtplayerandplayer_rect.left, txtplayerandplayer_rect.top = (game_bgsize[0] - txtplayerandplayer_rect.width) // 2, \
                                    (game_bgsize[1] - txtplayerandplayer_rect.height) // 2 - 100

txtplayerandcomputer = myfont.render('プレイヤー対コンピュータ', True, WHITE)
txtplayerandcomputer_rect = txtplayerandcomputer.get_rect()
txtplayerandcomputer_rect.left, txtplayerandcomputer_rect.top = (game_bgsize[0] - txtplayerandplayer_rect.width) // 2, \
                                    (game_bgsize[1] - txtplayerandplayer_rect.height) // 2

# その他のテキスト設定は省略...

# ソケット設定
HOST = ''
POST = 23338
BUFSIZE = 2048
HOSTADDR = (HOST, POST)
tcpserversocket = socket(AF_INET, SOCK_STREAM)
tcpserversocket.bind(HOSTADDR)
tcpserversocket.listen(1)

inputs = [tcpserversocket]

# 棋石配置可能判定関数
def isempty(theposition):
    global chess_map
    return chess_map.get(f"{theposition[0]},{theposition[1]}", 0) != 0

# コンピュータの着手選択
def computerdecision():
    max_attack = max_defense = 0
    attack_pos = defense_pos = ()
    
    # 攻撃位置の評価
    for i in range(rows):
        row = 28 + i * blocksize
        for j in range(rows):
            col = 28 + j * blocksize
            pos = (row, col)
            if isempty(pos):
                continue
            value = pointvalue(chess_map, pos, 1, 2)
            if value > max_attack:
                max_attack = value
                attack_pos = pos

    # 防御位置の評価
    for i in range(rows):
        row = 28 + i * blocksize
        for j in range(rows):
            col = 28 + j * blocksize
            pos = (row, col)
            if isempty(pos):
                continue
            value = pointvalue(chess_map, pos, 2, 1)
            if value > max_defense:
                max_defense = value
                defense_pos = pos
                
    return attack_pos if max_attack > max_defense else defense_pos

# 盤面初期化
def init():
    global chess_map
    chess_map = {f"{i},{j}": 0 for i in x for j in y}

# ゲームメインループ
def main():
    os.environ['SDL_VIDEO_CENTERED'] = '1'
    screen = pygame.display.set_mode(game_bgsize)
    pygame.display.set_caption('五目並べ サーバー')
    
    # 画像や音声の読み込み
    bgimagepath = os.path.join(Params.get('resourcePath'), Params.get('imgFolder'), 'bg.png')
    bg_image = pygame.image.load(bgimagepath).convert_alpha()
    
    zzimagepath = os.path.join(Params.get('resourcePath'), Params.get('imgFolder'), 'zz.png')
    zz_image = pygame.image.load(zzimagepath).convert_alpha()
    
    bgsoundpath = os.path.join(Params.get('resourcePath'), Params.get('soundFolder'), 'bg_music.mp3')
    pygame.mixer.music.load(bgsoundpath)
    pygame.mixer.music.set_volume(0.08)
    pygame.mixer.music.play(-1)
    
    clicksoundpath = os.path.join(Params.get('resourcePath'), Params.get('soundFolder'), 'drop.wav')
    piece_sound = pygame.mixer.Sound(clicksoundpath)
    
    clock = pygame.time.Clock()
    
    # ゲーム状態の初期化
    white_chesses = []
    black_chesses = []
    white_positions = []
    black_positions = []
    chesses = []
    
    is_player = False
    is_finish = False
    
    black_win = False
    white_win = False
    
    people2people = False
    people2computer = False
    is_choise = False
    is_play_sound = True
    is_playagain = False
    
    is_link = False
    is_againmsg = False
    is_reject = False
    
    init()
    
    running = True
    while running:
        screen.blit(bg_image, (0, 0))
        
        # メニュー表示
        if not is_choise:
            screen.blit(zz_image, (0, 0))
            screen.blit(txtplayerandplayer, txtplayerandplayer_rect)
            screen.blit(txtplayerandcomputer, txtplayerandcomputer_rect)
            screen.blit(txtplaysound if is_play_sound else txtclosesound, txtplaysound_rect)
        
        # ゲーム画面の描画
        if is_choise:
            if chesses:
                for i in chesses:
                    screen.blit(i.image, i.image_rect())
        
        # イベント処理
        for event in pygame.event.get():
            if event.type == QUIT:
                if is_link:
                    tcpclientsocket.close()
                if people2people:
                    tcpserversocket.close()
                pygame.quit()
                sys.exit()
                
            if event.type == MOUSEBUTTONDOWN:
                if event.button == 1:
                    # ゲーム終了時の処理
                    if is_finish:
                        pos = event.pos
                        # 再対戦処理
                        
                    # 再対戦メッセージ受信時の処理
                    if is_againmsg:
                        pos = event.pos
                        # 承諾/拒否処理
                        
                    # 対戦中の処理
                    if is_choise and is_player and not is_finish:
                        pos = event.pos
                        pblack = piece.Piece(pos, 'pieces_black.png')
                        if not isempty(pblack.pointtrans()):
                            # 棋石配置処理
                            if people2people:
                                tcpclientsocket.send(str(pos).encode('utf8'))
                            is_player = False
                            piece_sound.play()
                            # 勝敗判定
                        else:
                            del pblack
                    
                    # メニュー選択処理
                    
        # ソケット処理
        if people2people:
            readable, _, _ = select.select(inputs, [], [], 0)
            for sock in readable:
                if sock is tcpserversocket:
                    # 新規接続処理
                else:
                    try:
                        data = sock.recv(BUFSIZE)
                        if data:
                            if data.decode('utf8') == 'again':
                                is_againmsg = True
                            elif data.decode('utf8') == 'yes':
                                # 再対戦処理
                            elif data.decode('utf8') == 'no':
                                # 終了処理
                            else:
                                # 棋石座標受信処理
                                pwhite = piece.Piece(eval(data), 'pieces_white.png')
                                white_chesses.append(pwhite)
                                white_positions.append(pwhite.pointtrans())
                                chesses.append(pwhite)
                                is_player = True
                    except error:
                        # 接続エラー処理
        
        # コンピュータ対戦時の処理
        if people2computer and not is_player:
            # コンピュータの着手処理
            
        # 勝敗表示
        if black_win and is_finish:
            screen.blit(zz_image, (0, 0))
            screen.blit(failuretext, (successtext_rect.left, successtext_rect.top - 80))
            screen.blit(playagaintext, successtext_rect)
            screen.blit(menutext, (successtext_rect.left, successtext_rect.top + 80))
        
        # 再対戦メッセージ表示
        if is_againmsg:
            screen.blit(txtchallenge, txtchallenge_rect)
            screen.blit(txtrecive, txtrecive_rect)
            
        # 状態リセット処理
        if is_playagain:
            # 初期化処理
            
        pygame.display.flip()
        clock.tick(60)

if __name__ == '__main__':
    main()

3.2 クライアント側main.py

import pygame
from pygame.locals import *
import sys
import os
from socket import *
import select
import piece
from tkinter import *
from params import Params
from utils import *

pygame.init()
pygame.mixer.init()

# カラーや盤面の初期設定
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)

rows = int(Params.get('ROWS'))
blocksize = int(Params.get('blockSize'))
width = int(Params.get('WIDTH'))
height = int(Params.get('HEIGHT'))

game_bgsize = width, height

# 盤面の座標計算
x = []
y = []
for i in range(0, rows):
    x.append(28 + i * blocksize)
    y.append(28 + i * blocksize)

# 棋石管理用マップ
chess_map = {}

# 棋石配置可能判定関数
def isempty(theposition):
    global chess_map
    return chess_map.get(f"{theposition[0]},{theposition[1]}", 0) != 0

# コンピュータの着手選択
def computerdecision():
    max_attack = max_defense = 0
    attack_pos = defense_pos = ()
    
    # 攻撃位置の評価
    for i in range(rows):
        row = 28 + i * blocksize
        for j in range(rows):
            col = 28 + j * blocksize
            pos = (row, col)
            if isempty(pos):
                continue
            value = pointvalue(chess_map, pos, 1, 2)
            if value > max_attack:
                max_attack = value
                attack_pos = pos

    # 防御位置の評価
    for i in range(rows):
        row = 28 + i * blocksize
        for j in range(rows):
            col = 28 + j * blocksize
            pos = (row, col)
            if isempty(pos):
                continue
            value = pointvalue(chess_map, pos, 2, 1)
            if value > max_defense:
                max_defense = value
                defense_pos = pos
                
    return attack_pos if max_attack > max_defense else defense_pos

# 盤面初期化
def init():
    global chess_map
    chess_map = {f"{i},{j}": 0 for i in x for j in y}

# サーバー接続用GUI
def screenConnect():
    top = Tk()
    top.title('サーバーIP')
    top.geometry('%dx%d+%d+%d' % (300, 200, 418, 338))
    
    HOST = ''
    POST = 23338
    nonlocal tcpclientsocket
    
    def connect():
        nonlocal HOST, POST, tcpclientsocket
        HOST = txtIP.get()
        HOSTADDR = (HOST, POST)
        try:
            tcpclientsocket.connect(HOSTADDR)
            Label(top, text='接続成功').grid(row=2, column=1)
        except error:
            Label(top, text='接続失敗').grid(row=2, column=1)
    
    Label(top, text='IPアドレス:', padx=5, pady=30).grid(row=1, column=0)
    txtIP = Entry(top)
    txtIP.grid(row=1, column=1)
    Button(top, text='接続', command=connect).grid(row=2, column=0)
    
    top.mainloop()

# ゲームメインループ
def main():
    tcpclientsocket = socket(AF_INET, SOCK_STREAM)
    
    # ゲーム画面の初期化
    os.environ['SDL_VIDEO_CENTERED'] = '1'
    screen = pygame.display.set_mode(game_bgsize)
    pygame.display.set_caption('五目並べ クライアント')
    
    # 画像や音声の読み込み
    bgimagepath = os.path.join(Params.get('resourcePath'), Params.get('imgFolder'), 'bg.png')
    bg_image = pygame.image.load(bgimagepath).convert_alpha()
    
    zzimagepath = os.path.join(Params.get('resourcePath'), Params.get('imgFolder'), 'zz.png')
    zz_image = pygame.image.load(zzimagepath).convert_alpha()
    
    bgsoundpath = os.path.join(Params.get('resourcePath'), Params.get('soundFolder'), 'bg_music.mp3')
    pygame.mixer.music.load(bgsoundpath)
    pygame.mixer.music.set_volume(0.08)
    pygame.mixer.music.play(-1)
    
    clicksoundpath = os.path.join(Params.get('resourcePath'), Params.get('soundFolder'), 'drop.wav')
    piece_sound = pygame.mixer.Sound(clicksoundpath)
    
    clock = pygame.time.Clock()
    
    # ゲーム状態の初期化
    white_chesses = []
    black_chesses = []
    white_positions = []
    black_positions = []
    chesses = []
    
    is_player = True
    is_finish = False
    
    black_win = False
    white_win = False
    
    people2people = False
    people2computer = False
    is_choise = False
    is_play_sound = True
    is_playagain = False
    
    is_link = False
    is_againmsg = False
    is_reject = False
    
    init()
    
    running = True
    while running:
        screen.blit(bg_image, (0, 0))
        
        # メニュー表示
        if not is_choise:
            screen.blit(zz_image, (0, 0))
            screen.blit(txtplayerandplayer, txtplayerandplayer_rect)
            screen.blit(txtplayerandcomputer, txtplayerandcomputer_rect)
            screen.blit(txtplaysound if is_play_sound else txtclosesound, txtplaysound_rect)
        
        # ゲーム画面の描画
        if is_choise:
            if chesses:
                for i in chesses:
                    screen.blit(i.image, i.image_rect())
        
        # イベント処理
        for event in pygame.event.get():
            if event.type == QUIT:
                if people2people:
                    tcpclientsocket.close()
                pygame.quit()
                sys.exit()
                
            if event.type == MOUSEBUTTONDOWN:
                if event.button == 1:
                    # ゲーム終了時の処理
                    if is_finish:
                        pos = event.pos
                        # 再対戦処理
                        
                    # 再対戦メッセージ受信時の処理
                    if is_againmsg:
                        pos = event.pos
                        # 承諾/拒否処理
                        
                    # 対戦中の処理
                    if is_choise and is_player and not is_finish:
                        pos = event.pos
                        pwhite = piece.Piece(pos, 'pieces_white.png')
                        if not isempty(pwhite.pointtrans()):
                            white_chesses.append(pwhite)
                            white_positions.append(pwhite.pointtrans())
                            chesses.append(pwhite)
                            chess_map[f"{pwhite.pointtrans()[0]},{pwhite.pointtrans()[1]}"] = 1
                            if people2people:
                                tcpclientsocket.send(str(pos).encode('utf8'))
                            is_player = False
                            piece_sound.play()
                            
                            # 勝敗判定
                            if len(white_chesses) >= 5 and checkwin(white_positions, pwhite.pointtrans()[0], pwhite.pointtrans()[1]):
                                is_finish = True
                                white_win = True
                        else:
                            del pwhite
                    
                    # メニュー選択処理
                    
        # コンピュータ対戦時の処理
        if people2computer and not is_player:
            # コンピュータの着手処理
            
        # ソケット処理
        if people2people:
            readable, _, _ = select.select(inputs, [], [], 0)
            for sock in readable:
                if sock is tcpclientsocket:
                    try:
                        data = sock.recv(BUFSIZE)
                        if data:
                            if data.decode('utf8') == 'again':
                                is_againmsg = True
                            elif data.decode('utf8') == 'yes':
                                # 再対戦処理
                            elif data.decode('utf8') == 'no':
                                # 終了処理
                            else:
                                # 棋石座標受信処理
                                pblack = piece.Piece(eval(data), 'pieces_black.png')
                                black_chesses.append(pblack)
                                black_positions.append(pblack.pointtrans())
                                chesses.append(pblack)
                                is_player = True
                    except error:
                        # 接続エラー処理
        
        # 勝敗表示
        if black_win and is_finish:
            screen.blit(zz_image, (0, 0))
            screen.blit(failuretext, (successtext_rect.left, successtext_rect.top - 80))
            screen.blit(playagaintext, successtext_rect)
            screen.blit(menutext, (successtext_rect.left, successtext_rect.top + 80))
        
        # 再対戦メッセージ表示
        if is_againmsg:
            screen.blit(txtchallenge, txtchallenge_rect)
            screen.blit(txtrecive, txtrecive_rect)
            
        # 状態リセット処理
        if is_playagain:
            # 初期化処理
            
        pygame.display.flip()
        clock.tick(60)

if __name__ == '__main__':
    main()

3.3 実行手順

  1. まずserver側のmain.pyを実行
  2. 次にclient側のmain.pyを実行

まとめ

この記事では、PythonとPygameを使用してネットワーク対戦機能付きの五目並べゲームを実装する方法を解説しました。ソケット通信を基盤として、プレイヤー間のやり取りを実現し、ゲームの基本機能である対戦、勝敗判定、再対戦の流れを実装しました。

タグ: Python pygame socket Network-Programming game-development

5月22日 12:46 投稿