first version

This commit is contained in:
2021-06-17 16:02:50 +02:00
parent f09d2d79f9
commit 02e4569634
22 changed files with 3924 additions and 282 deletions

1
server/.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
en.json filter=lfs diff=lfs merge=lfs -text

320
server/crossword.py Normal file
View File

@ -0,0 +1,320 @@
import enum
import json
import logging
import numpy as np
from . import crossword_generator
from enum import Enum
import traceback
class HintOrientation(Enum):
VERTICAL = 0
HORIZONTAL = 1
BOTH = 2
class FieldType(Enum):
EMPTY = 0
HINT = 1
LETTER = 2
class Field(object):
def __init__(self, field_type: FieldType = FieldType.EMPTY):
self._field_type = field_type
def get_type(self) -> FieldType:
return self._field_type
def get_content(self) -> str:
return None
def serialize(self):
type_names = {
FieldType.EMPTY: "empty",
FieldType.HINT: "hint",
FieldType.LETTER: "letter"
}
return {
'cell_type': type_names[self._field_type]
}
class HintField(Field):
def __init__(self, horizontal_hint: str = None, vertical_hint: str = None):
super().__init__(field_type=FieldType.HINT)
self._horizontal_hint = horizontal_hint
self._vertical_hint = vertical_hint
def get_horizontal_hint(self) -> str:
return self._horizontal_hint
def get_vertical_hint(self) -> str:
return self._vertical_hint
def set_horizintal_hint(self, hint: str):
self._horizontal_hint = hint
def set_vertical_hint(self, hint: str):
self._vertical_hint = hint
def serialize(self):
result = super().serialize()
result['vertical_hint'] = self._vertical_hint
result['horizontal_hint'] = self._horizontal_hint
return result
class LetterField(Field):
def __init__(self, letter: str):
assert len(letter) <= 1
super().__init__(field_type=FieldType.LETTER)
self._letter = letter.lower()
self._revealed = False
self._user_letter = ""
def get_content(self) -> str:
return self._letter.upper()
def get_user_content(self) -> str:
return self._user_letter.upper()
def reveal(self):
self._revealed = True
def user_input(self, input_letter):
assert len(input_letter) <= 1
self._user_letter = input_letter.lower()
def is_revealed(self) -> bool:
return self._revealed
def serialize(self):
result = super().serialize()
result['letter'] = self._letter
return result
class Grid(object):
def __init__(self, width: int, height: int, lang_code: str, density=0.5):
self._width = width
self._height = height
self._lang_code = lang_code
self._density = density
self._grid = []
try:
self._build_grid()
except Exception as e:
logging.error("error in generation", str(e))
traceback.print_exc()
def serialize(self):
return [
[cell.serialize() for cell in row] for row in self._grid
]
def get_status(self):
status = []
for y, row in enumerate(self._grid):
for x, cell in enumerate(row):
if cell.get_type() == FieldType.LETTER:
user_content = cell.get_user_content()
if cell.is_revealed():
status.append({
'x': x,
'y': y,
'revealed': cell.get_content()
})
elif len(user_content) > 0:
status.append({
'x': x,
'y': y,
'user_input': user_content
})
return status
def check_and_reveal_horizontal(self, x: int, y: int) -> list:
# TODO: this would be much more perfomant and elegant, if every cell would hold a reference
# to it's own word^^
status_update = []
cells_to_reveal = []
x_start = x
while (self._grid[y][x_start-1].get_type() == FieldType.LETTER):
x_start -= 1
x_i = x_start - 1
while(x_i + 1 < self._width and self._grid[y][x_i+1].get_type() == FieldType.LETTER):
x_i += 1
cell = self._grid[y][x_i]
if cell.get_user_content() != cell.get_content():
return []
cells_to_reveal.append(cell)
if x_start - x_i == 0:
# we have a single letter, not a word
return []
for i, cell in enumerate(cells_to_reveal):
status_update.append({
'x': x_start + i,
'y': y,
'revealed': cell.get_content()
})
cell.reveal()
return status_update
def check_and_reveal_vertical(self, x: int, y: int) -> list:
# TODO: this would be much more perfomant and elegant, if every cell would hold a reference
# to it's own word^^
status_update = []
cells_to_reveal = []
y_start = y
while (self._grid[y_start - 1][x].get_type() == FieldType.LETTER):
y_start -= 1
y_i = y_start - 1
while(y_i + 1 < self._width and self._grid[y_i+1][x].get_type() == FieldType.LETTER):
y_i += 1
cell = self._grid[y_i][x]
if cell.get_user_content() != cell.get_content():
return []
cells_to_reveal.append(cell)
if y_start - y_i == 0:
# we have a single letter, not a word
return []
for i, cell in enumerate(cells_to_reveal):
status_update.append({
'x': x,
'y': y + i,
'revealed': cell.get_content()
})
cell.reveal()
return status_update
def check_and_reveal_word(self, x: int, y: int):
return self.check_and_reveal_horizontal(x, y) + self.check_and_reveal_vertival(x, y)
def user_input(self, x: int, y: int, letter: str) -> list:
assert len(letter) <= 1
cell = self._grid[y][x]
if cell.get_type() != FieldType.LETTER:
# should not happen if the client does everything right
logging.warning("try to modify wrong cell")
return []
if cell.is_revealed():
# user tries to modify already revealed change, telling him it's already revealed ;)
return [{
'x': x,
'y': y,
'revealed': cell.get_content()
}]
letter = letter.lower()
cell.user_input(letter.lower())
revealed_changes = self.check_and_reveal_vertical(
x, y) + self.check_and_reveal_horizontal(x, y)
if len(revealed_changes) == 0:
return [{
'x': x,
'y': y,
'user_input': cell.get_user_content()
}]
return revealed_changes
def _build_grid(self):
raw_grid, word_infos = crossword_generator.create_word_grid(
self._width - 1, self._height - 1, lang_code="en", target_density=self._density)
# note: we will append an additional row and column, to have enough space to place hint fields
self._grid = [[Field()] * self._width] # initialize with empty row
for y in range(self._height - 1):
row = [Field()] # initialize row with empty column
for x in range(self._width - 1):
raw_cell = raw_grid[y, x]
if raw_cell == " ":
row.append(Field())
else:
row.append(LetterField(raw_cell))
self._grid.append(row)
# place hint fields:
for word, info in word_infos.items():
y, x = info.get_hint_location()
# correct offset
y += 1
x += 1
cell = self._grid[y][x]
# check if we already have a hint here:
if cell.get_type() == FieldType.HINT:
if info.is_vertical():
cell.set_vertical_hint(info.get_hint())
else:
cell.set_horizintal_hint(info.get_hint())
elif cell.get_type() == FieldType.LETTER:
# edge case: a word has "eaten up" another one, skipping that case
pass
else:
if info.is_vertical():
self._grid[y][x] = HintField(vertical_hint=info.get_hint())
else:
self._grid[y][x] = HintField(
horizontal_hint=info.get_hint())
class Crossword(object):
def __init__(self, width: int, height: int, lang_code: str = "en"):
self._width = width
self._height = height
self._grid = Grid(width, height, lang_code)
def serialize(self):
return {
'w': self._width,
'h': self._height,
'grid': self._grid.serialize()
}
def user_input(self, x: int, y: int, letter: str) -> list:
return self._grid.user_input(x=x, y=y, letter=letter)
def get_status(self) -> list:
return self._grid.get_status()
if __name__ == "__main__":
logging.basicConfig(level=logging.INFO)
cw = Crossword(15, 15, "en")
print(cw.serialize())

