2018-05-13 18:08:12 +02:00
|
|
|
function Game(modules) {
|
2018-05-26 21:26:12 +02:00
|
|
|
var deck = document.getElementById("deck");
|
|
|
|
var rest = document.getElementById("rest");
|
2018-05-16 22:59:22 +02:00
|
|
|
var status = {
|
2018-05-21 23:28:33 +02:00
|
|
|
dom: document.getElementById("status"),
|
2018-05-16 22:59:22 +02:00
|
|
|
playing: false,
|
|
|
|
step: null,
|
2018-07-28 12:48:31 +02:00
|
|
|
month: null
|
2018-05-16 22:59:22 +02:00
|
|
|
};
|
2019-01-13 20:01:55 +01:00
|
|
|
var sets = buildSets();
|
2018-05-13 18:08:12 +02:00
|
|
|
var selected = null;
|
2019-10-18 19:01:13 +02:00
|
|
|
var turnedCard = null;
|
2019-01-13 20:01:55 +01:00
|
|
|
var queue = [];
|
2018-05-13 18:08:12 +02:00
|
|
|
|
2019-01-13 20:01:55 +01:00
|
|
|
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};
|
2018-08-02 00:00:13 +02:00
|
|
|
} else {
|
2019-01-13 20:01:55 +01:00
|
|
|
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]};
|
|
|
|
}
|
|
|
|
}
|
2018-08-02 00:00:13 +02:00
|
|
|
}
|
2019-01-13 20:01:55 +01:00
|
|
|
});
|
|
|
|
return sets;
|
|
|
|
}
|
|
|
|
|
2019-08-24 23:29:40 +02:00
|
|
|
window.addEventListener('focus', runQueue);
|
|
|
|
modules.messaging.addEventListener(["Game"], function(o) {
|
|
|
|
queue.push(handleGameMessage(o));
|
|
|
|
if(document.hasFocus() && queue.length == 1) {
|
|
|
|
runQueue();
|
|
|
|
} else {
|
|
|
|
modules.statusHandler.set("♪");
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2019-01-13 20:01:55 +01:00
|
|
|
function runQueue() {
|
|
|
|
if(queue.length > 0) {
|
2019-08-24 23:29:40 +02:00
|
|
|
var length = queue.length;
|
2019-01-13 20:01:55 +01:00
|
|
|
modules.async.run.apply(null, queue.concat(
|
2019-08-24 23:29:40 +02:00
|
|
|
modules.async.apply(function() {
|
|
|
|
queue = queue.slice(length);
|
|
|
|
runQueue();
|
|
|
|
})
|
2019-01-13 20:01:55 +01:00
|
|
|
));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function handleGameMessage(o) {
|
2019-10-17 19:25:35 +02:00
|
|
|
if(o.game.publicState.turns == 0) {
|
2019-08-12 23:01:08 +02:00
|
|
|
if(o.logs.length > 0) { // but still some logs, from the previous round
|
|
|
|
return modules.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
|
|
|
|
}
|
2019-01-13 20:01:55 +01:00
|
|
|
} else {
|
|
|
|
return applyDiff(o);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function setGame(o) {
|
|
|
|
return function(f) {
|
|
|
|
setStatus(o.game);
|
|
|
|
setCaptures(o.game);
|
|
|
|
[
|
|
|
|
[sets.river, o.game.river, RiverCard],
|
|
|
|
[sets.you.hand, o.game.players[modules.session.getKey()].hand, HandCard]
|
|
|
|
].forEach(function(args) {setCardSet.apply(null, args)});
|
|
|
|
setTheirCards(o.game);
|
|
|
|
handleStep(o)(f);
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function handleStep(o) {
|
|
|
|
return function(f) {
|
2019-08-12 23:01:08 +02:00
|
|
|
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 {
|
2019-01-13 20:01:55 +01:00
|
|
|
f();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2019-08-12 23:01:08 +02:00
|
|
|
function handleTurnedCard(o, f) {
|
|
|
|
if(status.step == "Turned") {
|
|
|
|
setTurned(o.game.step.contents);
|
|
|
|
} else {
|
|
|
|
if(status.step == "ToPlay" && o.game.playing == o.game.oyake) {
|
2019-10-17 19:25:35 +02:00
|
|
|
rest.className = ["card", "turn" + o.game.publicState.turns].join(' ');
|
2019-08-12 23:01:08 +02:00
|
|
|
}
|
|
|
|
if(deck.lastChild.id != "rest") {
|
|
|
|
deck.removeChild(deck.lastChild);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function askKoikoi(o, f) {
|
|
|
|
modules.screen.dialog({
|
|
|
|
text: modules.i18n.get('youScored'),
|
|
|
|
answers: [
|
|
|
|
{label: 'endRound', action: function() {play({koiKoi: false}); f();}},
|
|
|
|
{label: 'koikoi', action: function() {play({koiKoi: true}); f();}}
|
|
|
|
]
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
function theyScored(o, f) {
|
|
|
|
modules.screen.dialog({
|
|
|
|
text: modules.i18n.get('theyScored')(modules.room.name(o.game.playing)),
|
|
|
|
answers: [
|
|
|
|
{label: 'ok', action: f}
|
|
|
|
]
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function gameEnd(o, f) {
|
|
|
|
var winner, maxScore;
|
|
|
|
for(var key in o.game.scores) {
|
|
|
|
if(maxScore == undefined || o.game.scores[key] > maxScore) {
|
|
|
|
winner = key;
|
|
|
|
maxScore = o.game.scores[key];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
modules.screen.dialog({
|
|
|
|
text: modules.i18n.get(modules.session.is(winner) ? 'won' : 'lost'),
|
|
|
|
answers: [{
|
|
|
|
label: 'endGame',
|
|
|
|
action: function() {
|
|
|
|
modules.messaging.send({tag: "Quit"});
|
|
|
|
modules.screen.select('reception');
|
|
|
|
f();
|
|
|
|
}
|
|
|
|
}]
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2019-01-13 20:01:55 +01:00
|
|
|
function applyDiff(o) {
|
|
|
|
return modules.async.sequence.apply(null,
|
|
|
|
o.logs.map(animate).concat(
|
|
|
|
modules.async.apply(setStatus, o.game),
|
|
|
|
handleStep(o)
|
|
|
|
)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
function animate(movement) {
|
|
|
|
return modules.async.bind(
|
|
|
|
modules.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 = {};
|
2019-10-18 19:01:13 +02:00
|
|
|
cardSet[card.name] = turnedCard || new TurnedCard(card.name);
|
|
|
|
turnedCard = null;
|
2019-01-13 20:01:55 +01:00
|
|
|
movingCards.push([{card: cardSet, dom: deck}, dest, card]);
|
|
|
|
}
|
|
|
|
return movingCards;
|
|
|
|
}),
|
|
|
|
function(movingCards) {
|
|
|
|
return modules.async.parallel.apply(null,
|
|
|
|
movingCards.map(function(args) { return moveCard.apply(null, args); })
|
|
|
|
);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
function moveCard(fromSet, toSet, card) {
|
|
|
|
var from, originalCard;
|
2019-10-18 19:01:13 +02:00
|
|
|
var slot = modules.dom.make('li', {class: ['card', 'slot']});
|
2019-01-13 20:01:55 +01:00
|
|
|
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 modules.async.sequence(
|
|
|
|
modules.async.wait(10),
|
|
|
|
modules.async.apply(function() {
|
|
|
|
card.dom.style.left = 0;
|
|
|
|
card.dom.style.top = 0;
|
|
|
|
}),
|
|
|
|
modules.async.wait(1000),
|
|
|
|
modules.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);
|
2018-05-16 22:59:22 +02:00
|
|
|
}
|
2019-01-13 20:01:55 +01:00
|
|
|
}
|
2018-05-13 18:08:12 +02:00
|
|
|
|
2018-05-16 22:59:22 +02:00
|
|
|
function play(move) {
|
|
|
|
modules.messaging.send({
|
|
|
|
tag: "Play",
|
|
|
|
move: move
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2018-05-13 18:08:12 +02:00
|
|
|
function matchingInRiver(card) {
|
|
|
|
return modules.fun.mapFilter(
|
|
|
|
modules.fun.of(sets.river.card),
|
|
|
|
modules.fun.isSet
|
2018-05-16 22:59:22 +02:00
|
|
|
)(modules.hanafuda.sameMonth(card).map(modules.fun.proj('name')));
|
|
|
|
}
|
|
|
|
|
|
|
|
function setStatus(game) {
|
|
|
|
modules.dom.clear(status.dom);
|
2018-05-26 21:26:12 +02:00
|
|
|
status.step = game.step.tag;
|
|
|
|
if(game.month != status.month) {
|
|
|
|
status.month = game.month;
|
|
|
|
}
|
|
|
|
status.dom.appendChild(
|
2019-01-01 12:57:27 +01:00
|
|
|
modules.dom.make('li', {textContent: modules.i18n.get('monthFlower')(modules.i18n.get(status.month))})
|
2018-05-26 21:26:12 +02:00
|
|
|
);
|
|
|
|
var turn = null;
|
2018-05-16 22:59:22 +02:00
|
|
|
status.playing = modules.session.is(game.playing);
|
|
|
|
if(status.playing) {
|
2019-01-13 20:01:55 +01:00
|
|
|
sets.you.hand.dom.classList.toggle("yourTurn", status.step == "ToPlay");
|
2019-01-01 12:57:27 +01:00
|
|
|
turn = modules.i18n.get("yourTurn");
|
2018-05-16 22:59:22 +02:00
|
|
|
} else {
|
2019-01-13 20:01:55 +01:00
|
|
|
sets.you.hand.dom.classList.remove("yourTurn");
|
2019-01-01 12:57:27 +01:00
|
|
|
turn = modules.i18n.get('playing')(modules.room.name(game.playing));
|
2018-05-16 22:59:22 +02:00
|
|
|
}
|
2018-05-26 21:26:12 +02:00
|
|
|
status.dom.appendChild(modules.dom.make('li', {textContent: turn}));
|
2018-05-16 22:59:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
function setCaptures(game) {
|
|
|
|
for(var key in game.players) {
|
2018-05-21 23:28:33 +02:00
|
|
|
var elem = document.getElementById(modules.session.is(key) ? "you" : "them");
|
2018-05-16 22:59:22 +02:00
|
|
|
elem.getElementsByClassName('score')[0].textContent = game.scores[key] + " pts";
|
2018-05-21 23:28:33 +02:00
|
|
|
var byClass = {}
|
|
|
|
Object.values(modules.hanafuda.Family).forEach(function(family) {
|
|
|
|
byClass[family.class] = elem.getElementsByClassName(family.class)[0];
|
|
|
|
modules.dom.clear(byClass[family.class]);
|
|
|
|
});
|
2018-05-16 22:59:22 +02:00
|
|
|
game.players[key].meld.forEach(function(cardName) {
|
|
|
|
var card = new Card(cardName);
|
2018-05-21 23:28:33 +02:00
|
|
|
byClass[card.value.family.class].appendChild(card.dom);
|
2018-05-16 22:59:22 +02:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-13 20:01:55 +01:00
|
|
|
function setCardSet(set, cardNames, constructor) {
|
2018-05-21 23:28:33 +02:00
|
|
|
constructor = constructor || Card;
|
2018-05-16 22:59:22 +02:00
|
|
|
set.card = {};
|
2018-05-21 23:28:33 +02:00
|
|
|
modules.dom.clear(set.dom);
|
|
|
|
cardNames.forEach(function(cardName) {
|
2018-05-16 22:59:22 +02:00
|
|
|
var card = new constructor(cardName);
|
|
|
|
set.card[cardName] = card;
|
|
|
|
set.dom.appendChild(card.dom);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2018-07-28 12:48:31 +02:00
|
|
|
function setTheirCards(game) {
|
2018-05-26 21:26:12 +02:00
|
|
|
var turnsTheyPlayed = Math.floor(
|
2019-10-17 19:25:35 +02:00
|
|
|
(game.publicState.turns + (modules.session.is(game.oyake) ? 0 : 1)) / 2
|
2018-05-26 21:26:12 +02:00
|
|
|
);
|
2019-01-13 20:01:55 +01:00
|
|
|
modules.dom.clear(sets.them.hand.dom);
|
2018-05-26 21:26:12 +02:00
|
|
|
for(var i = 0; i < 8 - turnsTheyPlayed; i++) {
|
2019-01-13 20:01:55 +01:00
|
|
|
sets.them.hand.dom.appendChild(modules.dom.make('li', {class: "card"}));
|
2018-05-26 21:26:12 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-13 20:01:55 +01:00
|
|
|
function setTurned(cardName) {
|
2019-10-18 17:54:27 +02:00
|
|
|
turnedCard = new TurnedCard(cardName);
|
2018-05-21 23:28:33 +02:00
|
|
|
if(status.playing) {
|
2019-10-18 17:54:27 +02:00
|
|
|
selected = turnedCard;
|
|
|
|
showCandidates(modules.hanafuda.Card[cardName], true);
|
2018-05-21 23:28:33 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-16 22:59:22 +02:00
|
|
|
function showCandidates(card, yes) {
|
|
|
|
matchingInRiver(card).forEach(function(riverCard) {riverCard.setCandidate(yes);});
|
2018-05-13 18:08:12 +02:00
|
|
|
}
|
|
|
|
|
2019-10-18 17:54:27 +02:00
|
|
|
function setSelected(yes) {
|
|
|
|
selected = yes ? this : null;
|
|
|
|
this.dom.classList.toggle('selected', yes);
|
|
|
|
showCandidates(this.value, yes);
|
|
|
|
}
|
|
|
|
|
2018-05-13 18:08:12 +02:00
|
|
|
function Card(name) {
|
|
|
|
this.value = modules.hanafuda.Card[name];
|
|
|
|
this.name = name;
|
2018-05-26 21:26:12 +02:00
|
|
|
this.dom = modules.dom.make('li', {
|
|
|
|
class: [
|
|
|
|
"card",
|
|
|
|
"value" + modules.hanafuda.getValue(this.value),
|
|
|
|
"month" + this.value.flower
|
|
|
|
],
|
|
|
|
onClick: this.onClick()
|
|
|
|
});
|
2018-05-13 18:08:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
Card.prototype.onClick = function() {return function() {};};
|
|
|
|
|
|
|
|
function RiverCard() {
|
|
|
|
Card.apply(this, arguments);
|
|
|
|
this.candidate = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
RiverCard.prototype.onClick = function() {
|
|
|
|
var card = this;
|
|
|
|
return function() {
|
|
|
|
if(card.candidate) {
|
2019-10-18 17:54:27 +02:00
|
|
|
var withCard = selected.name;
|
|
|
|
selected.setSelected(false);
|
2018-05-16 22:59:22 +02:00
|
|
|
play(
|
|
|
|
status.step == 'ToPlay' ? {capture: [withCard, card.name]} : {choose: card.name}
|
|
|
|
);
|
2018-05-13 18:08:12 +02:00
|
|
|
}
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
RiverCard.prototype.setCandidate = function(yes) {
|
|
|
|
this.candidate = yes;
|
|
|
|
this.dom.classList.toggle("candidate", yes);
|
|
|
|
}
|
|
|
|
|
2019-10-18 17:54:27 +02:00
|
|
|
function TurnedCard() {
|
|
|
|
Card.apply(this, arguments);
|
|
|
|
this.dom.id = "turned";
|
|
|
|
deck.appendChild(this.dom);
|
|
|
|
}
|
|
|
|
|
|
|
|
TurnedCard.prototype.onClick = Card.prototype.onClick;
|
|
|
|
TurnedCard.prototype.setSelected = setSelected;
|
|
|
|
|
2018-05-13 18:08:12 +02:00
|
|
|
function HandCard() {
|
|
|
|
Card.apply(this, arguments);
|
|
|
|
}
|
|
|
|
|
|
|
|
HandCard.prototype.onClick = function() {
|
2019-01-13 20:01:55 +01:00
|
|
|
var card = this;
|
|
|
|
return function() {
|
|
|
|
if(status.playing && status.step == "ToPlay") {
|
2018-05-16 22:59:22 +02:00
|
|
|
if(selected != undefined) {
|
2019-10-18 17:54:27 +02:00
|
|
|
selected.setSelected(false);
|
2018-05-16 22:59:22 +02:00
|
|
|
} else {
|
|
|
|
card.play();
|
|
|
|
}
|
2019-01-13 20:01:55 +01:00
|
|
|
}
|
|
|
|
};
|
2018-05-13 18:08:12 +02:00
|
|
|
};
|
|
|
|
|
2019-10-18 17:54:27 +02:00
|
|
|
HandCard.prototype.setSelected = setSelected;
|
2018-05-13 18:08:12 +02:00
|
|
|
|
|
|
|
HandCard.prototype.play = function() {
|
|
|
|
var matching = matchingInRiver(this.value);
|
|
|
|
if(matching.length > 1) {
|
|
|
|
this.setSelected(true);
|
|
|
|
} else {
|
2018-05-16 22:59:22 +02:00
|
|
|
play({play: this.name});
|
2018-05-13 18:08:12 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|