172 lines
4.9 KiB
Python
172 lines
4.9 KiB
Python
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
|
|
|
|
# TODO: Instead of using clients_lock, synchronize clients throught incoming_messages
|
|
# Make add_client and remove_client simply push events into the queue
|
|
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()
|
|
|
|
# TODO: Examine Quart (basically an asyncio version of flask)
|
|
app = Flask(__name__, static_url_path='')
|
|
sock = Sock(app)
|
|
|
|
@app.route('/')
|
|
def index():
|
|
return app.send_static_file('index.html')
|
|
|
|
@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)
|