ネットワーク対戦機能付き五目並べの実装方法
前回のSocketプログラミングの基礎知識を基に、PythonとPygameを使用してネットワーク対戦機能付きの五目並べゲームを実装します。
実装手順
- サーバー側が接続を待機
- TCP接続でクライアントがサーバーに接続して対局開始
- 勝敗判定後、一方が再対戦を提案
- 相手が承諾すれば新規対局を開始
コード構造
サーバーとクライアントの違いは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 実行手順
- まずserver側のmain.pyを実行
- 次にclient側のmain.pyを実行
まとめ
この記事では、PythonとPygameを使用してネットワーク対戦機能付きの五目並べゲームを実装する方法を解説しました。ソケット通信を基盤として、プレイヤー間のやり取りを実現し、ゲームの基本機能である対戦、勝敗判定、再対戦の流れを実装しました。