Explorar o código

[add] ini scoreboard

isundil %!s(int64=8) %!d(string=hai) anos
pai
achega
7234af32fd
Modificáronse 16 ficheiros con 587 adicións e 337 borrados
  1. 121 0
      css/grid.less
  2. 3 116
      css/main.less
  3. 10 0
      css/scoreboard.less
  4. 1 0
      js/Makefile
  5. 54 9
      js/grid.js
  6. 62 0
      js/polling.js
  7. 5 1
      js/resources.js
  8. 2 134
      js/ui.js
  9. 134 0
      js/uiGrid.js
  10. 30 0
      js/uiScoreboard.js
  11. 45 55
      js/workflow.js
  12. 0 0
      public/crosswords.min.css
  13. 23 10
      public/crosswords.min.js
  14. 2 0
      public/game.html
  15. 75 7
      src/Grid.js
  16. 20 5
      src/httpServer.js

+ 121 - 0
css/grid.less

@@ -0,0 +1,121 @@
+
+.crossword {
+    margin-left: 250px;
+}
+
+.crossword-line {
+    display: block;
+    height: @cell-size;
+
+    .cell {
+        display: inline-block;
+        width: @cell-size;
+        height: 100%;
+        border-left: 1px solid @border-color;
+        border-top: 1px solid @border-color;
+        overflow: hidden;
+        text-align: center;
+
+        &:last-child {
+            border-right: 1px solid @border-color;
+        }
+
+        &.cell-disabled {
+            background: @disabled-color;
+            border-color: @disabled-color;
+        }
+
+        &.cell-definition > * {
+            display: flex;
+            justify-content: center;
+            flex-direction: column;
+            height: 100%;
+
+            .definition {
+                display: block;
+                font-size: .75em;
+
+                & > * {
+                    display: inline-block;
+                    width: @cell-size;
+                }
+
+                &:not(:only-child):not(:first-child) {
+                    border-top: 1px solid @border-color;
+                }
+
+            }
+        }
+
+        &.definition-right-vt, &.definition-right-hz, &.definition-bottom-vt, &.definition-bottom-hz {
+            position: relative;
+            &:after {
+                position: absolute;
+                height: @cell-size /6;
+                width: @cell-size /6;
+                content: " ";
+            }
+        }
+
+        &.definition-right-vt:after {
+            top: 0;
+            bottom: 0;
+            left: 0;
+            margin: auto;
+            width: @cell-size /8;
+            border-top: 1px solid @arrow-color;
+            border-right: 1px solid @arrow-color;
+        }
+        &.definition-right-hz:after {
+            top: 0;
+            bottom: 0;
+            left: 0;
+            width: @cell-size /8;
+            margin: auto;
+            border-top: 1px solid @arrow-color;
+        }
+        &.definition-bottom-vt:after {
+            left: 0;
+            right: 0;
+            top: 0;
+            margin: auto;
+            height: @cell-size /8;
+            border-left: 1px solid @arrow-color;
+        }
+        &.definition-bottom-hz:after {
+            left: 0;
+            right: 0;
+            top: 0;
+            margin: auto;
+            height: @cell-size /8;
+            border-left: 1px solid @arrow-color;
+            border-bottom: 1px solid @arrow-color;
+        }
+
+        &.cell-letter {
+            font-size: @cell-size /3;
+            line-height: @cell-size;
+            font-weight: 900;
+
+            &.cell-letter-correct {
+                color: green;
+            }
+            &.cell-letter-wrong {
+                background-color: #ffb6b6;
+            }
+        }
+
+        &.cell-selected {
+            background: #e1e8ff;
+        }
+
+        &.cell-input {
+            background: #b0c2ff;
+        }
+    }
+
+    &:last-child .cell {
+        border-bottom: 1px solid @border-color;
+    }
+}
+

+ 3 - 116
css/main.less

@@ -1,4 +1,7 @@
 
+@import "scoreboard.less";
+@import "grid.less";
+
 @cell-size: 75px;
 @border-color: darkblue;
 @arrow-color: darkblue;
@@ -10,119 +13,3 @@ html,body {
     padding: 0;
 }
 
-.crossword-line {
-    display: block;
-    height: @cell-size;
-
-    .cell {
-        display: inline-block;
-        width: @cell-size;
-        height: 100%;
-        border-left: 1px solid @border-color;
-        border-top: 1px solid @border-color;
-        overflow: hidden;
-        text-align: center;
-
-        &:last-child {
-            border-right: 1px solid @border-color;
-        }
-
-        &.cell-disabled {
-            background: @disabled-color;
-            border-color: @disabled-color;
-        }
-
-        &.cell-definition > * {
-            display: flex;
-            justify-content: center;
-            flex-direction: column;
-            height: 100%;
-
-            .definition {
-                display: block;
-                font-size: .75em;
-
-                & > * {
-                    display: inline-block;
-                    width: @cell-size;
-                }
-
-                &:not(:only-child):not(:first-child) {
-                    border-top: 1px solid @border-color;
-                }
-
-            }
-        }
-
-        &.definition-right-vt, &.definition-right-hz, &.definition-bottom-vt, &.definition-bottom-hz {
-            position: relative;
-            &:after {
-                position: absolute;
-                height: @cell-size /6;
-                width: @cell-size /6;
-                content: " ";
-            }
-        }
-
-        &.definition-right-vt:after {
-            top: 0;
-            bottom: 0;
-            left: 0;
-            margin: auto;
-            width: @cell-size /8;
-            border-top: 1px solid @arrow-color;
-            border-right: 1px solid @arrow-color;
-        }
-        &.definition-right-hz:after {
-            top: 0;
-            bottom: 0;
-            left: 0;
-            width: @cell-size /8;
-            margin: auto;
-            border-top: 1px solid @arrow-color;
-        }
-        &.definition-bottom-vt:after {
-            left: 0;
-            right: 0;
-            top: 0;
-            margin: auto;
-            height: @cell-size /8;
-            border-left: 1px solid @arrow-color;
-        }
-        &.definition-bottom-hz:after {
-            left: 0;
-            right: 0;
-            top: 0;
-            margin: auto;
-            height: @cell-size /8;
-            border-left: 1px solid @arrow-color;
-            border-bottom: 1px solid @arrow-color;
-        }
-
-        &.cell-letter {
-            font-size: @cell-size /3;
-            line-height: @cell-size;
-            font-weight: 900;
-
-            &.cell-letter-correct {
-                color: green;
-            }
-            &.cell-letter-wrong {
-                background-color: #ffb6b6;
-            }
-        }
-
-        &.cell-selected {
-            background: #e1e8ff;
-        }
-
-        &.cell-input {
-            background: #b0c2ff;
-        }
-    }
-
-    &:last-child .cell {
-        border-bottom: 1px solid @border-color;
-    }
-}
-

+ 10 - 0
css/scoreboard.less

@@ -0,0 +1,10 @@
+
+.scoreboard {
+    position: fixed;
+    top: 0;
+    bottom: 0;
+    left: 0;
+    width: 250px;
+    padding: 1em 0;
+}
+

