first version
This commit is contained in:
parent
f09d2d79f9
commit
02e4569634
2
.gitignore
vendored
2
.gitignore
vendored
@ -5,7 +5,7 @@ __pycache__/
|
|||||||
*$py.class
|
*$py.class
|
||||||
|
|
||||||
*.lock
|
*.lock
|
||||||
|
.vscode/
|
||||||
# C extensions
|
# C extensions
|
||||||
*.so
|
*.so
|
||||||
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
94
js_client/assets/hint_both.svg
Normal file
94
js_client/assets/hint_both.svg
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
width="100"
|
||||||
|
height="100"
|
||||||
|
viewBox="0 0 26.458333 26.458334"
|
||||||
|
version="1.1"
|
||||||
|
id="svg8"
|
||||||
|
inkscape:version="1.0.2 (e86c870879, 2021-01-15)"
|
||||||
|
sodipodi:docname="hint_both.svg">
|
||||||
|
<defs
|
||||||
|
id="defs2" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="base"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:zoom="4.3481303"
|
||||||
|
inkscape:cx="49.605183"
|
||||||
|
inkscape:cy="79.488474"
|
||||||
|
inkscape:document-units="mm"
|
||||||
|
inkscape:current-layer="layer1"
|
||||||
|
inkscape:document-rotation="0"
|
||||||
|
showgrid="true"
|
||||||
|
units="px"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="950"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="27"
|
||||||
|
inkscape:window-maximized="1">
|
||||||
|
<inkscape:grid
|
||||||
|
type="xygrid"
|
||||||
|
id="grid10"
|
||||||
|
spacingy="0.52916667"
|
||||||
|
spacingx="0.52916667" />
|
||||||
|
</sodipodi:namedview>
|
||||||
|
<metadata
|
||||||
|
id="metadata5">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title></dc:title>
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g
|
||||||
|
inkscape:label="Ebene 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1">
|
||||||
|
<path
|
||||||
|
sodipodi:type="star"
|
||||||
|
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.529167;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
|
||||||
|
id="path861"
|
||||||
|
sodipodi:sides="3"
|
||||||
|
sodipodi:cx="13.229166"
|
||||||
|
sodipodi:cy="23.8125"
|
||||||
|
sodipodi:r1="2.6458333"
|
||||||
|
sodipodi:r2="1.3229167"
|
||||||
|
sodipodi:arg1="1.5707963"
|
||||||
|
sodipodi:arg2="2.6179939"
|
||||||
|
inkscape:flatsided="true"
|
||||||
|
inkscape:rounded="0"
|
||||||
|
inkscape:randomized="0"
|
||||||
|
d="m 13.229166,26.458333 -2.291359,-3.96875 4.582718,0 z"
|
||||||
|
inkscape:transform-center-y="0.66145805" />
|
||||||
|
<path
|
||||||
|
sodipodi:type="star"
|
||||||
|
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.529167;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
|
||||||
|
id="path863"
|
||||||
|
sodipodi:sides="3"
|
||||||
|
sodipodi:cx="23.8125"
|
||||||
|
sodipodi:cy="13.229166"
|
||||||
|
sodipodi:r1="2.6458333"
|
||||||
|
sodipodi:r2="1.3229167"
|
||||||
|
sodipodi:arg1="0"
|
||||||
|
sodipodi:arg2="1.0471976"
|
||||||
|
inkscape:flatsided="true"
|
||||||
|
inkscape:rounded="0"
|
||||||
|
inkscape:randomized="0"
|
||||||
|
d="m 26.458333,13.229166 -3.96875,2.291359 v -4.582718 z"
|
||||||
|
inkscape:transform-center-x="-0.66145805" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 3.0 KiB |
78
js_client/assets/hint_horizontal.svg
Normal file
78
js_client/assets/hint_horizontal.svg
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
width="100"
|
||||||
|
height="100"
|
||||||
|
viewBox="0 0 26.458333 26.458334"
|
||||||
|
version="1.1"
|
||||||
|
id="svg8"
|
||||||
|
sodipodi:docname="hint_horizontal.svg"
|
||||||
|
inkscape:version="1.0.2 (e86c870879, 2021-01-15)">
|
||||||
|
<defs
|
||||||
|
id="defs2" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="base"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:zoom="4.3481303"
|
||||||
|
inkscape:cx="49.605183"
|
||||||
|
inkscape:cy="74.492628"
|
||||||
|
inkscape:document-units="mm"
|
||||||
|
inkscape:current-layer="layer1"
|
||||||
|
inkscape:document-rotation="0"
|
||||||
|
showgrid="true"
|
||||||
|
units="px"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="950"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="27"
|
||||||
|
inkscape:window-maximized="1">
|
||||||
|
<inkscape:grid
|
||||||
|
type="xygrid"
|
||||||
|
id="grid10"
|
||||||
|
spacingy="0.52916667"
|
||||||
|
spacingx="0.52916667" />
|
||||||
|
</sodipodi:namedview>
|
||||||
|
<metadata
|
||||||
|
id="metadata5">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title></dc:title>
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g
|
||||||
|
inkscape:label="Ebene 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1">
|
||||||
|
<path
|
||||||
|
sodipodi:type="star"
|
||||||
|
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.529167;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
|
||||||
|
id="path863"
|
||||||
|
sodipodi:sides="3"
|
||||||
|
sodipodi:cx="23.8125"
|
||||||
|
sodipodi:cy="13.229166"
|
||||||
|
sodipodi:r1="2.6458333"
|
||||||
|
sodipodi:r2="1.3229167"
|
||||||
|
sodipodi:arg1="0"
|
||||||
|
sodipodi:arg2="1.0471976"
|
||||||
|
inkscape:flatsided="true"
|
||||||
|
inkscape:rounded="0"
|
||||||
|
inkscape:randomized="0"
|
||||||
|
d="m 26.458333,13.229166 -3.96875,2.291359 v -4.582718 z"
|
||||||
|
inkscape:transform-center-x="-0.66145805" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.4 KiB |
34
js_client/assets/hint_vertical.svg
Normal file
34
js_client/assets/hint_vertical.svg
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="100"
|
||||||
|
height="100"
|
||||||
|
viewBox="0 0 26.458333 26.458334"
|
||||||
|
version="1.1"
|
||||||
|
id="svg8">
|
||||||
|
<defs
|
||||||
|
id="defs2" />
|
||||||
|
<metadata
|
||||||
|
id="metadata5">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title></dc:title>
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g
|
||||||
|
id="layer1">
|
||||||
|
<path
|
||||||
|
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.529167;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
|
||||||
|
id="path861"
|
||||||
|
d="m 13.229166,26.458333 -2.291359,-3.96875 4.582718,0 z" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.0 KiB |
23
js_client/cookie.js
Normal file
23
js_client/cookie.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
export function setCookie(cname, cvalue, exdays) {
|
||||||
|
var d = new Date();
|
||||||
|
d.setTime(d.getTime() + (exdays * 24 * 60 * 60 * 1000));
|
||||||
|
var expires = "expires=" + d.toUTCString();
|
||||||
|
document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCookie(cname) {
|
||||||
|
var name = cname + "=";
|
||||||
|
var decodedCookie = decodeURIComponent(document.cookie);
|
||||||
|
var ca = decodedCookie.split(';');
|
||||||
|
for (var i = 0; i < ca.length; i++) {
|
||||||
|
var c = ca[i];
|
||||||
|
while (c.charAt(0) == ' ') {
|
||||||
|
c = c.substring(1);
|
||||||
|
}
|
||||||
|
if (c.indexOf(name) == 0) {
|
||||||
|
return c.substring(name.length, c.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
772
js_client/grid.js
Normal file
772
js_client/grid.js
Normal file
@ -0,0 +1,772 @@
|
|||||||
|
import { html, css, LitElement, unsafeCSS } from 'https://unpkg.com/lit-element/lit-element.js?module';
|
||||||
|
import './infoBox.js'
|
||||||
|
|
||||||
|
const gridType = {
|
||||||
|
EMPTY: 0,
|
||||||
|
HINT: 1,
|
||||||
|
LETTER: 2
|
||||||
|
};
|
||||||
|
|
||||||
|
export class GridElement {
|
||||||
|
constructor(x, y, grid, type) {
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
this.type = type
|
||||||
|
this.grid = grid;
|
||||||
|
|
||||||
|
|
||||||
|
this.gridLetter = null;
|
||||||
|
this.hintBox = null;
|
||||||
|
this.emptyBox = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
setGridLetter(gridLetter) {
|
||||||
|
this.gridLetter = gridLetter;
|
||||||
|
}
|
||||||
|
|
||||||
|
getGridLetter() {
|
||||||
|
return this.gridLetter;
|
||||||
|
}
|
||||||
|
|
||||||
|
setHintBox(hintBox) {
|
||||||
|
this.hintBox = hintBox;
|
||||||
|
}
|
||||||
|
|
||||||
|
getHintBox() {
|
||||||
|
return this.hintBox;
|
||||||
|
}
|
||||||
|
|
||||||
|
setEmptyBox(emptyBox) {
|
||||||
|
this.emptyBox = emptyBox;
|
||||||
|
}
|
||||||
|
|
||||||
|
getEmptyBox() {
|
||||||
|
return this.emptyBox;
|
||||||
|
}
|
||||||
|
|
||||||
|
getGridType() {
|
||||||
|
return this.type;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class GridBox extends LitElement {
|
||||||
|
static get styles() {
|
||||||
|
return css`
|
||||||
|
input[type='text'] {
|
||||||
|
width: 1.5em;
|
||||||
|
height: 1.5em;
|
||||||
|
text-align: center;
|
||||||
|
border-style: solid;
|
||||||
|
border-color: black;
|
||||||
|
border-width: 0.2 em;
|
||||||
|
background-color: black;
|
||||||
|
outline: none;
|
||||||
|
color: transparent;
|
||||||
|
text-shadow: 0 0 1 black;
|
||||||
|
font-size: 2em;
|
||||||
|
user-select: none;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get properties() {
|
||||||
|
return {
|
||||||
|
x: { type: Number },
|
||||||
|
y: { type: Number },
|
||||||
|
grid_id: { type: String }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.x = 0;
|
||||||
|
this.y = 0;
|
||||||
|
this.gridElement = null;
|
||||||
|
this.grid_id = null;
|
||||||
|
this.crosswordGrid = null;
|
||||||
|
this.isRevealed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
update(props) {
|
||||||
|
super.update();
|
||||||
|
if (props.has("grid_id")) {
|
||||||
|
this.updateGridElement();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateGridElement() {
|
||||||
|
if (this.grid_id != null) {
|
||||||
|
|
||||||
|
this.crosswordGrid = document.getElementById(this.grid_id);
|
||||||
|
this.gridElement = this.crosswordGrid.getGridElement(this.x, this.y);
|
||||||
|
this.gridElement.setEmptyBox(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return html`<input type="text" id="${this.x}-${this.y}" maxlength="1" value="" disabled></input>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define('grid-box', GridBox);
|
||||||
|
|
||||||
|
|
||||||
|
export class GridLetter extends GridBox {
|
||||||
|
static get styles() {
|
||||||
|
return css`
|
||||||
|
input[type='text'] {
|
||||||
|
width: 1.5em;
|
||||||
|
height: 1.5em;
|
||||||
|
text-align: center;
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 0.2 em;
|
||||||
|
border-color: black;
|
||||||
|
outline: none;
|
||||||
|
color: black;
|
||||||
|
font-size: 2em;
|
||||||
|
user-select: none;
|
||||||
|
text-transform: capitalize;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type='text']:focus {
|
||||||
|
background-color: LightYellow;
|
||||||
|
border-color: Yellow;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type='text']:read-only {
|
||||||
|
background-color: LightGreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type='text']:selection {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get properties() {
|
||||||
|
return {
|
||||||
|
letter: { type: String },
|
||||||
|
x: { type: Number },
|
||||||
|
y: { type: Number },
|
||||||
|
grid_id: { type: String }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this._value = '';
|
||||||
|
this.is_focused = false;
|
||||||
|
this.key_delay = false;
|
||||||
|
this.updateGridElement();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
set value(value) {
|
||||||
|
const oldValue = this.value;
|
||||||
|
console.log("update value to: " + value);
|
||||||
|
|
||||||
|
this._value = value;
|
||||||
|
this.requestUpdate('value', oldValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
get value() {
|
||||||
|
return this._value;
|
||||||
|
}
|
||||||
|
|
||||||
|
onInput(e) {
|
||||||
|
|
||||||
|
if (!this.is_focused) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//if (this.key_delay){
|
||||||
|
// var el = this.shadowRoot.getElementById(`${this.x}-${this.y}`);
|
||||||
|
// el.value = this.value;
|
||||||
|
// el.select();
|
||||||
|
// e.handled = true;
|
||||||
|
// return;
|
||||||
|
//}
|
||||||
|
|
||||||
|
var oldVal = this.value;
|
||||||
|
this.value = e.target.value
|
||||||
|
|
||||||
|
if (this.revealed) {
|
||||||
|
this.value = oldVal;
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.value.length > 1) {
|
||||||
|
this.value = str.slice(0);
|
||||||
|
e.target.value = this.value
|
||||||
|
e.handled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!e.handled && this.value === " ") {
|
||||||
|
this.value = "";
|
||||||
|
e.target.value = "";
|
||||||
|
e.handled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!e.handled && this.value.length === 0) {
|
||||||
|
this.crosswordGrid.focusPrevCell(this.x, this.y);
|
||||||
|
e.handled = true;
|
||||||
|
}
|
||||||
|
else if (!e.handled && this.value.length === 1) {
|
||||||
|
this.crosswordGrid.focusNextCell(this.x, this.y);
|
||||||
|
e.handled = true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (oldVal != this.value) {
|
||||||
|
this.crosswordGrid.sendMessage({
|
||||||
|
'type': 'update',
|
||||||
|
'x': this.x,
|
||||||
|
'y': this.y,
|
||||||
|
'letter': this.value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!e.handled){
|
||||||
|
|
||||||
|
e.target.select();
|
||||||
|
|
||||||
|
e.handled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
onKeydown(e) {
|
||||||
|
|
||||||
|
if (!this.is_focused) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.key_delay = false;
|
||||||
|
|
||||||
|
var key = e.key;
|
||||||
|
|
||||||
|
console.log(key);
|
||||||
|
|
||||||
|
|
||||||
|
if (this.value.length == 0) {
|
||||||
|
if (key === 'Backspace' || key === 'Delete') {
|
||||||
|
this.crosswordGrid.focusPrevCell(this.x, this.y);
|
||||||
|
e.handled = true;
|
||||||
|
return
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check arrow keys
|
||||||
|
if (key === "ArrowLeft") {
|
||||||
|
// left
|
||||||
|
this.crosswordGrid.focusPrevCellHorizontal(this.x, this.y);
|
||||||
|
e.handled = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (key === "ArrowUp") {
|
||||||
|
// up
|
||||||
|
this.crosswordGrid.focusPrevCellVertical(this.x, this.y);
|
||||||
|
e.handled = true;
|
||||||
|
return;
|
||||||
|
|
||||||
|
}
|
||||||
|
else if (key === "ArrowRight") {
|
||||||
|
//right
|
||||||
|
this.crosswordGrid.focusNextCellHorizontal(this.x, this.y);
|
||||||
|
e.handled = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (key === "ArrowDown") {
|
||||||
|
//down
|
||||||
|
this.crosswordGrid.focusNextCellVertical(this.x, this.y);
|
||||||
|
e.handled = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
e.target.select();
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
onFocus(e) {
|
||||||
|
e.target.select();
|
||||||
|
this.is_focused = true;
|
||||||
|
this.key_delay = true;
|
||||||
|
|
||||||
|
this.crosswordGrid.updateHints(this.x, this.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
focus() {
|
||||||
|
var element = this.shadowRoot.getElementById(`${this.x}-${this.y}`);
|
||||||
|
element.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
blur() {
|
||||||
|
var element = this.shadowRoot.getElementById(`${this.x}-${this.y}`);
|
||||||
|
element.blur();
|
||||||
|
this.is_focused = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateGridElement() {
|
||||||
|
super.updateGridElement()
|
||||||
|
|
||||||
|
if (this.grid_id != null) {
|
||||||
|
|
||||||
|
this.gridElement.setGridLetter(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateLetter(letter, revealed) {
|
||||||
|
this.value = letter;
|
||||||
|
if (revealed)
|
||||||
|
{
|
||||||
|
console.log("rec")
|
||||||
|
this.shadowRoot.getElementById(`${this.x}-${this.y}`).readOnly = true;
|
||||||
|
this.blur();
|
||||||
|
this.revealed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
this.updateGridElement();
|
||||||
|
return html`<input type="text" id="${this.x}-${this.y}" value="${this.value}" @input=${this.onInput} @focus=${this.onFocus} @keydown=${this.onKeydown} autocomplete="off"></input>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define('grid-letter', GridLetter);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export class HintBox extends GridBox {
|
||||||
|
|
||||||
|
static get styles() {
|
||||||
|
|
||||||
|
return css`
|
||||||
|
|
||||||
|
.hint {
|
||||||
|
display: grid;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.triangle-down {
|
||||||
|
position: relative;
|
||||||
|
top: 3em;
|
||||||
|
left: 1em;
|
||||||
|
z-index: 10;
|
||||||
|
grid-column: 1;
|
||||||
|
grid-row: 1;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border-left: 0.75em solid transparent;
|
||||||
|
border-right: 0.75em solid transparent;
|
||||||
|
border-top: 1.3em solid #555;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.triangle-right {
|
||||||
|
position: relative;
|
||||||
|
top: 1em;
|
||||||
|
left: 3em;
|
||||||
|
z-index: 10;
|
||||||
|
grid-column: 1;
|
||||||
|
grid-row: 1;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border-top: 0.75em solid transparent;
|
||||||
|
border-bottom: 0.75em solid transparent;
|
||||||
|
border-left: 1.3em solid #555;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type='text']{
|
||||||
|
grid-column: 1;
|
||||||
|
grid-row: 1;
|
||||||
|
width: 1.5em;
|
||||||
|
height: 1.5em;
|
||||||
|
text-align: center;
|
||||||
|
border-style: solid;
|
||||||
|
border-color: black;
|
||||||
|
border-width: 0.2 em;
|
||||||
|
background-color: #555;
|
||||||
|
outline: none;
|
||||||
|
color: white;
|
||||||
|
text-shadow: 0 0 1 black;
|
||||||
|
font-size: 2em;
|
||||||
|
user-select: none;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get properties() {
|
||||||
|
return {
|
||||||
|
x: { type: Number },
|
||||||
|
y: { type: Number },
|
||||||
|
grid_id: { type: String },
|
||||||
|
horizontal_hint: { type: String },
|
||||||
|
vertical_hint: { type: String },
|
||||||
|
hint_id: { type: String }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.vertical_hint = "";
|
||||||
|
this.horizontal_hint = "";
|
||||||
|
this.hint_id = 0;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
getVerticalHint() {
|
||||||
|
return this.vertical_hint;
|
||||||
|
}
|
||||||
|
|
||||||
|
getHorizontalHint() {
|
||||||
|
return this.horizontal_hint;
|
||||||
|
}
|
||||||
|
|
||||||
|
hasVerticalHint() {
|
||||||
|
return this.vertical_hint.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
hasHorizontalHint() {
|
||||||
|
return this.horizontal_hint.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateGridElement() {
|
||||||
|
super.updateGridElement()
|
||||||
|
|
||||||
|
if (this.grid_id != null) {
|
||||||
|
|
||||||
|
this.gridElement.setHintBox(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
|
||||||
|
this.updateGridElement();
|
||||||
|
|
||||||
|
var verticalArrow = html``;
|
||||||
|
var horizontalArrow = html``;
|
||||||
|
|
||||||
|
if (this.hasVerticalHint()) {
|
||||||
|
verticalArrow = html`<div class="triangle-down"></div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.hasHorizontalHint()) {
|
||||||
|
horizontalArrow = html`<div class="triangle-right"></div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<div class="hint">
|
||||||
|
<input type="text" id="${this.x}-${this.y}" value="${this.hint_id}" disabled></input>
|
||||||
|
${verticalArrow}
|
||||||
|
${horizontalArrow}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define('hint-box', HintBox);
|
||||||
|
|
||||||
|
export class CrosswordGrid extends LitElement {
|
||||||
|
static get styles() {
|
||||||
|
return css`
|
||||||
|
body {
|
||||||
|
background-color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
position: relative;
|
||||||
|
top: 5em;
|
||||||
|
border-spacing: 0px;
|
||||||
|
border-style: solid;
|
||||||
|
border-color: black;
|
||||||
|
border-width: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
padding: 0px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get properties() {
|
||||||
|
return {
|
||||||
|
id: { type: String },
|
||||||
|
width: { type: Number },
|
||||||
|
height: { type: Number },
|
||||||
|
infobox_id: {type: String}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.width = 10;
|
||||||
|
this.height = 10;
|
||||||
|
this.grid = [];
|
||||||
|
|
||||||
|
this.json_grid = null;
|
||||||
|
|
||||||
|
this.num_hints = 0;
|
||||||
|
|
||||||
|
this.lastMoveVertical = false;
|
||||||
|
|
||||||
|
this.infobox = null;
|
||||||
|
|
||||||
|
this.serverConnection = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
createGridByJson(json_grid) {
|
||||||
|
this.num_hints = 0;
|
||||||
|
this.json_grid = json_grid;
|
||||||
|
this.grid = [];
|
||||||
|
const width = json_grid.w;
|
||||||
|
const height = json_grid.h;
|
||||||
|
|
||||||
|
var y;
|
||||||
|
var x;
|
||||||
|
|
||||||
|
for (y = 0; y < height; y++) {
|
||||||
|
var row = [];
|
||||||
|
for (x = 0; x < width; x++) {
|
||||||
|
const json_cell = json_grid.grid[y][x];
|
||||||
|
const cell_type = json_cell.cell_type;
|
||||||
|
|
||||||
|
switch (cell_type) {
|
||||||
|
case "empty": {
|
||||||
|
row.push(new GridElement(x, y, this, gridType.EMPTY));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "hint": {
|
||||||
|
row.push(new GridElement(x, y, this, gridType.HINT));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "letter": {
|
||||||
|
row.push(new GridElement(x, y, this, gridType.LETTER));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
this.grid.push(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.width = width;
|
||||||
|
this.height = height;
|
||||||
|
}
|
||||||
|
|
||||||
|
focusNextCellHorizontal(x, y) {
|
||||||
|
if (x + 1 < this.width && this.grid[y][x + 1].getGridType() === gridType.LETTER) {
|
||||||
|
if (this.grid[y][x].getGridType() === gridType.LETTER) {
|
||||||
|
this.grid[y][x].getGridLetter().blur();
|
||||||
|
}
|
||||||
|
this.grid[y][x + 1].getGridLetter().focus();
|
||||||
|
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
focusPrevCellHorizontal(x, y) {
|
||||||
|
if (x - 1 >= 0 && this.grid[y][x - 1].getGridType() === gridType.LETTER) {
|
||||||
|
if (this.grid[y][x].getGridType() === gridType.LETTER) {
|
||||||
|
this.grid[y][x].getGridLetter().blur();
|
||||||
|
}
|
||||||
|
this.grid[y][x - 1].getGridLetter().focus();
|
||||||
|
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
focusNextCellVertical(x, y) {
|
||||||
|
if (y + 1 < this.height && this.grid[y + 1][x].getGridType() === gridType.LETTER) {
|
||||||
|
if (this.grid[y][x].getGridType() === gridType.LETTER) {
|
||||||
|
this.grid[y][x].getGridLetter().blur();
|
||||||
|
}
|
||||||
|
this.grid[y + 1][x].getGridLetter().focus();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
focusPrevCellVertical(x, y) {
|
||||||
|
if (y - 1 >= 0 && this.grid[y - 1][x].getGridType() === gridType.LETTER) {
|
||||||
|
if (this.grid[y][x].getGridType() === gridType.LETTER) {
|
||||||
|
this.grid[y][x].getGridLetter().blur();
|
||||||
|
}
|
||||||
|
this.grid[y - 1][x].getGridLetter().focus();
|
||||||
|
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
focusNextCell(x, y) {
|
||||||
|
if (this.lastMoveVertical) {
|
||||||
|
if (this.focusNextCellVertical(x, y)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.focusNextCellHorizontal(x, y)) {
|
||||||
|
this.lastMoveVertical = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (this.focusNextCellHorizontal(x, y)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.focusNextCellVertical(x, y)) {
|
||||||
|
this.lastMoveVertical = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
focusPrevCell(x, y) {
|
||||||
|
if (this.lastMoveVertical) {
|
||||||
|
if (this.focusPrevCellVertical(x, y)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.focusPrevCellHorizontal(x, y)) {
|
||||||
|
this.lastMoveVertical = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (this.focusPrevCellHorizontal(x, y)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.focusPrevCellVertical(x, y)) {
|
||||||
|
this.lastMoveVertical = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getGridElement(x, y) {
|
||||||
|
return this.grid[y][x];
|
||||||
|
}
|
||||||
|
|
||||||
|
update(props) {
|
||||||
|
if (props.has("width") || props.has("height")) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
if (props.has("infobox_id")) {
|
||||||
|
this.infobox = document.getElementById(this.infobox_id);
|
||||||
|
}
|
||||||
|
super.update()
|
||||||
|
}
|
||||||
|
|
||||||
|
updateHints(x,y) {
|
||||||
|
var cell = this.grid[y][x];
|
||||||
|
if (cell.getGridType() != gridType.LETTER){
|
||||||
|
console.error("cannot get hints for non letter cell");
|
||||||
|
}
|
||||||
|
|
||||||
|
//get horizontal hint:
|
||||||
|
var upper_end = cell;
|
||||||
|
while (upper_end.getGridType() === gridType.LETTER) {
|
||||||
|
upper_end = this.grid[upper_end.y - 1][x]
|
||||||
|
}
|
||||||
|
|
||||||
|
var left_end = cell;
|
||||||
|
while (left_end.getGridType() === gridType.LETTER) {
|
||||||
|
left_end = this.grid[y][left_end.x - 1]
|
||||||
|
}
|
||||||
|
|
||||||
|
var hHint = "";
|
||||||
|
var vHint = "";
|
||||||
|
|
||||||
|
if (left_end.getGridType() === gridType.HINT) {
|
||||||
|
var box = left_end.getHintBox();
|
||||||
|
hHint = `${box.hint_id}: ${box.horizontal_hint}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (upper_end.getGridType() === gridType.HINT) {
|
||||||
|
var box = upper_end.getHintBox()
|
||||||
|
vHint = `${box.hint_id}: ${box.vertical_hint}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.infobox.horizontal_hint = hHint;
|
||||||
|
this.infobox.vertical_hint = vHint;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderCell(el) {
|
||||||
|
switch (el.type) {
|
||||||
|
case gridType.LETTER: {
|
||||||
|
return html`<grid-letter grid_id='${this.id}' x='${el.x}' y='${el.y}' ></grid-letter>`;
|
||||||
|
|
||||||
|
}
|
||||||
|
case gridType.EMPTY: {
|
||||||
|
return html`<grid-box grid_id='${this.id}' x='${el.x}' y='${el.y}' ></grid-box>`;
|
||||||
|
|
||||||
|
}
|
||||||
|
case gridType.HINT: {
|
||||||
|
var vertical_hint = this.json_grid.grid[el.y][el.x].vertical_hint;
|
||||||
|
var horizontal_hint = this.json_grid.grid[el.y][el.x].horizontal_hint;
|
||||||
|
|
||||||
|
if (!vertical_hint){
|
||||||
|
vertical_hint = "";
|
||||||
|
}
|
||||||
|
if (!horizontal_hint){
|
||||||
|
horizontal_hint = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
this.num_hints += 1;
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<hint-box grid_id='${this.id}' x='${el.x}' y='${el.y}' horizontal_hint='${horizontal_hint}' vertical_hint='${vertical_hint}' hint_id=${this.num_hints}></hint-box>
|
||||||
|
`;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateLetter(x,y,letter,revealed) {
|
||||||
|
var cell = this.grid[y][x];
|
||||||
|
if (cell.getGridType() != gridType.LETTER){
|
||||||
|
console.error("received bad message");
|
||||||
|
}
|
||||||
|
cell.getGridLetter().updateLetter(letter, revealed)
|
||||||
|
}
|
||||||
|
|
||||||
|
registerServerConnection(connection){
|
||||||
|
this.serverConnection = connection
|
||||||
|
}
|
||||||
|
|
||||||
|
sendMessage(msg){
|
||||||
|
if (!this.serverConnection) {
|
||||||
|
console.error("no server connection registered")
|
||||||
|
}
|
||||||
|
this.serverConnection.sendMessage(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
this.num_hints = 0;
|
||||||
|
console.log("refreshing grid");
|
||||||
|
return html`
|
||||||
|
<table>
|
||||||
|
${this.grid.map((row) => html`<tr>${row.map((el) => html`<td>${this.renderCell(el)}</td>`)}</tr>`)}
|
||||||
|
</table>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define('crossword-grid', CrosswordGrid);
|
20
js_client/index.html
Normal file
20
js_client/index.html
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<!-- Polyfills only needed for Firefox and Edge. -->
|
||||||
|
<script src="https://unpkg.com/@webcomponents/webcomponentsjs@latest/webcomponents-loader.js"></script>
|
||||||
|
<!-- Works only on browsers that support Javascript modules like
|
||||||
|
Chrome, Safari, Firefox 60, Edge 17 -->
|
||||||
|
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body style="background-color: black;">
|
||||||
|
<info-box id="infobox"></info-box>
|
||||||
|
<server-connection id="server-connection" grid_id="grid" url="ws://localhost:8765"></server-connection>
|
||||||
|
<crossword-grid id="grid" infobox_id="infobox" width="10" height="10"></crossword-grid>
|
||||||
|
<script type="module" src="./main.js"></script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
55
js_client/infoBox.js
Normal file
55
js_client/infoBox.js
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
import { html, css, LitElement, unsafeCSS } from 'https://unpkg.com/lit-element/lit-element.js?module';
|
||||||
|
|
||||||
|
export class InfoBox extends LitElement {
|
||||||
|
static get styles() {
|
||||||
|
return css`
|
||||||
|
.hinttext {
|
||||||
|
color: white;
|
||||||
|
margin-left: 1em;
|
||||||
|
margin-top: 0.8em;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.infobox {
|
||||||
|
z-index: 20;
|
||||||
|
position: fixed;
|
||||||
|
top: 0em;
|
||||||
|
left: 0em;
|
||||||
|
right: 0em;
|
||||||
|
height: 6em;
|
||||||
|
|
||||||
|
border-style: solid;
|
||||||
|
border-color: white;
|
||||||
|
border-width: 0.2 em;
|
||||||
|
background-color: #333;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get properties() {
|
||||||
|
return {
|
||||||
|
horizontal_hint: { type: String },
|
||||||
|
vertical_hint: { type: String }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.vertical_hint = "";
|
||||||
|
this.horizontal_hint = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return html`
|
||||||
|
<div class="infobox">
|
||||||
|
<div class="hinttext">▶ ${this.horizontal_hint}</div>
|
||||||
|
<div class="hinttext">▼ ${this.vertical_hint}</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define('info-box', InfoBox);
|
3
js_client/main.js
Normal file
3
js_client/main.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import '/grid.js'
|
||||||
|
import '/serverConnection.js'
|
||||||
|
|
12
js_client/serve.py
Normal file
12
js_client/serve.py
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
from http.server import HTTPServer, SimpleHTTPRequestHandler, test
|
||||||
|
import sys
|
||||||
|
|
||||||
|
class CORSRequestHandler (SimpleHTTPRequestHandler):
|
||||||
|
def end_headers (self):
|
||||||
|
self.send_header('Access-Control-Allow-Origin', '*')
|
||||||
|
SimpleHTTPRequestHandler.end_headers(self)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
test(CORSRequestHandler, HTTPServer, port=int(sys.argv[1]) if len(sys.argv) > 1 else 8000)
|
||||||
|
|
141
js_client/serverConnection.js
Normal file
141
js_client/serverConnection.js
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
import { html, css, LitElement } from 'https://unpkg.com/lit-element/lit-element.js?module';
|
||||||
|
import { WebsocketConnection } from '/websocket.js';
|
||||||
|
import { getCookie, setCookie } from '/cookie.js';
|
||||||
|
|
||||||
|
export class ServerConnection extends WebsocketConnection {
|
||||||
|
static get styles() {
|
||||||
|
return css``;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get properties() {
|
||||||
|
return {
|
||||||
|
url: { type: String },
|
||||||
|
grid_id: { type: String }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.sessionId = null;
|
||||||
|
this.isRegistered = false;
|
||||||
|
this.crossword_grid = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
update(props) {
|
||||||
|
if (props.has("grid_id")) {
|
||||||
|
this.crossword_grid = document.getElementById(this.grid_id);
|
||||||
|
this.crossword_grid.registerServerConnection(this);
|
||||||
|
}
|
||||||
|
super.update(props)
|
||||||
|
}
|
||||||
|
|
||||||
|
updateLocalSessionId() {
|
||||||
|
// look whether the session id is given in url params
|
||||||
|
// or stored as cookie
|
||||||
|
const queryString = window.location.search;
|
||||||
|
const params = new URLSearchParams(queryString);
|
||||||
|
if (params.has('session')) {
|
||||||
|
this.sessionId = params.get('session');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const cookie_session = getCookie('session');
|
||||||
|
if (cookie_session != "") {
|
||||||
|
this.sessionId = cookie_session;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
register() {
|
||||||
|
this.updateLocalSessionId();
|
||||||
|
console.log("register", this.sessionId);
|
||||||
|
this.sendMessage({
|
||||||
|
'type': 'register',
|
||||||
|
'sessionId': this.sessionId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onopen(event) {
|
||||||
|
super.onopen(event);
|
||||||
|
console.log("overloaded", this);
|
||||||
|
this.register();
|
||||||
|
}
|
||||||
|
|
||||||
|
handleRegistration(sessionId) {
|
||||||
|
if (!sessionId) {
|
||||||
|
console.warn("got undefined session id");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.sessionId = sessionId;
|
||||||
|
console.log("stored session", sessionId, "as cookie")
|
||||||
|
setCookie("session", sessionId, 2);
|
||||||
|
const urlparams = new URLSearchParams(window.location.search);
|
||||||
|
if (urlparams.has('session') && this.sessionId === urlparams.get('session')){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
urlparams.set('session', sessionId);
|
||||||
|
var newurl = window.location.protocol + "//" + window.location.host + window.location.pathname + '?session=' + sessionId;
|
||||||
|
window.history.pushState({ path: newurl }, '', newurl);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
handleCrossword(crossword) {
|
||||||
|
this.crossword_grid.createGridByJson(crossword);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleUpdate(updates) {
|
||||||
|
var i = 0;
|
||||||
|
for (i = 0; i < updates.length; i++) {
|
||||||
|
const item = updates[i];
|
||||||
|
|
||||||
|
const x = item['x'];
|
||||||
|
const y = item['y'];
|
||||||
|
|
||||||
|
if (item.hasOwnProperty("user_input")) {
|
||||||
|
const letter = item['user_input'];
|
||||||
|
this.crossword_grid.updateLetter(x, y, letter, false);
|
||||||
|
}
|
||||||
|
if (item.hasOwnProperty("revealed")) {
|
||||||
|
console.log("update");
|
||||||
|
const letter = item['revealed'];
|
||||||
|
this.crossword_grid.updateLetter(x, y, letter, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onmessage(event) {
|
||||||
|
super.onmessage(event)
|
||||||
|
try {
|
||||||
|
const msg = JSON.parse(event.data);
|
||||||
|
if (!msg.type) {
|
||||||
|
throw "missing type"
|
||||||
|
}
|
||||||
|
switch (msg.type) {
|
||||||
|
case 'register': {
|
||||||
|
this.handleRegistration(msg.sessionId);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'crossword': {
|
||||||
|
this.handleCrossword(msg.crossword);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'update': {
|
||||||
|
this.handleUpdate(msg.updates);
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
console.error("could not parse servermessage", err);
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define('server-connection', ServerConnection);
|
88
js_client/websocket.js
Normal file
88
js_client/websocket.js
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
import { html, css, LitElement } from 'https://unpkg.com/lit-element/lit-element.js?module';
|
||||||
|
|
||||||
|
export class WebsocketConnection extends LitElement {
|
||||||
|
static get styles() {
|
||||||
|
return css``;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get properties() {
|
||||||
|
return {
|
||||||
|
url: { type: String }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.url = '';
|
||||||
|
this.socket = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
onopen(event){
|
||||||
|
console.log("websocket connected");
|
||||||
|
}
|
||||||
|
|
||||||
|
onclose(event){
|
||||||
|
console.log("websocket closed");
|
||||||
|
}
|
||||||
|
|
||||||
|
onerror(event){
|
||||||
|
console.log("websocket error, closing...");
|
||||||
|
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
|
||||||
|
this.socket.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onmessage(event){
|
||||||
|
console.log("received message:", event.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
isSocketConnected(){
|
||||||
|
if (! this.socket){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.socket.readyState === WebSocket.OPEN) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
sendMessage(msg){
|
||||||
|
|
||||||
|
if (this.isSocketConnected()) {
|
||||||
|
var string_msg = JSON.stringify(msg);
|
||||||
|
this.socket.send(string_msg);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.error("cannot send message, websocket disconnected");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
connect() {
|
||||||
|
console.log(`connect to ${this.url}...`);
|
||||||
|
this.socket = new WebSocket(this.url);
|
||||||
|
this.socket.onopen = (event) => this.onopen(event);
|
||||||
|
this.socket.onclose = (event) => this.onclose(event);
|
||||||
|
this.socket.onerror = (event) => this.onerror(event);
|
||||||
|
this.socket.onmessage = (event) => this.onmessage(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
update(props) {
|
||||||
|
if (props.has("url")){
|
||||||
|
this.connect();
|
||||||
|
}
|
||||||
|
super.update(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
render() {
|
||||||
|
// do nothing
|
||||||
|
return html``;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define('websocket-connection', WebsocketConnection);
|
1
server/.gitattributes
vendored
Normal file
1
server/.gitattributes
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
en.json filter=lfs diff=lfs merge=lfs -text
|
320
server/crossword.py
Normal file
320
server/crossword.py
Normal 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())
|
112
server/crossword_connection.py
Normal file
112
server/crossword_connection.py
Normal 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
|
286
server/crossword_generator.py
Normal file
286
server/crossword_generator.py
Normal 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
BIN
server/en.json
(Stored with Git LFS)
Normal file
Binary file not shown.
80
server/json_websockets.py
Normal file
80
server/json_websockets.py
Normal 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
7
server/main.py
Normal 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
48
server/session.py
Normal 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
154
server/websockets.ipynb
Normal 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": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user