|
import random |
|
import json |
|
import socketio |
|
from flask import Flask |
|
from redis.sentinel import Sentinel |
|
import eventlet |
|
eventlet.monkey_patch() |
|
|
|
|
|
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) |
|
|
|
|
|
sio = socketio.Server(cors_allowed_origins='*') |
|
app = Flask(__name__) |
|
app.wsgi_app = socketio.WSGIApp(sio, app.wsgi_app) |
|
|
|
|
|
WAITING_PLAYERS_KEY = 'waiting_players' |
|
PLAYERS_HASH = 'players' |
|
ROOMS_HASH = 'rooms' |
|
GAME_STATES_HASH = 'game_states' |
|
|
|
|
|
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'] |
|
|
|
|
|
if event_type == 'opponent_move': |
|
room = data.get('room') |
|
if room: |
|
|
|
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': |
|
|
|
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') |
|
|
|
|
|
player_data = {'name': name} |
|
redis_master.hset(PLAYERS_HASH, sid, json.dumps(player_data)) |
|
|
|
|
|
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]}" |
|
|
|
|
|
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) |
|
|
|
|
|
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) |
|
|
|
|
|
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 |
|
|
|
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 |
|
|
|
|
|
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 |
|
|
|
|
|
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() |
|
|
|
|
|
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 |
|
|
|
|
|
state['turn'] = 'black' if turn == 'red' else 'red' |
|
state['board'] = board |
|
|
|
redis_master.hset(GAME_STATES_HASH, room, json.dumps(state)) |
|
|
|
|
|
publish_event('opponent_move', {'move': data, 'room': room, 'sid': sid}) |
|
|
|
@sio.event |
|
def disconnect(sid): |
|
print(f"{sid} disconnected") |
|
|
|
redis_master.lrem(WAITING_PLAYERS_KEY, 0, sid) |
|
|
|
room = redis_slave.hget(ROOMS_HASH, sid) |
|
if room: |
|
publish_event('opponent_disconnected', {'room': room}) |
|
|
|
|
|
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 |
|
|
|
pubsub_thread = threading.Thread(target=handle_pubsub, daemon=True) |
|
pubsub_thread.start() |
|
|
|
eventlet.wsgi.server(eventlet.listen(('0.0.0.0', 5000)), app) |