import json import simple_websocket from collections import OrderedDict from flask import Flask from flask_sock import Sock from queue import Queue from random import Random from threading import Thread, Lock class BoardGenerator: def __init__(self, seed): self.dice = [ 'aeaneg', 'wngeeh', 'ahspco', 'lnhnrz', 'aspffk', 'tstiyd', 'objoab', 'owtoat', 'iotmuc', 'erttyl', 'ryvdel', 'toessi', 'lreixd', 'terwhv', 'eiunes', 'nuihmq' ] self.rng = Random(seed) def generate_board(self): shuffled_dice = self.rng.sample(self.dice, k=len(self.dice)) roll_results = [self.rng.choice(die) for die in shuffled_dice] return [roll_results[4 * i : 4 * (i+1)] for i in range(0, 4)] class Client: def __init__(self, sock, client_id): self.sock = sock self.data = { 'id': client_id, 'name': '' } class GlebbyState: def __init__(self): self.clients_lock = Lock() # We want to preserve the order that clients arrived in, # e.g. for whose turn it is self.clients = OrderedDict() self.next_client_id_lock = Lock() self.next_client_id = 0 self.incoming_messages = Queue() self.board_generator = BoardGenerator(42) self.board = None # domain stuff def handle_message_from(self, client_id, payload): print(f"{client_id}: {payload}") if payload['type'] == 'set-name': self.clients[client_id].data['name'] = payload['name'] self.broadcast(client_id, { 'type': 'set-name', 'name': payload['name'] }) elif payload['type'] == 'chat': self.broadcast(client_id, { 'type': 'chat', 'message': payload['message'] }) elif payload['type'] == 'roll': self.board = self.board_generator.generate_board() self.broadcast(client_id, { 'type': 'roll', 'board': self.board }) else: print("Unhandled!") def get_state_dto(self, client_id): return { 'yourId': client_id, 'players': [ self.clients[other_client_id].data for other_client_id in self.clients ], 'board': self.board } # message receiving and sending def put_incoming_message(self, client_id, payload): self.incoming_messages.put({ 'from': client_id, 'payload': json.loads(payload) }) def send_to(self, client_id_from, client_id_to, payload): self.clients[client_id_to].sock.send(json.dumps({ 'from': client_id_from, 'payload': payload })) def broadcast(self, client_id_from, payload): with self.clients_lock: for client_id_to in self.clients: self.send_to(client_id_from, client_id_to, payload) # client management def _get_next_client_id(self): with self.next_client_id_lock: client_id = self.next_client_id self.next_client_id += 1 return client_id def add_client(self, sock): client_id = self._get_next_client_id() self.broadcast(client_id, {'type': 'join'}) with self.clients_lock: self.clients[client_id] = Client(sock, client_id) self.send_to(None, client_id, { 'type': 'init', 'state': self.get_state_dto(client_id) }) return client_id def remove_client(self, client_id): with self.clients_lock: del self.clients[client_id] self.broadcast(client_id, {'type': 'leave'}) # event thread stuff def start_event_thread(self): event_thread = Thread(target=lambda: self._event_thread()) event_thread.daemon = True event_thread.start() def _event_thread(self): while True: msg = self.incoming_messages.get() self.handle_message_from(msg['from'], msg['payload']) # Initialization state = GlebbyState() state.start_event_thread() app = Flask(__name__) sock = Sock(app) @sock.route('/glebby') def echo(sock): client_id = state.add_client(sock) try: while True: data = sock.receive() state.put_incoming_message(client_id, data) except simple_websocket.ConnectionClosed: state.remove_client(client_id)