View File

@ -0,0 +1,112 @@
import logging
import uuid
from . import json_websockets
from . import session
class CrosswordConnection(json_websockets.JsonWebsocketConnection):
sessions = {}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._session = None
async def send_crossword(self, sessionId: str):
if sessionId not in CrosswordConnection.sessions:
await self.send_error(msg="unknown session")
return
sess = CrosswordConnection.sessions[sessionId]
# NOTE: if there will be the possibility of private
# sessions, this has to be changed since this is leaking
# the information that a certain session exists!
if self not in sess.get_sockets():
await self.send_error(msg="you are not registered to given session")
return
crossword = sess.get_crossword()
await self.send({
'type': 'crossword',
'crossword': crossword.serialize()
})
# sending also the status as update:
await self.send({
'type': 'update',
'updates': crossword.get_status()
})
async def user_update(self, x: int, y: int, letter: str):
if len(letter) > 1:
await self.send_error(msg="received invalid userinput")
return
update_message = self._session.get_crossword().user_input(x=x, y=y, letter=letter)
for connection in self._session.get_sockets():
await connection.send({
'type': 'update',
'updates': update_message
})
async def register(self, sessionId: str = None):
if sessionId is None:
sessionId = uuid.uuid4().hex
while sessionId in CrosswordConnection.sessions:
sessionId = uuid.uuid4().hex
new_session = session.Session()
CrosswordConnection.sessions[sessionId] = new_session
if sessionId not in CrosswordConnection.sessions:
await self.send_error("unknown session id")
# register with new id:
await self.register()
return
sess = CrosswordConnection.sessions[sessionId]
sess.connect_socket(self)
self._session = sess
await self.send({
'type': 'register',
'sessionId': sessionId
})
await self.send_crossword(sessionId)
async def send_error(self, msg: str):
await self.send({
'type': 'error',
'message': msg
})
async def handle_message(self, message: dict):
logging.info("incoming message: %s", str(message))
if not "type" in message:
logging.error("received malformated message")
await self.send_error(msg="i do not understand the request")
return
if message['type'] == 'register':
sessionId = None
if 'sessionId' in message:
sessionId = message['sessionId']
await self.register(sessionId=sessionId)
return
if self._session is None:
await self.send_error(msg="you are not registered properly")
return
if message['type'] == "update":
await self.user_update(x=message['x'], y=message['y'], letter=message['letter'])
return

