players2 / server.py
Geek7's picture
Upload server.py
db98c95 verified
raw
history blame
6.54 kB
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)