webclient/js/GUI/Screen/Game.js

344 lines
8.7 KiB
JavaScript

import Hanafuda;
import I18n;
import Messaging;
import players from Room;
import * as Screen from GUI.Screen;
import Session;
import StatusHandler;
import * as Async from UnitJS.Async;
import * as Dom from UnitJS.Dom;
import * as Fun from UnitJS.Fun;
var deck = document.getElementById("deck");
var rest = document.getElementById("rest");
var status = {
dom: document.getElementById("status"),
game: null,
playing: false,
step: null,
month: null
};
var sets;
var selected = null;
var turnedCard = null;
var queue = [];
return {
init: init
}
function init(state) {
sets = buildSets();
window.addEventListener('focus', runQueue);
Messaging.addEventListener(["Game"], function(o) {
queue.push(handleGameMessage(o));
if(document.hasFocus() && queue.length == 1) {
runQueue();
} else {
StatusHandler.set("♪");
}
});
Async.run(setGame({tag: "Game", state: state, logs: []}));
}
function buildSets() {
var sets = {};
['river', 'you', 'them'].forEach(function(id) {
var dom = document.getElementById(id);
if(dom.tagName.toLowerCase() == 'ul') {
sets[id] = {card: null, dom: dom};
} else {
sets[id] = {};
for(var i = 0; i < dom.children.length; i++) {
if(dom.children[i].tagName.toLowerCase() == 'ul') {
sets[id][dom.children[i].className] = {card: {}, dom: dom.children[i]};
}
}
}
});
return sets;
}
function runQueue() {
if(queue.length > 0) {
var length = queue.length;
Async.run.apply(null, queue.concat(
Async.apply(function() {
queue = queue.slice(length);
runQueue();
})
));
}
}
function handleGameMessage(o) {
if(o.state.public.turns == 0) {
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(o); // directly set a whole new game
}
} else {
return applyDiff(o);
}
}
function setGame(o) {
return function(f) {
setStatus(o.state);
setCaptures(o.state);
[
[sets.river, o.state.public.river, RiverCard],
[sets.you.hand, o.state.playerHand, HandCard]
].forEach(function(args) {setCardSet.apply(null, args)});
setTheirCards(o.state);
handleStep(o)(f);
};
}
function handleStep(o) {
return function(f) {
handleTurnedCard(o, f);
if(status.step == "Scored") {
if(status.playing) {
askKoikoi(o, f);
} else {
theyScored(o, f);
}
} else if (status.step == "Over") {
gameEnd(o, f);
} else {
f();
}
};
}
function handleTurnedCard(o, f) {
if(status.step == "Turned") {
setTurned(o.state.public.step.contents);
} else {
if(status.step == "ToPlay" && o.state.public.playing == o.state.public.oyake) {
rest.className = ["card", "turn" + o.state.public.turns].join(' ');
}
if(deck.lastChild.id != "rest") {
deck.removeChild(deck.lastChild);
}
}
}
function askKoikoi(o, f) {
Screen.dialog({
text: I18n.get('youScored'),
answers: [
{label: 'endRound', action: function() {play({koiKoi: false}); f();}},
{label: 'koikoi', action: function() {play({koiKoi: true}); f();}}
]
});
}
function theyScored(o, f) {
Screen.dialog({
text: I18n.get('theyScored')(players.get(o.state.public.playing)),
answers: [
{label: 'ok', action: f}
]
});
}
function gameEnd(o, f) {
var winner, maxScore;
for(var key in o.state.public.scores) {
if(maxScore == undefined || o.state.public.scores[key] > maxScore) {
winner = key;
maxScore = o.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();
}
}]
});
}
function applyDiff(o) {
return Async.sequence.apply(null,
o.logs.map(animate).concat(
Async.apply(setStatus, o.state),
handleStep(o)
)
);
}
function animate(movement) {
return Async.bind(
Async.apply(function() {
var card;
var movingCards = [];
var side = (status.playing) ? 'you' : 'them';
var dest = sets.river;
if(movement.captures != undefined) {
card = new Card(movement.played);
dest = sets[side];
movingCards.push([sets.river, dest, new Card(movement.captures)]);
} else {
card = new RiverCard(movement.played);
}
if(movement.source == 'Hand') {
movingCards.push([sets[side].hand, dest, card]);
} else {
var cardSet = {};
cardSet[card.name] = turnedCard || new TurnedCard(card.name);
turnedCard = null;
movingCards.push([{card: cardSet, dom: deck}, dest, card]);
}
return movingCards;
}),
function(movingCards) {
return Async.parallel.apply(null,
movingCards.map(function(args) { return moveCard.apply(null, args); })
);
}
);
}
function moveCard(fromSet, toSet, card) {
var from, originalCard;
var slot = Dom.make('li', {class: ['card', 'slot']});
if (fromSet.card[card.name] != undefined) {
originalCard = fromSet.card[card.name].dom;
delete fromSet.card[card.name];
} else {
var 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);
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(function() {
card.dom.style.left = 0;
card.dom.style.top = 0;
}),
Async.wait(1000),
Async.apply(function() {
fromSet.dom.removeChild(slot);
card.dom.classList.remove('moving');
})
);
}
function insertCard(toSet, card) {
if(toSet.dom != undefined) {
toSet.card[card.name] = card;
toSet.dom.appendChild(card.dom);
} else {
insertCard(toSet[card.value.family.class], card);
}
}
function play(move) {
Messaging.send({
tag: "Play",
move: move,
onGame: status.game
});
}
function matchingInRiver(card) {
return Fun.mapFilter(
Fun.of(sets.river.card),
Fun.defined
)(Hanafuda.sameMonth(card).map(Fun.proj('name')));
}
function setStatus(game) {
Dom.clear(status.dom);
status.game = game;
status.step = game.public.step.tag;
if(game.public.month != status.month) {
status.month = game.public.month;
}
status.dom.appendChild(
Dom.make('li', {textContent: I18n.get('monthFlower')(I18n.get(status.month))})
);
var turn = null;
status.playing = Session.is(game.public.playing);
if(status.playing) {
sets.you.hand.dom.classList.toggle("yourTurn", status.step == "ToPlay");
turn = I18n.get("yourTurn");
} else {
sets.you.hand.dom.classList.remove("yourTurn");
turn = I18n.get('playing')(players.get(game.public.playing));
}
status.dom.appendChild(Dom.make('li', {textContent: turn}));
}
function setCaptures(game) {
for(var key in game.public.players) {
var elem = document.getElementById(Session.is(key) ? "you" : "them");
elem.getElementsByClassName('score')[0].textContent = game.public.scores[key] + " pts";
var byClass = {}
Object.values(Hanafuda.Family).forEach(function(family) {
byClass[family.class] = elem.getElementsByClassName(family.class)[0];
Dom.clear(byClass[family.class]);
});
game.public.players[key].meld.forEach(function(cardName) {
var card = new Card(cardName);
byClass[card.value.family.class].appendChild(card.dom);
});
}
}
function setCardSet(set, cardNames, constructor) {
constructor = constructor || Card;
set.card = {};
Dom.clear(set.dom);
cardNames.forEach(function(cardName) {
var card = new constructor(cardName);
set.card[cardName] = card;
set.dom.appendChild(card.dom);
});
}
function setTheirCards(game) {
var turnsTheyPlayed = Math.floor(
(game.public.turns + (Session.is(game.public.oyake) ? 0 : 1)) / 2
);
Dom.clear(sets.them.hand.dom);
for(var i = 0; i < 8 - turnsTheyPlayed; i++) {
sets.them.hand.dom.appendChild(Dom.make('li', {class: "card"}));
}
}
function setTurned(cardName) {
turnedCard = new TurnedCard(cardName);
if(status.playing) {
selected = turnedCard;
showCandidates(Hanafuda.Card[cardName], true);
}
}
function showCandidates(card, yes) {
matchingInRiver(card).forEach(function(riverCard) {riverCard.setCandidate(yes);});
}
function setSelected(yes) {
selected = yes ? this : null;
this.dom.classList.toggle('selected', yes);
showCandidates(this.value, yes);
}