View File

@ -0,0 +1,286 @@
import json
import random
import numpy as np
from string import digits
import pathlib
import logging
def get_database(lang: str = "en") -> dict:
if lang not in get_database._dbs:
current_folder = pathlib.Path(__file__).parents[0]
db_file = str(current_folder / f"{lang}.json")
logging.info("loading database: %s", lang)
with open(db_file, "r") as f:
db = json.load(f)
get_database._dbs[lang] = db
logging.info("database loaded")
return get_database._dbs[lang]
get_database._dbs = {}
class NoDataException(Exception):
pass
class WordInfo(object):
def __init__(self, word:str, y:int, x:int, is_vertical: bool, database: dict):
self._dictionary_database = database
self._y = y
self._x = x
self._word = word
self._hint = None
self._is_vertical = is_vertical
self.choose_info()
def get_attribute(self, attr: str):
attr = self._dictionary_database[self._word][attr]
if attr is None:
raise NoDataException
return attr
def get_best_antonym(self) -> str:
antonyms = self.get_attribute("antonyms")
return random.choice(antonyms)
def get_best_synonym(self) -> str:
synonyms = self.get_attribute("synonyms")
return random.choice(synonyms)
def get_best_sense(self) -> str:
senses = self.get_attribute("senses")
return random.choice(senses)
def choose_info(self):
# first choose antonyms, then synonyms, then senses
try:
self._hint = f"opposite of {self.get_best_antonym()}"
return
except NoDataException:
pass
try:
self._hint = f"other word for {self.get_best_synonym()}"
return
except NoDataException:
pass
self._hint = self.get_best_sense()
def get_hint(self) -> str:
return self._hint
def get_hint_location(self):
x = self._x if self._is_vertical else self._x - 1
y = self._y - 1 if self._is_vertical else self._y
return (y, x)
def is_vertical(self):
return self._is_vertical
def create_word_grid(w: int, h: int, lang_code: str = "en", target_density = 0.5):
logging.info("generate new crossword")
database = get_database(lang = lang_code)
list_words = list(database.keys())
grid = np.full(shape=(h,w), dtype=np.unicode, fill_value = ' ')
locations = {}
word_hints = {}
def store_location(char: str, y: int, x: int):
assert len(char) == 1
if char not in locations:
locations[char] = []
locations[char].append([y,x])
remove_digits = str.maketrans('', '', digits)
n_words = len(list_words)
def get_word(max_length: int, min_length = 0):
assert max_length > 1
index = random.randint(0,n_words-1)
word = list_words[index][:]
while len(word) >= max_length or not word.isalnum() or len(word) <= min_length:
index = random.randint(0,n_words-1)
word = list_words[index][:]
return word
def normalize_word(word:str):
word = word.translate(remove_digits)
return word.lower()
def place_word(word:str, y: int, x:int, vertical:bool = False):
normalized_word = normalize_word(word)
n = len(normalized_word)
if vertical:
assert grid.shape[0] - n >= y
for i, char in enumerate(normalized_word):
grid[y + i,x] = char
store_location(char, y+i, x)
else:
assert grid.shape[1] - n >= x
for i, char in enumerate(normalized_word):
grid[y,x + i] = char
store_location(char, y, x+i)
word_hints[normalized_word] = WordInfo(word, y, x, vertical, database)
def density():
return 1 - (grid == " ").sum() / (w * h)
def check_if_fits(word:str, y:int, x:int, vertical:bool):
n = len(word)
if vertical:
# check if there is space before and after
if y - 1 >= 0 and grid[y - 1, x] != " ":
return False
if y + n < grid.shape[0] - 1 and grid[y+n,x] != " ":
return False
if grid.shape[0] - n < y or y < 0:
# print("over board")
return False
for i, char in enumerate(word):
char_x = x
char_y = y + i
if not (grid[char_y, char_x] == " " or grid[char_y, char_x] == char):
# print("not matching")
return False
if grid[char_y, char_x] == " ":
# check for horizonatal neighbors:
if char_x - 1 >= 0 and grid[char_y, char_x - 1] != " ":
# print("3")
return False
if char_x + 1 < grid.shape[1] and grid[char_y, char_x + 1] != " ":
# print("4")
return False
else:
# check if there is space before and after
if x - 1 >= 0 and grid[y, x - 1] != " ":
return False
if x + n < grid.shape[1] - 1 and grid[y,x + n] != " ":
return False
if grid.shape[1] - n < x or x < 0:
# print("over board")
return False
for i, char in enumerate(word):
char_x = x + i
char_y = y
if not (grid[char_y, char_x] == " " or grid[char_y, char_x] == char):
# print("not matching")
return False
if grid[char_y, char_x] == " ":
# check for vertical neighbors:
if char_y - 1 >= 0 and grid[char_y - 1, char_x] != " ":
# print("1")
return False
if char_y + 1 < grid.shape[0] and grid[char_y + 1, char_x] != " ":
# print("2")
return False
return True
def get_crossover(word: str):
# returns Tuple of: (y,x, is_vertical?) or None
shuffled_order = list(range(len(word)))
random.shuffle(shuffled_order)
for index in shuffled_order:
# check for existing locations
char = word[index]
if char in locations:
char_locations = locations[char]
for char_loc in char_locations:
# test vertical
y = char_loc[0] - index
x = char_loc[1]
if check_if_fits(word, y, x, vertical=True):
return (y,x,True)
# test horizontal
y = char_loc[0]
x = char_loc[1] - index
if check_if_fits(word, y, x, vertical=False):
return (y,x,False)
return None
min_shape = min(w,h,30)
# place first word:
first_word = get_word(max_length=min_shape, min_length=min(10,grid.shape[1] - 2))
# find random place:
x = random.randint(0, grid.shape[1] - len(first_word) - 1)
y = random.randint(0, grid.shape[0] - 1)
place_word(first_word, y, x, vertical=False)
i = 0
current_density = density()
while current_density < target_density:
word = get_word(max_length=(1 - current_density ** 0.4) * min_shape,
min_length=max(min(10, 0.5 * (1 - current_density ** 0.3) * min_shape), 2))
normalized_word = normalize_word(word)
if normalized_word in word_hints:
continue
# check if matching characters exist:
crossover = get_crossover(normalized_word)
i += 1
if i % 100000 == 0:
print(i)
if i > 100000:
break
if crossover == None:
current_density = density()
continue
y,x,is_vertical = crossover
place_word(word, y,x, is_vertical)
current_density = density()
logging.info("crossword generation done after %s iterations", str(i))
return grid, word_hints

