File size: 6,536 Bytes
db98c95 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 |
import random
import json
import socketio
from flask import Flask, jsonify
import eventlet
import threading
from nats.aio.client import Client as NATS
import asyncpg
import asyncio
eventlet.monkey_patch()
sio = socketio.Server(cors_allowed_origins='*')
app = Flask(__name__)
app.wsgi_app = socketio.WSGIApp(sio, app.wsgi_app)
DB_DSN = "postgresql://postgres:postgres@pgbouncer:6432/postgres"
NATS_URL = "nats://nats:4222"
WAITING_PLAYERS = []
PLAYER_DATA = {}
GAME_STATES = {}
nats_client = NATS()
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
async def init_db():
return await asyncpg.create_pool(dsn=DB_DSN)
db_pool = loop.run_until_complete(init_db())
async def publish_event(event_type, data):
message = json.dumps({'type': event_type, 'data': data})
await nats_client.publish("checkers", message.encode())
async def subscribe():
async def message_handler(msg):
event = json.loads(msg.data.decode())
if event['type'] == 'opponent_move':
sio.emit('opponent_move', event['data']['move'], room=event['data']['room'], skip_sid=event['data']['sid'])
elif event['type'] == 'game_over':
sio.emit('game_over', event['data'], room=event['data']['room'])
elif event['type'] == 'opponent_disconnected':
sio.emit('opponent_disconnected', room=event['data']['room'])
await nats_client.connect(servers=[NATS_URL], loop=loop)
await nats_client.subscribe("checkers", cb=message_handler)
def init_board():
board = [[None for _ in range(8)] for _ in range(8)]
for row in range(3):
for col in range(8):
if (row + col) % 2 == 1:
board[row][col] = 'b'
for row in range(5, 8):
for col in range(8):
if (row + col) % 2 == 1:
board[row][col] = 'r'
return board
def get_valid_moves(board, row, col, color, is_king):
directions = [(-1, -1), (-1, 1)] if color == 'red' else [(1, -1), (1, 1)]
if is_king:
directions += [(-d[0], -d[1]) for d in directions]
moves, captures = [], []
for dr, dc in directions:
r, c = row + dr, col + dc
if 0 <= r < 8 and 0 <= c < 8 and board[r][c] is None:
moves.append((r, c))
elif 0 <= r < 8 and 0 <= c < 8:
mid_piece = board[r][c]
if mid_piece and mid_piece[0].lower() != color[0]:
r2, c2 = r + dr, c + dc
if 0 <= r2 < 8 and 0 <= c2 < 8 and board[r2][c2] is None:
captures.append(((r2, c2), (r, c)))
return moves, captures
@app.route('/')
def index():
return "Checkers server running."
@app.route('/state')
def game_state():
for sid, pdata in PLAYER_DATA.items():
if 'room' in pdata:
room = pdata['room']
if room in GAME_STATES:
return jsonify({'board': GAME_STATES[room]['board']})
return jsonify({'board': [[None]*8 for _ in range(8)]})
@sio.event
def connect(sid, environ):
print(f"{sid} connected")
@sio.event
def set_name(sid, data):
name = data.get('name', 'Player')
PLAYER_DATA[sid] = {'name': name}
if WAITING_PLAYERS:
opponent_sid = WAITING_PLAYERS.pop(0)
room = f"room_{sid[:4]}_{opponent_sid[:4]}"
for psid in [sid, opponent_sid]:
pdata = PLAYER_DATA[psid]
pdata['room'] = room
sio.enter_room(psid, room)
players = [(sid, 'red'), (opponent_sid, 'black')]
random.shuffle(players)
for psid, color in players:
PLAYER_DATA[psid]['color'] = color
opp_sid = opponent_sid if psid == sid else sid
opp_name = PLAYER_DATA[opp_sid]['name']
sio.emit("match_found", {'color': color, 'opponent': opp_name}, to=psid)
GAME_STATES[room] = {'board': init_board(), 'turn': 'red'}
else:
WAITING_PLAYERS.append(sid)
@sio.event
def move(sid, data):
pdata = PLAYER_DATA.get(sid)
if not pdata:
return
room = pdata.get('room')
state = GAME_STATES.get(room)
if not state:
return
board = state['board']
turn = state['turn']
color = pdata['color']
if color != turn:
return
from_row, from_col = data['from']
to_row, to_col = data['to']
piece = board[from_row][from_col]
if not piece or piece[0].lower() != color[0]:
return
is_king = piece.isupper()
_, captures = get_valid_moves(board, from_row, from_col, color, is_king)
move_is_capture = abs(to_row - from_row) == 2
if captures and not move_is_capture:
return
board[to_row][to_col] = piece
board[from_row][from_col] = None
if move_is_capture:
cap_row = (from_row + to_row) // 2
cap_col = (from_col + to_col) // 2
board[cap_row][cap_col] = None
_, new_captures = get_valid_moves(board, to_row, to_col, color, is_king or to_row in (0, 7))
if new_captures:
state['board'] = board
GAME_STATES[room] = state
sio.emit('multi_jump', {'from': [to_row, to_col]}, to=sid)
return
if (color == 'red' and to_row == 0) or (color == 'black' and to_row == 7):
board[to_row][to_col] = piece.upper()
flat = sum(board, [])
if all(p not in flat for p in ['r', 'R']):
loop.create_task(publish_event('game_over', {'winner': 'black', 'room': room}))
del GAME_STATES[room]
return
if all(p not in flat for p in ['b', 'B']):
loop.create_task(publish_event('game_over', {'winner': 'red', 'room': room}))
del GAME_STATES[room]
return
state['board'] = board
state['turn'] = 'black' if turn == 'red' else 'red'
GAME_STATES[room] = state
loop.create_task(publish_event('opponent_move', {'move': data, 'room': room, 'sid': sid}))
@sio.event
def disconnect(sid):
print(f"{sid} disconnected")
if sid in WAITING_PLAYERS:
WAITING_PLAYERS.remove(sid)
pdata = PLAYER_DATA.pop(sid, None)
if pdata and 'room' in pdata:
room = pdata['room']
for psid in list(PLAYER_DATA.keys()):
if PLAYER_DATA[psid].get('room') == room:
del PLAYER_DATA[psid]
if room in GAME_STATES:
del GAME_STATES[room]
loop.create_task(publish_event('opponent_disconnected', {'room': room}))
if __name__ == '__main__':
threading.Thread(target=lambda: loop.run_until_complete(subscribe()), daemon=True).start()
eventlet.wsgi.server(eventlet.listen(('0.0.0.0', 5000)), app) |