first server version
This commit is contained in:
parent
58633bab3d
commit
d0c8009834
90
README.md
90
README.md
@ -1,3 +1,91 @@
|
||||
# ultimate_tictactoe_server
|
||||
|
||||
a python server backend for ultimate tic-tac-toe
|
||||
a python server backend for ultimate tic-tac-toe.
|
||||
|
||||
communication with the web client is done by a (far from any standard and almost random) json protocol:
|
||||
|
||||
|
||||
|
||||
**register as player and in game queue:**
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "register",
|
||||
"data": {
|
||||
"id": "<player_id>",
|
||||
"name": "<player_name>"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
response:
|
||||
|
||||
```JSON
|
||||
{
|
||||
"type": "register_response",
|
||||
"data": {
|
||||
"success": true,
|
||||
"msg": "<additional info e.g. in case of error>"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
**message from server that game started**
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "game_starts",
|
||||
"data": {
|
||||
"msg": "...",
|
||||
"opponent_name": "...",
|
||||
"is_first_move": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
note: `is_first_move` indicates whether the player or it's opponent begins
|
||||
|
||||
|
||||
|
||||
**move**
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "move",
|
||||
"data": {
|
||||
"sub_x": "...",
|
||||
"sub_y": "...",
|
||||
"x": "...",
|
||||
"y": "..."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
response:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "move_response",
|
||||
"data": {
|
||||
"success": true,
|
||||
"msg": "..."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
**end game**
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "end_game",
|
||||
"data": {
|
||||
"msg": "..."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
(response?)
|
185
game_manager.py
Normal file
185
game_manager.py
Normal file
@ -0,0 +1,185 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import asyncio
|
||||
import websockets
|
||||
import json
|
||||
|
||||
player_id_queue = set()
|
||||
sockets = {}
|
||||
|
||||
player_games = {}
|
||||
|
||||
|
||||
async def register_user(id, socket):
|
||||
|
||||
if id in player_id_queue:
|
||||
return False
|
||||
|
||||
player_id_queue.add(id)
|
||||
sockets[id] = socket
|
||||
|
||||
await matchmaking()
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def unregister_user(id):
|
||||
if id in player_id_queue:
|
||||
player_id_queue.remove(id)
|
||||
del(sockets[id])
|
||||
|
||||
elif id in player_games:
|
||||
# we have an active game and have to end it
|
||||
await player_games[id]._on_end_game(id)
|
||||
del(player_games[id])
|
||||
|
||||
|
||||
async def process_message(id, json):
|
||||
if id in player_games:
|
||||
await player_games[id].processPlayerMessage(id, json)
|
||||
|
||||
|
||||
async def create_new_match():
|
||||
p_a = player_id_queue.pop()
|
||||
p_b = player_id_queue.pop()
|
||||
|
||||
s_a = sockets[p_a]
|
||||
s_b = sockets[p_b]
|
||||
|
||||
del(sockets[p_a])
|
||||
del(sockets[p_b])
|
||||
|
||||
new_game = GameManager(p_a, p_b, p_a, s_a, s_b)
|
||||
|
||||
player_games[p_a] = new_game
|
||||
player_games[p_b] = new_game
|
||||
|
||||
await new_game.startMatch()
|
||||
|
||||
|
||||
async def matchmaking():
|
||||
if len(player_id_queue) < 2:
|
||||
# we need at least 2 users for that
|
||||
return
|
||||
|
||||
else:
|
||||
asyncio.ensure_future(create_new_match())
|
||||
|
||||
|
||||
class GameManager(object):
|
||||
def __init__(self, player_a_id, player_b_id, start_player, socket_a, socket_b):
|
||||
self.player_a_id = player_a_id
|
||||
self.player_b_id = player_b_id
|
||||
|
||||
self.socket_a = socket_a
|
||||
self.socket_b = socket_b
|
||||
|
||||
self.current_player = start_player
|
||||
|
||||
self.game_finished = False
|
||||
|
||||
async def startMatch(self):
|
||||
|
||||
print("match starts")
|
||||
|
||||
start_msg_a = {
|
||||
'type': 'game_starts',
|
||||
'data': {
|
||||
'msg': '...',
|
||||
'opponent_name': self.player_b_id,
|
||||
'is_first_move': True
|
||||
}
|
||||
}
|
||||
|
||||
start_msg_b = {
|
||||
'type': 'game_starts',
|
||||
'data': {
|
||||
'msg': '...',
|
||||
'opponent_name': self.player_a_id,
|
||||
'is_first_move': False
|
||||
}
|
||||
}
|
||||
|
||||
await self.socket_a.send(json.dumps(start_msg_a))
|
||||
await self.socket_b.send(json.dumps(start_msg_b))
|
||||
|
||||
print("start message send to all players")
|
||||
|
||||
async def processPlayerMessage(self, player_id, json_str):
|
||||
if len(json_str) > 4096:
|
||||
# something is fishy here
|
||||
print("received strange message from client")
|
||||
|
||||
print("received message: " + json_str)
|
||||
|
||||
try:
|
||||
json_dict = json.loads(json_str)
|
||||
type = json_dict['type']
|
||||
data = json_dict['data']
|
||||
|
||||
if type == "move":
|
||||
await self._on_move(player_id, data)
|
||||
|
||||
elif type == "end_game":
|
||||
await self._on_end_game(player_id)
|
||||
|
||||
except Exception as e:
|
||||
print("" + str(e) + ": received wrong formated message")
|
||||
|
||||
async def _on_move(self, player_id, move_data):
|
||||
response = {'type': 'move_response'}
|
||||
response_data = {}
|
||||
|
||||
opponent_response = {'type': 'move'}
|
||||
opponent_response_data = {}
|
||||
|
||||
opponent_response_data['sub_x'] = move_data['sub_x']
|
||||
opponent_response_data['sub_y'] = move_data['sub_y']
|
||||
opponent_response_data['x'] = move_data['x']
|
||||
opponent_response_data['y'] = move_data['y']
|
||||
opponent_response['data'] = opponent_response_data
|
||||
|
||||
if player_id == self.current_player:
|
||||
|
||||
is_a = (self.player_a_id == player_id)
|
||||
current_socket = self.socket_a if is_a else self.socket_b
|
||||
opponent_socket = self.socket_b if is_a else self.socket_a
|
||||
|
||||
response_data['success'] = True
|
||||
response_data['msg'] = "move successful"
|
||||
|
||||
response['data'] = response_data
|
||||
|
||||
await opponent_socket.send(json.dumps(opponent_response))
|
||||
await current_socket.send(json.dumps(response))
|
||||
|
||||
# switch player
|
||||
self.current_player = self.player_b_id if is_a else self.player_a_id
|
||||
|
||||
else:
|
||||
print("received move from wrong player")
|
||||
|
||||
is_a = (self.player_a_id == player_id)
|
||||
current_socket = self.socket_a if is_a else self.socket_b
|
||||
|
||||
response_data["success"] = False
|
||||
response_data["msg"] = "not your turn!"
|
||||
|
||||
response['data'] = response_data
|
||||
|
||||
await current_socket.send(json.dumps(response))
|
||||
|
||||
async def _on_end_game(self, player_id):
|
||||
|
||||
if self.game_finished:
|
||||
return
|
||||
|
||||
is_a = (self.player_a_id == player_id)
|
||||
opponent_socket = self.socket_b if is_a else self.socket_a
|
||||
|
||||
opponent_response = {'type': 'end_game'}
|
||||
opponent_response['data'] = {'msg': 'game closed by opponent'}
|
||||
|
||||
await opponent_socket.send(json.dumps(opponent_response))
|
||||
|
||||
self.game_finished = True
|
65
main.py
Normal file
65
main.py
Normal file
@ -0,0 +1,65 @@
|
||||
import asyncio
|
||||
import websockets
|
||||
|
||||
from game_manager import *
|
||||
|
||||
|
||||
async def socket_worker(websocket, path):
|
||||
|
||||
registered = False
|
||||
id = None
|
||||
|
||||
print("new connection")
|
||||
|
||||
try:
|
||||
# get first message as register message
|
||||
raw_msg = await websocket.recv()
|
||||
|
||||
msg = json.loads(raw_msg)
|
||||
|
||||
if msg['type'] != 'register':
|
||||
print("got wrong registration")
|
||||
websocket.close()
|
||||
return
|
||||
|
||||
id = msg['data']['id']
|
||||
|
||||
registered = await register_user(id, websocket)
|
||||
|
||||
register_response = {
|
||||
'type': 'register_response', 'data': {
|
||||
'success': True, 'msg': '...'}}
|
||||
|
||||
if not registered:
|
||||
register_response['data']['success'] = False
|
||||
|
||||
await websocket.send(json.dumps(register_response))
|
||||
websocket.close()
|
||||
return
|
||||
|
||||
await websocket.send(json.dumps(register_response))
|
||||
|
||||
print("successful redisterd user " + id)
|
||||
|
||||
async for m in websocket:
|
||||
await process_message(id, m)
|
||||
|
||||
except Exception as e:
|
||||
# TODO: each disconnect is an exception so far
|
||||
if id is not None:
|
||||
print("catched exception in worker for user: " + id + ": " + str(e))
|
||||
else:
|
||||
print("catched exception in worker for unknown user")
|
||||
|
||||
finally:
|
||||
if registered:
|
||||
await unregister_user(id)
|
||||
|
||||
if id is None:
|
||||
id = "unknown_user"
|
||||
print("close connection to user: " + id)
|
||||
|
||||
start_server = websockets.serve(socket_worker, 'localhost', 5555)
|
||||
|
||||
asyncio.get_event_loop().run_until_complete(start_server)
|
||||
asyncio.get_event_loop().run_forever()
|
Loading…
Reference in New Issue
Block a user