BIN
server/en.json (Stored with Git LFS) Normal file

Binary file not shown.

80
server/json_websockets.py Normal file
View File

@ -0,0 +1,80 @@
import websockets
import asyncio
import json
import logging
logging.basicConfig(level=logging.INFO)
class JsonWebsocketConnection(object):
def __init__(self,
websocket: websockets.WebSocketServerProtocol):
logging.info("incoming connection")
self._websocket = websocket
self._is_closed = False;
def is_closed(self):
return self._is_closed
async def close(self):
logging.info("closing connection")
try:
if self._is_closed:
return
self._websocket.close()
self._is_closed = True
except Exception as e:
logging.warning("error closing connection: %s", str(e))
async def send(self, message: dict):
string_message = json.dumps(message)
logging.debug("sending message: %s", string_message)
try:
await self._websocket.send(string_message)
except Exception as e:
logging.warning("error sending message: %s", str(e))
async def handle_message(self, message: dict):
pass # override this function
async def run(self):
try:
async for message in self._websocket:
try:
json_message = json.loads(message)
await self.handle_message(json_message)
except ValueError as e:
logging.warning("received unprocessable message %s", str(e))
except Exception as e:
logging.warning("error in websocket connection: %s", str(e))
self._is_closed = True
finally:
self._is_closed = True
class JsonWebsocketServer(object):
def __init__(self, handler_class: JsonWebsocketConnection, host:str = 'localhost', port:int = 8765):
self._host = host
self._port = port
self._handler_class = handler_class
def run(self):
async def main(websocket: websockets.WebSocketServerProtocol,
path: str):
connection = self._handler_class(websocket)
await connection.run()
start_server = websockets.serve(main, self._host, self._port)
asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()

