diff --git a/js/GUI/Screen/Hall.js b/js/GUI/Screen/Hall.js index efaf06c..d7cae1b 100644 --- a/js/GUI/Screen/Hall.js +++ b/js/GUI/Screen/Hall.js @@ -1,39 +1,17 @@ import * as Dom from UnitJS.Dom; import I18n; -import * as ConnectedForm from GUI.ConnectedForm; -import * as ListSelector from GUI.ListSelector; +import * as Players from GUI.Screen.Hall.Players; +import * as Games from GUI.Screen.Hall.Games; import Messaging; -import room as players from Room; +import opponent from Room; import Session; import StatusHandler; -import Table; - -var room = ConnectedForm.get('room'); -var form = room.root; - -var playersList = ListSelector.make('players', showPlayer); -var games = Table.make(game, 'date'); -var gamesList = ListSelector.make('games', showGame); -var them = null; return { init: init }; function init() { - initDOMEvents(); - initMessageHandlers(); -} - -function initDOMEvents() { - form.addEventListener('submit', function(e) { - e.preventDefault(); - Messaging.send({tag: "Invitation", to: them}); - }); - form.them.addEventListener("input", function() {refreshPlayers();}); -} - -function initMessageHandlers() { Messaging.addEventListener(["Okaeri"], function(o) { refresh(); }); @@ -41,100 +19,33 @@ function initMessageHandlers() { refresh(); }); Messaging.addEventListener(["LogIn"], function(o) { - if(!Session.is(o.from)) { - refresh(); - } + refresh(); }); Messaging.addEventListener(["LogOut"], function(o) { + // Just in case there was a game proposal from that player, in which case the game proposal's ID is the player's ID + Games.table.remove(o.from); refresh(); }); Messaging.addEventListener(["Relay", "Invitation"], function(o) { - var from = players.get(o.from); - // invitations should come only from known players, in doubt say «no» - if(from != undefined && from.name) { - StatusHandler.set("🎴"); - games.insert(o.from, from.name); - refreshGames(); - } else { - Messaging.send({tag: "Answer", accept: false}); - } + StatusHandler.set("🎴"); + Games.table.insert(o.from, {vs: opponent(o.from), yourTurn: true}); + Games.refresh(); }); Messaging.addEventListener(["Relay", "Answer"], function(o) { - games.remove(o.from); - refreshGames(); - /* - if(o.message.accept) { - modules.screen.select("game"); - } - */ + Games.table.remove(o.from); + Games.refresh(); }); Messaging.addEventListener(["Game"], function(o) { + var sessionKey = Session.getKey(); + Games.table.insert(o.state.gameID, { + vs: opponent(o.state.public.nextPlayer[sessionKey]), + yourTurn: o.state.public.playing == sessionKey + }); + Games.refresh(); }); } -function showPlayer(player) { - return Dom.make('li', { - textContent: player.name, - onClick: function() {form.them.value = player.name; refreshPlayers();}, - class: 'player' - }); -} - -function game(key, vs) { - return { - key: key, - vs: vs, - date: Date.now() - }; -} - -function showGame(game) { - return Dom.make('li', {}, [ - Dom.make('button', { - textContent: I18n.get('accept'), - onClick: function() { - Messaging.send({tag: "Answer", accept: true, to: game.key}); - } - }), - Dom.make('span', {textContent: 'A game vs. ' + game.vs}), - ]); -} - function refresh() { - refreshPlayers(); - refreshGames(); -} - -function refreshPlayers() { - var name = form.them.value; - them = null; - var filtered = players.getAll( - function(player) {return player.name.match(name);} - ); - playersList.refresh(filtered); - var exact = filtered.find(exactMatch(name)); - if(exact != undefined) { - them = exact.key; - } else if(filtered.length == 1) { - them = filtered[0].key; - } else if(filtered.length == 0) { - playersList.message.textContent = I18n.get( - name.length > 0 ? "notFound" : "alone" - ); - } - room.enable(them != undefined); -} - -function exactMatch(name) { - return function(player) { - return player.name === name; - }; -} - -function refreshGames() { - var sortedGames = games.getAll(); - gamesList.refresh(sortedGames); - if(sortedGames.length < 1) { - gamesList.message.textContent = I18n.get('noGames'); - } + Players.refresh(); + Games.refresh(); } diff --git a/js/GUI/Screen/Hall/Games.js b/js/GUI/Screen/Hall/Games.js new file mode 100644 index 0000000..004bc2c --- /dev/null +++ b/js/GUI/Screen/Hall/Games.js @@ -0,0 +1,85 @@ +import * as ListSelector from GUI.ListSelector; +import I18n; +import Messaging; +import room from Room; +import dialog from GUI.Screen; +import Table; +import * as Dom from UnitJS.Dom; + +var games = Table.make(game, 'date'); +var list = ListSelector.make('games', showGame); + +return { + refresh: refresh, + table: games +}; + +function game(key, config) { + return { + key: key, + vs: config.vs, + yourTurn: config.yourTurn, + date: Date.now() + }; +} + +function showGame(game) { + var liContent; + if(game.key.match(/^Player#/)) { // Game proposals use the ID of the opponent as ID + liContent = gameProposal(game); + } else { + liContent = pendingGame(game); + } + return Dom.make('li', {}, liContent); +} + +function gameProposal(game) { + var properties = { + textContent: I18n.get('proposedGame')(game.yourTurn, game.vs.name), + }; + if(game.yourTurn) { + properties.onClick = answerDialog(game.vs.id); + properties.class = 'clickable'; + } + return [Dom.make('span', properties)]; +} + +function pendingGame(game) { + var status = room.get(game.vs.id) != undefined ? 'active' : 'inactive' + return [ + Dom.make('span', { textContent: status}), + Dom.make('a', { + textContent: I18n.get('pendingGame')(game.yourTurn, game.vs.name), + href: '/game/' + game.key + }) + ]; +} + +function answer(key, accept) { + return function() { + Messaging.send({tag: "Answer", accept: accept, to: key}); + games.remove(key); + refresh(); + } +} + +function answerDialog(key) { + return function() { + dialog({ + text: I18n.get('questionAccept'), + answers: [ + {label: 'accept', action: answer(key, true)}, + {label: 'decline', action: answer(key, false)}, + {label: 'notYet', action: function() {}} + ] + }); + }; +} + +function refresh() { + var sortedGames = games.getAll(); + list.refresh(sortedGames); + if(sortedGames.length < 1) { + list.message.textContent = I18n.get('noGames'); + } +} diff --git a/js/GUI/Screen/Hall/Players.js b/js/GUI/Screen/Hall/Players.js new file mode 100644 index 0000000..5a9bd52 --- /dev/null +++ b/js/GUI/Screen/Hall/Players.js @@ -0,0 +1,62 @@ +import * as ConnectedForm from GUI.ConnectedForm; +import * as ListSelector from GUI.ListSelector; +import * as Games from GUI.Screen.Hall.Games; +import I18n; +import Messaging; +import {opponent, room} from Room; +import * as Dom from UnitJS.Dom; + +var form = ConnectedForm.get('room'); +var list = ListSelector.make('players', showPlayer); +var them = null; +initDOM(); + +return { + refresh: refresh +}; + +function initDOM() { + form.root.getElementsByTagName('label')[0].textContent = I18n.get('startGameWith'); + form.root.invite.value = I18n.get('invite'); + form.root.addEventListener('submit', function(e) { + e.preventDefault(); + Messaging.send({tag: "Invitation", to: them}); + Games.table.insert(them, {vs: opponent(them), yourTurn: false}); + Games.refresh(); + }); + form.root.them.addEventListener("input", function() {refresh();}); +} + +function showPlayer(player) { + return Dom.make('li', { + textContent: player.name, + onClick: function() {form.root.them.value = player.name; refresh();}, + class: 'clickable' + }); +} + +function refresh() { + var name = form.root.them.value; + them = null; + var filtered = room.getAll( + function(player) {return player.name.match(name);} + ); + list.refresh(filtered); + var exact = filtered.find(exactMatch(name)); + if(exact != undefined) { + them = exact.key; + } else if(filtered.length == 1) { + them = filtered[0].key; + } else if(filtered.length == 0) { + list.message.textContent = I18n.get( + name.length > 0 ? "notFound" : "alone" + ); + } + form.enable(them != undefined); +} + +function exactMatch(name) { + return function(player) { + return player.name === name; + }; +} diff --git a/js/Room.js b/js/Room.js index b385421..853d454 100644 --- a/js/Room.js +++ b/js/Room.js @@ -6,9 +6,14 @@ var room = Table.make(player, 'name'); initMessageHandlers(); return { + opponent: opponent, room: room }; +function opponent(key) { + return {id: key, name: room.get(key).name}; +} + function player(key, name) { return { key: key, diff --git a/js/Session.js b/js/Session.js index acfea24..df54a7a 100644 --- a/js/Session.js +++ b/js/Session.js @@ -1,7 +1,6 @@ import Messaging; import Save; -var key = null; var playerKey = null; var name = null; var loggedIn = false; @@ -30,7 +29,7 @@ function is(somePlayerKey) { } function getKey() { - return key; + return playerKey; } function isLoggedIn() { diff --git a/js/Table.js b/js/Table.js index fb7e5d1..5819769 100644 --- a/js/Table.js +++ b/js/Table.js @@ -26,9 +26,20 @@ function Table(itemMaker, sortCriterion) { items[key] = itemMaker(key, value); } - function insertAll(itemsByKey) { - for(var key in itemsByKey) { - insert(key, itemsByKey[key]); + function insertAll(newItems) { + for(var key in newItems) { + insert(key, newItems[key]); + } + } + + function update(mapper) { + for(var key in items) { + var newValue = mapper(items[key]); + if(newValue != undefined) { + insert(key, newValue); + } else { + remove(key); + } } } diff --git a/js/Translations.js b/js/Translations.js index 58bab12..3bc9bea 100644 --- a/js/Translations.js +++ b/js/Translations.js @@ -12,16 +12,13 @@ return { SusukiGrass: "susuki grass", Willow: "willow", Wisteria: "wisteria", - accept: "Accept", + accept: "Let's go !", alone: "No one to play with yet ! Wait a little", - decline: "Decline", + decline: "No thanks", endRound: "End the round", endGame: "Return to main menu", join: "Join", invite: "Invite", - invited: function(name) { - return name + " has invited you to a game"; - }, koikoi: "KoiKoi !!", leave: "Leave", lost: "You lost the game", @@ -30,11 +27,25 @@ return { }, noGames: "No games being played", notFound: "No one goes by that name", + notYet: "Not yet", + ok: "Ok", + pendingGame: function(yourTurn, name) { + var whose = yourTurn ? 'your' : name + "'s"; + return 'Game vs. ' + name + ' (' + whose + ' turn)'; + }, pickName: "Pick a name you like", playing: function(name) { return name + " is playing"; }, - ok: "Ok", + proposedGame: function(yourTurn, name) { + var proposed = " proposed a game"; + if(yourTurn) { + return name + proposed; + } else { + return "You" + proposed + " to " + name; + } + }, + questionAccept: "Do you want to start the game ?", startGameWith: "Start a game with", theyScored: function(name) { return name + " scored"; @@ -56,16 +67,13 @@ return { SusukiGrass: "herbes susukis", Willow: "saules", Wisteria: "glycines", - accept: "Accepter", + accept: "C'est parti !", alone: "Personne pour jouer pour l'instant ! Attendez un peu", - decline: "Refuser", + decline: "Non merci", endRound: "Finir la manche", endGame: "Retourner au menu principal", join: "Entrer", invite: "Inviter", - invited: function(name) { - return name + " vous propose une partie"; - }, koikoi: "KoiKoi !!", leave: "Partir", lost: "Vous avez perdu", @@ -74,11 +82,25 @@ return { }, noGames: "Aucune partie en cours", notFound: "Personne ne s'appelle comme ça", + notYet: "Pas pour l'instant", + ok: "Ok", + pendingGame: function(yourTurn, name) { + var whose = yourTurn ? 'vous' : name; + return 'Partie en cours contre ' + name + ' (à ' + whose + ')'; + }, pickName: "Choisissez votre nom", playing: function(name) { return "C'est à " + name; }, - ok: "Ok", + proposedGame: function(yourTurn, name) { + var proposed = " proposé une partie"; + if(yourTurn) { + return name + " vous a" + proposed; + } else { + return "Vous avez" + proposed + " à " + name; + } + }, + questionAccept: "Voulez-vous commencer la partie ?", startGameWith: "Commencer une partie avec", theyScored: function(name) { return name + " a marqué"; diff --git a/skin/hall.css b/skin/hall.css index ccb0f8f..e61c95e 100644 --- a/skin/hall.css +++ b/skin/hall.css @@ -20,6 +20,6 @@ padding-left: 0; } -.listSelector .player { +.clickable { cursor: pointer; }