+ 1 - 0
js/Makefile

@@ -3,6 +3,7 @@ SRC=		\
 			resources.js		\
 			ui.js				\
 			grid.js				\
+			polling.js			\
 			workflow.js
 
 OUTPUT=		../public/crosswords.min.js

+ 54 - 9
js/grid.js

@@ -28,13 +28,17 @@ function Cell() {
     this.isBlack = false;
     /** @type {Array.<Definition>} */
     this.definitions = null;
-    /** @type {boolean} */
-    this.found = false;
+    /** @type {Player|null} */
+    this.found = null;
     /** @type {string|null} */
     this.letter = null;
 }
 
-Cell.prototype.update = function(data) {
+/**
+ * @param {*} data
+ * @param {Object.<string, Player>} players
+**/
+Cell.prototype.update = function(data, players) {
     if (data["type"] === null) {
         this.isBlack = true;
     } else if (data["definitions"] !== undefined) {
@@ -44,12 +48,36 @@ Cell.prototype.update = function(data) {
         }.bind(this));
     } else if (data["letter"]) {
         this.letter = data["letter"];
-        this.found = true;
+        this.found = players[data["found"]];
         return data["v"];
     }
     return 0;
 };
 
+/** @constructor */
+function Player(data) {
+    /** @const @type {string} */
+    this.id = data["name"];
+    /** @const @type {string} */
+    this.idEncoded = encodeURIComponent(data["name"]);
+    /** @type {number} */
+    this.score = data["score"];
+
+    var pos = data["name"].indexOf('|');
+    /** @const @type {string} */
+    this.name = data["name"].substr(0, pos);
+
+    /** @const @type {string} */
+    this.color = '#' +data["name"].substr(pos +1);
+
+    console.log(this);
+}
+
+Player.prototype.update = function(data) {
+    this.score = data["score"];
+    return data["v"];
+};
+
 /**
  * @constructor
 **/