7
server/main.py Normal file
View File

@ -0,0 +1,7 @@
from . import json_websockets
from . import crossword_connection
server = json_websockets.JsonWebsocketServer(
crossword_connection.CrosswordConnection
)
server.run()

48
server/session.py Normal file
View File

@ -0,0 +1,48 @@
import datetime as dt
from . import json_websockets
from . import crossword
class Session(object):
def __init__(self) -> None:
self.crossword = None
self.datetime_created = dt.datetime.utcnow()
self.connected_sockets = set()
def cleanup(self):
sockets_to_remove = []
for socket in self.connected_sockets:
if socket.is_closed():
sockets_to_remove.append(socket)
for socket in sockets_to_remove:
self.connected_sockets.remove(socket)
def connect_socket(self,
websocket: json_websockets.JsonWebsocketConnection) -> None:
self.cleanup()
self.connected_sockets.add(websocket)
def disconnect_socket(self,
websocket: json_websockets.JsonWebsocketConnection) -> None:
if websocket in self.connected_sockets:
self.connected_sockets.remove(websocket)
def get_sockets(self) -> json_websockets.JsonWebsocketConnection:
self.cleanup()
return self.connected_sockets
def get_datetime_created(self) -> dt.datetime:
return self.datetime_created
def create_crossword(self, width: int = 30, height: int = 30):
self.crossword = crossword.Crossword(width=width,
height=height,
lang_code="en")
def get_crossword(self) -> crossword.Crossword:
if self.crossword is None:
self.create_crossword()
return self.crossword

154
server/websockets.ipynb Normal file
View File

