Compare commits
18 commits
card-modul
...
main
Author | SHA1 | Date | |
---|---|---|---|
f15375c512 | |||
18a5fc3321 | |||
6615a7db65 | |||
49df598d7d | |||
a136cab1c8 | |||
f7a0ff1be7 | |||
394a7699d8 | |||
244215f09b | |||
313d91bcaf | |||
fb3bb64ff6 | |||
c5e5a7c958 | |||
81216e3f01 | |||
19aa959c64 | |||
428e48f6f4 | |||
ef947f7942 | |||
02edb77285 | |||
97b0bc0cc8 | |||
421b91c0ff |
21 changed files with 468 additions and 194 deletions
18
index.html
18
index.html
|
@ -7,15 +7,24 @@
|
|||
<link rel="stylesheet" href="skin.css" type="text/css"/>
|
||||
</head>
|
||||
<body>
|
||||
<div id="reception" class="on">
|
||||
<ul id="sidebar" class="unactive"></ul>
|
||||
<div id="login" class="on">
|
||||
<h1>KoiKoi</h1>
|
||||
<form id="login">
|
||||
<form id="loginForm">
|
||||
<input type="submit" name="submitButton" hidden disabled/>
|
||||
<p id="join">
|
||||
<label for="you"></label><input type="text" name="you"/>
|
||||
<input type="submit" name="join" disabled/>
|
||||
</p>
|
||||
</form>
|
||||
<form id="accountLoader">
|
||||
<input type="submit" name="submitButton" hidden disabled/>
|
||||
<p>or</p>
|
||||
<p id="loadAccount">
|
||||
<label for="pickFile"></label><input type="file" name="pickFile" accept="application/json,.json"/>
|
||||
<input type="submit" name="doLoad" disabled/>
|
||||
</p>
|
||||
</form>
|
||||
</div>
|
||||
<div id="hall">
|
||||
<form id="room">
|
||||
|
@ -34,6 +43,11 @@
|
|||
<ul></ul>
|
||||
</div>
|
||||
</div>
|
||||
<div id="settings">
|
||||
<p id="export">
|
||||
<span></span><a download="koikoi.json" href=""></a>
|
||||
</p>
|
||||
</div>
|
||||
<div id="dialog">
|
||||
</div>
|
||||
<p id="error"></p>
|
||||
|
|
|
@ -11,7 +11,7 @@ function HandCard(name) {
|
|||
return card;
|
||||
|
||||
function onClick() {
|
||||
if(State.state.playing && State.state.step == "ToPlay") {
|
||||
if(State.globalState.playing && State.globalState.step == "ToPlay") {
|
||||
if(State.getSelected() != undefined) {
|
||||
State.select(false);
|
||||
} else {
|
||||
|
|
|
@ -17,7 +17,7 @@ function RiverCard(name) {
|
|||
var withCard = State.getSelected().value.name;
|
||||
State.select(false);
|
||||
State.play(
|
||||
State.state.step == 'ToPlay' ?
|
||||
State.globalState.step == 'ToPlay' ?
|
||||
{capture: [withCard, name]} : {choose: name}
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import * as Dom from UnitJS.Dom;
|
||||
import I18n;
|
||||
|
||||
var sidebar = document.getElementById('sidebar');
|
||||
var current = document.querySelector("body > div.on");
|
||||
var errorBox = document.getElementById('error');
|
||||
errorBox.addEventListener('click', function() {
|
||||
|
@ -10,15 +11,10 @@ errorBox.addEventListener('click', function() {
|
|||
return {
|
||||
error: error,
|
||||
dialog: dialog,
|
||||
register: register,
|
||||
select: select
|
||||
};
|
||||
|
||||
function select(name) {
|
||||
current.className = "";
|
||||
current = document.getElementById(name);
|
||||
current.className = "on";
|
||||
}
|
||||
|
||||
function closeAndRun(dialog, action) {
|
||||
return function() {
|
||||
dialog.className = '';
|
||||
|
@ -47,3 +43,17 @@ function error(message) {
|
|||
errorBox.textContent = message;
|
||||
errorBox.className = "on";
|
||||
}
|
||||
|
||||
function register(name) {
|
||||
sidebar.appendChild(Dom.make('li', {
|
||||
onClick: function() {select(name);},
|
||||
textContent: name
|
||||
}));
|
||||
}
|
||||
|
||||
function select(name) {
|
||||
sidebar.className = "";
|
||||
current.className = "";
|
||||
current = document.getElementById(name);
|
||||
current.className = "on";
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import * as TurnedCard from GUI.Card.TurnedCard;
|
|||
import * as Screen from GUI.Screen;
|
||||
import {animate, catchUp, delay, getQueue} from GUI.Screen.Game.Animation;
|
||||
import {
|
||||
dom, init as initState, play, select, sets, state
|
||||
dom, init as initState, play, select, sets, globalState
|
||||
} from GUI.Screen.Game.State;
|
||||
import Hanafuda;
|
||||
import I18n;
|
||||
|
@ -27,31 +27,33 @@ function init(gameID) {
|
|||
initMessageHandlers();
|
||||
Async.run(
|
||||
Async.bind(
|
||||
getSavedState(gameID),
|
||||
function(o) {
|
||||
getSavedStates(gameID),
|
||||
function(states) {
|
||||
return Async.sequence(
|
||||
startSession(),
|
||||
setGame(o)
|
||||
previously(states)
|
||||
);
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function backToMain() {
|
||||
window.location = '..';
|
||||
}
|
||||
|
||||
function fail(errorCode) {
|
||||
return function(f) {
|
||||
Screen.dialog({
|
||||
text: I18n.get(errorCode),
|
||||
answers: [
|
||||
{label: 'backToMain', action: function() {window.location = '..';}}
|
||||
]
|
||||
answers: [{label: 'backToMain', action: backToMain}]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function getSavedState(gameID) {
|
||||
var gameState = Save.get('games.message.Game#' + gameID);
|
||||
return gameState != undefined ? Async.wrap(gameState) : fail('gameNotFound');
|
||||
function getSavedStates(gameID) {
|
||||
var states = Save.get('games.state.Game#' + gameID);
|
||||
return states != undefined ? Async.wrap(states) : fail('gameNotFound');
|
||||
}
|
||||
|
||||
function startSession() {
|
||||
|
@ -64,66 +66,79 @@ function startSession() {
|
|||
}
|
||||
}
|
||||
|
||||
function previously(states) {
|
||||
if(states.former != undefined && states.latest.logs.length > 0) {
|
||||
return Async.sequence(
|
||||
setGame(states.former),
|
||||
handleGameMessage(states.latest)
|
||||
);
|
||||
} else {
|
||||
return setGame(states.latest);
|
||||
}
|
||||
}
|
||||
|
||||
function initMessageHandlers() {
|
||||
window.addEventListener('focus', catchUp);
|
||||
Messaging.addEventListener(["Game"], function(o) {
|
||||
delay(handleGameMessage(o));
|
||||
if(document.hasFocus() && getQueue().length == 1) {
|
||||
catchUp();
|
||||
} else {
|
||||
StatusHandler.set("♪");
|
||||
if(o.state.public.coordinates.gameID == globalState.game.public.coordinates.gameID) {
|
||||
delay(handleGameMessage(o.state));
|
||||
if(document.hasFocus() && getQueue().length == 1) {
|
||||
catchUp();
|
||||
} else {
|
||||
StatusHandler.set("♪");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function handleGameMessage(o) {
|
||||
if(o.state.public.gameState.turns == 0 || state.game == null) {
|
||||
if(o.logs.length > 0) { // but still some logs, from the previous round
|
||||
return Async.sequence(applyDiff(o), setGame(o)); // so play the diff, then set the new round
|
||||
function handleGameMessage(state) {
|
||||
if(state.public.coordinates.turn == 0) {
|
||||
if(state.logs.length > 0) { // but still some logs, from the previous round
|
||||
return Async.sequence(applyDiff(state), setGame(state)); // so play the diff, then set the new round
|
||||
} else {
|
||||
return setGame(o); // directly set a whole new game
|
||||
return setGame(state); // directly set a whole new game
|
||||
}
|
||||
} else {
|
||||
return applyDiff(o);
|
||||
return applyDiff(state);
|
||||
}
|
||||
}
|
||||
|
||||
function setGame(o) {
|
||||
function setGame(state) {
|
||||
return function(f) {
|
||||
setStatus(o.state);
|
||||
setCaptures(o.state);
|
||||
setStatus(state);
|
||||
setCaptures(state);
|
||||
[
|
||||
[sets.river, o.state.public.river, RiverCard.make],
|
||||
[sets.you.hand, o.state.playerHand, HandCard.make]
|
||||
[sets.river, state.public.river, RiverCard.make],
|
||||
[sets.you.hand, state.playerHand, HandCard.make]
|
||||
].forEach(function(args) {setCardSet.apply(null, args)});
|
||||
setTheirCards(o.state);
|
||||
handleStep(o)(f);
|
||||
setTheirCards(state);
|
||||
handleStep(state)(f);
|
||||
};
|
||||
}
|
||||
|
||||
function handleStep(o) {
|
||||
function handleStep(state) {
|
||||
return function(f) {
|
||||
handleTurnedCard(o, f);
|
||||
if(state.step == "Scored") {
|
||||
if(state.playing) {
|
||||
askKoikoi(o, f);
|
||||
handleTurnedCard(state, f);
|
||||
if(globalState.step == "Scored") {
|
||||
if(globalState.playing) {
|
||||
askKoikoi(state, f);
|
||||
} else {
|
||||
theyScored(o, f);
|
||||
theyScored(state, f);
|
||||
}
|
||||
} else if (state.step == "Over") {
|
||||
gameEnd(o, f);
|
||||
} else if (globalState.step == "Over") {
|
||||
gameEnd(state, f);
|
||||
} else {
|
||||
f();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function handleTurnedCard(o, f) {
|
||||
if(state.step == "Turned") {
|
||||
setTurned(o.state.public.step.contents);
|
||||
function handleTurnedCard(state, f) {
|
||||
if(globalState.step == "Turned") {
|
||||
setTurned(state.public.step.contents);
|
||||
} else {
|
||||
if(state.step == "ToPlay" && o.state.public.playing == o.state.public.oyake) {
|
||||
rest.className = ["card", "turn" + o.state.public.gameState.turns].join(' ');
|
||||
if(globalState.step == "ToPlay" && state.public.playing == state.public.oyake) {
|
||||
dom.rest.className = ["card", "turn" + state.public.coordinates.turn].join(' ');
|
||||
}
|
||||
if(deck.lastChild.id != "rest") {
|
||||
deck.removeChild(deck.lastChild);
|
||||
|
@ -131,7 +146,7 @@ function handleTurnedCard(o, f) {
|
|||
}
|
||||
}
|
||||
|
||||
function askKoikoi(o, f) {
|
||||
function askKoikoi(state, f) {
|
||||
Screen.dialog({
|
||||
text: I18n.get('youScored'),
|
||||
answers: [
|
||||
|
@ -141,59 +156,52 @@ function askKoikoi(o, f) {
|
|||
});
|
||||
}
|
||||
|
||||
function theyScored(o, f) {
|
||||
function theyScored(state, f) {
|
||||
Screen.dialog({
|
||||
text: I18n.get('theyScored')(players.get(o.state.public.playing)),
|
||||
text: I18n.get('theyScored')(players.get(state.public.playing)),
|
||||
answers: [
|
||||
{label: 'ok', action: f}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
function gameEnd(o, f) {
|
||||
function gameEnd(state, f) {
|
||||
var winner, maxScore;
|
||||
for(var key in o.state.public.scores) {
|
||||
if(maxScore == undefined || o.state.public.scores[key] > maxScore) {
|
||||
for(var key in state.public.scores) {
|
||||
if(maxScore == undefined || state.public.scores[key] > maxScore) {
|
||||
winner = key;
|
||||
maxScore = o.state.public.scores[key];
|
||||
maxScore = state.public.scores[key];
|
||||
}
|
||||
}
|
||||
Screen.dialog({
|
||||
text: I18n.get(Session.is(winner) ? 'won' : 'lost'),
|
||||
answers: [{
|
||||
label: 'endGame',
|
||||
action: function() {
|
||||
Messaging.send({tag: "Quit"});
|
||||
Screen.select('reception');
|
||||
f();
|
||||
}
|
||||
}]
|
||||
answers: [{label: 'endGame', action: backToMain}]
|
||||
});
|
||||
}
|
||||
|
||||
function applyDiff(o) {
|
||||
function applyDiff(state) {
|
||||
return Async.sequence.apply(null,
|
||||
o.logs.map(animate).concat(
|
||||
Async.apply(setStatus, o.state),
|
||||
handleStep(o)
|
||||
state.logs.map(animate).concat(
|
||||
Async.apply(setStatus, state),
|
||||
handleStep(state)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function setStatus(game) {
|
||||
Dom.clear(dom.status);
|
||||
state.game = game;
|
||||
state.step = game.public.step.tag;
|
||||
if(game.public.month != state.month) {
|
||||
state.month = game.public.month;
|
||||
globalState.game = game;
|
||||
globalState.step = game.public.step.tag;
|
||||
if(game.public.coordinates.month != globalState.month) {
|
||||
globalState.month = game.public.coordinates.month;
|
||||
}
|
||||
dom.status.appendChild(
|
||||
Dom.make('li', {textContent: I18n.get('monthFlower')(I18n.get(state.month))})
|
||||
Dom.make('li', {textContent: I18n.get('monthFlower')(I18n.get(globalState.month))})
|
||||
);
|
||||
var turn = null;
|
||||
state.playing = Session.is(game.public.playing);
|
||||
if(state.playing) {
|
||||
sets.you.hand.dom.classList.toggle("yourTurn", state.step == "ToPlay");
|
||||
globalState.playing = Session.is(game.public.playing);
|
||||
if(globalState.playing) {
|
||||
sets.you.hand.dom.classList.toggle("yourTurn", globalState.step == "ToPlay");
|
||||
turn = I18n.get("yourTurn");
|
||||
} else {
|
||||
sets.you.hand.dom.classList.remove("yourTurn");
|
||||
|
@ -231,7 +239,7 @@ function setCardSet(set, cardNames, constructor) {
|
|||
|
||||
function setTheirCards(game) {
|
||||
var turnsTheyPlayed = Math.floor(
|
||||
(game.public.gameState.turns + (Session.is(game.public.oyake) ? 0 : 1)) / 2
|
||||
(game.public.coordinates.turn + (Session.is(game.public.oyake) ? 0 : 1)) / 2
|
||||
);
|
||||
Dom.clear(sets.them.hand.dom);
|
||||
for(var i = 0; i < 8 - turnsTheyPlayed; i++) {
|
||||
|
@ -240,8 +248,8 @@ function setTheirCards(game) {
|
|||
}
|
||||
|
||||
function setTurned(cardName) {
|
||||
state.turnedCard = TurnedCard.make(cardName);
|
||||
if(state.playing) {
|
||||
select(state.turnedCard);
|
||||
globalState.turnedCard = TurnedCard.make(cardName);
|
||||
if(globalState.playing) {
|
||||
select(globalState.turnedCard);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,38 +14,6 @@ return {
|
|||
getQueue: getQueue
|
||||
};
|
||||
|
||||
function animate(movement) {
|
||||
return Async.bind(
|
||||
Async.apply(function() {
|
||||
var card;
|
||||
var movingCards = [];
|
||||
var side = (State.state.playing) ? 'you' : 'them';
|
||||
var dest = State.sets.river;
|
||||
if(movement.captures != undefined) {
|
||||
card = Card.make(movement.played);
|
||||
dest = State.sets[side];
|
||||
movingCards.push([State.sets.river, dest, Card.make(movement.captures)]);
|
||||
} else {
|
||||
card = RiverCard.make(movement.played);
|
||||
}
|
||||
if(movement.source == 'Hand') {
|
||||
movingCards.push([State.sets[side].hand, dest, card]);
|
||||
} else {
|
||||
var cardSet = {};
|
||||
cardSet[card.value.name] = State.state.turnedCard || TurnedCard.make(card.value.name);
|
||||
State.state.turnedCard = null;
|
||||
movingCards.push([{card: cardSet, dom: State.dom.deck}, dest, card]);
|
||||
}
|
||||
return movingCards;
|
||||
}),
|
||||
function(movingCards) {
|
||||
return Async.parallel.apply(null,
|
||||
movingCards.map(function(args) { return moveCard.apply(null, args); })
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function catchUp() {
|
||||
if(queue.length > 0) {
|
||||
var length = queue.length;
|
||||
|
@ -66,6 +34,48 @@ function getQueue() {
|
|||
return queue;
|
||||
}
|
||||
|
||||
function animate(movement) {
|
||||
return Async.bind(
|
||||
Async.apply(cardMoves, movement),
|
||||
function(movingCards) {
|
||||
return Async.parallel.apply(null, movingCards);
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
function handleCaptures(movement, movingCards, side) {
|
||||
if(movement.captures != undefined) {
|
||||
var dest = State.sets[side];
|
||||
movingCards.push(moveCard('river', dest, Card.make(movement.captures)));
|
||||
return {card: Card.make(movement.played), dest: dest};
|
||||
} else {
|
||||
return {card: RiverCard.make(movement.played), dest: State.sets.river};
|
||||
}
|
||||
}
|
||||
|
||||
function handleSource(movement, movingCards, side, cardLeft) {
|
||||
if(movement.source == 'Hand') {
|
||||
movingCards.push(moveCard(side, cardLeft.dest, cardLeft.card));
|
||||
} else {
|
||||
var cardSet = {};
|
||||
var name = cardLeft.card.value.name;
|
||||
cardSet[name] = State.globalState.turnedCard || TurnedCard.make(name);
|
||||
State.globalState.turnedCard = null;
|
||||
movingCards.push(
|
||||
moveCard({card: cardSet, dom: State.dom.deck}, cardLeft.dest, cardLeft.card)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function cardMoves(movement) {
|
||||
var card;
|
||||
var movingCards = [];
|
||||
var side = (State.globalState.playing) ? 'you' : 'them';
|
||||
var cardLeft = handleCaptures(movement, movingCards, side);
|
||||
handleSource(movement, movingCards, side, cardLeft);
|
||||
return movingCards;
|
||||
}
|
||||
|
||||
function insertCard(toSet, card) {
|
||||
if(toSet.dom != undefined) {
|
||||
toSet.card[card.value.name] = card;
|
||||
|
@ -75,34 +85,54 @@ function insertCard(toSet, card) {
|
|||
}
|
||||
}
|
||||
|
||||
function moveCard(fromSet, toSet, card) {
|
||||
var from, originalCard;
|
||||
var slot = Dom.make('li', {class: ['card', 'slot']});
|
||||
if (fromSet.card[card.value.name] != undefined) {
|
||||
originalCard = fromSet.card[card.value.name].dom;
|
||||
delete fromSet.card[card.value.name];
|
||||
function getSource(fromSet, card) {
|
||||
var source, origin;
|
||||
if(fromSet == 'river') {
|
||||
source = State.sets.river;
|
||||
} else if(typeof fromSet == 'object') {
|
||||
source = fromSet;
|
||||
} else {
|
||||
originalCard = fromSet.dom.children[fromSet.dom.children.length - 1];
|
||||
source = State.sets[fromSet].hand;
|
||||
if(fromSet == 'them') {
|
||||
origin = source.dom.children[source.dom.children.length - 1];
|
||||
}
|
||||
}
|
||||
from = originalCard.getBoundingClientRect();
|
||||
fromSet.dom.replaceChild(slot, originalCard);
|
||||
origin = origin || source.card[card.value.name].dom;
|
||||
delete source.card[card.value.name];
|
||||
return {dom: source.dom, origin: origin};
|
||||
}
|
||||
|
||||
function offsetInPlaceOf(referenceCard, targetCard) {
|
||||
var from = referenceCard.getBoundingClientRect();
|
||||
var to = targetCard.getBoundingClientRect();
|
||||
targetCard.style.left = (from.left - to.left) + 'px';
|
||||
targetCard.style.top = (from.top - to.top) + 'px';
|
||||
targetCard.classList.add('moving');
|
||||
targetCard.style.visibility = null;
|
||||
}
|
||||
|
||||
function resetPostion(card) {
|
||||
return function() {card.dom.style.left = 0; card.dom.style.top = 0;};
|
||||
}
|
||||
|
||||
function cleanUpMove(source, card, slot) {
|
||||
return function() {
|
||||
source.dom.removeChild(slot);
|
||||
card.dom.classList.remove('moving');
|
||||
};
|
||||
}
|
||||
|
||||
function moveCard(fromSet, toSet, card) {
|
||||
var source = getSource(fromSet, card);
|
||||
var slot = Dom.make('li', {class: ['card', 'slot']});
|
||||
source.dom.replaceChild(slot, source.origin);
|
||||
card.dom.style.visibility = 'hidden';
|
||||
insertCard(toSet, card);
|
||||
var to = card.dom.getBoundingClientRect();
|
||||
card.dom.style.left = (from.left - to.left) + 'px';
|
||||
card.dom.style.top = (from.top - to.top) + 'px';
|
||||
card.dom.classList.add('moving');
|
||||
card.dom.style.visibility = null;
|
||||
offsetInPlaceOf(slot, card.dom)
|
||||
return Async.sequence(
|
||||
Async.wait(10),
|
||||
Async.apply(function() {
|
||||
card.dom.style.left = 0;
|
||||
card.dom.style.top = 0;
|
||||
}),
|
||||
Async.apply(resetPostion(card)),
|
||||
Async.wait(1000),
|
||||
Async.apply(function() {
|
||||
fromSet.dom.removeChild(slot);
|
||||
card.dom.classList.remove('moving');
|
||||
})
|
||||
Async.apply(cleanUpMove(source, card, slot))
|
||||
);
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ var dom = {
|
|||
};
|
||||
var selected;
|
||||
var sets = {};
|
||||
var state = {
|
||||
var globalState = {
|
||||
game: null,
|
||||
playing: false,
|
||||
step: null,
|
||||
|
@ -25,7 +25,7 @@ return {
|
|||
play: play,
|
||||
select: select,
|
||||
sets: sets,
|
||||
state: state
|
||||
globalState: globalState
|
||||
};
|
||||
|
||||
function init() {
|
||||
|
@ -64,7 +64,7 @@ function play(move) {
|
|||
Messaging.send({
|
||||
tag: "Play",
|
||||
move: move,
|
||||
onGame: state.game
|
||||
onGame: globalState.game
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import * as Dom from UnitJS.Dom;
|
||||
import Games;
|
||||
import I18n;
|
||||
import register from GUI.Screen;
|
||||
import * as Players from GUI.Screen.Hall.Players;
|
||||
import * as GamesGUI from GUI.Screen.Hall.Games;
|
||||
import Messaging;
|
||||
|
@ -11,6 +12,7 @@ return {
|
|||
};
|
||||
|
||||
function init() {
|
||||
register('hall');
|
||||
Players.init();
|
||||
GamesGUI.init();
|
||||
Messaging.addEventListener(["Okaeri"], function(o) {
|
||||
|
@ -24,7 +26,7 @@ function init() {
|
|||
});
|
||||
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.entries.remove(o.from);
|
||||
Games.metadata.remove(o.from);
|
||||
refresh();
|
||||
});
|
||||
Messaging.addEventListener(["Relay", "Invitation"], function(o) {
|
||||
|
@ -33,12 +35,12 @@ function init() {
|
|||
GamesGUI.refresh();
|
||||
});
|
||||
Messaging.addEventListener(["Relay", "Answer"], function(o) {
|
||||
var gameEntry = Games.entries.get(o.from);
|
||||
var gameEntry = Games.metadata.get(o.from);
|
||||
if(gameEntry != undefined) {
|
||||
if(!o.message.accept) {
|
||||
gameEntry.answer = false;
|
||||
} else {
|
||||
Games.entries.remove(o.from);
|
||||
Games.metadata.remove(o.from);
|
||||
}
|
||||
GamesGUI.refresh();
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ function gameProposal(game) {
|
|||
if(game.value.answer != undefined) {
|
||||
properties.textContent = I18n.get('refusedGame')(game.value.vs.name);
|
||||
properties.class = 'clickable';
|
||||
properties.onClick = function() {Games.entries.remove(game.key); refresh();};
|
||||
properties.onClick = function() {Games.metadata.remove(game.key); refresh();};
|
||||
} else {
|
||||
properties.textContent = I18n.get('proposedGame')(
|
||||
game.value.yourTurn,
|
||||
|
@ -60,7 +60,7 @@ function pendingGame(game) {
|
|||
function answer(key, accept) {
|
||||
return function() {
|
||||
Messaging.send({tag: "Answer", accept: accept, to: key});
|
||||
Games.entries.remove(key);
|
||||
Games.metadata.remove(key);
|
||||
refresh();
|
||||
}
|
||||
}
|
||||
|
@ -79,7 +79,7 @@ function answerDialog(key) {
|
|||
}
|
||||
|
||||
function refresh() {
|
||||
var sortedGames = Games.entries.getAll();
|
||||
var sortedGames = Games.metadata.getAll();
|
||||
list.refresh(sortedGames);
|
||||
if(sortedGames.length < 1) {
|
||||
list.message.textContent = I18n.get('noGames');
|
||||
|
|
|
@ -1,37 +1,53 @@
|
|||
import I18n;
|
||||
import GUI.ConnectedForm;
|
||||
import select from GUI.Screen;
|
||||
import {dialog, register, select} from GUI.Screen;
|
||||
import Games;
|
||||
import Messaging;
|
||||
import Session;
|
||||
import Save;
|
||||
|
||||
var login;
|
||||
var form;
|
||||
var loginForm;
|
||||
var accountLoader;
|
||||
var accountLoaderForm;
|
||||
|
||||
return {
|
||||
init: init
|
||||
};
|
||||
|
||||
function init() {
|
||||
login = GUI.ConnectedForm.get('login');
|
||||
form = login.root;
|
||||
initDOM();
|
||||
register('login');
|
||||
initLogin();
|
||||
initLoader();
|
||||
initMessageHandlers();
|
||||
var name = Save.get('player.name');
|
||||
if(name != undefined && name.length > 0) {
|
||||
form.you.value = name;
|
||||
login.enable();
|
||||
}
|
||||
restoreName();
|
||||
}
|
||||
|
||||
function initDOM() {
|
||||
form.getElementsByTagName('label')[0].textContent = I18n.get('pickName');
|
||||
form.join.value = I18n.get('join');
|
||||
form.addEventListener('submit', function(e) {
|
||||
function initLogin() {
|
||||
login = GUI.ConnectedForm.get('loginForm');
|
||||
loginForm = login.root;
|
||||
loginForm.getElementsByTagName('label')[0].textContent = I18n.get('pickName');
|
||||
loginForm.join.value = I18n.get('join');
|
||||
loginForm.addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
Session.start(form.you.value);
|
||||
if(!Session.isLoggedIn()) {
|
||||
Session.start(loginForm.you.value);
|
||||
} else {
|
||||
Messaging.send({tag: 'Hello', name: loginForm.you.value});
|
||||
}
|
||||
});
|
||||
loginForm.you.addEventListener("input", validate);
|
||||
}
|
||||
|
||||
function initLoader() {
|
||||
accountLoader = GUI.ConnectedForm.get('accountLoader');
|
||||
accountLoaderForm = accountLoader.root;
|
||||
accountLoaderForm.getElementsByTagName('label')[0].textContent = I18n.get('pickFile');
|
||||
accountLoaderForm.doLoad.value = I18n.get('doLoad');
|
||||
accountLoaderForm.addEventListener('submit', loadAccountConfirm);
|
||||
accountLoaderForm.pickFile.addEventListener("input", function() {
|
||||
accountLoader.enable();
|
||||
});
|
||||
form.you.addEventListener("input", validate);
|
||||
}
|
||||
|
||||
function initMessageHandlers() {
|
||||
|
@ -42,6 +58,44 @@ function initMessageHandlers() {
|
|||
});
|
||||
}
|
||||
|
||||
function loadAccountConfirm(e) {
|
||||
e.preventDefault();
|
||||
if(Save.get('player') != undefined) {
|
||||
dialog({
|
||||
text: I18n.get('warnExistingAccount'),
|
||||
answers: [
|
||||
{label: 'confirmReplace', action: loadAccount},
|
||||
{label: 'cancel', action: function() {}}
|
||||
]
|
||||
});
|
||||
} else {
|
||||
loadAccount();
|
||||
}
|
||||
}
|
||||
|
||||
function loadAccount() {
|
||||
var fileReader = new FileReader();
|
||||
fileReader.addEventListener('load', function() {
|
||||
Save.set(null, JSON.parse(fileReader.result));
|
||||
var name = restoreName();
|
||||
Games.reload();
|
||||
Messaging.reset();
|
||||
if(name != undefined) {
|
||||
Session.start(name);
|
||||
}
|
||||
});
|
||||
fileReader.readAsText(accountLoaderForm.pickFile.files[0]);
|
||||
}
|
||||
|
||||
function restoreName() {
|
||||
var name = Save.get('player.name');
|
||||
if(name != undefined && name.length > 0) {
|
||||
loginForm.you.value = name;
|
||||
login.enable();
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
function validate(e) {
|
||||
login.enable(e.target.value != "");
|
||||
}
|
||||
|
|
25
js/GUI/Screen/Settings.js
Normal file
25
js/GUI/Screen/Settings.js
Normal file
|
@ -0,0 +1,25 @@
|
|||
import I18n;
|
||||
import register from GUI.Screen;
|
||||
import Save;
|
||||
|
||||
var button;
|
||||
|
||||
return {
|
||||
init: init
|
||||
};
|
||||
|
||||
function init() {
|
||||
register('settings');
|
||||
var exportRoot = document.getElementById('export');
|
||||
var label = exportRoot.getElementsByTagName('span')[0];
|
||||
button = exportRoot.getElementsByTagName('a')[0];
|
||||
|
||||
label.textContent = I18n.get('exportLabel');
|
||||
button.textContent = I18n.get('doExport');
|
||||
button.addEventListener('click', doExport);
|
||||
}
|
||||
|
||||
function doExport() {
|
||||
var data = encodeURIComponent(JSON.stringify(Save.get()));
|
||||
button.href = 'data:application/json,' + data;
|
||||
}
|
97
js/Games.js
97
js/Games.js
|
@ -1,3 +1,4 @@
|
|||
import Flower from Hanafuda;
|
||||
import Messaging;
|
||||
import player from Room;
|
||||
import Save;
|
||||
|
@ -5,16 +6,79 @@ import Session;
|
|||
import Table;
|
||||
import Time;
|
||||
|
||||
var entries = Table.make(function(o) {return o.value.date;});
|
||||
entries.insertAll(Save.get('games.entry') || {});
|
||||
var states = Table.make(function(o) {return 0;});
|
||||
var metadata = Table.make(function(o) {return o.value.date;});
|
||||
reload();
|
||||
initMessageHandlers();
|
||||
|
||||
return {
|
||||
entries: entries,
|
||||
proposal: proposal
|
||||
metadata: metadata,
|
||||
proposal: proposal,
|
||||
reload: reload
|
||||
};
|
||||
|
||||
function makeEntry(state) {
|
||||
function initMessageHandlers() {
|
||||
Messaging.addEventListener(["Game"], function(o) {
|
||||
var gameID = o.state.public.coordinates.gameID;
|
||||
pushState(gameID, o.state);
|
||||
metadata.insert(gameID, getMetadata(o.state));
|
||||
metadata.save('games.metadata');
|
||||
});
|
||||
|
||||
Messaging.addEventListener(["LogIn"], function(o) {
|
||||
var gamesAgainst = states.getAll(
|
||||
function(game) {return isAgainst(game.value, o.from);}
|
||||
);
|
||||
gamesAgainst.forEach(function(game) {
|
||||
Messaging.send({
|
||||
tag: "Sync",
|
||||
latestKnown: game.value.latest.public.coordinates,
|
||||
to: o.from
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Messaging.addEventListener(["Relay", "Sync"], function(o) {
|
||||
var gameID = o.message.latestKnown.gameID;
|
||||
var state = states.get(gameID);
|
||||
if(state != undefined) {
|
||||
switch(compare(state.latest.public.coordinates, o.message.latestKnown)) {
|
||||
case -1: Messaging.send({tag: "Yield", onGameID: gameID, to: o.from});
|
||||
case 1: Messaging.send({tag: "Share", gameSave: state.latest});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Messaging.addEventListener(["Relay", "Yield"], function(o) {
|
||||
var gameID = o.message.onGameID;
|
||||
var state = states.get(gameID);
|
||||
if(state != undefined && isAgainst(state, o.from)) {
|
||||
Messaging.send({tag: "Share", gameSave: state.latest});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function isAgainst(state, playerID) {
|
||||
return state.latest.public.nextPlayer[Session.getKey()] == playerID;
|
||||
}
|
||||
|
||||
function compare(gameCoordinatesA, gameCoordinatesB) {
|
||||
var monthIndexA = Flower[gameCoordinatesA.month];
|
||||
var monthIndexB = Flower[gameCoordinatesB.month];
|
||||
if(monthIndexA < monthIndexB) {
|
||||
return -1;
|
||||
} else if(monthIndexA > monthIndexB) {
|
||||
return 1;
|
||||
} else if(gameCoordinatesA.turn < gameCoordinatesB.turn) {
|
||||
return -1;
|
||||
} else if(gameCoordinatesA.turn > gameCoordinatesB.turn) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
function getMetadata(state) {
|
||||
var sessionKey = Session.getKey();
|
||||
return Time.timestamp({
|
||||
vs: player(state.public.nextPlayer[sessionKey]),
|
||||
|
@ -23,18 +87,23 @@ function makeEntry(state) {
|
|||
}
|
||||
|
||||
function proposal(playerID, yourTurn) {
|
||||
entries.insert(
|
||||
metadata.insert(
|
||||
playerID,
|
||||
Time.timestamp({vs: player(playerID), yourTurn: yourTurn})
|
||||
);
|
||||
}
|
||||
|
||||
function initMessageHandlers() {
|
||||
Messaging.addEventListener(["Game"], function(o) {
|
||||
var gameID = o.state.public.gameState.gameID;
|
||||
var entry = makeEntry(o.state);
|
||||
Save.set("games.message." + gameID, o);
|
||||
Save.set("games.entry." + gameID, entry);
|
||||
entries.insert(gameID, entry);
|
||||
});
|
||||
function pushState(gameID, newState) {
|
||||
if(states.get(gameID) == undefined) {
|
||||
states.insert(gameID, {});
|
||||
}
|
||||
var state = states.get(gameID);
|
||||
state.former = state.latest;
|
||||
state.latest = newState;
|
||||
states.save('games.state');
|
||||
}
|
||||
|
||||
function reload() {
|
||||
states.load('games.state');
|
||||
metadata.load('games.metadata');
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import * as Login from GUI.Screen.Login;
|
||||
import * as Hall from GUI.Screen.Hall;
|
||||
import * as Game from GUI.Screen.Game;
|
||||
import * as Settings from GUI.Screen.Settings;
|
||||
|
||||
var gamePath = window.location.pathname.match(/\/game\/([0-9A-Fa-f]+)/);
|
||||
|
||||
|
@ -9,4 +10,5 @@ if(gamePath) {
|
|||
} else {
|
||||
Login.init();
|
||||
Hall.init();
|
||||
Settings.init();
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ init();
|
|||
return {
|
||||
addEventListener: addEventListener,
|
||||
isOn: isOn,
|
||||
reset: reset,
|
||||
send: send
|
||||
};
|
||||
|
||||
|
@ -136,3 +137,9 @@ function ping() {
|
|||
}
|
||||
}, keepAlivePeriod * s);
|
||||
}
|
||||
|
||||
function reset() {
|
||||
on = false; // this should be handled by the close event but when other operations are performed right after the reset, the event might not have had time to fire and the other functions, in particular send(), will assume the socket is still open and fail to actually send them instead of gently queueing them for when connectivity goes back up
|
||||
reconnectDelay = 0.125; // since we're forcing the connection to close, there's no reason to assume the server went down so we manually lower the reconnectDelay to make the reconnection more fluid (there's no point making the user wait the usual 1 s. and the worse that could happen is we overflow the server a little with 3 connection attempts in 1 s.)
|
||||
ws.close();
|
||||
}
|
||||
|
|
32
js/Room.js
32
js/Room.js
|
@ -14,19 +14,21 @@ function player(key) {
|
|||
return {id: key, name: players.get(key)};
|
||||
}
|
||||
|
||||
function initMessageHandlers() {
|
||||
Messaging.addEventListener(["Okaeri"], function(o) {
|
||||
players.insertAll(o.room);
|
||||
});
|
||||
Messaging.addEventListener(["Welcome"], function(o) {
|
||||
players.insertAll(o.room);
|
||||
});
|
||||
Messaging.addEventListener(["LogIn"], function(o) {
|
||||
if(!Session.is(o.from)) {
|
||||
players.insert(o.from, o.as);
|
||||
}
|
||||
});
|
||||
Messaging.addEventListener(["LogOut"], function(o) {
|
||||
players.remove(o.from);
|
||||
});
|
||||
function enterAll(o) {
|
||||
for(key in o) {
|
||||
enterPlayer(key, o[key]);
|
||||
}
|
||||
}
|
||||
|
||||
function enterPlayer(key, name) {
|
||||
if(!Session.is(key)) {
|
||||
players.insert(key, name);
|
||||
}
|
||||
}
|
||||
|
||||
function initMessageHandlers() {
|
||||
Messaging.addEventListener(["Okaeri"], function(o) {enterAll(o.room);});
|
||||
Messaging.addEventListener(["Welcome"], function(o) {enterAll(o.room);});
|
||||
Messaging.addEventListener(["LogIn"], function(o) {enterPlayer(o.from, o.as);});
|
||||
Messaging.addEventListener(["LogOut"], function(o) {players.remove(o.from);});
|
||||
}
|
||||
|
|
|
@ -27,6 +27,8 @@ function get(key) {
|
|||
} else {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return save;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -17,6 +17,8 @@ Messaging.addEventListener(["LogIn"], function(o) {
|
|||
}
|
||||
});
|
||||
|
||||
Messaging.addEventListener('close', function() {loggedIn = false;});
|
||||
|
||||
return {
|
||||
is: is,
|
||||
getKey: getKey,
|
||||
|
|
14
js/Table.js
14
js/Table.js
|
@ -1,3 +1,4 @@
|
|||
import Save;
|
||||
import of from UnitJS.Fun;
|
||||
import {compare, of, proj} from UnitJS.Fun;
|
||||
|
||||
|
@ -8,7 +9,9 @@ function Table(sortCriterion) {
|
|||
getAll: getAll,
|
||||
insert: insert,
|
||||
insertAll: insertAll,
|
||||
remove: remove
|
||||
load: load,
|
||||
remove: remove,
|
||||
save
|
||||
};
|
||||
|
||||
function get(key) {
|
||||
|
@ -22,7 +25,6 @@ function Table(sortCriterion) {
|
|||
.sort(compare(sortCriterion));
|
||||
}
|
||||
|
||||
|
||||
function insert(key, value) {
|
||||
items[key] = value;
|
||||
}
|
||||
|
@ -33,9 +35,17 @@ function Table(sortCriterion) {
|
|||
}
|
||||
}
|
||||
|
||||
function load(path) {
|
||||
items = Save.get(path) || {};
|
||||
}
|
||||
|
||||
function remove(key) {
|
||||
delete items[key];
|
||||
}
|
||||
|
||||
function save(path) {
|
||||
Save.set(path, items);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
|
@ -15,9 +15,14 @@ return {
|
|||
accept: "Let's go !",
|
||||
alone: "No one to play with yet ! Wait a little",
|
||||
backToMain: "Back to main menu",
|
||||
cancel: "Cancel",
|
||||
confirmReplace: "Yes, do replace my existing account",
|
||||
decline: "No thanks",
|
||||
doExport: "Save",
|
||||
doLoad: "Load",
|
||||
endRound: "End the round",
|
||||
endGame: "Return to main menu",
|
||||
exportLabel: "Save your account data to load it somewhere else",
|
||||
gameNotFound: "You followed a fishy link, this game is no more",
|
||||
join: "Join",
|
||||
invite: "Invite",
|
||||
|
@ -36,6 +41,7 @@ return {
|
|||
var whose = yourTurn ? 'your' : name + "'s";
|
||||
return 'Game vs. ' + name + ' (' + whose + ' turn)';
|
||||
},
|
||||
pickFile: "Load an existing account",
|
||||
pickName: "Pick a name you like",
|
||||
playing: function(name) {
|
||||
return name + " is playing";
|
||||
|
@ -45,7 +51,7 @@ return {
|
|||
if(yourTurn) {
|
||||
return name + proposed;
|
||||
} else {
|
||||
return "You" + proposed + " to " + name;
|
||||
return "You" + proposed + " to " + name;
|
||||
}
|
||||
},
|
||||
questionAccept: "Do you want to start the game ?",
|
||||
|
@ -56,6 +62,7 @@ return {
|
|||
theyScored: function(name) {
|
||||
return name + " scored";
|
||||
},
|
||||
warnExistingAccount: "Your current account will be erased and lost forever",
|
||||
won: "You won !",
|
||||
yourTurn: "Your turn",
|
||||
youScored: "You scored ! Do you want to get your points and end the round or KoiKoi ?"
|
||||
|
@ -76,9 +83,14 @@ return {
|
|||
accept: "C'est parti !",
|
||||
alone: "Personne pour jouer pour l'instant ! Attendez un peu",
|
||||
backToMain: "Retourner au menu principal",
|
||||
cancel: "Annuler",
|
||||
confirmReplace: "Oui, remplacer mon compte",
|
||||
decline: "Non merci",
|
||||
doExport: "Télécharger",
|
||||
doLoad: "Charger",
|
||||
endRound: "Finir la manche",
|
||||
endGame: "Retourner au menu principal",
|
||||
exportLabel: "Télécharger les données de votre compte pour les charger ailleurs",
|
||||
gameNotFound: "Ce lien est louche, la partie n'est plus",
|
||||
join: "Entrer",
|
||||
invite: "Inviter",
|
||||
|
@ -97,6 +109,7 @@ return {
|
|||
var whose = yourTurn ? 'vous' : name;
|
||||
return 'Partie en cours contre ' + name + ' (à ' + whose + ')';
|
||||
},
|
||||
pickFile: "Charger un compte existant",
|
||||
pickName: "Choisissez votre nom",
|
||||
playing: function(name) {
|
||||
return "C'est à " + name;
|
||||
|
@ -117,6 +130,7 @@ return {
|
|||
theyScored: function(name) {
|
||||
return name + " a marqué";
|
||||
},
|
||||
warnExistingAccount: "Le compte existant sera remplacé et perdu à tout jamais",
|
||||
won: "Vous avez gagné !",
|
||||
yourTurn: "À vous",
|
||||
youScored: "Vous avez marqué ! Voulez-vous empocher vos gains et terminer la manche ou faire KoiKoi ?"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
body > div {
|
||||
body > div, ul#sidebar.unactive {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
@ -52,3 +52,18 @@ body > div.on {
|
|||
#error.on {
|
||||
display: block;
|
||||
}
|
||||
|
||||
ul#sidebar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
ul#sidebar > li {
|
||||
cursor: pointer;
|
||||
float: left;
|
||||
padding: 0.5em 1em;
|
||||
}
|
||||
|
|
8
skin/settings.css
Normal file
8
skin/settings.css
Normal file
|
@ -0,0 +1,8 @@
|
|||
#export a {
|
||||
padding: 0.5em;
|
||||
margin: 0 1em;
|
||||
border-radius: 0.5em;
|
||||
text-decoration: none;
|
||||
background: #35e;
|
||||
color: #fff;
|
||||
}
|
Loading…
Reference in a new issue