aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cyberschuessel/schuessel.py51
-rw-r--r--cyberschuessel/templates/index.html287
2 files changed, 196 insertions, 142 deletions
diff --git a/cyberschuessel/schuessel.py b/cyberschuessel/schuessel.py
index fc3812d..c79815f 100644
--- a/cyberschuessel/schuessel.py
+++ b/cyberschuessel/schuessel.py
@@ -1,4 +1,3 @@
-import itertools
import json
import logging
import random
@@ -13,20 +12,22 @@ class Schuessel:
def __init__(self):
self.phase = 0
self.words = []
- self.drawn = []
+ self.guessed = []
+ self.draw_counter = 0
- self.counter = itertools.count()
+ self.session_counter = 0
self.clients = {}
def reset(self):
self.phase = 0
self.words = []
- self.drawn = []
+ self.guessed = []
def schuessel_state(self):
return {
'words': len(self.words),
- 'drawn': len(self.drawn),
+ 'guessed': len(self.guessed),
+ 'draw_counter': self.draw_counter,
'phase': self.phase
}
@@ -39,23 +40,27 @@ class Schuessel:
# first draw -> start game
if self.phase == 0:
self.phase = 1
- # we ignore invalid last_words
- elif last_word and last_word in self.words:
- self.drawn.append(last_word)
- del (self.words[self.words.index(last_word)])
+
+ # we ignore invalid or outdated last_words
+ elif last_word and last_word['word'] in self.words and last_word[
+ 'draw_counter'] == self.draw_counter:
+ self.guessed.append(last_word['word'])
+ del (self.words[self.words.index(last_word['word'])])
if len(self.words) == 0:
# empty schuessel
- if len(self.drawn) == 0:
- return
+ if len(self.guessed) == 0:
+ return None, False
- # move drawn words back into the schuessel
- tmp = self.drawn
- self.drawn = self.words
+ # move guessed words back into the schuessel
+ tmp = self.guessed
+ self.guessed = self.words
self.words = tmp
self.phase = self.phase + 1
phase_change = True
+ self.draw_counter += 1
+
word = random.choice(self.words)
return word, phase_change
@@ -70,7 +75,7 @@ class Schuessel:
for session_id in broken_sessions:
log.info(f'remove broken session_id {session_id}')
- del(self.clients[session_id])
+ del (self.clients[session_id])
async def close_all(self):
for ws in list(self.clients.values()):
@@ -84,7 +89,9 @@ class Schuessel:
await ws.prepare(request)
- session_id = next(self.counter)
+ session_id = self.session_counter
+ self.session_counter += 1
+
self.clients[session_id] = ws
init_msg = {'action': 'init'}
@@ -93,7 +100,7 @@ class Schuessel:
try:
await ws.send_json(init_msg)
except ConnectionResetError:
- del(self.clients[session_id])
+ del (self.clients[session_id])
return ws
async for msg in ws:
@@ -102,7 +109,7 @@ class Schuessel:
data = json.loads(msg.data)
if data['action'] == 'draw':
- last_word = data.get('last_word', '')
+ last_word = data['last_word']
new_word, phase_change = self.draw(last_word)
drawn_msg = {'action': 'drawn'}
@@ -118,9 +125,10 @@ class Schuessel:
'action': 'draw',
'drawn_word': new_word,
'phase_change': phase_change,
+ 'draw_counter': self.draw_counter,
})
except ConnectionResetError:
- del(self.clients[session_id])
+ del (self.clients[session_id])
return ws
elif data['action'] == 'add':
@@ -131,12 +139,13 @@ class Schuessel:
elif data['action'] == 'reset':
self.reset()
- reset_msg = {'action': 'reset'}
+ reset_msg = {'action': 'init'}
reset_msg.update(self.schuessel_state())
await self.broadcast(reset_msg)
else:
- log.error(f'unknown action received: {data} from {session_id}')
+ log.error(
+ f'unknown action received: {data} from {session_id}')
elif msg.type == aiohttp.WSMsgType.ERROR:
del (self.clients[session_id])
print(
diff --git a/cyberschuessel/templates/index.html b/cyberschuessel/templates/index.html
index 1eb36b2..2cfb3a0 100644
--- a/cyberschuessel/templates/index.html
+++ b/cyberschuessel/templates/index.html
@@ -9,7 +9,7 @@
<title>Cyberschuessel</title>
- <!-- css needed to keep tehe footer at the bottom of the viewport -->
+ <!-- css needed to keep the footer at the bottom of the viewport -->
<style>
html {
position: relative;
@@ -61,140 +61,170 @@
for the JavaScript code in this page.
*/
- document.addEventListener('DOMContentLoaded', function() {
- let strings = {};
- strings.phase_prefix = {'en': 'Round:', 'de': 'Runde:'};
- strings.words_prefix = {'en': 'Words in the bowl:', 'de': 'Wörter in der Schüssel:'};
- strings.drawn_prefix = {'en': 'Words already guessed:', 'de': 'Wörter bereits erraten:'};
- strings.drawn_word_prefix = {'en': 'Current word:', 'de': 'Aktuelles Wort:'};
- strings.draw_button = {'en': 'Draw word', 'de': 'Wort ziehen'};
- strings.reset_button = {'en': 'Reset', 'de': 'Neu'};
- strings.add_button = {'en': 'Add word', 'de': 'Wort hinzufügen:'};
- strings.add_word = {'en': 'Anything', 'de': 'Irgendwas'};
- strings.phase_changed = {'en': 'Next Phase', 'de': 'Nächste Runde'};
-
- let userLang = navigator.language || navigator.userLanguage;
- userLang = userLang.substring(0, 2);
-
- let phase = 0;
- let words_in_schuessel = 0;
- let words_drawn = 0;
-
- let wsUri = (window.location.protocol=='https:'&&'wss://'||'ws://') + window.location.host;
- let conn = new WebSocket(wsUri);
+ // Extend Storage prototype to support objects
+ Storage.prototype.setObject = function(key, value) {
+ this.setItem(key, JSON.stringify(value));
+ }
- // init visual strings
- {
- let reset_button = document.getElementById("reset_button");
- reset_button.value = strings.reset_button[userLang];
+ Storage.prototype.getObject = function(key) {
+ var value = this.getItem(key);
+ return value && JSON.parse(value);
+ }
- let add_word = document.getElementById('add_word');
- add_word.placeholder = strings.add_word[userLang];
+ let strings = {};
+ strings.phase_prefix = {'en': 'Round:', 'de': 'Runde:'};
+ strings.words_prefix = {'en': 'Words to guess:', 'de': 'Wörter noch zu erraten:'};
+ strings.guessed_prefix = {'en': 'Words already guessed:', 'de': 'Wörter bereits erraten:'};
+ strings.drawn_word_prefix = {'en': 'Current word:', 'de': 'Aktuelles Wort:'};
+ strings.draw_button = {'en': 'Draw word', 'de': 'Wort ziehen'};
+ strings.reset_button = {'en': 'Reset', 'de': 'Neu'};
+ strings.add_button = {'en': 'Add word', 'de': 'Wort hinzufügen:'};
+ strings.add_word = {'en': 'Anything', 'de': 'Irgendwas'};
+ strings.phase_changed = {'en': 'Next Phase', 'de': 'Nächste Runde'};
+ strings.reload = {'en': 'Connection not available. Trying to reload ...',
+ 'de': 'Verbindung unterbrochen. Seite wird neu geladen ...'};
+
+ let userLang = navigator.language || navigator.userLanguage;
+ userLang = userLang.substring(0, 2);
+
+ let phase = 0;
+ let words_to_guess = 0;
+ let words_guessed = 0;
+
+ // logical clock counting all draw events to check if our stored last_word is outdated
+ let draw_counter = 0;
+
+ set_words_to_guess = function(val) {
+ words_to_guess = val;
+ let word_count_text = document.getElementById('word_count');
+ word_count_text.innerText = strings.words_prefix[userLang] + " " + words_to_guess;
+ }
- let add_button = document.getElementById('add_button');
- add_button.value = strings.add_button[userLang];
+ change_words_to_guess = function(diff) {
+ set_words_to_guess(words_to_guess+ diff);
+ }
- let draw_button = document.getElementById("draw_button");
- draw_button.value = strings.draw_button[userLang];
- }
+ set_words_guessed = function(val) {
+ words_guessed = val;
+ let words_guessed_text = document.getElementById('guessed_count');
+ words_guessed_text.innerText = strings.guessed_prefix[userLang] + " " + words_guessed;
+ }
- set_words_in_schuessel = function(val) {
- words_in_schuessel = val;
- let word_count_text = document.getElementById('word_count');
- word_count_text.innerText = strings.words_prefix[userLang] + " " + words_in_schuessel;
+ update_drawn_word = function(draw_counter) {
+ let drawn_word = document.getElementById('drawn_word')
+ let last_word = localStorage.getObject('last_word');
+ // we drew last -> show our word
+ if (last_word && last_word.draw_counter == draw_counter) {
+ drawn_word.innerText = strings.drawn_word_prefix[userLang] + " " + last_word.word;
+ } else {
+ drawn_word.innerText = "";
}
+ }
- change_words_in_schuessel = function(diff) {
- set_words_in_schuessel(words_in_schuessel + diff);
- }
+ set_phase = function(val) {
+ phase = val;
+ let phase_text = document.getElementById('schuessel_phase');
+ phase_text.innerText = strings.phase_prefix[userLang] + " " + phase;
- set_words_drawn = function(val) {
- words_drawn = val;
- let words_drawn_text = document.getElementById('drawn_count');
- words_drawn_text.innerText = strings.drawn_prefix[userLang] + " " + words_drawn;
+ let add_div = document.getElementById('add_div');
+ if (phase != 0) {
+ add_div.style.display = 'none';
+ } else {
+ add_div.style.display = 'block';
}
+ }
- change_words_drawn = function(diff) {
- set_words_drawn(words_drawn + diff);
- }
+ conn_open = function(uri) {
+ let ws = new WebSocket(uri);
+
+ ws.onerror = conn_onerror;
+ ws.onmessage = conn_onmessage;
+ ws.onclose = conn_onclose;
+
+ return ws;
+ }
- set_drawn_word = function(word) {
- let drawn_word = document.getElementById('drawn_word')
- if (word == '') {
- drawn_word.innerText = "";
- } else {
- drawn_word.innerText = strings.drawn_word_prefix[userLang] + " " + word;
+ conn_onerror = function(e) {
+ console.log("Websocket error " + e);
+ conn = conn_open(wsUri);
+ };
+
+ conn_onclose = function(e) {
+ console.log("Websocket closed " + e);
+ conn = conn_open(wsUri);
+ };
+
+ conn_onmessage = function(e) {
+ let data = JSON.parse(e.data);
+ switch (data.action) {
+ case 'init':
+ set_words_to_guess(data.words);
+ set_words_guessed(data.guessed);
+ set_phase(data.phase);
+
+ if (draw_counter > data.draw_counter) {
+ localStorage.removeItem('last_word');
}
- localStorage.setItem('last_word', word);
- }
+ update_drawn_word(data.draw_counter)
+ draw_counter = data.draw_counter;
+ break;
- reset_drawn_word = function() {
- set_drawn_word('');
- }
+ case 'drawn':
+ set_words_to_guess(data.words);
+ set_words_guessed(data.guessed);
+ set_phase(data.phase);
- set_phase = function(val) {
- phase = val;
- let words_drawn_text = document.getElementById('schuessel_phase');
- words_drawn_text.innerText = strings.phase_prefix[userLang] + " " + phase;
-
- let add_div = document.getElementById('add_div');
- if (phase != 0) {
- add_div.style.display = 'none';
- set_drawn_word(localStorage.getItem('last_word') || '');
- } else {
- add_div.style.display = 'block';
- reset_drawn_word();
+ if (data.draw_counter > draw_counter) {
+ draw_counter = data.draw_counter;
}
- }
- conn.onmessage = function(e) {
- let data = JSON.parse(e.data);
- switch (data.action) {
- case 'reset':
- localStorage.setItem('last_word', '');
- // fall through
-
- case 'init':
- if (data.phase == 0) {
- set_words_in_schuessel(data.words);
- } else {
- // we are playing some one must have a word in hand
- set_words_in_schuessel(data.words - 1);
- }
- set_words_drawn(data.drawn);
- set_phase(data.phase);
- break;
-
- case 'drawn':
- // -1 because some one has a word in hand
- set_words_in_schuessel(data.words);
- set_words_drawn(data.drawn);
- set_phase(data.phase);
-
- // reset last_drawn word
- // This reset is always save because its effect is overwritten by a
- // the next 'draw' msg.
- // And the'draw' msg is always received after a 'drawn' event
- // because websockets are streams
- reset_drawn_word();
- break;
-
- case 'draw':
- set_drawn_word(data.drawn_word);
- // schuessel is empty when we draw
- // alert the user that a phase change occurred
- if (data.phase_change) {
- alert(strings.phase_changed[userLang]);
- }
- change_words_in_schuessel(-1);
- break;
+ // update drawn word element
+ update_drawn_word(draw_counter);
+ break;
- case 'add':
- change_words_in_schuessel(1);
- break;
+ case 'draw':
+ if (data.draw_counter >= draw_counter) {
+ draw_counter = data.draw_counter;
+ last_word = {'word': data.drawn_word, 'draw_counter': draw_counter};
+ localStorage.setObject('last_word', last_word);
}
- };
+
+ update_drawn_word(draw_counter);
+ // schuessel is empty when we draw
+ // alert the user that a phase change occurred
+ if (data.phase_change) {
+ alert(strings.phase_changed[userLang]);
+ }
+ break;
+
+ case 'add':
+ change_words_to_guess(1);
+ break;
+ }
+ }
+
+ let wsUri = (window.location.protocol=='https:'&&'wss://'||'ws://') + window.location.host;
+ // our websocket connection
+ let conn;
+
+ document.addEventListener('DOMContentLoaded', function() {
+ // init visual strings
+ let reset_button = document.getElementById("reset_button");
+ reset_button.value = strings.reset_button[userLang];
+
+ let add_word = document.getElementById('add_word');
+ add_word.placeholder = strings.add_word[userLang];
+
+ let add_button = document.getElementById('add_button');
+ add_button.value = strings.add_button[userLang];
+
+ let draw_button = document.getElementById("draw_button");
+ draw_button.value = strings.draw_button[userLang];
+
+ // open websocket connection if DOM was loaded to make all received events
+ // can access the DOM elements
+ conn = conn_open(wsUri);
document.getElementById("add_form").onsubmit = function() {
let word_add_form = document.getElementById('add_form');
@@ -204,7 +234,12 @@
let data = {};
data.action = 'add';
data.word = word;
- conn.send(JSON.stringify(data));
+ try {
+ conn.send(JSON.stringify(data));
+ } catch(err) {
+ alert(strings.reload[userLang]);
+ location.reload();
+ }
}
document.getElementById('add_form').reset();
return false;
@@ -213,15 +248,25 @@
document.getElementById("draw_button").onclick = function() {
let data = {};
data.action = 'draw';
- data.last_word = localStorage.getItem('last_word');
- conn.send(JSON.stringify(data));
+ data.last_word = localStorage.getObject('last_word');
+ try {
+ conn.send(JSON.stringify(data));
+ } catch(err) {
+ alert(strings.reload[userLang]);
+ location.reload();
+ }
return false;
}
document.getElementById("reset_button").onclick = function() {
let data = {};
data.action = 'reset';
- conn.send(JSON.stringify(data));
+ try {
+ conn.send(JSON.stringify(data));
+ } catch(err) {
+ alert(strings.reload[userLang]);
+ location.reload();
+ }
return false;
}
});
@@ -234,7 +279,7 @@
<input id="reset_button" type="button"/>
<h3 id="schuessel_phase" style="display:inline-block" ></h3>
<h3 id="word_count"></h3>
- <h3 id="drawn_count"></h3>
+ <h3 id="guessed_count"></h3>
<div id="add_div">
<form id="add_form">
<input id="add_word" type="text"/>