@ -0,0 +1,154 @@
{
"metadata": {
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.5"
},
"orig_nbformat": 2,
"kernelspec": {
"name": "python395jvsc74a57bd0916dbcbb3f70747c44a77c7bcd40155683ae19c65e1c03b4aa3499c5328201f1",
"display_name": "Python 3.9.5 64-bit"
},
"metadata": {
"interpreter": {
"hash": "916dbcbb3f70747c44a77c7bcd40155683ae19c65e1c03b4aa3499c5328201f1"
}
}
},
"nbformat": 4,
"nbformat_minor": 2,
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"import websockets\n",
"import asyncio\n",
"import json\n",
"import logging"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"class JsonWebsocketConnection(object):\n",
"\n",
" def __init__(self,\n",
" websocket: websockets.WebSocketServerProtocol):\n",
" \n",
" logging.debug(\"incoming connection\")\n",
" self._websocket = websocket\n",
" self._is_closed = False;\n",
" \n",
" def is_closed(self):\n",
" return self.is_closed\n",
" \n",
" async def close(self):\n",
" logging.debug(\"closing connection\")\n",
" try:\n",
" if self._is_closed:\n",
" return\n",
" self._websocket.close()\n",
" self._is_closed = True\n",
" except Exception as e:\n",
" logging.warning(\"error closing connection: %s\", str(e))\n",
" \n",
" async def send(self, message: dict):\n",
" string_message = json.dumps(message)\n",
" logging.debug(\"sending message: %s\", string_message)\n",
" try:\n",
" await self._websocket.send(string_message)\n",
" except Exception as e:\n",
" logging.warning(\"error sending message: %s\", str(e))\n",
" \n",
" async def handle_message(self, message: dict):\n",
" pass # override this function\n",
"\n",
" async def run(self):\n",
" try:\n",
" async for message in self._websocket:\n",
" try:\n",
" json_message = json.loads(message)\n",
" except ValueError as e:\n",
" logging.warning(\"received unprocessable message %s\", str(e))\n",
" await self.handle_message(message) \n",
" \n",
" except Exception as e:\n",
" logging.warning(\"error in websocket connection: %s\", str(e))\n",
"\n",
"\n",
"\n",
"class JsonWebsocketServer(object):\n",
" def __init__(self, handler_class: JsonWebsocketConnection, host:str = 'localhost', port:int = 8765):\n",
" self._host = host\n",
" self._port = port\n",
" self._handler_class = handler_class\n",
"\n",
" def run(self):\n",
" async def main(websocket: websockets.WebSocketServerProtocol,\n",
" path: str):\n",
"\n",
" connection = self._handler_class(websocket)\n",
"\n",
" connection.run()\n",
" \n",
" start_server = websockets.serve(main, self._host, self._port)\n",
"\n",
" asyncio.get_event_loop().run_until_complete(start_server)\n",
" asyncio.get_event_loop().run_forever()\n",
" \n",
"\n",
"def CrosswordConnection(JsonWebsocketConnection): \n",
" async def handle_message(self, message: dict):\n",
" await self.send({\"message\": \"hello\"})\n",
" \n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"output_type": "error",
"ename": "RuntimeError",
"evalue": "This event loop is already running",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mRuntimeError\u001b[0m Traceback (most recent call last)",
"\u001b[0;32m<ipython-input-3-ecab008e957f>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0mserver\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mJsonWebsocketServer\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mCrosswordConnection\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0mserver\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrun\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
"\u001b[0;32m<ipython-input-2-3c372b72dd90>\u001b[0m in \u001b[0;36mrun\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 62\u001b[0m \u001b[0mstart_server\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mwebsockets\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mserve\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmain\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_host\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_port\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 63\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 64\u001b[0;31m \u001b[0masyncio\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mget_event_loop\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrun_until_complete\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mstart_server\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 65\u001b[0m \u001b[0masyncio\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mget_event_loop\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrun_forever\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 66\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;32m/usr/lib/python3.9/asyncio/base_events.py\u001b[0m in \u001b[0;36mrun_until_complete\u001b[0;34m(self, future)\u001b[0m\n\u001b[1;32m 616\u001b[0m \"\"\"\n\u001b[1;32m 617\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_check_closed\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 618\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_check_running\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 619\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 620\u001b[0m \u001b[0mnew_task\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0mfutures\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0misfuture\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfuture\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;32m/usr/lib/python3.9/asyncio/base_events.py\u001b[0m in \u001b[0;36m_check_running\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 576\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m_check_running\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 577\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mis_running\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 578\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mRuntimeError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'This event loop is already running'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 579\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mevents\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_get_running_loop\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mis\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 580\u001b[0m raise RuntimeError(\n",
"\u001b[0;31mRuntimeError\u001b[0m: This event loop is already running"
]
}
],
"source": [
"server = JsonWebsocketServer(CrosswordConnection)\n",
"server.run()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
]
}