function Game(modules) { var deck = document.getElementById("deck"); var rest = document.getElementById("rest"); var status = { dom: document.getElementById("status"), playing: false, step: null, month: null }; var sets = buildSets(); var selected = null; var queue = []; 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; } 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("♪"); } }); function runQueue() { if(queue.length > 0) { var length = queue.length; modules.async.run.apply(null, queue.concat( modules.async.apply(function() { queue = queue.slice(length); runQueue(); }) )); } } function handleGameMessage(o) { if(o.game.deck == 24) { // deck is full, means new round 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 } } 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) { 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.game.step.contents); } else { if(status.step == "ToPlay" && o.game.playing == o.game.oyake) { rest.className = ["card", "count" + o.game.deck].join(' '); } 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(); } }] }); } 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 = {}; cardSet[card.name] = new TurnedCard(card.name); 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; var slot = modules.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 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); } } function play(move) { modules.messaging.send({ tag: "Play", move: move }); } function matchingInRiver(card) { return modules.fun.mapFilter( modules.fun.of(sets.river.card), modules.fun.isSet )(modules.hanafuda.sameMonth(card).map(modules.fun.proj('name'))); } function setStatus(game) { modules.dom.clear(status.dom); status.step = game.step.tag; if(game.month != status.month) { status.month = game.month; } status.dom.appendChild( modules.dom.make('li', {textContent: modules.i18n.get('monthFlower')(modules.i18n.get(status.month))}) ); var turn = null; status.playing = modules.session.is(game.playing); if(status.playing) { sets.you.hand.dom.classList.toggle("yourTurn", status.step == "ToPlay"); turn = modules.i18n.get("yourTurn"); } else { sets.you.hand.dom.classList.remove("yourTurn"); turn = modules.i18n.get('playing')(modules.room.name(game.playing)); } status.dom.appendChild(modules.dom.make('li', {textContent: turn})); } function setCaptures(game) { for(var key in game.players) { var elem = document.getElementById(modules.session.is(key) ? "you" : "them"); elem.getElementsByClassName('score')[0].textContent = game.scores[key] + " pts"; var byClass = {} Object.values(modules.hanafuda.Family).forEach(function(family) { byClass[family.class] = elem.getElementsByClassName(family.class)[0]; modules.dom.clear(byClass[family.class]); }); game.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 = {}; modules.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( (24 - game.deck + (modules.session.is(game.oyake) ? 0 : 1)) / 2 ); modules.dom.clear(sets.them.hand.dom); for(var i = 0; i < 8 - turnsTheyPlayed; i++) { sets.them.hand.dom.appendChild(modules.dom.make('li', {class: "card"})); } } function setTurned(cardName) { turnedCard = new TurnedCard(cardName); if(status.playing) { selected = turnedCard; showCandidates(modules.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); } function Card(name) { this.value = modules.hanafuda.Card[name]; this.name = name; this.dom = modules.dom.make('li', { class: [ "card", "value" + modules.hanafuda.getValue(this.value), "month" + this.value.flower ], onClick: this.onClick() }); } 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) { var withCard = selected.name; selected.setSelected(false); play( status.step == 'ToPlay' ? {capture: [withCard, card.name]} : {choose: card.name} ); } }; }; RiverCard.prototype.setCandidate = function(yes) { this.candidate = yes; this.dom.classList.toggle("candidate", yes); } function TurnedCard() { Card.apply(this, arguments); this.dom.id = "turned"; deck.appendChild(this.dom); } TurnedCard.prototype.onClick = Card.prototype.onClick; TurnedCard.prototype.setSelected = setSelected; function HandCard() { Card.apply(this, arguments); } HandCard.prototype.onClick = function() { var card = this; return function() { if(status.playing && status.step == "ToPlay") { if(selected != undefined) { selected.setSelected(false); } else { card.play(); } } }; }; HandCard.prototype.setSelected = setSelected; HandCard.prototype.play = function() { var matching = matchingInRiver(this.value); if(matching.length > 1) { this.setSelected(true); } else { play({play: this.name}); } } }