players / app.py
Geek7's picture
Update app.py
22a71cf verified
import random
import json
import socketio
from flask import Flask
from redis.sentinel import Sentinel
import eventlet
eventlet.monkey_patch()
# Redis Sentinel Setup
SENTINELS = [('sentinel1', 26379), ('sentinel2', 26379), ('sentinel3', 26379)]
MASTER_NAME = 'mymaster'
sentinel = Sentinel(SENTINELS, socket_timeout=0.1)
redis_master = sentinel.master_for(MASTER_NAME, socket_timeout=0.1, decode_responses=True)
redis_slave = sentinel.slave_for(MASTER_NAME, socket_timeout=0.1, decode_responses=True)
# Flask + Socket.IO setup
sio = socketio.Server(cors_allowed_origins='*')
app = Flask(__name__)
app.wsgi_app = socketio.WSGIApp(sio, app.wsgi_app)
# Redis keys for data storage
WAITING_PLAYERS_KEY = 'waiting_players' # Redis List
PLAYERS_HASH = 'players' # Redis Hash: sid -> json string
ROOMS_HASH = 'rooms' # Redis Hash: sid -> room
GAME_STATES_HASH = 'game_states' # Redis Hash: room -> json string
# Pub/Sub channel for broadcasting moves and events
CHANNEL = 'checkers_channel'
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 publish_event(event_type, data):
message = json.dumps({'type': event_type, 'data': data})
redis_master.publish(CHANNEL, message)
def handle_pubsub():
pubsub = redis_slave.pubsub()
pubsub.subscribe(CHANNEL)
for message in pubsub.listen():
if message['type'] == 'message':
event = json.loads(message['data'])
event_type = event['type']
data = event['data']
# Broadcast to all connected sockets as needed
if event_type == 'opponent_move':
room = data.get('room')
if room:
# Emit to the room, skip sender
sio.emit('opponent_move', data['move'], room=room, skip_sid=data['sid'])
elif event_type == 'game_over':
sio.emit('game_over', data, room=data.get('room'))
elif event_type == 'opponent_disconnected':
sio.emit('opponent_disconnected', room=data.get('room'))
elif event_type == 'match_found':
# This can be handled if needed for multi-instance matchmaking
pass
@app.route('/')
def index():
return "Checkers server with Redis Sentinel is running."
@sio.event
def connect(sid, environ):
print(f"{sid} connected")
@sio.event
def set_name(sid, data):
name = data.get('name', 'Player')
# Store player info in Redis
player_data = {'name': name}
redis_master.hset(PLAYERS_HASH, sid, json.dumps(player_data))
# Check waiting players
waiting = redis_master.lrange(WAITING_PLAYERS_KEY, 0, -1)
if waiting:
opponent_sid = waiting[0]
redis_master.lpop(WAITING_PLAYERS_KEY)
room = f"room_{sid[:4]}_{opponent_sid[:4]}"
# Update players with room
player_data['room'] = room
redis_master.hset(PLAYERS_HASH, sid, json.dumps(player_data))
opponent_data_json = redis_master.hget(PLAYERS_HASH, opponent_sid)
opponent_data = json.loads(opponent_data_json)
opponent_data['room'] = room
redis_master.hset(PLAYERS_HASH, opponent_sid, json.dumps(opponent_data))
redis_master.hset(ROOMS_HASH, sid, room)
redis_master.hset(ROOMS_HASH, opponent_sid, room)
sio.enter_room(sid, room)
sio.enter_room(opponent_sid, room)
# Random color assignment
pair = [(sid, 'red'), (opponent_sid, 'black')]
random.shuffle(pair)
for psid, color in pair:
pdata_json = redis_master.hget(PLAYERS_HASH, psid)
pdata = json.loads(pdata_json)
pdata['color'] = color
redis_master.hset(PLAYERS_HASH, psid, json.dumps(pdata))
opponent_name = players_get_name(opponent_sid if psid == sid else sid)
sio.emit("match_found", {'color': color, 'opponent': opponent_name}, to=psid)
# Initialize game state in Redis
state = {
'board': init_board(),
'turn': 'red'
}
redis_master.hset(GAME_STATES_HASH, room, json.dumps(state))
else:
redis_master.rpush(WAITING_PLAYERS_KEY, sid)
def players_get_name(sid):
pdata_json = redis_slave.hget(PLAYERS_HASH, sid)
if pdata_json:
pdata = json.loads(pdata_json)
return pdata.get('name', 'Player')
return 'Player'
@sio.event
def move(sid, data):
room = redis_slave.hget(ROOMS_HASH, sid)
if not room:
return
state_json = redis_slave.hget(GAME_STATES_HASH, room)
if not state_json:
return
state = json.loads(state_json)
board = state['board']
turn = state['turn']
pdata_json = redis_slave.hget(PLAYERS_HASH, sid)
if not pdata_json:
return
pdata = json.loads(pdata_json)
color = pdata.get('color')
if color != turn:
return # not your turn
from_row, from_col = data['from']
to_row, to_col = data['to']
captured = data.get('captured')
piece = board[from_row][from_col]
if not piece or piece[0] != color[0]:
return
# Simple move
board[to_row][to_col] = board[from_row][from_col]
board[from_row][from_col] = None
if captured:
cap_row, cap_col = captured
board[cap_row][cap_col] = None
# Crown
if (color == 'red' and to_row == 0) or (color == 'black' and to_row == 7):
board[to_row][to_col] = board[to_row][to_col].upper()
# Win check
flat = sum(board, [])
winner = None
if 'r' not in flat and 'R' not in flat:
winner = 'black'
elif 'b' not in flat and 'B' not in flat:
winner = 'red'
if winner:
publish_event('game_over', {'winner': winner, 'room': room})
redis_master.hdel(GAME_STATES_HASH, room)
return
# Switch turn
state['turn'] = 'black' if turn == 'red' else 'red'
state['board'] = board
redis_master.hset(GAME_STATES_HASH, room, json.dumps(state))
# Broadcast move to other player via Redis pubsub
publish_event('opponent_move', {'move': data, 'room': room, 'sid': sid})
@sio.event
def disconnect(sid):
print(f"{sid} disconnected")
# Remove from waiting list if present
redis_master.lrem(WAITING_PLAYERS_KEY, 0, sid)
room = redis_slave.hget(ROOMS_HASH, sid)
if room:
publish_event('opponent_disconnected', {'room': room})
# Clean up Redis data for this room & players
keys_to_delete = []
players_in_room = []
all_players = redis_slave.hkeys(PLAYERS_HASH)
for psid in all_players:
pdata_json = redis_slave.hget(PLAYERS_HASH, psid)
pdata = json.loads(pdata_json)
if pdata.get('room') == room:
keys_to_delete.append(psid)
players_in_room.append(psid)
for psid in players_in_room:
redis_master.hdel(PLAYERS_HASH, psid)
redis_master.hdel(ROOMS_HASH, psid)
redis_master.hdel(GAME_STATES_HASH, room)
if __name__ == '__main__':
import threading
# Start background thread for Redis Pub/Sub listening
pubsub_thread = threading.Thread(target=handle_pubsub, daemon=True)
pubsub_thread.start()
eventlet.wsgi.server(eventlet.listen(('0.0.0.0', 5000)), app)