@@ -63,6 +91,12 @@ function Grid(data) {
     /** @type {number} */
     this.height = data["h"];
 
+    /** @type {Object.<string, Player>} */
+    this.players = {};
+
+    /** @type {Player|null} */
+    this.playerSelf = null;
+
     this.words = [];
     this.grid = [];
     for (var i =0; i < this.width; i++) {
@@ -95,27 +129,38 @@ Grid.prototype.getWord = function(x, y) {
     return words;
 };
 
+Grid.prototype.updatePlayers = function(playerData) {
+    var maxVersion = 0;
+
+    playerData.forEach(function(player) {
+        var localPlayer = this.players[player["name"]];
+        if (!localPlayer)
+            localPlayer = this.players[player["name"]] = new Player(player);
+        maxVersion = Math.max(maxVersion, localPlayer.update(player));
+    }.bind(this));
+    return maxVersion;
+};
+
 /**
  * @return {number|null}
 **/
 Grid.prototype.update = function(data) {
     var maxVersion = null
-        ,grid = this.grid
         ,topologyUpdated = false;
 
     data.forEach(function(cellData) {
-        var updateResult = grid[cellData["x"]][cellData["y"]].update(cellData);
+        var updateResult = this.grid[cellData["x"]][cellData["y"]].update(cellData, this.players);
         maxVersion = Math.max(maxVersion || 0, updateResult);
         if (updateResult === 0) {
             topologyUpdated = true;
         }
-    });
+    }.bind(this));
     if (topologyUpdated) {
         var words = [];
         for (var i =0; i < this.width; i++) {
             for (var j =0; j < this.height; j++) {
-                if (grid[i][j].definitions) {
-                    grid[i][j].definitions.forEach(function(definition) {
+                if (this.grid[i][j].definitions) {
+                    this.grid[i][j].definitions.forEach(function(definition) {
                         var word;
                         switch (definition.direction) {
                             case Definition.RIGHT_VERTICAL:

+ 62 - 0
js/polling.js

@@ -0,0 +1,62 @@
+
+/** @const @type {number} */
+var POLL_INTERVAL = 5000;
+
+function doGet(url, callback) {
+    var xhr = new XMLHttpRequest();
+    xhr.onreadystatechange = function(e) {
+        if (xhr.readyState === 4) {
+            var resp = null;
+
+            if (xhr.status === 200) {
+                resp = xhr.response;
+                try {
+                    resp = JSON.parse(/** @type {string} */ (resp));
+                } catch (e) {
+                    resp = null;
+                }
+            }
+            callback(xhr.status, resp);
+        }
+    };
+    xhr.open('GET', url, true);
+    xhr.send(null);
+}
+
+function initPolling() {
+    lazyGetPseudonyme(function(pseudo) {
+        if (pseudo) {
+            doGet("/api/poll?grid=" +GRID_PUBLIC_ID +"&v=" +KNOWN_VERSION, function(status, resp) {
+                if (resp) {
+                    GRID = new Grid(resp);
+                    updateOnPollResult(resp);
+                    GRID.playerSelf = GRID.players[pseudo];
+                    scheduleNextPoll();
+                } // TODO else cannot init party
+            });
+        } // TODO else cannot init pseudo
+    });
+}
+
+function pollNow() {
+    if (pollNow.polling !== true) {
+        if (pollNow.pollSchedule) {
+            clearTimeout(pollNow.pollSchedule);
+            pollNow.pollSchedule = 0;
+        }
+        pollNow.polling = true;
+        doGet("/api/poll?grid=" +GRID_PUBLIC_ID +"&v=" +KNOWN_VERSION, function(status, resp) {
+            pollNow.polling = false;
+            if (resp) {
+                updateOnPollResult(resp);
+            }
+            scheduleNextPoll();
+        });
+    }
+}
+
+function scheduleNextPoll() {
+    if (!pollNow.pollSchedule)
+        pollNow.pollSchedule = setInterval(pollNow, POLL_INTERVAL);
+};
+

+ 5 - 1
js/resources.js

@@ -1,6 +1,10 @@
 
 var R = {
-    klass: {
+	id: {
+		scoreboard: "scoreboard"
+		,grid: "grid"
+	}
+    ,klass: {
         line: "crossword-line"
         ,cell: {
             item: "cell"

+ 2 - 134
js/ui.js

@@ -1,136 +1,4 @@
 
-var UI_CELLS = [];
-
-
-function dCreate(domName) {
-    return document.createElement(domName);
-}
-
-function uiCreateCell(cellData, x, y) {
-    var cell = dCreate("div");
-
-    cell.className = R.klass.cell.item;
-    if (cellData.isBlack) {
-        cell.classList.add(R.klass.cell.black);
-    } else if (cellData.definitions !== null) {
-        cell.classList.add(R.klass.cell.definition);
-        var cellContent =dCreate("span");
-        for (var i =0, nbDefinitions = cellData.definitions.length; i < nbDefinitions; i++) {
-            var definition = cellData.definitions[i]
-                ,domDefinition = dCreate("span");
-            domDefinition.dataset.x = x;
-            domDefinition.dataset.y = y;
-            domDefinition.dataset.definition = i;
-            domDefinition.className = R.klass.cell.definitions.item;
-            domDefinition.innerHTML = definition.text.join("<br/>");
-            cellContent.appendChild(domDefinition);
-        }
-        cell.appendChild(cellContent);
-    } else {
-        cell.classList.add(R.klass.cell.letter);
-    }
-    return cell;
-}
-
-function uiCreateGrid() {
-    var frag = document.createDocumentFragment();
-
-    for (var i =0; i < GRID.height; i++) {
-        var line = dCreate("div");
-
-        line.className = R.klass.line;
-        frag.appendChild(line);
-        for (var j =0; j < GRID.width; j++) {
-            var cell = uiCreateCell(GRID.grid[j][i], j, i);
-            cell.dataset.x = j;
-            cell.dataset.y = i;
-            line.appendChild(cell);
-            UI_CELLS.push({
-                x: j
-                ,y: i
-                ,dom: cell
-                ,data: GRID.grid[j][i]
-            });
-        }
-    }
-    for (var i =0; i < GRID.height; i++) {
-        for (var j =0; j < GRID.width; j++) {
-            var cell = UI_CELLS[i *GRID.width +j];
-            if (cell.data.definitions) {
-                cell.data.definitions.forEach(function(d) {
-                    switch (d.direction) {
-                        case Definition.RIGHT_VERTICAL:
-                            UI_CELLS[i *GRID.width +j +1].dom.classList.add(R.klass.cell.definitions.rightVertical);
-                        break;
-
-                        case Definition.RIGHT_HORIZONTAL:
-                            UI_CELLS[i *GRID.width +j +1].dom.classList.add(R.klass.cell.definitions.rightHorizontal);
-                        break;
-
-                        case Definition.BOTTOM_VERTICAL:
-                            UI_CELLS[(i +1) *GRID.width +j].dom.classList.add(R.klass.cell.definitions.bottomVertical);
-                        break;
-
-                        case Definition.BOTTOM_HORIZONTAL:
-                            UI_CELLS[(i +1) *GRID.width +j].dom.classList.add(R.klass.cell.definitions.bottomHorizontal);
-                        break;
-                    }
-                });
-            }
-        }
-    }
-    onGridUpdated();
-    document.body.textContent = "";
-    document.body.appendChild(frag);
-}
-
-function onGridUpdated() {
-    UI_CELLS.forEach(function(i) {
-        if (!i.data.definitions && !i.data.isBlack) {
-            if (i.data.letter) {
-                i.dom.textContent = i.data.letter;
-                if (i.data.found) {
-                    i.dom.classList.add(R.klass.cell.found);
-                }
-            } else {
-                i.dom.textContent = "";
-            }
-        }
-    });
-}
-
-function gridClickDelegate(e) {
-    var target = e.target;
-    while (target && (target.dataset && !target.dataset.x))
-        target = target.parentElement;
-    if (target && target.dataset && target.dataset.x && target.dataset.y) {
-        var clickedCell = UI_CELLS[parseInt(target.dataset.x, 10) +parseInt(target.dataset.y, 10) *GRID.width];
-        if (clickedCell.data.isBlack) {
-            unselect();
-        } else if (clickedCell.data.definitions) {
-            var first = true;
-            unselect();
-            if (clickedCell.data.definitions[target.dataset.definition]) {
-                clickedCell.data.definitions[target.dataset.definition].word.forEach(function(coordinates) {
-                    select(coordinates[0], coordinates[1]);
-                    if (first && !UI_CELLS[coordinates[0] +coordinates[1] *GRID.width].data.found) {
-                        CURRENTINPUT = UI_CELLS[coordinates[0] +coordinates[1] *GRID.width];
-                        CURRENTINPUT.dom.classList.add(R.klass.cell.currentInput);
-                    }
-                    first = false;
-                });
-            }
-        } else {
-            var words = GRID.getWord(clickedCell.x, clickedCell.y);
-            unselect();
-            words.forEach(function (word) {
-                word.forEach(function (coordinates) {
-                    select(coordinates[0], coordinates[1]);
-                });
-            });
-            target.classList.add(R.klass.cell.currentInput);
-            CURRENTINPUT = clickedCell;
-        }
-    }
-}
+function dCreate(domName) { return document.createElement(domName); }
+function dGet(id) { return document.getElementById(id); }
 

+ 134 - 0
js/uiGrid.js

@@ -0,0 +1,134 @@
+
+var UI_CELLS = [];
+
+function uiCreateCell(cellData, x, y) {
+    var cell = dCreate("div");
+
+    cell.className = R.klass.cell.item;
+    if (cellData.isBlack) {
+        cell.classList.add(R.klass.cell.black);
+    } else if (cellData.definitions !== null) {
+        cell.classList.add(R.klass.cell.definition);
+        var cellContent =dCreate("span");
+        for (var i =0, nbDefinitions = cellData.definitions.length; i < nbDefinitions; i++) {
+            var definition = cellData.definitions[i]
+                ,domDefinition = dCreate("span");
+            domDefinition.dataset.x = x;
+            domDefinition.dataset.y = y;
+            domDefinition.dataset.definition = i;
+            domDefinition.className = R.klass.cell.definitions.item;
+            domDefinition.innerHTML = definition.text.join("<br/>");
+            cellContent.appendChild(domDefinition);
+        }
+        cell.appendChild(cellContent);
+    } else {
+        cell.classList.add(R.klass.cell.letter);
+    }
+    return cell;
+}
+
+function uiCreateGrid() {
+    var frag = document.createDocumentFragment();
+
+    for (var i =0; i < GRID.height; i++) {
+        var line = dCreate("div");
+
+        line.className = R.klass.line;
+        frag.appendChild(line);
+        for (var j =0; j < GRID.width; j++) {
+            var cell = uiCreateCell(GRID.grid[j][i], j, i);
+            cell.dataset.x = j;
+            cell.dataset.y = i;
+            line.appendChild(cell);
+            UI_CELLS.push({
+                x: j
+                ,y: i
+                ,dom: cell
+                ,data: GRID.grid[j][i]
+            });
+        }
+    }
+    for (var i =0; i < GRID.height; i++) {
+        for (var j =0; j < GRID.width; j++) {
+            var cell = UI_CELLS[i *GRID.width +j];
+            if (cell.data.definitions) {
+                cell.data.definitions.forEach(function(d) {
+                    switch (d.direction) {
+                        case Definition.RIGHT_VERTICAL:
+                            UI_CELLS[i *GRID.width +j +1].dom.classList.add(R.klass.cell.definitions.rightVertical);
+                        break;
+
+                        case Definition.RIGHT_HORIZONTAL:
+                            UI_CELLS[i *GRID.width +j +1].dom.classList.add(R.klass.cell.definitions.rightHorizontal);
+                        break;
+
+                        case Definition.BOTTOM_VERTICAL:
+                            UI_CELLS[(i +1) *GRID.width +j].dom.classList.add(R.klass.cell.definitions.bottomVertical);
+                        break;
+
+                        case Definition.BOTTOM_HORIZONTAL:
+                            UI_CELLS[(i +1) *GRID.width +j].dom.classList.add(R.klass.cell.definitions.bottomHorizontal);
+                        break;
+                    }
+                });
+            }
+        }
+    }
+    onGridUpdated();
+    var gridContainer = dGet(R.id.grid);
+    gridContainer.textContent = "";
+    gridContainer.appendChild(frag);
+}
+
+function onGridUpdated() {
+    UI_CELLS.forEach(function(i) {
+        if (!i.data.definitions && !i.data.isBlack) {
+            if (i.data.letter) {
+                i.dom.textContent = i.data.letter;
+                if (i.data.found) {
+                    i.dom.classList.add(R.klass.cell.found);
+                    i.dom.classList.remove(R.klass.cell.wrong);
+                    i.dom.style.color = i.data.found.color;
+                }
+            } else {
+                i.dom.textContent = "";
+            }
+        }
+    });
+}
+
+function gridClickDelegate(e) {
+    var target = e.target;
+    while (target && (target.dataset && !target.dataset.x))
+        target = target.parentElement;
+    if (target && target.dataset && target.dataset.x && target.dataset.y) {
+        var clickedCell = UI_CELLS[parseInt(target.dataset.x, 10) +parseInt(target.dataset.y, 10) *GRID.width];
+        if (clickedCell.data.isBlack) {
+            unselect();
+        } else if (clickedCell.data.definitions) {
+            var first = true;
+            unselect();
+            if (clickedCell.data.definitions[target.dataset.definition]) {
+                clickedCell.data.definitions[target.dataset.definition].word.forEach(function(coordinates) {
+                    select(coordinates[0], coordinates[1]);
+                    if (first && !UI_CELLS[coordinates[0] +coordinates[1] *GRID.width].data.found) {
+                        CURRENTINPUT = UI_CELLS[coordinates[0] +coordinates[1] *GRID.width];
+                        CURRENTINPUT.dom.classList.add(R.klass.cell.currentInput);
+                    }
+                    first = false;
+                });
+            }
+        } else {
+            var words = GRID.getWord(clickedCell.x, clickedCell.y);
+            unselect();
+            words.forEach(function (word) {
+                word.forEach(function (coordinates) {
+                    select(coordinates[0], coordinates[1]);
+                });
+            });
+            target.classList.add(R.klass.cell.currentInput);
+            CURRENTINPUT = clickedCell;
+        }
+    }
+}
+

+ 30 - 0
js/uiScoreboard.js

@@ -0,0 +1,30 @@
+
+var UI_PLAYERS = {};
+
+function uiCreatePlayer(player) {
+    var dom = dCreate("li")
+        ,playerName = dCreate("span");
+    playerName.textContent = player.name;
+    dom.appendChild(playerName);
+    dom.score = dCreate("span");
+    dom.appendChild(dom.score);
+    dom.style.color = player.color;
+    return dom;
+}
+
+function onPlayersUpdated() {
+    var container;
+
+    for (var i in GRID.players) {
+        var uiPlayer = UI_PLAYERS[i];
+        if (!uiPlayer) {
+            uiPlayer = UI_PLAYERS[i] = uiCreatePlayer(GRID.players[i]);
+            if (!container)
+                container = dGet(R.id.scoreboard);
+            container.appendChild(uiPlayer);
+        }
+        uiPlayer.score.textContent = GRID.players[i].score;
+    }
+    //TODO resort
+}
+

+ 45 - 55
js/workflow.js

@@ -6,73 +6,53 @@ var GRID_PUBLIC_ID
     ,SELECTED = []
     ,CURRENTINPUT = null;
 
-function doGet(url, callback) {
-    var xhr = new XMLHttpRequest();
-    xhr.onreadystatechange = function(e) {
-        if (xhr.readyState === 4) {
-            var resp = null;
-
-            if (xhr.status === 200) {
-                resp = xhr.response;
-                try {
-                    resp = JSON.parse(/** @type {string} */ (resp));
-                } catch (e) {
-                    resp = null;
-                }
-            }
-            callback(xhr.status, resp);
-        }
-    };
-    xhr.open('GET', url, true);
-    xhr.send(null);
-}
-
-function initPolling() {
-    doGet("/api/poll?grid=" +GRID_PUBLIC_ID +"&v=" +KNOWN_VERSION, function(status, resp) {
-        if (resp) {
-            GRID = new Grid(resp);
-            if (resp["grid"]) {
-                KNOWN_VERSION = Math.max(GRID.update(resp["grid"]) || 0, KNOWN_VERSION);
-                uiCreateGrid();
+function lazyGetPseudonyme(cb) {
+    var pseudo = window["sessionStorage"].getItem("pseudonyme_" +GRID_PUBLIC_ID);
+    if (pseudo) {
+        cb(pseudo);
+    } else {
+        doGet("/api/register?grid=" +GRID_PUBLIC_ID, function(status, pseudo) {
+            if (status && pseudo) {
+                window["sessionStorage"].setItem("pseudonyme_" +GRID_PUBLIC_ID, pseudo);
+                cb(pseudo);
+            } else {
+                cb(null);
             }
-            KNOWN_VERSION = Math.max(KNOWN_VERSION, resp["v"] || 0);
-        } // TODO else cannot init party
-    });
-}
-
-function pollNow() {
-    // TODO avoid duplicate call / abort planned poll
-    doGet("/api/poll?grid=" +GRID_PUBLIC_ID +"&v=" +KNOWN_VERSION, function(status, resp) {
-        if (resp && resp["grid"]) {
-            var newVersion = Math.max(GRID.update(resp["grid"]) || 0, KNOWN_VERSION);
-            if (newVersion !== KNOWN_VERSION) {
-                onGridUpdated();
-                KNOWN_VERSION = newVersion;
-            }
-        }
-        // TODO plan next poll
-    });
+        });
+    }
 }
 
 function keyPressHandler(cell, key) {
     cell.dom.classList.add(R.klass.cell.pending);
-    doGet("/api/put?grid=" +GRID_PUBLIC_ID +"&key=" +key +"&x=" +cell.x +"&y=" +cell.y, function(status, resp) {
+    doGet("/api/put?grid=" +GRID_PUBLIC_ID +"&key=" +key +"&x=" +cell.x +"&y=" +cell.y +"&me=" +GRID.playerSelf.idEncoded, function(status, resp) {
         cell.dom.classList.remove(R.klass.cell.pending);
         if (status === 403 && !cell.data.found) {
             cell.dom.classList.add(R.klass.cell.wrong);
-            // TODO wrong
+
         } else if (status === 204) {
-            cell.dom.classList.remove(R.klass.cell.wrong);
-            cell.data.letter = key;
-            cell.data.found = true;
-            onGridUpdated();
+            pollNow();
+
         } else {
             // out of sync ?
             pollNow();
+
         }
     });
 }
 
+function updateOnPollResult(resp) {
+    if (resp["players"]) {
+        GRID.updatePlayers(resp["players"]);
+        onPlayersUpdated();
+        console.log("players updated");
+    }
+    if (resp["grid"]) {
+        GRID.update(resp["grid"]);
+        uiCreateGrid();
+    }
+    KNOWN_VERSION = Math.max(KNOWN_VERSION, resp["v"] || 0);
+}
+
 function unselect() {
     SELECTED.forEach(function (cell) {
         cell.dom.classList.remove(R.klass.cell.selected);
@@ -101,13 +81,23 @@ document.addEventListener('DOMContentLoaded', function() {
     document.addEventListener('click', gridClickDelegate);
     document.addEventListener('keypress', function(e) {
         if (CURRENTINPUT && !CURRENTINPUT.data.found) {
-            var key = e.key.charAt(0).toUpperCase();
-            if (key.match(/[A-Z]/)) {
-                CURRENTINPUT.data.letter = key;
-                keyPressHandler(CURRENTINPUT, key);
+            if (e.key.length === 1) {
+                var key = e.key.charAt(0).toUpperCase();
+                if (key.match(/[A-Z]/)) {
+                    CURRENTINPUT.data.letter = key;
+                    keyPressHandler(CURRENTINPUT, key);
+                    onGridUpdated();
+                }
+            } else if (e.key.toUpperCase() == "DELETE") {
+                CURRENTINPUT.data.letter = null;
+                onGridUpdated();
+            } else if (e.key.toUpperCase() == "BACKSPACE") {
+                CURRENTINPUT.data.letter = null;
+                //TODO go back
                 onGridUpdated();
             }
         }
+        console.log(e);
     });
     initPolling();
 });

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 0
public/crosswords.min.css


+ 23 - 10
public/crosswords.min.js

@@ -1,10 +1,23 @@
-var f=[];function h(a,b,c){var d=document.createElement("div");d.className="cell";if(a.i)d.classList.add("cell-disabled");else if(null!==a.b){d.classList.add("cell-definition");for(var e=document.createElement("span"),g=0,k=a.b.length;g<k;g++){var A=a.b[g],n=document.createElement("span");n.dataset.x=b;n.dataset.y=c;n.dataset.definition=g;n.className="definition";n.innerHTML=A.text.join("<br/>");e.appendChild(n)}d.appendChild(e)}else d.classList.add("cell-letter");return d}
-function l(){for(var a=document.createDocumentFragment(),b=0;b<m.l;b++){var c=document.createElement("div");c.className="crossword-line";a.appendChild(c);for(var d=0;d<m.c;d++){var e=h(m.f[d][b],d,b);e.dataset.x=d;e.dataset.y=b;c.appendChild(e);f.push({x:d,y:b,a:e,data:m.f[d][b]})}}for(b=0;b<m.l;b++)for(d=0;d<m.c;d++)e=f[b*m.c+d],e.data.b&&e.data.b.forEach(function(a){switch(a.direction){case 2:f[b*m.c+d+1].a.classList.add("definition-right-vt");break;case 1:f[b*m.c+d+1].a.classList.add("definition-right-hz");
-break;case 4:f[(b+1)*m.c+d].a.classList.add("definition-bottom-vt");break;case 3:f[(b+1)*m.c+d].a.classList.add("definition-bottom-hz")}});p();document.body.textContent="";document.body.appendChild(a)}function p(){f.forEach(function(a){a.data.b||a.data.i||(a.data.j?(a.a.textContent=a.data.j,a.data.g&&a.a.classList.add("cell-letter-correct")):a.a.textContent="")})}
-function q(a){for(a=a.target;a&&a.dataset&&!a.dataset.x;)a=a.parentElement;if(a&&a.dataset&&a.dataset.x&&a.dataset.y){var b=f[parseInt(a.dataset.x,10)+parseInt(a.dataset.y,10)*m.c];if(b.data.i)r();else if(b.data.b){var c=!0;r();b.data.b[a.dataset.definition]&&b.data.b[a.dataset.definition].o.forEach(function(a){t(a[0],a[1]);c&&!f[a[0]+a[1]*m.c].data.g&&(u=f[a[0]+a[1]*m.c],u.a.classList.add("cell-input"));c=!1})}else{var d=v(b.x,b.y);r();d.forEach(function(a){a.forEach(function(a){t(a[0],a[1])})});
-a.classList.add("cell-input");u=b}}};function w(a){this.text=a.text;this.direction=a.pos;this.o=null}function x(){this.i=!1;this.b=null;this.g=!1;this.j=null}x.prototype.update=function(a){if(null===a.type)this.i=!0;else if(void 0!==a.definitions)this.b=[],a.definitions.forEach(function(a){this.b.push(new w(a))}.bind(this));else if(a.letter)return this.j=a.letter,this.g=!0,a.v;return 0};function y(a){this.c=a.w;this.l=a.h;this.m=[];this.f=[];for(a=0;a<this.c;a++){this.f[a]=[];for(var b=0;b<this.l;b++)this.f[a][b]=new x}}
-function z(a,b,c,d,e){if(!a.f[b+d]||!a.f[b+d][c+e]||a.f[b+d][c+e].b||a.f[b+d][c+e].i)return[[b,c]];a=z(a,b+d,c+e,d,e);a.unshift([b,c]);return a}function v(a,b){var c=[];m.m.forEach(function(d){for(var e=0,g=d.length;e<g;e++)if(d[e][0]==a&&d[e][1]==b){c.push(d);break}});return c}
-y.prototype.update=function(a){var b=null,c=this.f,d=!1;a.forEach(function(a){a=c[a.x][a.y].update(a);b=Math.max(b||0,a);0===a&&(d=!0)});if(d){for(var e=[],g=0;g<this.c;g++)for(var k=0;k<this.l;k++)c[g][k].b&&c[g][k].b.forEach(function(a){var b;switch(a.direction){case 2:b=z(this,g+1,k,0,1);break;case 1:b=z(this,g+1,k,1,0);break;case 4:b=z(this,g,k+1,0,1);break;case 3:b=z(this,g,k+1,1,0)}e.push(b);a.o=b}.bind(this));this.m=e}return b};var B,C=0,m,D=[],u=null;function E(a,b){var c=new XMLHttpRequest;c.onreadystatechange=function(){if(4===c.readyState){var a=null;if(200===c.status){a=c.response;try{a=JSON.parse(a)}catch(e){a=null}}b(c.status,a)}};c.open("GET",a,!0);c.send(null)}function F(){E("/api/poll?grid="+B+"&v="+C,function(a,b){b&&(m=new y(b),b.grid&&(C=Math.max(m.update(b.grid)||0,C),l()),C=Math.max(C,b.v||0))})}
-function G(){E("/api/poll?grid="+B+"&v="+C,function(a,b){if(b&&b.grid){var c=Math.max(m.update(b.grid)||0,C);c!==C&&(p(),C=c)}})}function H(a){var b=u;b.a.classList.add("cell-letter-pending");E("/api/put?grid="+B+"&key="+a+"&x="+b.x+"&y="+b.y,function(c){b.a.classList.remove("cell-letter-pending");403!==c||b.data.g?204===c?(b.a.classList.remove("cell-letter-wrong"),b.data.j=a,b.data.g=!0,p()):G():b.a.classList.add("cell-letter-wrong")})}
-function r(){D.forEach(function(a){a.a.classList.remove("cell-selected")});u&&(u.a.classList.remove("cell-input"),u=null);D=[]}function t(a,b){var c=f[a+b*m.c];D.push(c);c.a.classList.add("cell-selected")}
-document.addEventListener("DOMContentLoaded",function(){B=document.location.hash.substr(1);""==B?document.location.href="/":(document.addEventListener("click",q),document.addEventListener("keypress",function(a){u&&!u.data.g&&(a=a.key.charAt(0).toUpperCase(),a.match(/[A-Z]/)&&(u.data.j=a,H(a),p()))}),F())});
+var R={id:{scoreboard:"scoreboard",grid:"grid"},klass:{line:"crossword-line",cell:{item:"cell",black:"cell-disabled",definition:"cell-definition",letter:"cell-letter",definitions:{item:"definition",rightVertical:"definition-right-vt",rightHorizontal:"definition-right-hz",bottomVertical:"definition-bottom-vt",bottomHorizontal:"definition-bottom-hz"},found:"cell-letter-correct",pending:"cell-letter-pending",wrong:"cell-letter-wrong",selected:"cell-selected",currentInput:"cell-input"}}};var UI_CELLS=[],UI_PLAYERS={},playerDomList;function dCreate(domName){return document.createElement(domName)}function dGet(id){return document.getElementById(id)}
+function uiCreateCell(cellData,x,y){var cell=dCreate("div");cell.className=R.klass.cell.item;if(cellData.isBlack)cell.classList.add(R.klass.cell.black);else if(cellData.definitions!==null){cell.classList.add(R.klass.cell.definition);var cellContent=dCreate("span");for(var i=0,nbDefinitions=cellData.definitions.length;i<nbDefinitions;i++){var definition=cellData.definitions[i],domDefinition=dCreate("span");domDefinition.dataset.x=x;domDefinition.dataset.y=y;domDefinition.dataset.definition=i;domDefinition.className=
+R.klass.cell.definitions.item;domDefinition.innerHTML=definition.text.join("<br/>");cellContent.appendChild(domDefinition)}cell.appendChild(cellContent)}else cell.classList.add(R.klass.cell.letter);return cell}function uiCreatePlayer(player){var dom=dCreate("li"),playerName=dCreate("span");playerName.textContent=player.name;dom.appendChild(playerName);dom.score=dCreate("span");dom.appendChild(dom.score);dom.style.color=player.color;return dom}
+function uiCreateGrid(){var frag=document.createDocumentFragment();for(var i=0;i<GRID.height;i++){var line=dCreate("div");line.className=R.klass.line;frag.appendChild(line);for(var j=0;j<GRID.width;j++){var cell=uiCreateCell(GRID.grid[j][i],j,i);cell.dataset.x=j;cell.dataset.y=i;line.appendChild(cell);UI_CELLS.push({x:j,y:i,dom:cell,data:GRID.grid[j][i]})}}for(var i=0;i<GRID.height;i++)for(var j=0;j<GRID.width;j++){var cell=UI_CELLS[i*GRID.width+j];if(cell.data.definitions)cell.data.definitions.forEach(function(d){switch(d.direction){case Definition.RIGHT_VERTICAL:UI_CELLS[i*
+GRID.width+j+1].dom.classList.add(R.klass.cell.definitions.rightVertical);break;case Definition.RIGHT_HORIZONTAL:UI_CELLS[i*GRID.width+j+1].dom.classList.add(R.klass.cell.definitions.rightHorizontal);break;case Definition.BOTTOM_VERTICAL:UI_CELLS[(i+1)*GRID.width+j].dom.classList.add(R.klass.cell.definitions.bottomVertical);break;case Definition.BOTTOM_HORIZONTAL:UI_CELLS[(i+1)*GRID.width+j].dom.classList.add(R.klass.cell.definitions.bottomHorizontal);break}})}onGridUpdated();var gridContainer=dGet(R.id.grid);
+gridContainer.textContent="";gridContainer.appendChild(frag)}function onGridUpdated(){UI_CELLS.forEach(function(i){if(!i.data.definitions&&!i.data.isBlack)if(i.data.letter){i.dom.textContent=i.data.letter;if(i.data.found){i.dom.classList.add(R.klass.cell.found);i.dom.classList.remove(R.klass.cell.wrong);i.dom.style.color=i.data.found.color}}else i.dom.textContent=""})}
+function onPlayersUpdated(){var container;for(var i in GRID.players){var uiPlayer=UI_PLAYERS[i];if(!uiPlayer){uiPlayer=UI_PLAYERS[i]=uiCreatePlayer(GRID.players[i]);if(!container)container=dGet(R.id.scoreboard);container.appendChild(uiPlayer)}uiPlayer.score.textContent=GRID.players[i].score}}
+function gridClickDelegate(e){var target=e.target;while(target&&(target.dataset&&!target.dataset.x))target=target.parentElement;if(target&&target.dataset&&target.dataset.x&&target.dataset.y){var clickedCell=UI_CELLS[parseInt(target.dataset.x,10)+parseInt(target.dataset.y,10)*GRID.width];if(clickedCell.data.isBlack)unselect();else if(clickedCell.data.definitions){var first=true;unselect();if(clickedCell.data.definitions[target.dataset.definition])clickedCell.data.definitions[target.dataset.definition].word.forEach(function(coordinates){select(coordinates[0],
+coordinates[1]);if(first&&!UI_CELLS[coordinates[0]+coordinates[1]*GRID.width].data.found){CURRENTINPUT=UI_CELLS[coordinates[0]+coordinates[1]*GRID.width];CURRENTINPUT.dom.classList.add(R.klass.cell.currentInput)}first=false})}else{var words=GRID.getWord(clickedCell.x,clickedCell.y);unselect();words.forEach(function(word){word.forEach(function(coordinates){select(coordinates[0],coordinates[1])})});target.classList.add(R.klass.cell.currentInput);CURRENTINPUT=clickedCell}}};function Definition(data){this.text=data["text"];this.direction=data["pos"];this.word=null}Definition.RIGHT_HORIZONTAL=1;Definition.RIGHT_VERTICAL=2;Definition.BOTTOM_HORIZONTAL=3;Definition.BOTTOM_VERTICAL=4;function Cell(){this.isBlack=false;this.definitions=null;this.found=null;this.letter=null}
+Cell.prototype.update=function(data,players){if(data["type"]===null)this.isBlack=true;else if(data["definitions"]!==undefined){this.definitions=[];data["definitions"].forEach(function(definition){this.definitions.push(new Definition(definition))}.bind(this))}else if(data["letter"]){this.letter=data["letter"];this.found=players[data["found"]];return data["v"]}return 0};
+function Player(data){this.id=data["name"];this.idEncoded=encodeURIComponent(data["name"]);this.score=data["score"];var pos=data["name"].indexOf("|");this.name=data["name"].substr(0,pos);this.color="#"+data["name"].substr(pos+1);console.log(this)}Player.prototype.update=function(data){this.score=data["score"];return data["v"]};
+function Grid(data){this.title=data["title"]||"";this.difficulty=data["difficulty"];this.width=data["w"];this.height=data["h"];this.players={};this.playerSelf=null;this.words=[];this.grid=[];for(var i=0;i<this.width;i++){this.grid[i]=[];for(var j=0;j<this.height;j++)this.grid[i][j]=new Cell}}
+Grid.prototype.computeWord=function(x,y,dx,dy){if(!this.grid[x+dx]||!this.grid[x+dx][y+dy]||this.grid[x+dx][y+dy].definitions||this.grid[x+dx][y+dy].isBlack)return[[x,y]];var word=this.computeWord(x+dx,y+dy,dx,dy);word.unshift([x,y]);return word};Grid.prototype.getWord=function(x,y){var words=[];this.words.forEach(function(word){for(var i=0,nbLetters=word.length;i<nbLetters;i++)if(word[i][0]==x&&word[i][1]==y){words.push(word);break}});return words};
+Grid.prototype.updatePlayers=function(playerData){var maxVersion=0;playerData.forEach(function(player){var localPlayer=this.players[player["name"]];if(!localPlayer)localPlayer=this.players[player["name"]]=new Player(player);maxVersion=Math.max(maxVersion,localPlayer.update(player))}.bind(this));return maxVersion};
+Grid.prototype.update=function(data){var maxVersion=null,topologyUpdated=false;data.forEach(function(cellData){var updateResult=this.grid[cellData["x"]][cellData["y"]].update(cellData,this.players);maxVersion=Math.max(maxVersion||0,updateResult);if(updateResult===0)topologyUpdated=true}.bind(this));if(topologyUpdated){var words=[];for(var i=0;i<this.width;i++)for(var j=0;j<this.height;j++)if(this.grid[i][j].definitions)this.grid[i][j].definitions.forEach(function(definition){var word;switch(definition.direction){case Definition.RIGHT_VERTICAL:word=
+this.computeWord(i+1,j,0,1);break;case Definition.RIGHT_HORIZONTAL:word=this.computeWord(i+1,j,1,0);break;case Definition.BOTTOM_VERTICAL:word=this.computeWord(i,j+1,0,1);break;case Definition.BOTTOM_HORIZONTAL:word=this.computeWord(i,j+1,1,0);break}words.push(word);definition.word=word}.bind(this));this.words=words}return maxVersion};var POLL_INTERVAL=5E3;function doGet(url,callback){var xhr=new XMLHttpRequest;xhr.onreadystatechange=function(e){if(xhr.readyState===4){var resp=null;if(xhr.status===200){resp=xhr.response;try{resp=JSON.parse((resp))}catch(e){resp=null}}callback(xhr.status,resp)}};xhr.open("GET",url,true);xhr.send(null)}
+function initPolling(){lazyGetPseudonyme(function(pseudo){if(pseudo)doGet("/api/poll?grid="+GRID_PUBLIC_ID+"&v="+KNOWN_VERSION,function(status,resp){if(resp){GRID=new Grid(resp);updateOnPollResult(resp);GRID.playerSelf=GRID.players[pseudo];scheduleNextPoll()}})})}
+function pollNow(){if(pollNow.polling!==true){if(pollNow.pollSchedule){clearTimeout(pollNow.pollSchedule);pollNow.pollSchedule=0}pollNow.polling=true;doGet("/api/poll?grid="+GRID_PUBLIC_ID+"&v="+KNOWN_VERSION,function(status,resp){pollNow.polling=false;if(resp)updateOnPollResult(resp);scheduleNextPoll()})}}function scheduleNextPoll(){if(!pollNow.pollSchedule)pollNow.pollSchedule=setInterval(pollNow,POLL_INTERVAL)};var GRID_PUBLIC_ID,KNOWN_VERSION=0,GRID,SELECTED=[],CURRENTINPUT=null;function lazyGetPseudonyme(cb){var pseudo=window["sessionStorage"].getItem("pseudonyme_"+GRID_PUBLIC_ID);if(pseudo)cb(pseudo);else doGet("/api/register?grid="+GRID_PUBLIC_ID,function(status,pseudo){if(status&&pseudo){window["sessionStorage"].setItem("pseudonyme_"+GRID_PUBLIC_ID,pseudo);cb(pseudo)}else cb(null)})}
+function keyPressHandler(cell,key){cell.dom.classList.add(R.klass.cell.pending);doGet("/api/put?grid="+GRID_PUBLIC_ID+"&key="+key+"&x="+cell.x+"&y="+cell.y+"&me="+GRID.playerSelf.idEncoded,function(status,resp){cell.dom.classList.remove(R.klass.cell.pending);if(status===403&&!cell.data.found)cell.dom.classList.add(R.klass.cell.wrong);else if(status===204)pollNow();else pollNow()})}
+function updateOnPollResult(resp){if(resp["players"]){GRID.updatePlayers(resp["players"]);onPlayersUpdated();console.log("players updated")}if(resp["grid"]){GRID.update(resp["grid"]);uiCreateGrid()}KNOWN_VERSION=Math.max(KNOWN_VERSION,resp["v"]||0)}function unselect(){SELECTED.forEach(function(cell){cell.dom.classList.remove(R.klass.cell.selected)});if(CURRENTINPUT){CURRENTINPUT.dom.classList.remove(R.klass.cell.currentInput);CURRENTINPUT=null}SELECTED=[]}
+function select(x,y){var cell=UI_CELLS[x+y*GRID.width];SELECTED.push(cell);cell.dom.classList.add(R.klass.cell.selected)}
+document.addEventListener("DOMContentLoaded",function(){GRID_PUBLIC_ID=document.location.hash.substr(1);if(GRID_PUBLIC_ID==""){document.location.href="/";return}document.addEventListener("click",gridClickDelegate);document.addEventListener("keypress",function(e){if(CURRENTINPUT&&!CURRENTINPUT.data.found)if(e.key.length===1){var key=e.key.charAt(0).toUpperCase();if(key.match(/[A-Z]/)){CURRENTINPUT.data.letter=key;keyPressHandler(CURRENTINPUT,key);onGridUpdated()}}else if(e.key.toUpperCase()=="DELETE"){CURRENTINPUT.data.letter=
+null;onGridUpdated()}else if(e.key.toUpperCase()=="BACKSPACE"){CURRENTINPUT.data.letter=null;onGridUpdated()}console.log(e)});initPolling()});

+ 2 - 0
public/game.html

@@ -6,6 +6,8 @@
 		<link href="crosswords.min.css" rel="stylesheet"/>
 	</head>
 	<body>
+		<ul id="scoreboard" class="scoreboard"></ul>
+		<div id="grid" class="crossword"></div>
 		<script src="crosswords.min.js"></script>
 	</body>
 </html>

+ 75 - 7
src/Grid.js

@@ -24,7 +24,7 @@ EmptyCell.prototype.toStatic = function() {
 function LetterCell(x, y, letter) {
     GridCell.call(this, x, y);
     this.letter = letter;
-    this.found = false;
+    this.found = null;
 }
 LetterCell.prototype = Object.create(GridCell);
 LetterCell.prototype.constructor = LetterCell;
@@ -32,11 +32,14 @@ LetterCell.prototype.constructor = LetterCell;
 LetterCell.prototype.toStatic = function() {
     var ret = GridCell.prototype.toStatic.call(this);
     ret.letter = this.letter;
-    ret.v = this.version;
+    ret.found = this.found;
     return ret;
 };
 
-var debugFound = {};
+LetterCell.prototype.setFound = function(playerId, t) {
+    this.found = playerId;
+    this.version = Math.max(this.version, t);
+};
 
 function Definition(position, alignment, text) {
     this.position = position;
@@ -67,7 +70,6 @@ Definition.prototype.toStatic = function() {
 };
 
 function DefinitionCell(x, y, code, definitionArray, currentDefinition) {
-    if (debugFound[code]) debugFound[code].push({x:x,y:y}); else debugFound[code] = [{x:x,y:y}];
     GridCell.call(this, x, y);
     this.definitions = [];
     switch (code) {
@@ -158,6 +160,11 @@ function parseGrid(grid, definitions, w, h) {
     return resultGrid;
 }
 
+function Player(t) {
+    this.score = 0;
+    this.version = t;
+}
+
 function Grid(publicId, data) {
     this.title = data["titre"];
     this.difficulty = data["force"];
@@ -166,6 +173,10 @@ function Grid(publicId, data) {
     this.grid = parseGrid(data["grille"], data["definitions"], this.width, this.height);
     this.publicId = publicId;
     this.minVersion = 0;
+    /** @type {Object.<string, Player>} playerId -> score */
+    this.players = {};
+
+    this.colors = shuffleArray([ "007b00", "00407b", "42007b", "7b0059", "7b0000", "00747b", "007b39", "7b6300", "ff5f5f", "5c7cff" ]);
 };
 
 Grid.prototype.getCell = function(x, y) {
@@ -177,14 +188,57 @@ Grid.prototype.getCell = function(x, y) {
     return null;
 };
 
+function shuffleArray(arr) {
+    var i = arr.length;
+
+    while (i) {
+        var rand = Math.floor(Math.random() * i)
+            ,tmp = arr[--i];
+
+        arr[i] = arr[rand];
+        arr[rand] = tmp;
+    }
+    return arr;
+}
+
+Grid.prototype.generateNewPlayerId = function() {
+    var playerName = (function() {
+        var dessertType = shuffleArray([ "muffin", "cookie", "cake", "marshmallow", "pizza", "pie", "mousse" ])
+            ,ingredient = shuffleArray([ "blueberry", "chocolate", "cherry", "apple", "pear", "caramell" ])
+            ,players = {};
+        for (var playerId in this.players)
+            players[playerId.substr(0, playerId.indexOf('|'))] = true;
+        for (var i =0, iMax = dessertType.length; i < iMax; i++)
+            for (var j =0, jMax = ingredient.length; j < jMax; j++) {
+                var id = ingredient[i] +' ' +dessertType[j];
+                if (!players[id]) {
+                    return id;
+                }
+            }
+        var i =1
+            ,baseId = ingredient[0] +' ' +dessertType[0] +' ';
+        while (players[baseId +i])
+            i++;
+        return baseId +i;
+    })();
+    return playerName +'|' +this.colors[Object.keys(this.players).length % this.colors.length];
+};
+
+Grid.prototype.registerNewPlayer = function(t) {
+    var playerId = this.generateNewPlayerId();
+    this.players[playerId] = new Player(t);
+    return playerId;
+};
+
 Grid.prototype.toStatic = function(v) {
-    var ret = {};
+    var ret = {}
+        ,maxV = this.minVersion;
+
     if (!v) {
         ret.title = this.title;
         ret.difficulty = this.difficulty;
         ret.w = this.width;
         ret.h = this.height;
-        ret.v = this.minVersion;
     }
     var grid = [];
     this.grid.forEach((cell) => {
@@ -194,11 +248,25 @@ Grid.prototype.toStatic = function(v) {
             }
         } else if (cell.found && cell.version > v) {
             grid.push(cell.toStatic());
+            maxV = Math.max(cell.version, maxV);
         }
     });
     if (grid.length)
         ret.grid = grid;
-    return grid.length || !v ? ret : null;
+    for (var i in this.players) {
+        if (this.players[i].version > v) {
+            if (!ret.players) {
+                ret.players = [];
+            }
+            ret.players.push({
+                name: i
+                ,score: this.players[i].score
+            });
+            maxV = Math.max(this.players[i].version, maxV);
+        }
+    }
+    ret.v = maxV;
+    return ret.grid || !v || ret.players ? ret : null;
 };
 
 module.exports.Grid = Grid;

+ 20 - 5
src/httpServer.js

@@ -155,32 +155,47 @@ HttpServer.prototype.serveApi = function(req, url, res) {
         }
 
     } else if (url === "put") {
-        if (!urlToken["x"] || !urlToken["y"] || !urlToken["key"] || !urlToken["grid"]) {
+        if (!urlToken["x"] || !urlToken["y"] || !urlToken["key"] || !urlToken["grid"] || !urlToken["me"]) {
             res.writeHeader("400", "Bad request");
             res.end();
             return;
         }
         var x = parseFloat(urlToken["x"][0])
             ,y = parseFloat(urlToken["y"][0])
+            ,playerID = decodeURIComponent(urlToken["me"][0])
             ,grid = GridManager.get(urlToken["grid"][0]);
-        if (!grid) {
+        if (!grid || !grid.players[playerID]) {
             throw new HttpServer.Error404();
         }
-        var cell = grid.getCell(x, y);
+        var cell = grid.getCell(x, y)
+            ,playerObj = grid.players[playerID];
         if (!cell || !cell instanceof LetterCell || cell.found) {
             res.writeHeader("400", "Bad request");
             res.end();
             return;
         }
         if (cell.letter !== urlToken["key"][0]) {
+            //TODO lower player score
             res.writeHeader("403");
             res.end();
             return;
         }
-        cell.found = true;
-        cell.version = req.reqT.getTime();
+        // TODO inc player score
+        cell.setFound(playerID, req.reqT.getTime());
         res.writeHeader("204");
         res.end();
+    } else if (url === "register") {
+        if (!urlToken["grid"]) {
+            res.writeHeader("400", "Bad request");
+            res.end();
+            return;
+        }
+        var grid = GridManager.get(urlToken["grid"][0]);
+        if (!grid) {
+            throw new HttpServer.Error404();
+        }
+        var playerId = grid.registerNewPlayer(req.reqT.getTime());
+        res.end(JSON.stringify(playerId));
     } else {
         throw new HttpServer.Error404(url);
     }

Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio