init
This commit is contained in:
13
Pipfile
Normal file
13
Pipfile
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
[[source]]
|
||||||
|
url = "https://pypi.org/simple"
|
||||||
|
verify_ssl = true
|
||||||
|
name = "pypi"
|
||||||
|
|
||||||
|
[packages]
|
||||||
|
numpy = "*"
|
||||||
|
jsonpickle = "*"
|
||||||
|
|
||||||
|
[dev-packages]
|
||||||
|
|
||||||
|
[requires]
|
||||||
|
python_version = "3.7"
|
58
Pipfile.lock
generated
Normal file
58
Pipfile.lock
generated
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
{
|
||||||
|
"_meta": {
|
||||||
|
"hash": {
|
||||||
|
"sha256": "e6a28f99a9431629e2780f9040349bda28f7b058b716eb2dd761d7b13a1decf7"
|
||||||
|
},
|
||||||
|
"pipfile-spec": 6,
|
||||||
|
"requires": {
|
||||||
|
"python_version": "3.7"
|
||||||
|
},
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"name": "pypi",
|
||||||
|
"url": "https://pypi.org/simple",
|
||||||
|
"verify_ssl": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"default": {
|
||||||
|
"jsonpickle": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:0231d6f7ebc4723169310141352d9c9b7bbbd6f3be110cf634575d2bf2af91f0",
|
||||||
|
"sha256:625098cc8e5854b8c23b587aec33bc8e33e0e597636bfaca76152249c78fe5c1"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==1.1"
|
||||||
|
},
|
||||||
|
"numpy": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:1980f8d84548d74921685f68096911585fee393975f53797614b34d4f409b6da",
|
||||||
|
"sha256:22752cd809272671b273bb86df0f505f505a12368a3a5fc0aa811c7ece4dfd5c",
|
||||||
|
"sha256:23cc40313036cffd5d1873ef3ce2e949bdee0646c5d6f375bf7ee4f368db2511",
|
||||||
|
"sha256:2b0b118ff547fecabc247a2668f48f48b3b1f7d63676ebc5be7352a5fd9e85a5",
|
||||||
|
"sha256:3a0bd1edf64f6a911427b608a894111f9fcdb25284f724016f34a84c9a3a6ea9",
|
||||||
|
"sha256:3f25f6c7b0d000017e5ac55977a3999b0b1a74491eacb3c1aa716f0e01f6dcd1",
|
||||||
|
"sha256:4061c79ac2230594a7419151028e808239450e676c39e58302ad296232e3c2e8",
|
||||||
|
"sha256:560ceaa24f971ab37dede7ba030fc5d8fa173305d94365f814d9523ffd5d5916",
|
||||||
|
"sha256:62be044cd58da2a947b7e7b2252a10b42920df9520fc3d39f5c4c70d5460b8ba",
|
||||||
|
"sha256:6c692e3879dde0b67a9dc78f9bfb6f61c666b4562fd8619632d7043fb5b691b0",
|
||||||
|
"sha256:6f65e37b5a331df950ef6ff03bd4136b3c0bbcf44d4b8e99135d68a537711b5a",
|
||||||
|
"sha256:7a78cc4ddb253a55971115f8320a7ce28fd23a065fc33166d601f51760eecfa9",
|
||||||
|
"sha256:80a41edf64a3626e729a62df7dd278474fc1726836552b67a8c6396fd7e86760",
|
||||||
|
"sha256:893f4d75255f25a7b8516feb5766c6b63c54780323b9bd4bc51cdd7efc943c73",
|
||||||
|
"sha256:972ea92f9c1b54cc1c1a3d8508e326c0114aaf0f34996772a30f3f52b73b942f",
|
||||||
|
"sha256:9f1d4865436f794accdabadc57a8395bd3faa755449b4f65b88b7df65ae05f89",
|
||||||
|
"sha256:9f4cd7832b35e736b739be03b55875706c8c3e5fe334a06210f1a61e5c2c8ca5",
|
||||||
|
"sha256:adab43bf657488300d3aeeb8030d7f024fcc86e3a9b8848741ea2ea903e56610",
|
||||||
|
"sha256:bd2834d496ba9b1bdda3a6cf3de4dc0d4a0e7be306335940402ec95132ad063d",
|
||||||
|
"sha256:d20c0360940f30003a23c0adae2fe50a0a04f3e48dc05c298493b51fd6280197",
|
||||||
|
"sha256:d3b3ed87061d2314ff3659bb73896e622252da52558f2380f12c421fbdee3d89",
|
||||||
|
"sha256:dc235bf29a406dfda5790d01b998a1c01d7d37f449128c0b1b7d1c89a84fae8b",
|
||||||
|
"sha256:fb3c83554f39f48f3fa3123b9c24aecf681b1c289f9334f8215c1d3c8e2f6e5b"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==1.16.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"develop": {}
|
||||||
|
}
|
30
src/client.py
Normal file
30
src/client.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import asyncio
|
||||||
|
|
||||||
|
import msgs
|
||||||
|
from clientstates import ClientInitState
|
||||||
|
from misc import PORT
|
||||||
|
from state import StateMachine
|
||||||
|
|
||||||
|
|
||||||
|
async def run_client():
|
||||||
|
reader, writer = await asyncio.open_connection('abra.me', PORT)
|
||||||
|
|
||||||
|
state_machine = StateMachine()
|
||||||
|
init_state = ClientInitState(writer, state_machine)
|
||||||
|
|
||||||
|
async def receiver_coro():
|
||||||
|
while True:
|
||||||
|
msg = await msgs.recv(reader)
|
||||||
|
await state_machine.enqueue_msg(msg)
|
||||||
|
|
||||||
|
receiver_task = asyncio.create_task(receiver_coro())
|
||||||
|
|
||||||
|
await state_machine.run(init_state)
|
||||||
|
|
||||||
|
receiver_task.cancel()
|
||||||
|
|
||||||
|
print('Close the connection')
|
||||||
|
writer.close()
|
||||||
|
|
||||||
|
|
||||||
|
asyncio.run(run_client())
|
28
src/clientmsgs.py
Normal file
28
src/clientmsgs.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from msgs import Msg
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ClientDisconnected(Msg):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ClientCreateLobby(Msg):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ClientJoinLobby(Msg):
|
||||||
|
lobby_id: str
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ClientSherlockAnswered(Msg):
|
||||||
|
text: str
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ClientWatsonAsked(Msg):
|
||||||
|
text: str
|
137
src/clientstates.py
Normal file
137
src/clientstates.py
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
import asyncio
|
||||||
|
|
||||||
|
from clientmsgs import *
|
||||||
|
from servermsgs import *
|
||||||
|
from misc import user_input, Option, stdout_print
|
||||||
|
from state import State, EndState
|
||||||
|
|
||||||
|
|
||||||
|
class ClientInitState(State):
|
||||||
|
def __init__(self, writer, state_machine):
|
||||||
|
super().__init__(writer, state_machine)
|
||||||
|
|
||||||
|
async def recv_ServerHelloMsg(self, msg: ServerHelloMsg):
|
||||||
|
await stdout_print(msg.motd)
|
||||||
|
|
||||||
|
option_new_lobby = Option(r'1')
|
||||||
|
option_join_lobby = Option(r'2')
|
||||||
|
|
||||||
|
result = await user_input(
|
||||||
|
'1) Create a new lobby\n'
|
||||||
|
'2) Join an existing lobby\n'
|
||||||
|
'> ',
|
||||||
|
option_new_lobby,
|
||||||
|
option_join_lobby,
|
||||||
|
)
|
||||||
|
|
||||||
|
if result == option_new_lobby:
|
||||||
|
await self.send(ClientCreateLobby())
|
||||||
|
return ClientJoiningLobby(self.writer, self.state_machine, True)
|
||||||
|
else:
|
||||||
|
option_lobby_id = Option(r'.+')
|
||||||
|
|
||||||
|
result = await user_input(
|
||||||
|
'Lobby id: ',
|
||||||
|
option_lobby_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
await self.send(ClientJoinLobby(lobby_id=result.text))
|
||||||
|
return ClientJoiningLobby(self.writer, self.state_machine, False)
|
||||||
|
|
||||||
|
|
||||||
|
class ClientJoiningLobby(State):
|
||||||
|
is_sherlock: bool
|
||||||
|
|
||||||
|
def __init__(self, writer, state_machine, is_sherlock):
|
||||||
|
super().__init__(writer, state_machine)
|
||||||
|
self.is_sherlock = is_sherlock
|
||||||
|
|
||||||
|
async def recv_ServerJoinedLobby(self, msg: ServerJoinedLobby):
|
||||||
|
lobby_id = msg.lobby_id
|
||||||
|
|
||||||
|
if self.is_sherlock:
|
||||||
|
return ClientSherlockState(self.writer, self.state_machine, lobby_id)
|
||||||
|
else:
|
||||||
|
return ClientWatsonState(self.writer, self.state_machine, lobby_id)
|
||||||
|
|
||||||
|
async def recv_ServerNoSuchLobby(self, msg: ServerNoSuchLobby):
|
||||||
|
await stdout_print('No such lobby :(')
|
||||||
|
|
||||||
|
return ClientInitState(self.writer, self.state_machine)
|
||||||
|
|
||||||
|
async def recv_ServerFullLobby(self, msg: ServerFullLobby):
|
||||||
|
await stdout_print('Lobby is already full :(')
|
||||||
|
|
||||||
|
return ClientInitState(self.writer, self.state_machine)
|
||||||
|
|
||||||
|
|
||||||
|
class ClientSherlockState(State):
|
||||||
|
lobby_id: str
|
||||||
|
ui_task: asyncio.Task
|
||||||
|
|
||||||
|
def __init__(self, writer, state_machine, lobby_id):
|
||||||
|
super().__init__(writer, state_machine)
|
||||||
|
|
||||||
|
self.lobby_id = lobby_id
|
||||||
|
self.ui_task = None
|
||||||
|
|
||||||
|
async def init(self):
|
||||||
|
await stdout_print(f'You joined lobby {self.lobby_id}')
|
||||||
|
|
||||||
|
self.ui_task = asyncio.create_task(self.ui())
|
||||||
|
|
||||||
|
async def ui(self):
|
||||||
|
option = Option(r'Y|N')
|
||||||
|
|
||||||
|
while True:
|
||||||
|
await user_input('Wait for a question and answer with Y/N\n', option)
|
||||||
|
|
||||||
|
await self.send(ClientSherlockAnswered(option.text))
|
||||||
|
|
||||||
|
async def recv_ServerNoWatson(self, msg: ServerNoWatson):
|
||||||
|
await stdout_print('Wait, there\'s no Watson yet!')
|
||||||
|
|
||||||
|
async def recv_ServerWatsonAsked(self, msg: ServerWatsonAsked):
|
||||||
|
await stdout_print(f'Watson asked: {msg.text}')
|
||||||
|
|
||||||
|
async def recv_ServerWatsonConnected(self, msg: ServerWatsonConnected):
|
||||||
|
await stdout_print(f'A Watson connected to your lobby!')
|
||||||
|
|
||||||
|
async def recv_ServerPartnerDisconnected(self, msg: ServerPartnerDisconnected):
|
||||||
|
await stdout_print('Your Watson died untimely :(')
|
||||||
|
|
||||||
|
self.ui_task.cancel()
|
||||||
|
|
||||||
|
return EndState(self.writer, self.state_machine)
|
||||||
|
|
||||||
|
|
||||||
|
class ClientWatsonState(State):
|
||||||
|
lobby_id: str
|
||||||
|
ui_task: asyncio.Task
|
||||||
|
|
||||||
|
def __init__(self, writer, state_machine, lobby_id):
|
||||||
|
super().__init__(writer, state_machine)
|
||||||
|
|
||||||
|
self.lobby_id = lobby_id
|
||||||
|
self.ui_task = None
|
||||||
|
|
||||||
|
async def init(self):
|
||||||
|
await stdout_print(f'You joined lobby {self.lobby_id}')
|
||||||
|
|
||||||
|
self.ui_task = asyncio.create_task(self.ui())
|
||||||
|
|
||||||
|
async def ui(self):
|
||||||
|
option = Option(r'.+\?')
|
||||||
|
|
||||||
|
while True:
|
||||||
|
await user_input('Ask a yes/no question: ', option)
|
||||||
|
|
||||||
|
await self.send(ClientWatsonAsked(option.text))
|
||||||
|
|
||||||
|
async def recv_ServerSherlockAnswered(self, msg: ServerSherlockAnswered):
|
||||||
|
await stdout_print(f'Sherlock answered: {msg.text}')
|
||||||
|
|
||||||
|
async def recv_ServerPartnerDisconnected(self, msg: ServerPartnerDisconnected):
|
||||||
|
await stdout_print('Your Sherlock died untimely :(')
|
||||||
|
|
||||||
|
return EndState(self.writer, self.state_machine)
|
74
src/misc.py
Normal file
74
src/misc.py
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
import asyncio
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
PORT = 51423
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Option:
|
||||||
|
regex: str
|
||||||
|
text: str
|
||||||
|
|
||||||
|
def __init__(self, regex):
|
||||||
|
self.regex = regex
|
||||||
|
self.text = None
|
||||||
|
|
||||||
|
|
||||||
|
async def user_input(prompt, *options):
|
||||||
|
while True:
|
||||||
|
await stdout_print(prompt, end='')
|
||||||
|
|
||||||
|
text = await stdin_readline()
|
||||||
|
|
||||||
|
# await stdout_print(f'user->"{text}"')
|
||||||
|
|
||||||
|
for option in options:
|
||||||
|
if re.fullmatch(option.regex, text):
|
||||||
|
option.text = text
|
||||||
|
# await stdout_print(f'OK {option}')
|
||||||
|
return option
|
||||||
|
|
||||||
|
await stdout_print('Unrecognized option')
|
||||||
|
|
||||||
|
|
||||||
|
async def stdio(limit=asyncio.streams._DEFAULT_LIMIT, loop=None):
|
||||||
|
if loop is None:
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
|
||||||
|
reader = asyncio.StreamReader(limit=limit, loop=loop)
|
||||||
|
await loop.connect_read_pipe(
|
||||||
|
lambda: asyncio.StreamReaderProtocol(reader, loop=loop), sys.stdin)
|
||||||
|
|
||||||
|
writer_transport, writer_protocol = await loop.connect_write_pipe(
|
||||||
|
lambda: asyncio.streams.FlowControlMixin(loop=loop),
|
||||||
|
os.fdopen(sys.stdout.fileno(), 'wb'))
|
||||||
|
writer = asyncio.streams.StreamWriter(
|
||||||
|
writer_transport, writer_protocol, None, loop)
|
||||||
|
|
||||||
|
return reader, writer
|
||||||
|
|
||||||
|
|
||||||
|
stdio_reader = None
|
||||||
|
stdio_writer = None
|
||||||
|
|
||||||
|
|
||||||
|
async def stdin_readline():
|
||||||
|
global stdio_reader, stdio_writer
|
||||||
|
|
||||||
|
if stdio_reader is None:
|
||||||
|
stdio_reader, stdio_writer = await stdio()
|
||||||
|
|
||||||
|
return (await stdio_reader.readline()).decode().strip()
|
||||||
|
|
||||||
|
|
||||||
|
async def stdout_print(*args, end='\n'):
|
||||||
|
global stdio_reader, stdio_writer
|
||||||
|
|
||||||
|
if stdio_reader is None:
|
||||||
|
stdio_reader, stdio_writer = await stdio()
|
||||||
|
|
||||||
|
stdio_writer.write((' '.join(args) + end).encode())
|
||||||
|
await stdio_writer.drain()
|
33
src/msgs.py
Normal file
33
src/msgs.py
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
from asyncio import StreamReader, StreamWriter
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
import jsonpickle
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Msg:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
async def send(writer: StreamWriter, msg: Msg):
|
||||||
|
msg_str = jsonpickle.dumps(msg) + '\n'
|
||||||
|
msg_bytes = msg_str.encode()
|
||||||
|
|
||||||
|
# print(f'Sending -> {msg_str}')
|
||||||
|
|
||||||
|
writer.write(msg_bytes)
|
||||||
|
await writer.drain()
|
||||||
|
|
||||||
|
|
||||||
|
async def recv(reader: StreamReader) -> Msg:
|
||||||
|
msg_bytes = await reader.readline()
|
||||||
|
msg_str = msg_bytes.decode()
|
||||||
|
|
||||||
|
# print(f'Received <- {msg_str}')
|
||||||
|
|
||||||
|
msg = jsonpickle.loads(msg_str)
|
||||||
|
|
||||||
|
if not isinstance(msg, Msg):
|
||||||
|
raise ValueError()
|
||||||
|
|
||||||
|
return msg
|
60
src/server.py
Normal file
60
src/server.py
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import asyncio
|
||||||
|
from asyncio import StreamReader, StreamWriter
|
||||||
|
|
||||||
|
import msgs
|
||||||
|
from clientmsgs import ClientDisconnected
|
||||||
|
from misc import PORT
|
||||||
|
from serverstates import ServerInitState
|
||||||
|
from state import StateMachine
|
||||||
|
|
||||||
|
|
||||||
|
async def run_server(reader: StreamReader, writer: StreamWriter):
|
||||||
|
addr = writer.get_extra_info('peername')
|
||||||
|
print(f'New connection from {addr}.')
|
||||||
|
|
||||||
|
state_machine = StateMachine()
|
||||||
|
init_state = ServerInitState(writer, state_machine)
|
||||||
|
|
||||||
|
# separate concurrent coroutine to read incoming messages
|
||||||
|
|
||||||
|
async def receiver_coro():
|
||||||
|
while True:
|
||||||
|
msg = await msgs.recv(reader)
|
||||||
|
await state_machine.enqueue_msg(msg)
|
||||||
|
|
||||||
|
receiver_task = asyncio.create_task(receiver_coro())
|
||||||
|
|
||||||
|
# and another to check for client drops
|
||||||
|
|
||||||
|
async def disconnect_checker_coro():
|
||||||
|
while True:
|
||||||
|
if reader.at_eof():
|
||||||
|
await state_machine.enqueue_msg(ClientDisconnected())
|
||||||
|
return
|
||||||
|
await asyncio.sleep(0.1)
|
||||||
|
|
||||||
|
disconnect_checker_task = asyncio.create_task(disconnect_checker_coro())
|
||||||
|
|
||||||
|
# run the machine!
|
||||||
|
|
||||||
|
await state_machine.run(init_state)
|
||||||
|
|
||||||
|
receiver_task.cancel()
|
||||||
|
disconnect_checker_task.cancel()
|
||||||
|
|
||||||
|
print(f'Connection over {addr}.')
|
||||||
|
writer.close()
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
server = await asyncio.start_server(run_server, '0.0.0.0', PORT)
|
||||||
|
|
||||||
|
addr = server.sockets[0].getsockname()
|
||||||
|
print(f'Serving on {addr}')
|
||||||
|
|
||||||
|
async with server:
|
||||||
|
await server.serve_forever()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
asyncio.run(main())
|
48
src/servermsgs.py
Normal file
48
src/servermsgs.py
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from msgs import Msg
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ServerHelloMsg(Msg):
|
||||||
|
motd: str
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ServerJoinedLobby(Msg):
|
||||||
|
lobby_id: str
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ServerNoSuchLobby(Msg):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ServerFullLobby(Msg):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ServerPartnerDisconnected(Msg):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ServerSherlockAnswered(Msg):
|
||||||
|
text: str
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ServerWatsonAsked(Msg):
|
||||||
|
text: str
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ServerNoWatson(Msg):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ServerWatsonConnected(Msg):
|
||||||
|
pass
|
138
src/serverstates.py
Normal file
138
src/serverstates.py
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
import uuid
|
||||||
|
|
||||||
|
from servermsgs import *
|
||||||
|
from clientmsgs import *
|
||||||
|
from state import State, StateMachine, EndState
|
||||||
|
|
||||||
|
lobbies = {}
|
||||||
|
|
||||||
|
|
||||||
|
class Lobby:
|
||||||
|
id: str
|
||||||
|
sherlock_sm: StateMachine
|
||||||
|
watson_sm: StateMachine
|
||||||
|
|
||||||
|
def __init__(self, id, sherlock_sm):
|
||||||
|
self.id = id
|
||||||
|
self.sherlock_sm = sherlock_sm
|
||||||
|
self.watson_sm = None
|
||||||
|
|
||||||
|
|
||||||
|
# noinspection PyUnusedLocal
|
||||||
|
class ServerInitState(State):
|
||||||
|
def __init__(self, writer, state_machine):
|
||||||
|
super().__init__(writer, state_machine)
|
||||||
|
|
||||||
|
async def init(self):
|
||||||
|
await self.hello()
|
||||||
|
|
||||||
|
async def hello(self):
|
||||||
|
await self.send(ServerHelloMsg(
|
||||||
|
motd='Privet!!!\n'
|
||||||
|
f'There are {len(lobbies)} lobbies'
|
||||||
|
))
|
||||||
|
|
||||||
|
async def recv_ClientCreateLobby(self, msg: ClientCreateLobby):
|
||||||
|
lobby_id = uuid.uuid4().hex
|
||||||
|
|
||||||
|
lobby = Lobby(id=lobby_id, sherlock_sm=self.state_machine)
|
||||||
|
lobbies[lobby_id] = lobby
|
||||||
|
|
||||||
|
await self.send(ServerJoinedLobby(lobby_id))
|
||||||
|
return ServerSherlockState(self.writer, self.state_machine, lobby)
|
||||||
|
|
||||||
|
async def recv_ClientJoinLobby(self, msg: ClientJoinLobby):
|
||||||
|
lobby_id = msg.lobby_id
|
||||||
|
|
||||||
|
if lobby_id not in lobbies:
|
||||||
|
await self.send(ServerNoSuchLobby())
|
||||||
|
await self.hello()
|
||||||
|
return
|
||||||
|
|
||||||
|
lobby = lobbies[lobby_id]
|
||||||
|
|
||||||
|
if lobby.watson_sm is not None:
|
||||||
|
await self.send(ServerFullLobby())
|
||||||
|
await self.hello()
|
||||||
|
return
|
||||||
|
|
||||||
|
lobby.watson_sm = self.state_machine
|
||||||
|
await lobby.sherlock_sm.enqueue_msg(ServerWatsonConnected())
|
||||||
|
|
||||||
|
await self.send(ServerJoinedLobby(lobby_id))
|
||||||
|
return ServerWatsonState(self.writer, self.state_machine, lobby)
|
||||||
|
|
||||||
|
async def recv_ClientDisconnected(self, msg: ClientDisconnected):
|
||||||
|
print('Client died :(')
|
||||||
|
return EndState(self.writer, self.state_machine)
|
||||||
|
|
||||||
|
|
||||||
|
class ServerSherlockState(State):
|
||||||
|
lobby: Lobby
|
||||||
|
|
||||||
|
def __init__(self, writer, state_machine, lobby):
|
||||||
|
super().__init__(writer, state_machine)
|
||||||
|
|
||||||
|
self.lobby = lobby
|
||||||
|
|
||||||
|
async def recv_ServerWatsonConnected(self, msg: ServerWatsonConnected):
|
||||||
|
await self.send(msg)
|
||||||
|
|
||||||
|
async def recv_ClientSherlockAnswered(self, msg: ClientSherlockAnswered):
|
||||||
|
if not self.lobby.watson_sm:
|
||||||
|
await self.send(ServerNoWatson())
|
||||||
|
return
|
||||||
|
|
||||||
|
await self.lobby.watson_sm.enqueue_msg(ServerSherlockAnswered(msg.text))
|
||||||
|
|
||||||
|
async def recv_ServerWatsonAsked(self, msg: ServerWatsonAsked):
|
||||||
|
await self.send(msg)
|
||||||
|
|
||||||
|
def delist(self):
|
||||||
|
del lobbies[self.lobby.id]
|
||||||
|
print(f'Delisted lobby {self.lobby.id}')
|
||||||
|
|
||||||
|
async def recv_ClientDisconnected(self, msg: ClientDisconnected):
|
||||||
|
print('Shelock died :(')
|
||||||
|
|
||||||
|
self.delist()
|
||||||
|
|
||||||
|
if self.lobby.watson_sm:
|
||||||
|
await self.lobby.watson_sm.enqueue_msg(ServerPartnerDisconnected())
|
||||||
|
print(f'Killing watson')
|
||||||
|
|
||||||
|
return EndState(self.writer, self.state_machine)
|
||||||
|
|
||||||
|
async def recv_ServerPartnerDisconnected(self, msg: ServerPartnerDisconnected):
|
||||||
|
await self.send(ServerPartnerDisconnected())
|
||||||
|
|
||||||
|
self.delist()
|
||||||
|
|
||||||
|
return EndState(self.writer, self.state_machine)
|
||||||
|
|
||||||
|
|
||||||
|
class ServerWatsonState(State):
|
||||||
|
lobby: Lobby
|
||||||
|
|
||||||
|
def __init__(self, writer, state_machine, lobby):
|
||||||
|
super().__init__(writer, state_machine)
|
||||||
|
|
||||||
|
self.lobby = lobby
|
||||||
|
|
||||||
|
async def recv_ClientWatsonAsked(self, msg: ClientWatsonAsked):
|
||||||
|
await self.lobby.sherlock_sm.enqueue_msg(ServerWatsonAsked(msg.text))
|
||||||
|
|
||||||
|
async def recv_ServerSherlockAnswered(self, msg: ServerSherlockAnswered):
|
||||||
|
await self.send(msg)
|
||||||
|
|
||||||
|
async def recv_ClientDisconnected(self, msg: ClientDisconnected):
|
||||||
|
print('Watson died :(')
|
||||||
|
|
||||||
|
await self.lobby.sherlock_sm.enqueue_msg(ServerPartnerDisconnected())
|
||||||
|
|
||||||
|
return EndState(self.writer, self.state_machine)
|
||||||
|
|
||||||
|
async def recv_ServerPartnerDisconnected(self, msg: ServerPartnerDisconnected):
|
||||||
|
await self.send(ServerPartnerDisconnected())
|
||||||
|
|
||||||
|
return EndState(self.writer, self.state_machine)
|
79
src/state.py
Normal file
79
src/state.py
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
from asyncio import StreamWriter, Queue
|
||||||
|
|
||||||
|
import msgs
|
||||||
|
from msgs import Msg
|
||||||
|
|
||||||
|
|
||||||
|
class State:
|
||||||
|
writer: StreamWriter
|
||||||
|
state_machine: 'StateMachine'
|
||||||
|
|
||||||
|
def __init__(self, writer, state_machine):
|
||||||
|
self.writer = writer
|
||||||
|
self.state_machine = state_machine
|
||||||
|
|
||||||
|
async def init(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def send(self, msg: Msg):
|
||||||
|
await msgs.send(self.writer, msg)
|
||||||
|
|
||||||
|
async def route_msg(self, msg):
|
||||||
|
if not isinstance(msg, Msg):
|
||||||
|
raise ValueError()
|
||||||
|
|
||||||
|
msg_class_name = msg.__class__.__name__
|
||||||
|
|
||||||
|
method_name = f'recv_{msg_class_name}'
|
||||||
|
|
||||||
|
if not hasattr(self, method_name):
|
||||||
|
print(f'ignoring msg: {self} <- {msg}')
|
||||||
|
return
|
||||||
|
|
||||||
|
method = getattr(self, method_name)
|
||||||
|
|
||||||
|
return await method(msg=msg)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f'{self.__class__.__name__}'
|
||||||
|
|
||||||
|
|
||||||
|
class EndState(State):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class StateMachine:
|
||||||
|
state: State
|
||||||
|
msg_queue: Queue
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.state = None
|
||||||
|
self.msg_queue = Queue()
|
||||||
|
|
||||||
|
async def enqueue_msg(self, msg: Msg):
|
||||||
|
await self.msg_queue.put(msg)
|
||||||
|
|
||||||
|
async def run(self, init_state):
|
||||||
|
self.state = init_state
|
||||||
|
state_initialized = False
|
||||||
|
|
||||||
|
while not isinstance(self.state, EndState):
|
||||||
|
if not state_initialized:
|
||||||
|
await self.state.init()
|
||||||
|
state_initialized = True
|
||||||
|
|
||||||
|
msg = await self.msg_queue.get()
|
||||||
|
|
||||||
|
# print(f'state {self.state} received {msg}')
|
||||||
|
|
||||||
|
next_state = await self.state.route_msg(msg)
|
||||||
|
|
||||||
|
if next_state is None:
|
||||||
|
next_state = self.state
|
||||||
|
|
||||||
|
if next_state is not self.state:
|
||||||
|
state_initialized = False
|
||||||
|
|
||||||
|
# print(f'{self.state} + {msg} = {next_state}')
|
||||||
|
|
||||||
|
self.state = next_state
|
13
src/test.py
Normal file
13
src/test.py
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import json
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class A:
|
||||||
|
a: int
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
print(
|
||||||
|
"""1) Create a new lobby
|
||||||
|
2) Join an existing lobby""")
|
Reference in New Issue
Block a user