diff options
| -rw-r--r-- | cyberschuessel/schuessel.py | 51 | ||||
| -rw-r--r-- | cyberschuessel/templates/index.html | 287 |
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"/> |
