Compare commits
No commits in common. "main" and "card-modules" have entirely different histories.
main
...
card-modul
21 changed files with 193 additions and 467 deletions
18
index.html
18
index.html
|
@ -7,24 +7,15 @@
|
|||
<link rel="stylesheet" href="skin.css" type="text/css"/>
|
||||
</head>
|
||||
<body>
|
||||
<ul id="sidebar" class="unactive"></ul>
|
||||
<div id="login" class="on">
|
||||
<div id="reception" class="on">
|
||||
<h1>KoiKoi</h1>
|
||||
<form id="loginForm">
|
||||
<form id="login">
|
||||
<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">
|
||||
|
@ -43,11 +34,6 @@
|
|||
<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.globalState.playing && State.globalState.step == "ToPlay") {
|
||||
if(State.state.playing && State.state.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.globalState.step == 'ToPlay' ?
|
||||
State.state.step == 'ToPlay' ?
|
||||
{capture: [withCard, name]} : {choose: name}
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
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() {
|
||||
|
@ -11,10 +10,15 @@ 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 = '';
|
||||
|
@ -43,17 +47,3 @@ 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, globalState
|
||||
dom, init as initState, play, select, sets, state
|
||||
} from GUI.Screen.Game.State;
|
||||
import Hanafuda;
|
||||
import I18n;
|
||||
|
@ -27,33 +27,31 @@ function init(gameID) {
|
|||
initMessageHandlers();
|
||||
Async.run(
|
||||
Async.bind(
|
||||
getSavedStates(gameID),
|
||||
function(states) {
|
||||
getSavedState(gameID),
|
||||
function(o) {
|
||||
return Async.sequence(
|
||||
startSession(),
|
||||
previously(states)
|
||||
setGame(o)
|
||||
);
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function backToMain() {
|
||||
window.location = '..';
|
||||
}
|
||||
|
||||
function fail(errorCode) {
|
||||
return function(f) {
|
||||
Screen.dialog({
|
||||
text: I18n.get(errorCode),
|
||||
answers: [{label: 'backToMain', action: backToMain}]
|
||||
answers: [
|
||||
{label: 'backToMain', action: function() {window.location = '..';}}
|
||||
]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function getSavedStates(gameID) {
|
||||
var states = Save.get('games.state.Game#' + gameID);
|
||||
return states != undefined ? Async.wrap(states) : fail('gameNotFound');
|
||||
function getSavedState(gameID) {
|
||||
var gameState = Save.get('games.message.Game#' + gameID);
|
||||
return gameState != undefined ? Async.wrap(gameState) : fail('gameNotFound');
|
||||
}
|
||||
|
||||
function startSession() {
|
||||
|
@ -66,79 +64,66 @@ 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) {
|
||||
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("♪");
|
||||
}
|
||||
delay(handleGameMessage(o));
|
||||
if(document.hasFocus() && getQueue().length == 1) {
|
||||
catchUp();
|
||||
} else {
|
||||
StatusHandler.set("♪");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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
|
||||
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
|
||||
} else {
|
||||
return setGame(state); // directly set a whole new game
|
||||
return setGame(o); // directly set a whole new game
|
||||
}
|
||||
} else {
|
||||
return applyDiff(state);
|
||||
return applyDiff(o);
|
||||
}
|
||||
}
|
||||
|
||||
function setGame(state) {
|
||||
function setGame(o) {
|
||||
return function(f) {
|
||||
setStatus(state);
|
||||
setCaptures(state);
|
||||
setStatus(o.state);
|
||||
setCaptures(o.state);
|
||||
[
|
||||
[sets.river, state.public.river, RiverCard.make],
|
||||
[sets.you.hand, state.playerHand, HandCard.make]
|
||||
[sets.river, o.state.public.river, RiverCard.make],
|
||||
[sets.you.hand, o.state.playerHand, HandCard.make]
|
||||
].forEach(function(args) {setCardSet.apply(null, args)});
|
||||
setTheirCards(state);
|
||||
handleStep(state)(f);
|
||||
setTheirCards(o.state);
|
||||
handleStep(o)(f);
|
||||
};
|
||||
}
|
||||
|
||||
function handleStep(state) {
|
||||
function handleStep(o) {
|
||||
return function(f) {
|
||||
handleTurnedCard(state, f);
|
||||
if(globalState.step == "Scored") {
|
||||
if(globalState.playing) {
|
||||
askKoikoi(state, f);
|
||||
handleTurnedCard(o, f);
|
||||
if(state.step == "Scored") {
|
||||
if(state.playing) {
|
||||
askKoikoi(o, f);
|
||||
} else {
|
||||
theyScored(state, f);
|
||||
theyScored(o, f);
|
||||
}
|
||||
} else if (globalState.step == "Over") {
|
||||
gameEnd(state, f);
|
||||
} else if (state.step == "Over") {
|
||||
gameEnd(o, f);
|
||||
} else {
|
||||
f();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function handleTurnedCard(state, f) {
|
||||
if(globalState.step == "Turned") {
|
||||
setTurned(state.public.step.contents);
|
||||
function handleTurnedCard(o, f) {
|
||||
if(state.step == "Turned") {
|
||||
setTurned(o.state.public.step.contents);
|
||||
} else {
|
||||
if(globalState.step == "ToPlay" && state.public.playing == state.public.oyake) {
|
||||
dom.rest.className = ["card", "turn" + state.public.coordinates.turn].join(' ');
|
||||
if(state.step == "ToPlay" && o.state.public.playing == o.state.public.oyake) {
|
||||
rest.className = ["card", "turn" + o.state.public.gameState.turns].join(' ');
|
||||
}
|
||||
if(deck.lastChild.id != "rest") {
|
||||
deck.removeChild(deck.lastChild);
|
||||
|
@ -146,7 +131,7 @@ function handleTurnedCard(state, f) {
|
|||
}
|
||||
}
|
||||
|
||||
function askKoikoi(state, f) {
|
||||
function askKoikoi(o, f) {
|
||||
Screen.dialog({
|
||||
text: I18n.get('youScored'),
|
||||
answers: [
|
||||
|
@ -156,52 +141,59 @@ function askKoikoi(state, f) {
|
|||
});
|
||||
}
|
||||
|
||||
function theyScored(state, f) {
|
||||
function theyScored(o, f) {
|
||||
Screen.dialog({
|
||||
text: I18n.get('theyScored')(players.get(state.public.playing)),
|
||||
text: I18n.get('theyScored')(players.get(o.state.public.playing)),
|
||||
answers: [
|
||||
{label: 'ok', action: f}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
function gameEnd(state, f) {
|
||||
function gameEnd(o, f) {
|
||||
var winner, maxScore;
|
||||
for(var key in state.public.scores) {
|
||||
if(maxScore == undefined || state.public.scores[key] > maxScore) {
|
||||
for(var key in o.state.public.scores) {
|
||||
if(maxScore == undefined || o.state.public.scores[key] > maxScore) {
|
||||
winner = key;
|
||||
maxScore = state.public.scores[key];
|
||||
maxScore = o.state.public.scores[key];
|
||||
}
|
||||
}
|
||||
Screen.dialog({
|
||||
text: I18n.get(Session.is(winner) ? 'won' : 'lost'),
|
||||
answers: [{label: 'endGame', action: backToMain}]
|
||||
answers: [{
|
||||
label: 'endGame',
|
||||
action: function() {
|
||||
Messaging.send({tag: "Quit"});
|
||||
Screen.select('reception');
|
||||
f();
|
||||
}
|
||||
}]
|
||||
});
|
||||
}
|
||||
|
||||
function applyDiff(state) {
|
||||
function applyDiff(o) {
|
||||
return Async.sequence.apply(null,
|
||||
state.logs.map(animate).concat(
|
||||
Async.apply(setStatus, state),
|
||||
handleStep(state)
|
||||
o.logs.map(animate).concat(
|
||||
Async.apply(setStatus, o.state),
|
||||
handleStep(o)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function setStatus(game) {
|
||||
Dom.clear(dom.status);
|
||||
globalState.game = game;
|
||||
globalState.step = game.public.step.tag;
|
||||
if(game.public.coordinates.month != globalState.month) {
|
||||
globalState.month = game.public.coordinates.month;
|
||||
state.game = game;
|
||||
state.step = game.public.step.tag;
|
||||
if(game.public.month != state.month) {
|
||||
state.month = game.public.month;
|
||||
}
|
||||
dom.status.appendChild(
|
||||
Dom.make('li', {textContent: I18n.get('monthFlower')(I18n.get(globalState.month))})
|
||||
Dom.make('li', {textContent: I18n.get('monthFlower')(I18n.get(state.month))})
|
||||
);
|
||||
var turn = null;
|
||||
globalState.playing = Session.is(game.public.playing);
|
||||
if(globalState.playing) {
|
||||
sets.you.hand.dom.classList.toggle("yourTurn", globalState.step == "ToPlay");
|
||||
state.playing = Session.is(game.public.playing);
|
||||
if(state.playing) {
|
||||
sets.you.hand.dom.classList.toggle("yourTurn", state.step == "ToPlay");
|
||||
turn = I18n.get("yourTurn");
|
||||
} else {
|
||||
sets.you.hand.dom.classList.remove("yourTurn");
|
||||
|
@ -239,7 +231,7 @@ function setCardSet(set, cardNames, constructor) {
|
|||
|
||||
function setTheirCards(game) {
|
||||
var turnsTheyPlayed = Math.floor(
|
||||
(game.public.coordinates.turn + (Session.is(game.public.oyake) ? 0 : 1)) / 2
|
||||
(game.public.gameState.turns + (Session.is(game.public.oyake) ? 0 : 1)) / 2
|
||||
);
|
||||
Dom.clear(sets.them.hand.dom);
|
||||
for(var i = 0; i < 8 - turnsTheyPlayed; i++) {
|
||||
|
@ -248,8 +240,8 @@ function setTheirCards(game) {
|
|||
}
|
||||
|
||||
function setTurned(cardName) {
|
||||
globalState.turnedCard = TurnedCard.make(cardName);
|
||||
if(globalState.playing) {
|
||||
select(globalState.turnedCard);
|
||||
state.turnedCard = TurnedCard.make(cardName);
|
||||
if(state.playing) {
|
||||
select(state.turnedCard);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,38 @@ 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;
|
||||
|
@ -34,48 +66,6 @@ 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;
|
||||
|
@ -85,54 +75,34 @@ function insertCard(toSet, card) {
|
|||
}
|
||||
}
|
||||
|
||||
function getSource(fromSet, card) {
|
||||
var source, origin;
|
||||
if(fromSet == 'river') {
|
||||
source = State.sets.river;
|
||||
} else if(typeof fromSet == 'object') {
|
||||
source = fromSet;
|
||||
} else {
|
||||
source = State.sets[fromSet].hand;
|
||||
if(fromSet == 'them') {
|
||||
origin = source.dom.children[source.dom.children.length - 1];
|
||||
}
|
||||
}
|
||||
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 from, originalCard;
|
||||
var slot = Dom.make('li', {class: ['card', 'slot']});
|
||||
source.dom.replaceChild(slot, source.origin);
|
||||
if (fromSet.card[card.value.name] != undefined) {
|
||||
originalCard = fromSet.card[card.value.name].dom;
|
||||
delete fromSet.card[card.value.name];
|
||||
} else {
|
||||
originalCard = fromSet.dom.children[fromSet.dom.children.length - 1];
|
||||
}
|
||||
from = originalCard.getBoundingClientRect();
|
||||
fromSet.dom.replaceChild(slot, originalCard);
|
||||
card.dom.style.visibility = 'hidden';
|
||||
insertCard(toSet, card);
|
||||
offsetInPlaceOf(slot, card.dom)
|
||||
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;
|
||||
return Async.sequence(
|
||||
Async.wait(10),
|
||||
Async.apply(resetPostion(card)),
|
||||
Async.apply(function() {
|
||||
card.dom.style.left = 0;
|
||||
card.dom.style.top = 0;
|
||||
}),
|
||||
Async.wait(1000),
|
||||
Async.apply(cleanUpMove(source, card, slot))
|
||||
Async.apply(function() {
|
||||
fromSet.dom.removeChild(slot);
|
||||
card.dom.classList.remove('moving');
|
||||
})
|
||||
);
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ var dom = {
|
|||
};
|
||||
var selected;
|
||||
var sets = {};
|
||||
var globalState = {
|
||||
var state = {
|
||||
game: null,
|
||||
playing: false,
|
||||
step: null,
|
||||
|
@ -25,7 +25,7 @@ return {
|
|||
play: play,
|
||||
select: select,
|
||||
sets: sets,
|
||||
globalState: globalState
|
||||
state: state
|
||||
};
|
||||
|
||||
function init() {
|
||||
|
@ -64,7 +64,7 @@ function play(move) {
|
|||
Messaging.send({
|
||||
tag: "Play",
|
||||
move: move,
|
||||
onGame: globalState.game
|
||||
onGame: state.game
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
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;
|
||||
|
@ -12,7 +11,6 @@ return {
|
|||
};
|
||||
|
||||
function init() {
|
||||
register('hall');
|
||||
Players.init();
|
||||
GamesGUI.init();
|
||||
Messaging.addEventListener(["Okaeri"], function(o) {
|
||||
|
@ -26,7 +24,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.metadata.remove(o.from);
|
||||
Games.entries.remove(o.from);
|
||||
refresh();
|
||||
});
|
||||
Messaging.addEventListener(["Relay", "Invitation"], function(o) {
|
||||
|
@ -35,12 +33,12 @@ function init() {
|
|||
GamesGUI.refresh();
|
||||
});
|
||||
Messaging.addEventListener(["Relay", "Answer"], function(o) {
|
||||
var gameEntry = Games.metadata.get(o.from);
|
||||
var gameEntry = Games.entries.get(o.from);
|
||||
if(gameEntry != undefined) {
|
||||
if(!o.message.accept) {
|
||||
gameEntry.answer = false;
|
||||
} else {
|
||||
Games.metadata.remove(o.from);
|
||||
Games.entries.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.metadata.remove(game.key); refresh();};
|
||||
properties.onClick = function() {Games.entries.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.metadata.remove(key);
|
||||
Games.entries.remove(key);
|
||||
refresh();
|
||||
}
|
||||
}
|
||||
|
@ -79,7 +79,7 @@ function answerDialog(key) {
|
|||
}
|
||||
|
||||
function refresh() {
|
||||
var sortedGames = Games.metadata.getAll();
|
||||
var sortedGames = Games.entries.getAll();
|
||||
list.refresh(sortedGames);
|
||||
if(sortedGames.length < 1) {
|
||||
list.message.textContent = I18n.get('noGames');
|
||||
|
|
|
@ -1,53 +1,37 @@
|
|||
import I18n;
|
||||
import GUI.ConnectedForm;
|
||||
import {dialog, register, select} from GUI.Screen;
|
||||
import Games;
|
||||
import select from GUI.Screen;
|
||||
import Messaging;
|
||||
import Session;
|
||||
import Save;
|
||||
|
||||
var login;
|
||||
var loginForm;
|
||||
var accountLoader;
|
||||
var accountLoaderForm;
|
||||
var form;
|
||||
|
||||
return {
|
||||
init: init
|
||||
};
|
||||
|
||||
function init() {
|
||||
register('login');
|
||||
initLogin();
|
||||
initLoader();
|
||||
login = GUI.ConnectedForm.get('login');
|
||||
form = login.root;
|
||||
initDOM();
|
||||
initMessageHandlers();
|
||||
restoreName();
|
||||
var name = Save.get('player.name');
|
||||
if(name != undefined && name.length > 0) {
|
||||
form.you.value = name;
|
||||
login.enable();
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
function initDOM() {
|
||||
form.getElementsByTagName('label')[0].textContent = I18n.get('pickName');
|
||||
form.join.value = I18n.get('join');
|
||||
form.addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
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();
|
||||
Session.start(form.you.value);
|
||||
});
|
||||
form.you.addEventListener("input", validate);
|
||||
}
|
||||
|
||||
function initMessageHandlers() {
|
||||
|
@ -58,44 +42,6 @@ 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 != "");
|
||||
}
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
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,4 +1,3 @@
|
|||
import Flower from Hanafuda;
|
||||
import Messaging;
|
||||
import player from Room;
|
||||
import Save;
|
||||
|
@ -6,79 +5,16 @@ import Session;
|
|||
import Table;
|
||||
import Time;
|
||||
|
||||
var states = Table.make(function(o) {return 0;});
|
||||
var metadata = Table.make(function(o) {return o.value.date;});
|
||||
reload();
|
||||
var entries = Table.make(function(o) {return o.value.date;});
|
||||
entries.insertAll(Save.get('games.entry') || {});
|
||||
initMessageHandlers();
|
||||
|
||||
return {
|
||||
metadata: metadata,
|
||||
proposal: proposal,
|
||||
reload: reload
|
||||
entries: entries,
|
||||
proposal: proposal
|
||||
};
|
||||
|
||||
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) {
|
||||
function makeEntry(state) {
|
||||
var sessionKey = Session.getKey();
|
||||
return Time.timestamp({
|
||||
vs: player(state.public.nextPlayer[sessionKey]),
|
||||
|
@ -87,23 +23,18 @@ function getMetadata(state) {
|
|||
}
|
||||
|
||||
function proposal(playerID, yourTurn) {
|
||||
metadata.insert(
|
||||
entries.insert(
|
||||
playerID,
|
||||
Time.timestamp({vs: player(playerID), yourTurn: yourTurn})
|
||||
);
|
||||
}
|
||||
|
||||
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');
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
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]+)/);
|
||||
|
||||
|
@ -10,5 +9,4 @@ if(gamePath) {
|
|||
} else {
|
||||
Login.init();
|
||||
Hall.init();
|
||||
Settings.init();
|
||||
}
|
||||
|
|
|
@ -20,7 +20,6 @@ init();
|
|||
return {
|
||||
addEventListener: addEventListener,
|
||||
isOn: isOn,
|
||||
reset: reset,
|
||||
send: send
|
||||
};
|
||||
|
||||
|
@ -137,9 +136,3 @@ 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();
|
||||
}
|
||||
|
|
30
js/Room.js
30
js/Room.js
|
@ -14,21 +14,19 @@ function player(key) {
|
|||
return {id: key, name: players.get(key)};
|
||||
}
|
||||
|
||||
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);});
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -27,8 +27,6 @@ function get(key) {
|
|||
} else {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return save;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -17,8 +17,6 @@ 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,4 +1,3 @@
|
|||
import Save;
|
||||
import of from UnitJS.Fun;
|
||||
import {compare, of, proj} from UnitJS.Fun;
|
||||
|
||||
|
@ -9,9 +8,7 @@ function Table(sortCriterion) {
|
|||
getAll: getAll,
|
||||
insert: insert,
|
||||
insertAll: insertAll,
|
||||
load: load,
|
||||
remove: remove,
|
||||
save
|
||||
remove: remove
|
||||
};
|
||||
|
||||
function get(key) {
|
||||
|
@ -25,6 +22,7 @@ function Table(sortCriterion) {
|
|||
.sort(compare(sortCriterion));
|
||||
}
|
||||
|
||||
|
||||
function insert(key, value) {
|
||||
items[key] = value;
|
||||
}
|
||||
|
@ -35,17 +33,9 @@ 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,14 +15,9 @@ 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",
|
||||
|
@ -41,7 +36,6 @@ 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";
|
||||
|
@ -51,7 +45,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 ?",
|
||||
|
@ -62,7 +56,6 @@ 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 ?"
|
||||
|
@ -83,14 +76,9 @@ 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",
|
||||
|
@ -109,7 +97,6 @@ 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;
|
||||
|
@ -130,7 +117,6 @@ 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, ul#sidebar.unactive {
|
||||
body > div {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
@ -52,18 +52,3 @@ 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;
|
||||
}
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
#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