├── .gitignore ├── img ├── club.png ├── flip.png ├── heart.png ├── spade.png └── diamond.png ├── js ├── RandomBrain.js ├── layout.js ├── Brain.js ├── util.js ├── config.js ├── prematureOptimization.js ├── BrainWorker.js ├── rules.js ├── Card.js ├── Ai.js ├── main.js ├── test.js ├── Waste.js ├── SimpleBrain.js ├── ui.js ├── AsyncBrain.js ├── Human.js ├── Player.js ├── board.js ├── Row.js ├── domBinding.js ├── Simulator.js ├── PomDPSimulator.js ├── McBrain.js ├── game.js ├── lib │ └── require.js ├── PomDPBrain.js └── game_old.js ├── manifest.appcache ├── README.md ├── LICENSE ├── index.html └── style.css /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | bashTest.js 3 | -------------------------------------------------------------------------------- /img/club.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yyjhao/html5-hearts/HEAD/img/club.png -------------------------------------------------------------------------------- /img/flip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yyjhao/html5-hearts/HEAD/img/flip.png -------------------------------------------------------------------------------- /img/heart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yyjhao/html5-hearts/HEAD/img/heart.png -------------------------------------------------------------------------------- /img/spade.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yyjhao/html5-hearts/HEAD/img/spade.png -------------------------------------------------------------------------------- /img/diamond.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yyjhao/html5-hearts/HEAD/img/diamond.png -------------------------------------------------------------------------------- /js/RandomBrain.js: -------------------------------------------------------------------------------- 1 | define(["Brain", "jquery"], 2 | function(Brain, $){ 3 | "use strict"; 4 | 5 | var RandomBrain = function(user){ 6 | Brain.call(this, user); 7 | }; 8 | 9 | RandomBrain.prototype = Object.create(Brain.prototype); 10 | 11 | RandomBrain.prototype.decide = function(validCards){ 12 | return $.Deferred().resolve(validCards[Math.floor(Math.random() * validCards.length)].ind); 13 | }; 14 | 15 | return RandomBrain; 16 | }); -------------------------------------------------------------------------------- /js/layout.js: -------------------------------------------------------------------------------- 1 | define(function(){ 2 | return { 3 | width: 500, 4 | height: 500, 5 | cardSep: 30, 6 | cardHeight: 130, 7 | cardWidth: 85, 8 | rowMargin: 10, 9 | boardHeight: 55, 10 | boardWidth: 250, 11 | region: null, 12 | adjust: function(){ 13 | if(!this.region) return; 14 | this.width = this.region.offsetWidth; 15 | this.height = this.region.offsetHeight; 16 | } 17 | }; 18 | }); -------------------------------------------------------------------------------- /js/Brain.js: -------------------------------------------------------------------------------- 1 | define(function(){ 2 | "use strict"; 3 | 4 | var Brain = function(user){ 5 | this.user = user; 6 | this.playerInfo = [[], [], [], []]; 7 | }; 8 | 9 | Brain.prototype.watch = function(info){}; 10 | 11 | Brain.prototype.confirmCards = function(){ 12 | return { 13 | done: function(cb){ 14 | cb(); 15 | } 16 | }; 17 | }; 18 | 19 | Brain.prototype.init = function(){ 20 | return $.Deferred().resolve(); 21 | }; 22 | 23 | Brain.prototype.terminate = function(){ 24 | 25 | }; 26 | 27 | return Brain; 28 | }); -------------------------------------------------------------------------------- /js/util.js: -------------------------------------------------------------------------------- 1 | define(function(){ 2 | var $ = function(query){ 3 | return document.querySelectorAll(query); 4 | }; 5 | 6 | var vendorPrefix = (function(){ 7 | if(window.isDebug) return ""; 8 | var prefixes = ['Moz', 'Webkit', 'O', 'ms'], 9 | tran = "Transform"; 10 | 11 | var el = document.createElement('div'); 12 | 13 | for (var i=0; i 10){ 16 | numtext = ({ 17 | 11: 'J', 18 | 12: 'Q', 19 | 13: 'K', 20 | 14: 'A' 21 | })[acutualNum]; 22 | } 23 | this.display = domBinding.createCardDisplay(numtext, this.suit); 24 | this.display.onClick = this.shift.bind(this); 25 | }; 26 | 27 | Card.suits = suits; 28 | 29 | Card.prototype.adjustPos = function(noUpdate){ 30 | if(!noUpdate) this.pos = this.parent.getPosFor(this.ind); 31 | this.display.adjustPos(this.pos); 32 | }; 33 | 34 | Card.prototype.shift = function(){ 35 | if(!this.display.isSelectable()) return; 36 | if(!this.parent.curShifted) return; 37 | if(this.parent.curShifted.indexOf(this) !== -1){ 38 | this.parent.removeShift(this); 39 | }else{ 40 | this.parent.addShift(this); 41 | } 42 | }; 43 | 44 | Card.prototype.out = function(){ 45 | this.display.out(); 46 | }; 47 | 48 | return Card; 49 | }); 50 | -------------------------------------------------------------------------------- /js/Ai.js: -------------------------------------------------------------------------------- 1 | define(["Player", "jquery"], 2 | function(Player, $){ 3 | "use strict"; 4 | 5 | var Ai = function(id, name){ 6 | Player.call(this, id, name); 7 | }; 8 | 9 | Ai.prototype = Object.create(Player.prototype); 10 | 11 | Ai.prototype.prepareTransfer = function(){ 12 | var selected = [], cards = []; 13 | while(selected.length < 3){ 14 | var s = Math.floor(Math.random() * this.row.cards.length); 15 | if(selected.indexOf(s) === -1){ 16 | selected.push(s); 17 | } 18 | } 19 | for(var i = 0; i < 3; i++){ 20 | cards.push(this.row.cards[selected[i]]); 21 | } 22 | this.selected = cards; 23 | return $.Deferred().resolve(); 24 | }; 25 | 26 | Ai.prototype.confirmTransfer = function(){ 27 | return this.brain.confirmCards(); 28 | }; 29 | 30 | Ai.prototype.transferTo = function(other){ 31 | var selected = this.selected; 32 | Player.prototype.transferTo.call(this, other); 33 | this.brain.watch({ 34 | type: "in", 35 | player: other, 36 | cards: selected 37 | }); 38 | }; 39 | 40 | Ai.prototype.watch = function(info){ 41 | this.brain.watch(info); 42 | }; 43 | 44 | Ai.prototype.decide = function(validCards, boardCards, boardPlayers, scores){ 45 | return this.brain.decide(validCards, boardCards, boardPlayers, scores).then(function(c){ 46 | return this.row.cards[c]; 47 | }.bind(this)); 48 | }; 49 | 50 | return Ai; 51 | }); -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013-present, Yujian Yao http://yjyao.com, source code available 2 | at https://github.com/yyjhao/html5-hearts 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 2. Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 18 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 21 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | The views and conclusions contained in the software and documentation are those 26 | of the authors and should not be interpreted as representing official policies, 27 | either expressed or implied, of the FreeBSD Project. 28 | -------------------------------------------------------------------------------- /js/main.js: -------------------------------------------------------------------------------- 1 | require({ 2 | baseUrl: 'js', 3 | paths: { 4 | jquery: 'lib/jquery-2.0.3.min' 5 | } 6 | }, 7 | ["game", "jquery", "domBinding", "layout", "config"], 8 | function(game, $, domBinding, layout, config){ 9 | "use strict"; 10 | 11 | layout.region = $('#game-region')[0]; 12 | layout.adjust(); 13 | 14 | domBinding.fragmentToDom($('#game-region')[0]); 15 | game.adjustLayout(); 16 | 17 | $(window).resize(function(){ 18 | layout.adjust(); 19 | game.adjustLayout(); 20 | }); 21 | 22 | var nums = ['one', 'two', 'three', 'four']; 23 | $('#control-region>button').on("click", function(){ 24 | $('#control-region')[0].hidden = true; 25 | }); 26 | $('#control-region>.newgame-but').on("click", function(){ 27 | config.names.forEach(function(n, ind){ 28 | config.levels[ind] = $('.player-diff.' + nums[ind] + ' input').val(); 29 | config.names[ind] = $('.player-set-name.' + nums[ind]).text(); 30 | }); 31 | config.sync(); 32 | }); 33 | $('.newgame-but').on("click", function(){ 34 | if(confirm("This will end the current game. Are you sure?")){ 35 | game.newGame(); 36 | } 37 | }); 38 | $('#settings-but').on("click", function(){ 39 | $('#settings-dialog')[0].hidden = false; 40 | config.names.forEach(function(n,ind){ 41 | $('.player-set-name.' + nums[ind])[0].innerHTML = n; 42 | $('.player-diff.' + nums[ind] + ' input').val(parseInt(config.levels[ind])); 43 | console.log(parseInt(config.levels[ind])); 44 | }); 45 | $('#control-region')[0].hidden = false; 46 | }); 47 | game.newGame(); 48 | }); -------------------------------------------------------------------------------- /js/test.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | var allScores = [], 3 | games = 0, 4 | maxGames = 1000, 5 | scoreSums = [0, 0, 0, 0]; 6 | var showStats = function(){ 7 | console.log(allScores); 8 | var sums = [0, 0, 0, 0]; 9 | allScores.forEach(function(ss){ 10 | ss.forEach(function(s, ind){ 11 | sums[ind] += s; 12 | }); 13 | }); 14 | console.log("sums: ", sums); 15 | }; 16 | var test = window.tester = { 17 | log: function(msg, players, cards){ 18 | if(!window.isDebug) return; 19 | if(!(players instanceof Array)){ 20 | players = [players]; 21 | } 22 | 23 | if(!(cards instanceof Array)){ 24 | cards = [cards]; 25 | } 26 | 27 | var text = "[log] " + msg + ": players [" + 28 | players.map(function(p){ return p.id; }).join(", ") + 29 | "] cards [" + 30 | cards.map(function(c){ 31 | return "{" + (c.num + 1) + ", " + (Card.suits[c.suit]) + "}"; 32 | }).join(", "); 33 | 34 | // console.log(text + "
"); 35 | }, 36 | informNewGame: function(){ 37 | if(!window.isDebug) return; 38 | console.log("Current game: " + games); 39 | games++; 40 | if(games > maxGames){ 41 | showStats(); 42 | process.exit(); 43 | } 44 | }, 45 | recordScore: function(scores){ 46 | if(!window.isDebug) return; 47 | allScores.push(scores); 48 | scores.forEach(function(s, ind){ 49 | scoreSums[ind] += s; 50 | }); 51 | console.log(scoreSums); 52 | } 53 | }; 54 | })(); -------------------------------------------------------------------------------- /js/Waste.js: -------------------------------------------------------------------------------- 1 | define(['layout'], 2 | function(layout){ 3 | var Waste = function(id, player){ 4 | this.id = id; 5 | this.isVertical = id % 2; 6 | this.rotation = 90 * ((id + 1) % 4) -90; 7 | this.cards = []; 8 | this.playedBy = player; 9 | }; 10 | 11 | Waste.prototype.adjustPos = function(){ 12 | if(this.isVertical){ 13 | this.distance = layout.width / 2 + layout.rowMargin + layout.cardHeight / 2; 14 | }else{ 15 | this.distance = layout.height / 2 + layout.rowMargin + layout.cardHeight / 2; 16 | } 17 | this.cards.forEach(function(c){ 18 | c.adjustPos(); 19 | }); 20 | }; 21 | 22 | Waste.prototype.getPosFor = function(ind){ 23 | var pos = { 24 | x: 0, 25 | y: this.distance, 26 | rotation: this.rotation, 27 | z: ind + 52, 28 | rotateY: 0 29 | }; 30 | return pos; 31 | }; 32 | 33 | Waste.prototype.addCards = function(cards){ 34 | this.playedBy.incrementScore(cards.reduce(function(p, c){ 35 | if(c.suit === 1){ 36 | return p + 1; 37 | }else if(c.suit === 0 && c.num === 11){ 38 | return p + 13; 39 | }else{ 40 | return p; 41 | } 42 | }, 0)); 43 | var finalCard; 44 | for(var i = 0; i < cards.length; i++){ 45 | if(cards[i].pos.rotation === this.rotation){ 46 | cards[i].pos.z = 104; 47 | finalCard = cards[i]; 48 | }else{ 49 | cards[i].pos.rotation = this.rotation; 50 | this.addCard(cards[i]); 51 | } 52 | cards[i].adjustPos(true); 53 | } 54 | this.addCard(finalCard); 55 | var self = this; 56 | setTimeout(function(){ 57 | self.adjustPos(); 58 | }, 300); 59 | }; 60 | 61 | Waste.prototype.addCard = function(card){ 62 | card.parent = this; 63 | card.ind = this.cards.length; 64 | this.cards.push(card); 65 | }; 66 | 67 | return Waste; 68 | }); 69 | -------------------------------------------------------------------------------- /js/SimpleBrain.js: -------------------------------------------------------------------------------- 1 | define(["Brain"], 2 | function(Brain){ 3 | "use strict"; 4 | 5 | var SimpleBrain = function(user){ 6 | Brain.call(this, user); 7 | }; 8 | 9 | SimpleBrain.prototype = Object.create(Brain.prototype); 10 | 11 | SimpleBrain.prototype.decide = function(vc, board){ 12 | var len = vc.length, 13 | suit = -1, maxNum = -1; 14 | 15 | return $.Deferred().resolve((function(){ 16 | if(board.length){ 17 | suit = board[0].suit; 18 | maxNum = board.reduce(function(prev, cur){ 19 | if(cur.suit === suit && cur.num > prev){ 20 | return cur.num; 21 | }else{ 22 | return prev; 23 | } 24 | }, 0); 25 | return vc.reduce(function(prev, cur){ 26 | if(prev.suit === cur.suit){ 27 | if(cur.suit === suit){ 28 | if(cur.num < maxNum){ 29 | if(prev.num > maxNum || prev.num < cur.num) return cur; 30 | else return prev; 31 | }else if(cur.num > maxNum && prev.num > maxNum && board.length === 3){ 32 | if(cur.num > prev.num) return cur; 33 | else return prev; 34 | }else if(cur.num < prev.num){ 35 | return cur; 36 | }else{ 37 | return prev; 38 | } 39 | }else{ 40 | if(cur.num > prev.num) return cur; 41 | else return prev; 42 | } 43 | }else{ 44 | if(cur.suit === 0 && cur.num === 11) return cur; 45 | if(prev.suit === 0 && prev.num === 11) return prev; 46 | if(cur.suit === 1) return cur; 47 | if(prev.suit === 1) return prev; 48 | if(cur.num > prev.num) return cur; 49 | return prev; 50 | } 51 | }).ind; 52 | }else{ 53 | return vc.reduce(function(prev, cur){ 54 | if(prev.num > cur.num) return cur; 55 | else return prev; 56 | }).ind; 57 | } 58 | })()); 59 | }; 60 | 61 | return SimpleBrain; 62 | }); -------------------------------------------------------------------------------- /js/ui.js: -------------------------------------------------------------------------------- 1 | define(function(){ 2 | "use strict"; 3 | 4 | var arrow = document.createElement('div'), 5 | button = document.createElement('button'), 6 | message = document.createElement('div'), 7 | endMessage = document.createElement("div"); 8 | 9 | button.id = 'play-button'; 10 | message.id = 'game-message'; 11 | arrow.innerHTML = "←"; 12 | arrow.id = 'pass-arrow'; 13 | endMessage.id = "end-message"; 14 | 15 | document.body.appendChild(arrow); 16 | document.body.appendChild(button); 17 | document.body.appendChild(message); 18 | document.body.appendChild(endMessage); 19 | 20 | return { 21 | clearEvents: function(){ 22 | $(button).off("click"); 23 | $(arrow).off("click"); 24 | }, 25 | showArrow: function(){ 26 | arrow.classList.add('show'); 27 | }, 28 | hideArrow: function(){ 29 | arrow.classList.remove('show'); 30 | }, 31 | showButton: function(text){ 32 | button.innerHTML = text; 33 | button.classList.add('show'); 34 | }, 35 | hideButton: function(text){ 36 | button.classList.remove('show'); 37 | }, 38 | arrowClickOnce: function(cb){ 39 | $(arrow).on("click", function(){ 40 | cb(); 41 | $(this).off("click"); 42 | }); 43 | }, 44 | buttonClickOnce: function(cb){ 45 | $(button).on("click", function(){ 46 | cb(); 47 | $(this).off("click"); 48 | }); 49 | }, 50 | showWin: function(won){ 51 | endMessage.innerHTML = won ? "YOU WON!" : "YOU LOST!"; 52 | endMessage.classList.add("show"); 53 | }, 54 | hideWin: function(){ 55 | endMessage.classList.remove("show"); 56 | }, 57 | showMessage: function(msg){ 58 | message.innerHTML = msg; 59 | message.style.display = 'block'; 60 | }, 61 | showPassingScreen: function(dir){ 62 | var directions = ['left', 'right', 'opposite']; 63 | this.showMessage("Pass three cards to the " + directions[dir]); 64 | [function(){ 65 | $(arrow).css("transform", 'rotate(0)'); 66 | },function(){ 67 | $(arrow).css("transform", 'rotate(180deg)'); 68 | },function(){ 69 | $(arrow).css("transform", 'rotate(90deg)'); 70 | }][dir](); 71 | }, 72 | hideMessage: function(){ 73 | message.style.display = ''; 74 | } 75 | }; 76 | }); 77 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Hearts 12 | 13 | 14 | 15 |
16 | 17 | 18 |
19 |
20 | 61 | Fork me on GitHub 62 | 63 | 73 | 74 | -------------------------------------------------------------------------------- /js/AsyncBrain.js: -------------------------------------------------------------------------------- 1 | define(["Brain"], 2 | function(Brain){ 3 | "use strict"; 4 | 5 | var AsyncBrain = function(user, brainName, options){ 6 | Brain.call(this, user); 7 | var worker = this.worker = new Worker("js/BrainWorker.js"); 8 | this.initDefer = $.Deferred(); 9 | var self = this; 10 | this.worker.onmessage = function(e){ 11 | if(e.data.type === "decision"){ 12 | self.curDefer.resolve(e.data.result); 13 | self.curDefer = null; 14 | } else if(e.data.type === "loaded"){ 15 | worker.postMessage({ 16 | type: "ini", 17 | userId: user.id, 18 | brain: brainName, 19 | options: options 20 | }); 21 | } else if(e.data.type === "ini-ed"){ 22 | self.initDefer.resolve(); 23 | } else if(e.data.type === "confirmed"){ 24 | self.confirmDefer.resolve(); 25 | } 26 | }; 27 | }; 28 | 29 | AsyncBrain.prototype = Object.create(Brain.prototype); 30 | 31 | AsyncBrain.prototype.terminate = function(){ 32 | this.initDefer && this.initDefer.reject(); 33 | this.curDefer && this.curDefer.reject(); 34 | this.confirmDefer && this.confirmDefer.reject(); 35 | }; 36 | 37 | AsyncBrain.prototype.init = function(){ 38 | return this.initDefer; 39 | }; 40 | 41 | AsyncBrain.prototype.watch = function(info){ 42 | var tinfo = { 43 | type: info.type, 44 | player: info.player.id 45 | }; 46 | if(info.cards){ 47 | tinfo.cards = info.cards.map(function(c){ return c.id; }); 48 | } 49 | if(info.card){ 50 | tinfo.card = info.card.id; 51 | } 52 | if(info.curSuit){ 53 | tinfo.curSuit = info.curSuit; 54 | } 55 | this.worker.postMessage({ 56 | type: "watch", 57 | params: tinfo 58 | }); 59 | }; 60 | 61 | AsyncBrain.prototype.confirmCards = function(){ 62 | this.worker.postMessage({ 63 | type: "confirm", 64 | cards: this.user.row.cards.map(function(c){ return c.id; }) 65 | }); 66 | this.confirmDefer = $.Deferred(); 67 | return this.confirmDefer; 68 | }; 69 | 70 | AsyncBrain.prototype.decide = function(validCards, boardCards, boardPlayers, scores){ 71 | this.worker.postMessage({ 72 | type: "decide", 73 | params: { 74 | validCards: validCards.map(function(c){ return { id: c.id, ind: c.ind }; }), 75 | boardCards: boardCards.map(function(c){ return c.id; }), 76 | boardPlayers: boardPlayers.map(function(c){ return c.id; }), 77 | scores: scores 78 | } 79 | }); 80 | this.curDefer = $.Deferred(); 81 | return this.curDefer; 82 | }; 83 | 84 | return AsyncBrain; 85 | }); -------------------------------------------------------------------------------- /js/Human.js: -------------------------------------------------------------------------------- 1 | define(["Player", "jquery", "ui"], 2 | function(Player, $, ui){ 3 | "use strict"; 4 | 5 | var Human = function(id, name){ 6 | Player.call(this, id, name); 7 | this.row.flipped = false; 8 | this.display.setHuman(true); 9 | }; 10 | 11 | Human.prototype = Object.create(Player.prototype); 12 | 13 | Human.prototype.takeIn = function(cards){ 14 | Player.prototype.takeIn.call(this,cards); 15 | this.row.setSelected(cards); 16 | }; 17 | 18 | Human.prototype.decide = function(validCards){ 19 | validCards.forEach(function(c){ 20 | c.display.setSelectable(true); 21 | }); 22 | if(validCards.length === 1 && validCards[0].id === 26){ 23 | ui.showMessage('Please start with 2 of Clubs.'); 24 | } 25 | var d = $.Deferred(); 26 | var row = this.row; 27 | ui.buttonClickOnce(function(){ 28 | ui.hideMessage(); 29 | ui.hideButton(); 30 | validCards.forEach(function(c){ 31 | c.display.setSelectable(false); 32 | }); 33 | d.resolve(row.getSelected()[0]); 34 | }); 35 | return d; 36 | }; 37 | 38 | Human.prototype.confirmTransfer = function(){ 39 | ui.showButton("Confirm"); 40 | ui.hideArrow(); 41 | ui.hideMessage(); 42 | var d = $.Deferred(); 43 | ui.buttonClickOnce(function(){ 44 | this.doneTransfer(); 45 | d.resolve(); 46 | }.bind(this)); 47 | return d; 48 | }; 49 | 50 | Human.prototype.doneTransfer = function(){ 51 | this.row.curShifted = []; 52 | this.row.adjustPos(); 53 | ui.hideButton(); 54 | }; 55 | 56 | Human.prototype.initForNewRound = function(){ 57 | Player.prototype.initForNewRound.call(this); 58 | this.row.curShifted = []; 59 | }; 60 | 61 | Human.prototype.prepareTransfer = function(dir){ 62 | ui.showPassingScreen(dir); 63 | this.row.cards.forEach(function(c){ 64 | c.display.setSelectable(true); 65 | }); 66 | this.row.maxShift = 3; 67 | var d = $.Deferred(); 68 | var row = this.row; 69 | ui.arrowClickOnce(function(){ 70 | this.selected = row.getSelected(); 71 | this.row.maxShift = 1; 72 | this.row.cards.forEach(function(c){ 73 | c.display.setSelectable(false); 74 | }); 75 | d.resolve(); 76 | }.bind(this)); 77 | 78 | return d; 79 | }; 80 | 81 | Human.prototype.rowSelected = function(){ 82 | if(this.row.maxShift === 3){ 83 | ui.showArrow(); 84 | } else { 85 | ui.showButton("Go!"); 86 | } 87 | }; 88 | 89 | Human.prototype.rowDeselected = function(){ 90 | if(this.row.maxShift === 3){ 91 | ui.hideArrow(); 92 | } else { 93 | ui.hideButton(); 94 | } 95 | }; 96 | 97 | return Human; 98 | }); -------------------------------------------------------------------------------- /js/Player.js: -------------------------------------------------------------------------------- 1 | define(["Row", "Waste", "domBinding"], 2 | function(Row , Waste, domBinding){ 3 | "use strict"; 4 | 5 | var Player = function(id, name){ 6 | this.row = new Row(id, this); 7 | this.waste = new Waste(id, this); 8 | this.id = id; 9 | this._score = 0; 10 | this._oldScore = 0; 11 | this.display = domBinding.createPlayerDisplay(id, name); 12 | this.brain = null; 13 | this.selected = null; 14 | 15 | Object.seal(this); 16 | }; 17 | 18 | Player.prototype.setName = function(name){ 19 | this.display.setName(name); 20 | }; 21 | 22 | Player.prototype.adjustPos = function(){ 23 | this.row.adjustPos(); 24 | this.waste.adjustPos(); 25 | this.display.adjustPos(); 26 | }; 27 | 28 | Player.prototype.initForNewRound = function(){ 29 | this._score = 0; 30 | this.row.cards = []; 31 | this.waste.cards = []; 32 | this.display.rank = null; 33 | this.display.moveUp = false; 34 | this.display.adjustPos(); 35 | this.display.setScoreText(this._oldScore); 36 | 37 | // if(this.id % 2 === 1) this.brain = new McBrain(this); 38 | // if(this.id === 2) this.brain = new McBrain(this); 39 | // else if(this.id === 1) this.brain = new PomDPBrain(this); 40 | // else if(this.id === 2) this.brain = new randomBrain(this); 41 | // else this.brain = new SimpleBrain(this); 42 | // this.brain = new RandomBrain(); 43 | }; 44 | 45 | Player.prototype.out = function(outCards){ 46 | var self = this; 47 | outCards.forEach(function(c){ 48 | self.row.out(c); 49 | }); 50 | }; 51 | 52 | Player.prototype.takeIn = function(inCards){ 53 | var self = this; 54 | inCards.forEach(function(c){ 55 | self.row.addCard(c); 56 | }); 57 | }; 58 | 59 | Player.prototype.clearScore = function(){ 60 | this._score = this._oldScore = 0; 61 | }; 62 | 63 | Player.prototype.setScore = function(val){ 64 | this._score = val; 65 | this.display.setScoreText(this._oldScore + " + " + this._score); 66 | }; 67 | 68 | Player.prototype.finalizeScore = function(){ 69 | this._oldScore += this._score; 70 | this._score = 0; 71 | this.display.setFinalText(this._oldScore); 72 | }; 73 | 74 | Player.prototype.incrementScore = function(val){ 75 | this.setScore(this._score + val); 76 | if(val > 0) this.display.highlight(); 77 | }; 78 | 79 | Player.prototype.getScore = function(){ 80 | return this._score; 81 | }; 82 | 83 | Player.prototype.setActive = function(yes){ 84 | this.display.setHighlight(yes); 85 | }; 86 | 87 | Player.prototype.watch = function(){}; 88 | 89 | Player.prototype.transferTo = function(other){ 90 | var cards = this.selected; 91 | this.selected = null; 92 | this.out(cards); 93 | other.takeIn(cards); 94 | }; 95 | 96 | return Player; 97 | }); -------------------------------------------------------------------------------- /js/board.js: -------------------------------------------------------------------------------- 1 | define(["Card", "jquery", "layout"], 2 | function(Card, $, layout){ 3 | var cards = []; 4 | 5 | for(var i = 0; i < 52; i++){ 6 | cards.push(new Card(i)); 7 | } 8 | 9 | var carddeck = []; 10 | for(i = 0; i < 52; i++) { 11 | carddeck.push(i); 12 | } 13 | return { 14 | cards: cards, 15 | init: function(){ 16 | this.desk.cards.length = 0; 17 | this.desk.players.length = 0; 18 | var self = this; 19 | this.cards.forEach(function(c){ 20 | c.parent = self; 21 | c.display.setSelectable(false); 22 | }); 23 | }, 24 | shuffleDeck: function(){ 25 | var i; 26 | 27 | for(i = 0; i < 52; i++){ 28 | var ran = Math.floor(Math.random() * (52 - i)); 29 | var tmp = carddeck[ran]; 30 | carddeck[ran] = carddeck[51-i]; 31 | carddeck[51 - i] = tmp; 32 | } 33 | 34 | for(i = 51; i >= 0; i--){ 35 | this.cards[carddeck[i]].ind = carddeck[i]; 36 | this.cards[carddeck[i]].adjustPos(); 37 | } 38 | }, 39 | distribute: function(players){ 40 | var curI = 0; 41 | var d = $.Deferred(); 42 | function move(){ 43 | if(curI === cards.length){ 44 | setTimeout(function(){ 45 | d.resolve(); 46 | }, 200); 47 | return; 48 | } 49 | players[curI % 4].row.addCard(cards[carddeck[curI]]); 50 | players[curI % 4].row.adjustPos(); 51 | curI++; 52 | setTimeout(move, 10); 53 | } 54 | setTimeout(function(){move();}, 300); 55 | return d; 56 | }, 57 | getPosFor: function(ind){ 58 | return { 59 | x: (52 - ind) / 4, 60 | y: (52 - ind) / 4, 61 | z: -i, 62 | rotateY: 180 63 | }; 64 | }, 65 | desk: { 66 | cards: [], 67 | players: [], 68 | curScore: 0, 69 | getPosFor: function(ind){ 70 | var pos = { 71 | x: 0, 72 | y: layout.cardHeight / 2 + layout.cardWidth / 2, 73 | z: ind + 52, 74 | rotateY: 0 75 | }; 76 | pos.rotation = this.cards[ind].pos.rotation; 77 | return pos; 78 | }, 79 | addCard: function(card, player){ 80 | card.ind = this.cards.length; 81 | this.cards.push(card); 82 | this.players.push(player); 83 | card.parent = this; 84 | }, 85 | adjustPos: function(){ 86 | this.cards.forEach(function(c){ 87 | c.adjustPos(); 88 | }); 89 | }, 90 | score: function(){ 91 | var max = 0; 92 | for(var i = 1; i < 4; i++){ 93 | if(this.cards[i].suit === this.cards[max].suit && (this.cards[i].num > this.cards[max].num)){ 94 | max = i; 95 | } 96 | } 97 | var p = this.players[max], 98 | self = this; 99 | var nextTime = 600, 100 | time = 800; 101 | if(window.isDebug){ 102 | nextTime = 0; 103 | time = 0; 104 | } 105 | var info = [this.players[max], [].concat(this.cards)]; 106 | this.players.length = 0; 107 | this.cards.length = 0; 108 | 109 | return info; 110 | } 111 | } 112 | }; 113 | }); 114 | -------------------------------------------------------------------------------- /js/Row.js: -------------------------------------------------------------------------------- 1 | define(["layout"], 2 | function(layout){ 3 | "use strict"; 4 | 5 | var Row = function(id, player){ 6 | this.id = id; 7 | this.cards = []; 8 | this.isVertical = id%2; 9 | this.rotation = 90 * (( id + 1) % 4) - 90; 10 | this.curShifted = []; 11 | this.flipped = true; 12 | this.playedBy = player; 13 | }; 14 | 15 | Row.prototype.addCard = function(card){ 16 | card.parent = this; 17 | card.ind = this.cards.length; 18 | this.cards.push(card); 19 | }; 20 | 21 | Row.prototype.getSelected = function(){ 22 | return [].concat(this.curShifted); 23 | }; 24 | 25 | Row.prototype.setSelected = function(cards){ 26 | this.curShifted = [].concat(cards); 27 | }; 28 | 29 | Row.prototype.adjustPos = function(){ 30 | if(this.isVertical){ 31 | this.distance = layout.width / 2 - layout.rowMargin - layout.cardHeight / 2; 32 | }else{ 33 | this.distance = layout.height / 2 - layout.rowMargin - layout.cardHeight / 2; 34 | } 35 | this.left = -((this.cards.length - 1) * layout.cardSep) / 2; 36 | this.cards.forEach(function(c){ 37 | c.adjustPos(); 38 | }); 39 | }; 40 | 41 | Row.prototype.getPosFor = function(ind){ 42 | var pos = { 43 | x: this.left + ind * layout.cardSep, 44 | y: this.distance, 45 | rotation: this.rotation, 46 | rotateY: this.flipped ? 180 : 0, 47 | z: ind 48 | }; 49 | if(this.curShifted.indexOf(this.cards[ind]) > -1){ 50 | pos.y -= 30; 51 | } 52 | return pos; 53 | }; 54 | 55 | Row.prototype.sort = function(){ 56 | this.cards.sort(function(a, b){ 57 | if(a.suit != b.suit) return b.suit - a.suit; 58 | return a.num - b.num; 59 | }).forEach(function(v, ind){ 60 | v.ind = ind; 61 | }); 62 | this.adjustPos(); 63 | }; 64 | 65 | Row.prototype.addShift = function(nc){ 66 | if(this.curShifted.length === this.maxShift){ 67 | this.curShifted.shift(); 68 | } 69 | this.curShifted.push(nc); 70 | if(this.curShifted.length === this.maxShift){ 71 | this.playedBy.rowSelected(this.maxShift); 72 | } 73 | this.adjustPos(); 74 | }; 75 | 76 | Row.prototype.out = function(card){ 77 | card.parent = null; 78 | var ind = this.cards.indexOf(card); 79 | this.curShifted = []; 80 | this.cards.splice(ind, 1); 81 | for(var i = ind; i < this.cards.length; i++){ 82 | this.cards[i].ind = i; 83 | } 84 | this.adjustPos(); 85 | }; 86 | 87 | Row.prototype.removeShift = function(nc){ 88 | this.curShifted = this.curShifted.filter(function(v){ 89 | return v !== nc; 90 | }); 91 | this.playedBy.rowDeselected(); 92 | this.adjustPos(); 93 | }; 94 | 95 | Row.prototype.hideOut = function(ind){ 96 | var tmp = this.cards[ind]; 97 | var mid = Math.floor(this.cards.length / 2); 98 | this.cards[ind] = this.cards[mid]; 99 | this.cards[mid] = tmp; 100 | this.cards[ind].ind = ind; 101 | this.cards[mid].ind = mid; 102 | if(!window.isDebug){ 103 | this.cards[ind].display.style[vendorPrefix + 'Transition'] = 'none'; 104 | this.cards[mid].display.style[vendorPrefix + 'Transition'] = 'none'; 105 | } 106 | this.cards[mid].adjustPos(); 107 | this.cards[ind].adjustPos(); 108 | if(!window.isDebug){ 109 | this.cards[ind].display.style[vendorPrefix + 'Transition'] = ''; 110 | this.cards[mid].display.style[vendorPrefix + 'Transition'] = ''; 111 | } 112 | var self = this; 113 | setTimeout(function(){ 114 | self.out(mid, true); 115 | }, window.isDebug ? 0 : 100); 116 | }; 117 | 118 | return Row; 119 | }); 120 | -------------------------------------------------------------------------------- /js/domBinding.js: -------------------------------------------------------------------------------- 1 | define(function(){ 2 | 3 | var suits = ['spade', 'heart', 'club', 'diamond']; 4 | 5 | var frag; 6 | 7 | var CardDisplay = function(dom){ 8 | this.dom = $(dom); 9 | this.dom.on("click", function(){ 10 | this.onClick && this.onClick(); 11 | }.bind(this)); 12 | }; 13 | 14 | CardDisplay.prototype.adjustPos = function(pos){ 15 | if(!pos.rotation){ 16 | pos.rotation = 0; 17 | } 18 | if(!pos.rotateY){ 19 | pos.rotateY = 0; 20 | } 21 | this.dom.css({ 22 | zIndex: 10 + pos.z, 23 | transform: 'rotate(' + pos.rotation + 'deg) translate3d(' + pos.x + 'px, ' + pos.y + 'px, ' + pos.z + 'px) rotateY(' + pos.rotateY + 'deg)' 24 | }); 25 | }; 26 | 27 | CardDisplay.prototype.setSelectable = function(yes){ 28 | if(yes){ 29 | this.dom.addClass("movable"); 30 | } else { 31 | this.dom.removeClass("movable"); 32 | } 33 | }; 34 | 35 | CardDisplay.prototype.isSelectable = function(){ 36 | return this.dom.is(".movable"); 37 | }; 38 | 39 | 40 | var PlayerDisplay = function(id, name, human){ 41 | this.id = id; 42 | this.display = document.createElement('div'); 43 | this.display.className = 'info-board board-' + id; 44 | this.nametext = document.createElement('div'); 45 | this.nametext.className = 'player-name'; 46 | this.nametext.innerHTML = name; 47 | this.scoretext = document.createElement('div'); 48 | this.scoretext.className = 'player-score'; 49 | this.scoretext.innerHTML = 0; 50 | this.finaltext = document.createElement('div'); 51 | this.finaltext.className = 'final-score'; 52 | this.finaltext.innerHTML = 0; 53 | 54 | this.display.appendChild(this.nametext); 55 | this.display.appendChild(this.scoretext); 56 | this.display.appendChild(this.finaltext); 57 | 58 | frag.appendChild(this.display); 59 | 60 | this.rank = null; 61 | }; 62 | 63 | PlayerDisplay.prototype.setName = function(name){ 64 | this.nametext.innerHTML = name; 65 | }; 66 | 67 | 68 | PlayerDisplay.prototype.setHuman = function(yes){ 69 | if(yes){ 70 | this.display.className += " human"; 71 | } 72 | }; 73 | 74 | PlayerDisplay.prototype.setHighlight = function(yes){ 75 | if(yes){ 76 | $(this.display).addClass("highlight"); 77 | } else { 78 | $(this.display).removeClass("highlight"); 79 | } 80 | }; 81 | 82 | PlayerDisplay.prototype.adjustPos = function(){ 83 | var d = $(this.display); 84 | if(this.rank === null){ 85 | var adjust = this.finaltext.classList.contains("show") ? 55 : 0; 86 | this.finaltext.classList.remove('show'); 87 | d.css({ 88 | marginLeft: -d.width() / 2 + adjust, 89 | marginTop: -d.height() / 2, 90 | transform: "", 91 | top: "", 92 | left: "" 93 | }).removeClass("table"); 94 | } else { 95 | this.finaltext.classList.add('show'); 96 | d.css({ 97 | top: this.moveUp ? "20%" : "50%", 98 | left: "50%", 99 | marginLeft: -d.width() / 2 - 55, 100 | marginTop: -d.height() / 2, 101 | transform: "translateY(" + ((1.2 * d.height()) * (this.rank - 2)) + "px)" 102 | }).addClass("table"); 103 | } 104 | }; 105 | 106 | PlayerDisplay.prototype.setScoreText = function(text){ 107 | this.scoretext.innerHTML = text; 108 | }; 109 | 110 | PlayerDisplay.prototype.setFinalText = function(text){ 111 | this.finaltext.innerHTML = text; 112 | }; 113 | 114 | PlayerDisplay.prototype.highlight = function(){ 115 | var b = this.scoretext.classList; 116 | b.add('highlight'); 117 | setTimeout(function(){ 118 | b.remove('highlight'); 119 | }, 100); 120 | }; 121 | 122 | return { 123 | fragmentToDom: function(dom){ 124 | if(frag){ 125 | dom.appendChild(frag); 126 | frag = null; 127 | } 128 | }, 129 | createPlayerDisplay: function(id, name){ 130 | return new PlayerDisplay(id, name); 131 | }, 132 | createCardDisplay: function(numtext, suit){ 133 | if(!frag){ 134 | frag = document.createDocumentFragment(); 135 | } 136 | var display = document.createElement('div'); 137 | display.className = 'card flipped'; 138 | $(display).css({ 139 | transform: 'rotateY(180deg)' 140 | }); 141 | 142 | var numText = document.createElement('div'); 143 | numText.className = 'num'; 144 | numText.innerHTML = numtext; 145 | 146 | var front = document.createElement('div'); 147 | front.className = 'front'; 148 | front.appendChild(numText); 149 | display.classList.add(suits[suit]); 150 | 151 | var icon = document.createElement('div'); 152 | icon.className = 'icon'; 153 | front.appendChild(icon); 154 | 155 | display.appendChild(front); 156 | 157 | var back = document.createElement('div'); 158 | back.className = 'back'; 159 | 160 | display.appendChild(back); 161 | 162 | frag.appendChild(display); 163 | 164 | return new CardDisplay(display); 165 | } 166 | }; 167 | }); -------------------------------------------------------------------------------- /js/Simulator.js: -------------------------------------------------------------------------------- 1 | define(["prematureOptimization"], 2 | function(op){ 3 | "use strict"; 4 | 5 | var cardsInfo = op.cardsInfo, 6 | infoToCardId = op.infoToCardId, 7 | removeFromUnorderedArray = op.removeFromUnorderedArray; 8 | 9 | var Simulator = function(){ 10 | this.curCards = [[], [], [], []]; 11 | this.curPlayers = []; 12 | this.curBoard = []; 13 | this.heartBroken = false; 14 | this.curP = 0; 15 | 16 | this.tmpVc = []; 17 | }; 18 | 19 | Simulator.prototype.run = function(curPlayers, curBoard, heartBroken, curCards, cardToPlay, myID, scores){ 20 | this.curCards.forEach(function(t, ind){ 21 | t.length = 0; 22 | [].push.apply(t, curCards[ind]); 23 | }); 24 | 25 | this.curPlayers.length = 0; 26 | [].push.apply(this.curPlayers, curPlayers); 27 | 28 | this.curBoard.length = 0; 29 | [].push.apply(this.curBoard, curBoard); 30 | 31 | this.heartBroken = heartBroken; 32 | 33 | this.curP = myID; 34 | 35 | this.scores = scores; 36 | this._play(myID, cardToPlay); 37 | this._rollout(); 38 | 39 | var moonShooter = -1; 40 | this.scores.forEach(function(s, ind){ 41 | if(s === 26) moonShooter = ind; 42 | }); 43 | 44 | if(moonShooter !== -1){ 45 | if(moonShooter === this.curP) return -26; 46 | else return 26; 47 | } 48 | 49 | return this.scores[this.curP]; 50 | }; 51 | 52 | Simulator.prototype._play = function(player, card){ 53 | this.curPlayers.push(player); 54 | this.curBoard.push(card); 55 | 56 | if(cardsInfo[card].suit === 1){ 57 | this.heartBroken = true; 58 | } 59 | 60 | removeFromUnorderedArray(this.curCards[player], card); 61 | }; 62 | 63 | Simulator.prototype._getValidCards = function(cards){ 64 | this.tmpVc.length = 0; 65 | var vc = this.tmpVc; 66 | if(this.curBoard.length === 0){ 67 | if(this.heartBroken){ 68 | [].push.apply(this.tmpVc, cards); 69 | } else { 70 | cards.forEach(function(c){ 71 | if(cardsInfo[c].suit !== 1){ 72 | vc.push(c); 73 | } 74 | }); 75 | } 76 | } else { 77 | var suit = cardsInfo[this.curBoard[0]].suit; 78 | cards.forEach(function(c){ 79 | if(cardsInfo[c].suit === suit){ 80 | vc.push(c); 81 | } 82 | }); 83 | } 84 | if(!vc.length){ 85 | [].push.apply(vc, cards); 86 | } 87 | 88 | return this.tmpVc; 89 | }; 90 | 91 | Simulator.prototype._rollout = function(){ 92 | var curPlayer = this.curP; 93 | curPlayer++; 94 | curPlayer %= 4; 95 | 96 | while(this.curBoard.length < 4){ 97 | this._play(curPlayer, this._othersDecide(this.curCards[curPlayer])); 98 | curPlayer++; 99 | curPlayer %= 4; 100 | } 101 | 102 | // end the first round 103 | this._endRound(); 104 | 105 | while(this.curCards[this.nextFirst].length){ 106 | curPlayer = this.nextFirst; 107 | while(this.curBoard.length < 4){ 108 | if(curPlayer === this.curP){ 109 | this._play(curPlayer, this._iDecide(this.curCards[curPlayer])); 110 | }else{ 111 | this._play(curPlayer, this._othersDecide(this.curCards[curPlayer])); 112 | } 113 | curPlayer++; 114 | curPlayer %= 4; 115 | } 116 | this._endRound(); 117 | } 118 | }; 119 | 120 | Simulator.prototype._endRound = function(){ 121 | var len = this.curCards[0].length; 122 | for(var i = 1; i < 4; i++){ 123 | if(len != this.curCards[i].length){ 124 | console.log(this.curCards) 125 | throw "what!"; 126 | } 127 | } 128 | var curSuit = cardsInfo[this.curBoard[0]].suit, 129 | maxCard = 0, 130 | maxNum = cardsInfo[this.curBoard[0]].num, 131 | i, 132 | score = 0; 133 | 134 | for(i = 0; i < 4; i++){ 135 | var c = cardsInfo[this.curBoard[i]]; 136 | if(c.suit === curSuit && c.num > maxNum){ 137 | maxNum = c.num; 138 | maxCard = i; 139 | } 140 | 141 | if(c.suit === 1) score += 1; 142 | if(c.suit === 0 && c.num === 11) score += 13; 143 | } 144 | 145 | this.scores[this.curPlayers[maxCard]] += score; 146 | 147 | this.nextFirst = this.curPlayers[maxCard]; 148 | this.curBoard.length = 0; 149 | this.curPlayers.length = 0; 150 | }; 151 | 152 | Simulator.prototype._othersDecide = function(cards){ 153 | var vc = this._getValidCards(cards), 154 | len = vc.length, 155 | suit = -1, maxNum = -1, 156 | board = this.curBoard; 157 | // return vc[Math.floor(vc.length * Math.random())]; 158 | 159 | if(board.length){ 160 | suit = cardsInfo[board[0]].suit; 161 | maxNum = board.reduce(function(prev, curc){ 162 | var cur = cardsInfo[curc]; 163 | if(cur.suit === suit && cur.num > prev){ 164 | return cur.num; 165 | }else{ 166 | return prev; 167 | } 168 | }, 0); 169 | return vc.reduce(function(prevc, curc){ 170 | var cur = cardsInfo[curc], 171 | prev = cardsInfo[prevc]; 172 | if(prev.suit === cur.suit){ 173 | if(cur.suit === suit){ 174 | if(cur.num < maxNum){ 175 | if(prev.num > maxNum || prev.num < cur.num) return curc; 176 | else return prevc; 177 | }else if(cur.num > maxNum && prev.num > maxNum && board.length === 3){ 178 | if(cur.num > prev.num) return curc; 179 | else return prevc; 180 | }else if(cur.num < prev.num){ 181 | return curc; 182 | }else{ 183 | return prevc; 184 | } 185 | }else{ 186 | if(cur.num > prev.num) return curc; 187 | else return prevc; 188 | } 189 | }else{ 190 | if(cur.suit === 0 && cur.num === 11) return curc; 191 | if(prev.suit === 0 && prev.num === 11) return prevc; 192 | if(cur.suit === 1) return curc; 193 | if(prev.suit === 1) return prevc; 194 | if(cur.num > prev.num) return curc; 195 | return prevc; 196 | } 197 | }); 198 | }else{ 199 | return vc.reduce(function(prev, cur){ 200 | if(cardsInfo[prev].num > cardsInfo[cur].num) return cur; 201 | else return prev; 202 | }); 203 | } 204 | }; 205 | 206 | 207 | // Simulator.prototype._iDecide = function(cards){ 208 | // var vc = this._getValidCards(cards); 209 | // return vc[Math.floor(vc.length * Math.random())]; 210 | // }; 211 | Simulator.prototype._iDecide = Simulator.prototype._othersDecide; 212 | 213 | return Simulator; 214 | }); -------------------------------------------------------------------------------- /js/PomDPSimulator.js: -------------------------------------------------------------------------------- 1 | define(["prematureOptimization"], 2 | function(op){ 3 | "use strict"; 4 | 5 | var removeFromUnorderedArray = op.removeFromUnorderedArray; 6 | var cardsInfo = op.cardsInfo; 7 | 8 | var PomDPSimulator = function(id){ 9 | this.observationBuffer = []; 10 | this.playerId = id; 11 | this.tmpVc = []; 12 | }; 13 | 14 | PomDPSimulator.prototype._playCard = function(card) { 15 | var playerCard = (this.curPlayer + 1) * 100 + card; 16 | 17 | var board = this.state.board; 18 | this.state.board.push(playerCard); 19 | this.observationBuffer.push(playerCard); 20 | 21 | removeFromUnorderedArray(this.state.players[this.curPlayer], card); 22 | 23 | var boardScore = 0, 24 | curSuit, 25 | maxPlayer, 26 | maxNum = 0; 27 | 28 | if(this.state.board.length === 4){ 29 | curSuit = cardsInfo[board[0] % 100].suit; 30 | for(var i = 0; i < 4; i++){ 31 | var player = ((board[i] / 100) | 0) - 1, 32 | c = cardsInfo[board[i] % 100]; 33 | if(c.suit === 1){ 34 | boardScore += 1; 35 | } else if (c.num === 11 && c.suit === 0){ 36 | boardScore += 13; 37 | } 38 | if(c.suit === curSuit && c.num > maxNum) { 39 | maxNum = c.num; 40 | maxPlayer = player; 41 | } 42 | } 43 | this.state.scores[maxPlayer] += boardScore; 44 | this.curPlayer = maxPlayer; 45 | this.state.board.length = 0; 46 | } else { 47 | this.curPlayer = (this.curPlayer + 1) % 4; 48 | } 49 | }; 50 | 51 | PomDPSimulator.prototype._getValidCards = function(cards){ 52 | var vc = this.tmpVc; 53 | this.tmpVc.length = 0; 54 | if(this.state.board.length === 0){ 55 | if(cards.length === 13) { 56 | this.tmpVc.push(26); 57 | }else if(this.state.heartBroken){ 58 | [].push.apply(this.tmpVc, cards); 59 | } else { 60 | cards.forEach(function(c){ 61 | if(cardsInfo[c].suit !== 1){ 62 | vc.push(c); 63 | } 64 | }); 65 | } 66 | } else { 67 | var suit = cardsInfo[this.state.board[0] % 100].suit; 68 | cards.forEach(function(c){ 69 | if(cardsInfo[c].suit === suit){ 70 | vc.push(c); 71 | } 72 | }); 73 | } 74 | if(!vc.length){ 75 | [].push.apply(vc, cards); 76 | } 77 | 78 | return this.tmpVc; 79 | }; 80 | 81 | PomDPSimulator.prototype._decide = function(player) { 82 | var cards = this.state.players[player]; 83 | if(!cards.length) return null; 84 | var vc = this._getValidCards(cards), 85 | len = vc.length, 86 | suit = -1, maxNum = -1, 87 | board = this.state.board; 88 | 89 | if(board.length){ 90 | suit = cardsInfo[board[0] % 100].suit; 91 | maxNum = board.reduce(function(prev, curc){ 92 | var cur = cardsInfo[curc % 100]; 93 | if(cur.suit === suit && cur.num > prev){ 94 | return cur.num; 95 | }else{ 96 | return prev; 97 | } 98 | }, 0); 99 | return vc.reduce(function(prevc, curc){ 100 | var cur = cardsInfo[curc], 101 | prev = cardsInfo[prevc]; 102 | if(prev.suit === cur.suit){ 103 | if(cur.suit === suit){ 104 | if(cur.num < maxNum){ 105 | if(prev.num > maxNum || prev.num < cur.num) return curc; 106 | else return prevc; 107 | }else if(cur.num > maxNum && prev.num > maxNum && board.length === 3){ 108 | if(cur.num > prev.num) return curc; 109 | else return prevc; 110 | }else if(cur.num < prev.num){ 111 | return curc; 112 | }else{ 113 | return prevc; 114 | } 115 | }else{ 116 | if(cur.num > prev.num) return curc; 117 | else return prevc; 118 | } 119 | }else{ 120 | if(cur.suit === 0 && cur.num === 11) return curc; 121 | if(prev.suit === 0 && prev.num === 11) return prevc; 122 | if(cur.suit === 1) return curc; 123 | if(prev.suit === 1) return prevc; 124 | if(cur.num > prev.num) return curc; 125 | return prevc; 126 | } 127 | }); 128 | }else{ 129 | return vc.reduce(function(prev, cur){ 130 | if(cardsInfo[prev].num > cardsInfo[cur].num) return cur; 131 | else return prev; 132 | }); 133 | } 134 | }; 135 | 136 | PomDPSimulator.prototype.step = function(s, a){ 137 | var players = s.players, 138 | heartBroken = s.heartBroken, 139 | board = s.board, 140 | scores = s.scores, 141 | oriScore = s.scores[this.playerId]; 142 | this.state = s; 143 | this.curPlayer = this.playerId; 144 | 145 | this._playCard(a); 146 | 147 | while(this.curPlayer !== this.playerId){ 148 | var toPlay = this._decide(this.curPlayer); 149 | if(toPlay === null) break; 150 | this._playCard(toPlay); 151 | } 152 | var moonShooter = -1, outputScore = oriScore - this.state.scores[this.playerId]; 153 | this.state.scores.forEach(function(s, ind){ 154 | if(s === 26) { 155 | moonShooter = ind; 156 | } 157 | }); 158 | if(moonShooter !== -1){ 159 | if(moonShooter === this.playerId){ 160 | outputScore = oriScore + 26; 161 | } else { 162 | outputScore = oriScore - 26; 163 | } 164 | } 165 | var result = { 166 | state: s, 167 | observation: this.observationBuffer.concat([]), 168 | score: outputScore 169 | }; 170 | 171 | this.observationBuffer.length = 0; 172 | 173 | return result; 174 | }; 175 | 176 | PomDPSimulator.prototype.run = function(s){ 177 | var players = s.players, 178 | heartBroken = s.heartBroken, 179 | board = s.board, 180 | oriScore = s.scores[this.playerId]; 181 | this.state = s; 182 | this.curPlayer = this.playerId; 183 | 184 | while(1){ 185 | var toPlay = this._decide(this.curPlayer); 186 | if(toPlay === null) break; 187 | this._playCard(toPlay); 188 | } 189 | var moonShooter = -1, outputScore = oriScore - this.state.scores[this.playerId]; 190 | this.state.scores.forEach(function(s, ind){ 191 | if(s === 26) moonShooter = ind; 192 | }); 193 | if(moonShooter !== -1){ 194 | if(moonShooter === this.playerId){ 195 | outputScore = oriScore + 26; 196 | } else { 197 | outputScore = oriScore - 26; 198 | } 199 | } 200 | 201 | this.observationBuffer.length = 0; 202 | 203 | return outputScore; 204 | }; 205 | 206 | return PomDPSimulator; 207 | }); -------------------------------------------------------------------------------- /js/McBrain.js: -------------------------------------------------------------------------------- 1 | define([ "Simulator", "Brain", "prematureOptimization"], 2 | function( Simulator , Brain, op){ 3 | "use strict"; 4 | 5 | var cardsInfo = op.cardsInfo; 6 | var removeFromUnorderedArray = op.removeFromUnorderedArray; 7 | var defaultOptions = { 8 | time: 1000 9 | }; 10 | 11 | var McBrain = function(userid, options){ 12 | Brain.call(this, userid); 13 | 14 | if(!options){ 15 | options = defaultOptions; 16 | } 17 | 18 | this.maxTime = options.time; 19 | 20 | this.samplePlayers = [[], [], [], []]; 21 | this.tmpSample = [[], [], [], []]; 22 | 23 | this.heartBroken = false; 24 | 25 | this.playersInfo = [ 26 | { 27 | hasCard: [], 28 | lackCard: {}, 29 | numCards: 13, 30 | score: 0 31 | }, 32 | { 33 | hasCard: [], 34 | lackCard: {}, 35 | numCards: 13, 36 | score: 0 37 | }, 38 | { 39 | hasCard: [], 40 | lackCard: {}, 41 | numCards: 13, 42 | score: 0 43 | }, 44 | { 45 | hasCard: [], 46 | lackCard: {}, 47 | numCards: 13, 48 | score: 0 49 | } 50 | ]; 51 | 52 | this.remainingCards = []; 53 | for(var i = 0; i < 52; i++){ 54 | this.remainingCards.push(i); 55 | } 56 | 57 | this.cardLackCount = cardsInfo.map(function(){ 58 | return 0; 59 | }); 60 | 61 | this.simulator = new Simulator(); 62 | }; 63 | 64 | McBrain.prototype = Object.create(Brain.prototype); 65 | 66 | McBrain.prototype.removeRemainingCard = function(id){ 67 | removeFromUnorderedArray(this.remainingCards, id); 68 | this.playersInfo.forEach(function(p){ 69 | removeFromUnorderedArray(p.hasCard, id); 70 | }); 71 | }; 72 | 73 | McBrain.prototype.markLackCard = function(c, player){ 74 | if(!player.lackCard[c]){ 75 | player.lackCard[c] = true; 76 | this.cardLackCount[c]++; 77 | } 78 | }; 79 | 80 | McBrain.prototype.watch = function(info){ 81 | if(info.type === "in"){ 82 | info.cards.forEach(function(c){ 83 | this.removeRemainingCard(c); 84 | }.bind(this)); 85 | [].push.apply(this.playersInfo[info.player].hasCard, info.cards); 86 | }else{ 87 | this.playersInfo[info.player].numCards--; 88 | this.removeRemainingCard(info.card); 89 | if(cardsInfo[info.card].suit === 1) this.heartBroken = true; 90 | var markLackCard = this.markLackCard.bind(this), 91 | lackCardPlayer = this.playersInfo[info.player]; 92 | if(info.curSuit !== cardsInfo[info.card].suit){ 93 | this.remainingCards.forEach(function(c){ 94 | if(cardsInfo[c].suit === info.curSuit){ 95 | markLackCard(c, lackCardPlayer); 96 | } 97 | }); 98 | } 99 | } 100 | }; 101 | 102 | McBrain.prototype.confirmCards = function(cards){ 103 | cards.forEach(function(c){ 104 | this.removeRemainingCard(c); 105 | this.samplePlayers[this.user].push(c); 106 | }.bind(this)); 107 | }; 108 | 109 | McBrain.prototype.decide = function(vc, cids, pids, pscores){ 110 | var r; 111 | 112 | if(vc.length === 1){ 113 | r = vc[0]; 114 | }else{ 115 | 116 | var samples = 0, 117 | endTime = Date.now() + this.maxTime; 118 | var scores = vc.map(function(c){ 119 | return 0; 120 | }); 121 | var i; 122 | this.preGenSample(); 123 | while(Date.now() < endTime){ 124 | samples++; 125 | this.genSample(); 126 | for(i = 0; i < vc.length; i++){ 127 | scores[i] += this.simulator.run( 128 | pids, 129 | cids, 130 | this.heartBroken, 131 | this.samplePlayers, 132 | vc[i].id, 133 | this.user, 134 | [].concat(pscores)); 135 | } 136 | // alert(samples); 137 | } 138 | 139 | var minScore = 1/0, bestC; 140 | for(i = 0; i < scores.length; i++){ 141 | if(minScore > scores[i]){ 142 | minScore = scores[i]; 143 | bestC = i; 144 | } 145 | } 146 | r = vc[bestC]; 147 | 148 | // console.log(minScore, bestC, scores, vc); 149 | } 150 | 151 | removeFromUnorderedArray(this.samplePlayers[this.user], r.id); 152 | 153 | return r.ind; 154 | }; 155 | 156 | McBrain.prototype.preGenSample = function(){ 157 | var cardLackCount = this.cardLackCount; 158 | this.remainingCards.sort(function(a, b){ 159 | return cardLackCount[b] - cardLackCount[a]; 160 | }); 161 | }; 162 | 163 | McBrain.prototype.genSample = function(){ 164 | var id = this.user, 165 | sample = this.samplePlayers, 166 | playersInfo = this.playersInfo; 167 | 168 | var tryT = 1000000, ind; 169 | while(tryT--){ 170 | sample.forEach(function(p, ind){ 171 | if(ind !== id){ 172 | p.length = 0; 173 | } 174 | p.id = ind; 175 | }); 176 | this.playersInfo.forEach(function(p, ind){ 177 | [].push.apply(sample[ind], p.hasCard); 178 | }); 179 | var toAdd = sample.filter(function(s, ind){ 180 | return s.length < playersInfo[ind].numCards; 181 | }); 182 | ind = 0; 183 | var sum = 0; 184 | var summ = 0; 185 | toAdd.forEach(function(to){ 186 | sum += to.length; 187 | summ += playersInfo[to.id].numCards; 188 | }); 189 | while(ind < this.remainingCards.length){ 190 | var c = this.remainingCards[ind]; 191 | var allPossible = toAdd.length; 192 | var aid = 0; 193 | while(aid < allPossible){ 194 | if(this.playersInfo[toAdd[aid].id].lackCard[c]){ 195 | allPossible--; 196 | var tmp = toAdd[allPossible]; 197 | toAdd[allPossible] = toAdd[aid]; 198 | toAdd[aid] = tmp; 199 | }else{ 200 | aid++; 201 | } 202 | } 203 | if(allPossible === 0){ 204 | break; 205 | } 206 | var pToAdd = Math.floor(Math.random() * allPossible); 207 | toAdd[pToAdd].push(c); 208 | ind++; 209 | if(toAdd[pToAdd].length === playersInfo[toAdd[pToAdd].id].numCards){ 210 | removeFromUnorderedArray(toAdd, toAdd[pToAdd]); 211 | if(toAdd.length === 0){ 212 | break; 213 | } 214 | } 215 | } 216 | if(ind === this.remainingCards.length){ 217 | break; 218 | } 219 | } 220 | if(tryT === -1){ 221 | console.log(this.remainingCards, this.playersInfo); 222 | alert("fail to gen sample"); 223 | } 224 | if(sample.some(function(s, ind){ 225 | return s.length !== playersInfo[ind].numCards; 226 | })){ 227 | console.log(this.remainingCards.length, sample, playersInfo, tryT); 228 | throw "eh"; 229 | } 230 | }; 231 | 232 | return McBrain; 233 | }); -------------------------------------------------------------------------------- /js/game.js: -------------------------------------------------------------------------------- 1 | define(["ui", "Human", "Ai", "board", "config", "jquery", "rules", "RandomBrain", "AsyncBrain", "SimpleBrain", "PomDPBrain"], 2 | function(ui, Human, Ai, board, config, $, rules, RandomBrain, AsyncBrain, SimpleBrain, PomDPBrain){ 3 | "use strict"; 4 | 5 | var rounds = 0; 6 | var players = [ 7 | new Human(0, config.names[0]), 8 | new Ai(1, config.names[1]), 9 | new Ai(2, config.names[2]), 10 | new Ai(3, config.names[3]) 11 | ]; 12 | 13 | var status = "prepare", 14 | currentPlay = 0, 15 | played = 0; 16 | 17 | var heartBroken = false; 18 | 19 | var nextTimer = 0; 20 | 21 | var waitDefer = function(time){ 22 | var d = $.Deferred(); 23 | setTimeout(function(){ 24 | d.resolve(); 25 | }, time); 26 | return d; 27 | }; 28 | 29 | var initBrains = function(){ 30 | // players[0].brain = new AsyncBrain(players[0], "PomDPBrain"); 31 | 32 | if(players[1].brain){ 33 | players[1].brain.terminate(); 34 | players[2].brain.terminate(); 35 | players[3].brain.terminate(); 36 | } 37 | 38 | for(var i = 1; i < 4; i++){ 39 | if(config.levels[i] == 1){ 40 | players[i].brain = new SimpleBrain(players[i]); 41 | } else if(config.levels[i] == 2){ 42 | players[i].brain = new AsyncBrain(players[i], "McBrain"); 43 | } else if(config.levels[i] == 3){ 44 | players[i].brain = new AsyncBrain(players[i], "PomDPBrain"); 45 | } else if(config.levels[i] == 4){ 46 | players[i].brain = new AsyncBrain(players[i], "PomDPBrain", {time: 2000}); 47 | } 48 | } 49 | 50 | return $.when(players[1].brain.init(), 51 | players[2].brain.init(), 52 | players[3].brain.init()); 53 | }; 54 | 55 | var informCardOut = function(player, card){ 56 | if(card.suit === 1){ 57 | heartBroken = true; 58 | } 59 | players.forEach(function(p){ 60 | p.watch({ 61 | type: "out", 62 | player: player, 63 | card: card, 64 | curSuit: board.desk.cards[0].suit 65 | }); 66 | }); 67 | }; 68 | 69 | var adds = [1, 3, 2]; 70 | var getPlayerForTransfer = function(id){ 71 | return (id + adds[rounds % 3]) % 4; 72 | }; 73 | 74 | return { 75 | adjustLayout: function(){ 76 | players.forEach(function(r){ 77 | r.adjustPos(); 78 | }); 79 | board.desk.adjustPos(); 80 | }, 81 | newGame: function(){ 82 | clearTimeout(nextTimer); 83 | ui.hideWin(); 84 | players.forEach(function(p, i){ 85 | p.clearScore(); 86 | p.setActive(false); 87 | p.setName(config.names[i]) 88 | }); 89 | rounds = 0; 90 | ui.clearEvents(); 91 | status = 'prepare'; 92 | this.proceed(); 93 | }, 94 | next: function(){ 95 | console.log(status, "next"); 96 | if (status == 'confirming'){ 97 | currentPlay = board.cards[26].parent.playedBy.id; 98 | played = 0; 99 | } else if (status == 'playing'){ 100 | currentPlay = (currentPlay + 1) % 4; 101 | played++; 102 | } 103 | if(played == 4){ 104 | status = 'endRound'; 105 | played = 0; 106 | } else if (status == 'endRound' && players[0].row.cards.length === 0){ 107 | status = 'end'; 108 | } else { 109 | status = ({ 110 | 'prepare': 'distribute', 111 | 'distribute': 'start', 112 | 'start': 'passing', 113 | 'passing': 'confirming', 114 | 'confirming': 'playing', 115 | 'playing': 'playing', 116 | 'endRound': 'playing', 117 | 'end': 'prepare' 118 | })[status]; 119 | } 120 | var waitTime = { 121 | 'playing': 100, 122 | 'endRound': 900, 123 | 'distribute': 300, 124 | 'end': 900 125 | }; 126 | var wait = waitTime[status] || 0; 127 | nextTimer = setTimeout(this.proceed.bind(this), wait); 128 | }, 129 | proceed: function(){ 130 | ({ 131 | 'prepare': function(){ 132 | ui.hideMessage(); 133 | ui.hideButton(); 134 | players.forEach(function(p){ 135 | p.initForNewRound(); 136 | }); 137 | board.init(); 138 | heartBroken = false; 139 | board.shuffleDeck(); 140 | initBrains().done(this.next.bind(this)); 141 | }, 142 | 'distribute': function(){ 143 | var self = this; 144 | board.distribute(players).done(function(){ 145 | players.forEach(function(p){ 146 | p.row.sort(); 147 | }); 148 | self.next(); 149 | }); 150 | }, 151 | 'start': function(){ 152 | rounds++; 153 | $.when.apply($, players.map(function(p){ 154 | return p.prepareTransfer(rounds % 3); 155 | })).done(this.next.bind(this)); 156 | }, 157 | 'passing': function(){ 158 | for(var i = 0; i < 4; i++){ 159 | players[i].transferTo(players[getPlayerForTransfer(i)]); 160 | } 161 | this.next(); 162 | }, 163 | 'confirming': function(){ 164 | players.forEach(function(r){ 165 | r.row.sort(); 166 | }); 167 | $.when.apply($, players.map(function(p){ 168 | return p.confirmTransfer(); 169 | })).done(this.next.bind(this)); 170 | }, 171 | 'playing': function(){ 172 | players[currentPlay].setActive(true); 173 | $.when(players[currentPlay].decide( 174 | rules.getValidCards(players[currentPlay].row.cards, 175 | board.desk.cards[0] ? board.desk.cards[0].suit : -1, 176 | heartBroken), 177 | board.desk.cards, 178 | board.desk.players, 179 | players.map(function(p){ 180 | return p.getScore(); 181 | })), waitDefer(200)) 182 | .done(function(card){ 183 | players[currentPlay].setActive(false); 184 | card.parent.out(card); 185 | board.desk.addCard(card, players[currentPlay]); 186 | card.adjustPos(); 187 | informCardOut(players[currentPlay], card); 188 | this.next(); 189 | }.bind(this)); 190 | }, 191 | 'endRound': function(){ 192 | var info = board.desk.score(); 193 | currentPlay = info[0].id; 194 | info[0].waste.addCards(info[1]); 195 | this.next(); 196 | }, 197 | 'end': function(){ 198 | if(players.some(function(p){ 199 | return p.getScore() === 26; 200 | })){ 201 | players.forEach(function(p){ 202 | if(p.getScore() !== 26){ 203 | p.setScore(26); 204 | }else{ 205 | p.setScore(0); 206 | } 207 | }); 208 | } 209 | players.forEach(function(p){ 210 | p.finalizeScore(); 211 | }); 212 | var rank = players.map(function(c){ 213 | return c; 214 | }); 215 | rank.sort(function(a,b){ 216 | return a._oldScore - b._oldScore; 217 | }); 218 | rank.forEach(function(r,ind){ 219 | r.display.rank = ind; 220 | }); 221 | players.forEach(function(p){ 222 | p.adjustPos(); 223 | }); 224 | if(players.some(function(p){ 225 | return p._oldScore >= 100; 226 | })){ 227 | players.forEach(function(p){ 228 | p.display.moveUp = true; 229 | p.display.adjustPos(); 230 | }); 231 | ui.showWin(players[0] === rank[0]); 232 | ui.showButton("Restart"); 233 | ui.buttonClickOnce(this.newGame.bind(this)); 234 | } else { 235 | ui.showButton("Continue"); 236 | ui.buttonClickOnce(this.next.bind(this)); 237 | } 238 | } 239 | })[status].bind(this)(); 240 | } 241 | }; 242 | }); -------------------------------------------------------------------------------- /js/lib/require.js: -------------------------------------------------------------------------------- 1 | /* 2 | RequireJS 2.1.9 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved. 3 | Available via the MIT or new BSD license. 4 | see: http://github.com/jrburke/requirejs for details 5 | */ 6 | var requirejs,require,define; 7 | (function(Z){function H(b){return"[object Function]"===L.call(b)}function I(b){return"[object Array]"===L.call(b)}function y(b,c){if(b){var e;for(e=0;ethis.depCount&&!this.defined){if(H(m)){if(this.events.error&&this.map.isDefine||j.onError!==aa)try{d=i.execCb(c,m,b,d)}catch(e){a=e}else d=i.execCb(c,m,b,d);this.map.isDefine&&((b=this.module)&&void 0!==b.exports&&b.exports!== 19 | this.exports?d=b.exports:void 0===d&&this.usingExports&&(d=this.exports));if(a)return a.requireMap=this.map,a.requireModules=this.map.isDefine?[this.map.id]:null,a.requireType=this.map.isDefine?"define":"require",v(this.error=a)}else d=m;this.exports=d;if(this.map.isDefine&&!this.ignore&&(r[c]=d,j.onResourceLoad))j.onResourceLoad(i,this.map,this.depMaps);x(c);this.defined=!0}this.defining=!1;this.defined&&!this.defineEmitted&&(this.defineEmitted=!0,this.emit("defined",this.exports),this.defineEmitComplete= 20 | !0)}}else this.fetch()}},callPlugin:function(){var a=this.map,b=a.id,e=n(a.prefix);this.depMaps.push(e);s(e,"defined",u(this,function(d){var m,e;e=this.map.name;var g=this.map.parentMap?this.map.parentMap.name:null,h=i.makeRequire(a.parentMap,{enableBuildCallback:!0});if(this.map.unnormalized){if(d.normalize&&(e=d.normalize(e,function(a){return c(a,g,!0)})||""),d=n(a.prefix+"!"+e,this.map.parentMap),s(d,"defined",u(this,function(a){this.init([],function(){return a},null,{enabled:!0,ignore:!0})})), 21 | e=l(p,d.id)){this.depMaps.push(d);if(this.events.error)e.on("error",u(this,function(a){this.emit("error",a)}));e.enable()}}else m=u(this,function(a){this.init([],function(){return a},null,{enabled:!0})}),m.error=u(this,function(a){this.inited=!0;this.error=a;a.requireModules=[b];F(p,function(a){0===a.map.id.indexOf(b+"_unnormalized")&&x(a.map.id)});v(a)}),m.fromText=u(this,function(d,c){var e=a.name,g=n(e),B=O;c&&(d=c);B&&(O=!1);q(g);t(k.config,b)&&(k.config[e]=k.config[b]);try{j.exec(d)}catch(ca){return v(A("fromtexteval", 22 | "fromText eval for "+b+" failed: "+ca,ca,[b]))}B&&(O=!0);this.depMaps.push(g);i.completeLoad(e);h([e],m)}),d.load(a.name,h,m,k)}));i.enable(e,this);this.pluginMaps[e.id]=e},enable:function(){T[this.map.id]=this;this.enabling=this.enabled=!0;y(this.depMaps,u(this,function(a,b){var c,d;if("string"===typeof a){a=n(a,this.map.isDefine?this.map:this.map.parentMap,!1,!this.skipMap);this.depMaps[b]=a;if(c=l(N,a.id)){this.depExports[b]=c(this);return}this.depCount+=1;s(a,"defined",u(this,function(a){this.defineDep(b, 23 | a);this.check()}));this.errback&&s(a,"error",u(this,this.errback))}c=a.id;d=p[c];!t(N,c)&&(d&&!d.enabled)&&i.enable(a,this)}));F(this.pluginMaps,u(this,function(a){var b=l(p,a.id);b&&!b.enabled&&i.enable(a,this)}));this.enabling=!1;this.check()},on:function(a,b){var c=this.events[a];c||(c=this.events[a]=[]);c.push(b)},emit:function(a,b){y(this.events[a],function(a){a(b)});"error"===a&&delete this.events[a]}};i={config:k,contextName:b,registry:p,defined:r,urlFetched:S,defQueue:G,Module:X,makeModuleMap:n, 24 | nextTick:j.nextTick,onError:v,configure:function(a){a.baseUrl&&"/"!==a.baseUrl.charAt(a.baseUrl.length-1)&&(a.baseUrl+="/");var b=k.pkgs,c=k.shim,d={paths:!0,config:!0,map:!0};F(a,function(a,b){d[b]?"map"===b?(k.map||(k.map={}),Q(k[b],a,!0,!0)):Q(k[b],a,!0):k[b]=a});a.shim&&(F(a.shim,function(a,b){I(a)&&(a={deps:a});if((a.exports||a.init)&&!a.exportsFn)a.exportsFn=i.makeShimExports(a);c[b]=a}),k.shim=c);a.packages&&(y(a.packages,function(a){a="string"===typeof a?{name:a}:a;b[a.name]={name:a.name, 25 | location:a.location||a.name,main:(a.main||"main").replace(ja,"").replace(ea,"")}}),k.pkgs=b);F(p,function(a,b){!a.inited&&!a.map.unnormalized&&(a.map=n(b))});if(a.deps||a.callback)i.require(a.deps||[],a.callback)},makeShimExports:function(a){return function(){var b;a.init&&(b=a.init.apply(Z,arguments));return b||a.exports&&ba(a.exports)}},makeRequire:function(a,f){function h(d,c,e){var g,k;f.enableBuildCallback&&(c&&H(c))&&(c.__requireJsBuild=!0);if("string"===typeof d){if(H(c))return v(A("requireargs", 26 | "Invalid require call"),e);if(a&&t(N,d))return N[d](p[a.id]);if(j.get)return j.get(i,d,a,h);g=n(d,a,!1,!0);g=g.id;return!t(r,g)?v(A("notloaded",'Module name "'+g+'" has not been loaded yet for context: '+b+(a?"":". Use require([])"))):r[g]}K();i.nextTick(function(){K();k=q(n(null,a));k.skipMap=f.skipMap;k.init(d,c,e,{enabled:!0});C()});return h}f=f||{};Q(h,{isBrowser:z,toUrl:function(b){var f,e=b.lastIndexOf("."),g=b.split("/")[0];if(-1!==e&&(!("."===g||".."===g)||1h.attachEvent.toString().indexOf("[native code"))&&!W?(O=!0,h.attachEvent("onreadystatechange",b.onScriptLoad)):(h.addEventListener("load",b.onScriptLoad,!1),h.addEventListener("error", 34 | b.onScriptError,!1)),h.src=e,K=h,C?x.insertBefore(h,C):x.appendChild(h),K=null,h;if(da)try{importScripts(e),b.completeLoad(c)}catch(l){b.onError(A("importscripts","importScripts failed for "+c+" at "+e,l,[c]))}};z&&!s.skipDataMain&&M(document.getElementsByTagName("script"),function(b){x||(x=b.parentNode);if(J=b.getAttribute("data-main"))return q=J,s.baseUrl||(D=q.split("/"),q=D.pop(),fa=D.length?D.join("/")+"/":"./",s.baseUrl=fa),q=q.replace(ea,""),j.jsExtRegExp.test(q)&&(q=J),s.deps=s.deps?s.deps.concat(q): 35 | [q],!0});define=function(b,c,e){var h,j;"string"!==typeof b&&(e=c,c=b,b=null);I(c)||(e=c,c=null);!c&&H(e)&&(c=[],e.length&&(e.toString().replace(la,"").replace(ma,function(b,e){c.push(e)}),c=(1===e.length?["require"]:["require","exports","module"]).concat(c)));if(O){if(!(h=K))P&&"interactive"===P.readyState||M(document.getElementsByTagName("script"),function(b){if("interactive"===b.readyState)return P=b}),h=P;h&&(b||(b=h.getAttribute("data-requiremodule")),j=E[h.getAttribute("data-requirecontext")])}(j? 36 | j.defQueue:R).push([b,c,e])};define.amd={jQuery:!0};j.exec=function(b){return eval(b)};j(s)}})(this); -------------------------------------------------------------------------------- /js/PomDPBrain.js: -------------------------------------------------------------------------------- 1 | define(["Brain", "prematureOptimization", "PomDPSimulator"], 2 | function(Brain , op , PomDPSimulator){ 3 | "use strict"; 4 | 5 | var removeFromUnorderedArray = op.removeFromUnorderedArray; 6 | var cardsInfo = op.cardsInfo; 7 | 8 | var defaultOptions = { 9 | time: 1000, 10 | c: 10 11 | }; 12 | 13 | var PomDPBrain = function(user, options){ 14 | if(!options){ 15 | options = defaultOptions; 16 | } 17 | 18 | this.c = options.c || 10; 19 | this.maxTime = options.time || 1000; 20 | 21 | this.user = user; 22 | this.ind = user; 23 | this.simulator = new PomDPSimulator(user); 24 | var remainingCards = []; 25 | for(var i = 0; i < 52; i++){ 26 | remainingCards.push(i); 27 | } 28 | this.root = { 29 | count: 0, 30 | value: 0, 31 | observations: {}, 32 | info: { 33 | playersInfo: [ 34 | { 35 | hasCards: [], 36 | lackCard: {}, 37 | numCards: 13, 38 | score: 0 39 | }, 40 | { 41 | hasCards: [], 42 | lackCard: {}, 43 | numCards: 13, 44 | score: 0 45 | }, 46 | { 47 | hasCards: [], 48 | lackCard: {}, 49 | numCards: 13, 50 | score: 0 51 | }, 52 | { 53 | hasCards: [], 54 | lackCard: {}, 55 | numCards: 13, 56 | score: 0 57 | } 58 | ], 59 | remainingCards: remainingCards, 60 | curBoard: [], 61 | heartBroken: false, 62 | cardLackCount: remainingCards.map(function(){ return 0; }) 63 | } 64 | }; 65 | this.observationBuffer = []; 66 | }; 67 | 68 | PomDPBrain.prototype = Object.create(Brain.prototype); 69 | 70 | PomDPBrain.prototype.search = function(vc){ 71 | var endTime = Date.now() + this.maxTime; 72 | var times = 0; 73 | while(Date.now() < endTime){ 74 | var state = this.genSample(this.root); 75 | this.simulate(state, this.root, 0); 76 | times++; 77 | } 78 | var actions = Object.keys(this.root.actions).map(function(a) { return parseInt(a, 10); }), 79 | gameactions = vc.map(function(v){ return v.id; }); 80 | actions.forEach(function(a){ 81 | if(gameactions.indexOf(a) === -1){ 82 | throw "mismatch " + a; 83 | } 84 | removeFromUnorderedArray(gameactions, a); 85 | }); 86 | if(gameactions.length) throw "mismatch " + gameactions.join(" "); 87 | 88 | var best = -1/0, 89 | bestAction = 0; 90 | for(var a in this.root.actions){ 91 | if(this.root.actions[a].value > best){ 92 | best = this.root.actions[a].value; 93 | bestAction = a; 94 | } 95 | } 96 | this.root = this.root.actions[bestAction]; 97 | return bestAction; 98 | }; 99 | 100 | PomDPBrain.prototype.rollout = function(s, h, depth){ 101 | // h.count++; 102 | var val = this.simulator.run(s); 103 | // h.value = ((h.count - 1) * h.value + val) / h.count; 104 | return val; 105 | }; 106 | 107 | PomDPBrain.prototype.simulate = function(s, h, depth){ 108 | if(h.terminate) return 0; 109 | if(!h.actions){ 110 | var as = h.actions = {}; 111 | this.getAllActions(h).forEach(function(a){ 112 | if(a == "undefined" || (!a && a !== 0)) throw a; 113 | as[a] = this.initAction(h, a); 114 | }.bind(this)); 115 | return this.rollout(s, h, depth); 116 | } 117 | var best, 118 | bestScore = -1/0; 119 | for(var a in h.actions){ 120 | var score = this.getScore(h.actions[a]); 121 | if(score > bestScore){ 122 | bestScore = score; 123 | best = a; 124 | } 125 | } 126 | if(!best){ 127 | throw "eh"; 128 | } 129 | 130 | var simulateResult = this.simulator.step(s, parseInt(best, 10)); 131 | 132 | var ha = h.actions[best], 133 | ohash = simulateResult.observation.join(""); 134 | if(!(ohash in ha.observations)) { 135 | ha.observations[ohash] = this.initObservation(ha, simulateResult.observation); 136 | } 137 | 138 | var r = simulateResult.score + this.simulate(simulateResult.state, ha.observations[ohash], depth + 1); 139 | h.count++; 140 | ha.count++; 141 | ha.value = (ha.value * (ha.count - 1) + r) / ha.count; 142 | return r; 143 | }; 144 | 145 | PomDPBrain.prototype.getScore = function(action){ 146 | if(!action.count) return 1/0; 147 | return action.value + this.c * Math.sqrt(Math.log(action.parent.count) / action.count); 148 | }; 149 | 150 | PomDPBrain.prototype.getAllActions = function(history){ 151 | var info = history.info; 152 | if(info.curBoard.length){ 153 | var suit = cardsInfo[info.curBoard[0] % 100].suit; 154 | var r = info.playersInfo[this.ind].hasCards.filter(function(c){ 155 | return cardsInfo[c].suit === suit; 156 | }); 157 | if(!r.length){ 158 | return [].concat(info.playersInfo[this.ind].hasCards); 159 | } else { 160 | return r; 161 | } 162 | } else if (info.playersInfo[this.ind].hasCards.length === 13) { 163 | return [26]; 164 | }else if (info.heartBroken) { 165 | return [].concat(info.playersInfo[this.ind].hasCards); 166 | } else { 167 | var possible = info.playersInfo[this.ind].hasCards.filter(function(c){ 168 | return cardsInfo[c].suit !== 1; 169 | }); 170 | if(possible.length) return possible; 171 | else return [].concat(info.playersInfo[this.ind].hasCards); 172 | } 173 | }; 174 | 175 | PomDPBrain.prototype.initObservation = function(history, observation){ 176 | var pinfo = history.info; 177 | var curBoard = [].concat(pinfo.curBoard), 178 | heartBroken = pinfo.heartBroken, 179 | playersInfo = pinfo.playersInfo.map(function(info){ 180 | return { 181 | hasCards: [].concat(info.hasCards), 182 | lackCard: Object.create(info.lackCard), 183 | numCards: info.numCards, 184 | score: info.score 185 | }; 186 | }), 187 | remainingCards = [].concat(pinfo.remainingCards), 188 | cardLackCount = [].concat(pinfo.cardLackCount); 189 | var info = { 190 | curBoard: curBoard, 191 | heartBroken: heartBroken, 192 | playersInfo: playersInfo, 193 | hash: observation.join(""), 194 | cardLackCount: cardLackCount, 195 | remainingCards: remainingCards 196 | }; 197 | observation.forEach(function(ob){ 198 | var pid = ((ob / 100) | 0) - 1; 199 | playersInfo[pid].numCards--; 200 | this.removeRemainingCard(ob % 100, info); 201 | heartBroken = heartBroken || (cardsInfo[ob % 100].suit === 1); 202 | var curSuit; 203 | if(curBoard.length){ 204 | curSuit = cardsInfo[curBoard[0] % 100].suit; 205 | if(curSuit){ 206 | if(curSuit !== cardsInfo[ob % 100].suit){ 207 | var lackCardPlayer = playersInfo[pid]; 208 | remainingCards.forEach(function(c){ 209 | if(cardsInfo[c].suit === curSuit){ 210 | lackCardPlayer.lackCard[c] = true; 211 | cardLackCount[c]++; 212 | } 213 | }); 214 | } 215 | } 216 | } 217 | curBoard.push(ob); 218 | if(curBoard.length === 4){ 219 | var maxNum = -1, maxPlayer = 0, boardScore = 0; 220 | for(var i = 0; i < 4; i++){ 221 | var bcard = cardsInfo[curBoard[i] % 100]; 222 | if(bcard.suit === curSuit && bcard.num > maxNum){ 223 | maxPlayer = ((curBoard[i] / 100) | 0) - 1; 224 | maxNum = bcard.num; 225 | } 226 | if(bcard.suit === 1) boardScore++; 227 | else if(bcard.suit === 0 && bcard.num === 11) boardScore += 13; 228 | } 229 | playersInfo[maxPlayer].score += boardScore; 230 | curBoard.length = 0; 231 | } 232 | }.bind(this)); 233 | info.heartBroken = heartBroken; 234 | remainingCards.sort(function(a, b){ 235 | return cardLackCount[b] - cardLackCount[a]; 236 | }); 237 | 238 | var terminate = !playersInfo.some(function(p){ 239 | return p.numCards > 0; 240 | }); 241 | 242 | return { 243 | info: info, 244 | count: 0, 245 | value: 0, 246 | terminate: terminate 247 | }; 248 | }; 249 | 250 | PomDPBrain.prototype.initAction = function(history, action){ 251 | var info = history.info; 252 | var curBoard = [].concat(info.curBoard), 253 | heartBroken = info.heartBroken, 254 | playersInfo = info.playersInfo.map(function(info){ 255 | return { 256 | hasCards: [].concat(info.hasCards), 257 | lackCard: Object.create(info.lackCard), 258 | numCards: info.numCards, 259 | score: info.score 260 | }; 261 | }), 262 | remainingCards = [].concat(info.remainingCards), 263 | cardLackCount = [].concat(info.cardLackCount); 264 | return { 265 | value: 0, 266 | count: 0, 267 | parent: history, 268 | action: action, 269 | observations: {}, 270 | info : { 271 | curBoard: curBoard, 272 | heartBroken: heartBroken, 273 | playersInfo: playersInfo, 274 | cardLackCount: cardLackCount, 275 | remainingCards: remainingCards 276 | } 277 | }; 278 | }; 279 | 280 | PomDPBrain.prototype.removeRemainingCard = function(id, info){ 281 | removeFromUnorderedArray(info.remainingCards, id); 282 | info.playersInfo.forEach(function(p, ind){ 283 | removeFromUnorderedArray(p.hasCards, id); 284 | }); 285 | }; 286 | 287 | PomDPBrain.prototype.watch = function(info){ 288 | if(info.type === "in"){ 289 | info.cards.forEach(function(c){ 290 | this.removeRemainingCard(c, this.root.info); 291 | }.bind(this)); 292 | [].push.apply(this.root.info.playersInfo[info.player].hasCards, info.cards); 293 | }else{ 294 | this.observationBuffer.push(info.card + (info.player + 1) * 100); 295 | } 296 | }; 297 | 298 | PomDPBrain.prototype.confirmCards = function(cards){ 299 | cards.forEach(function(c){ 300 | this.removeRemainingCard(c, this.root.info); 301 | this.root.info.playersInfo[this.ind].hasCards.push(c); 302 | }.bind(this)); 303 | }; 304 | 305 | PomDPBrain.prototype.decide = function(vc){ 306 | if(this.observationBuffer.join("") in this.root){ 307 | this.root = this.root[this.observationBuffer.join("")]; 308 | } else { 309 | this.root = this.initObservation(this.root, this.observationBuffer); 310 | } 311 | this.observationBuffer = []; 312 | 313 | var action = parseInt(this.search(vc, this.root), 10); 314 | 315 | for(var i = 0; i < vc.length; i++){ 316 | if(vc[i].id === action){ 317 | return vc[i].ind; 318 | } 319 | } 320 | throw "failed to find card, something must be of wrongness"; 321 | }; 322 | 323 | PomDPBrain.prototype.genSample = function(node){ 324 | var id = this.ind, 325 | sample = [[], [], [], []], 326 | playersInfo = node.info.playersInfo, 327 | remainingCards = node.info.remainingCards; 328 | 329 | var tryT = 1000, ind; 330 | while(tryT--){ 331 | sample.forEach(function(p, ind){ 332 | p.length = 0; 333 | p.id = ind; 334 | }); 335 | playersInfo.forEach(function(p, ind){ 336 | [].push.apply(sample[ind], p.hasCards); 337 | }); 338 | var toAdd = sample.filter(function(s, ind){ 339 | return s.length < playersInfo[ind].numCards; 340 | }); 341 | ind = 0; 342 | var sum = 0; 343 | var summ = 0; 344 | toAdd.forEach(function(to){ 345 | sum += to.length; 346 | summ += playersInfo[to.id].numCards; 347 | }); 348 | while(ind < remainingCards.length){ 349 | var c = remainingCards[ind]; 350 | var allPossible = toAdd.length; 351 | var aid = 0; 352 | while(aid < allPossible){ 353 | if(playersInfo[toAdd[aid].id].lackCard[c]){ 354 | allPossible--; 355 | var tmp = toAdd[allPossible]; 356 | toAdd[allPossible] = toAdd[aid]; 357 | toAdd[aid] = tmp; 358 | }else{ 359 | aid++; 360 | } 361 | } 362 | if(allPossible === 0){ 363 | break; 364 | } 365 | var pToAdd = Math.floor(Math.random() * allPossible); 366 | toAdd[pToAdd].push(c); 367 | ind++; 368 | if(toAdd[pToAdd].length === playersInfo[toAdd[pToAdd].id].numCards){ 369 | removeFromUnorderedArray(toAdd, toAdd[pToAdd]); 370 | if(toAdd.length === 0){ 371 | break; 372 | } 373 | } 374 | } 375 | if(ind === remainingCards.length){ 376 | break; 377 | } 378 | } 379 | if(tryT === -1){ 380 | alert("fail to gen sample"); 381 | } 382 | if(sample.some(function(s, ind){ 383 | return s.length !== playersInfo[ind].numCards; 384 | })){ 385 | throw "eh"; 386 | } 387 | return { 388 | players: sample, 389 | scores: node.info.playersInfo.map(function(info){ return info.score; }), 390 | board: node.info.curBoard.concat([]), 391 | heartBroken: node.info.heartBroken 392 | }; 393 | }; 394 | 395 | return PomDPBrain; 396 | }); -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | @keyframes "pulsing" { 2 | from { 3 | box-shadow: 0 0 0 black, inset 0 0 0 lightblue, 0 0 0 0 lightblue; 4 | } 5 | to { 6 | box-shadow: 0 0 0 black, inset 0 0 10px lightblue, 0 0 10px 10px lightblue; 7 | } 8 | } 9 | 10 | @-webkit-keyframes "pulsing" { 11 | from { 12 | box-shadow: 0 0 0 black, inset 0 0 0 lightblue, 0 0 0 0 lightblue; 13 | } 14 | to { 15 | box-shadow: 0 0 0 black, inset 0 0 10px lightblue, 0 0 10px 10px lightblue; 16 | } 17 | } 18 | 19 | 20 | @-moz-keyframes "pulsing" { 21 | from { 22 | box-shadow: 0 0 0 black, inset 0 0 0 lightblue, 0 0 0 0 lightblue; 23 | } 24 | to { 25 | box-shadow: 0 0 0 black, inset 0 0 10px lightblue, 0 0 10px 10px lightblue; 26 | } 27 | } 28 | 29 | 30 | @-ms-keyframes "pulsing" { 31 | from { 32 | box-shadow: 0 0 0 black, inset 0 0 0 lightblue, 0 0 0 0 lightblue; 33 | } 34 | to { 35 | box-shadow: 0 0 0 black, inset 0 0 10px lightblue, 0 0 10px 10px lightblue; 36 | } 37 | } 38 | 39 | html, body{ 40 | padding: 0; 41 | margin: 0; 42 | -webkit-user-select: none; 43 | -moz-user-select: none; 44 | -ms-user-select: none; 45 | -o-user-select: none; 46 | user-select: none; 47 | -webkit-tap-highlight-color: transparent; 48 | height: 100%; 49 | width: 100%; 50 | overflow: hidden; 51 | font-family: Arial; 52 | } 53 | 54 | td[contentEditable]{ 55 | -webkit-user-select: text; 56 | -moz-user-select: text; 57 | -ms-user-select: text; 58 | -o-user-select: text; 59 | user-select: text; 60 | } 61 | 62 | #control-region{ 63 | display: block; 64 | position: absolute; 65 | left: 0; 66 | right: 0; 67 | top: 0; 68 | bottom: 0; 69 | z-index: 1000; 70 | text-align: center; 71 | background-image:-webkit-linear-gradient(top, rgba(10,10,10,0.8), rgba(0,0,0,0.5)); 72 | background-image:-moz-linear-gradient(top, rgba(10,10,10,0.8), rgba(0,0,0,0.5)); 73 | background-image:-ms-linear-gradient(top, rgba(10,10,10,0.8), rgba(0,0,0,0.5)); 74 | background-image:-o-linear-gradient(top, rgba(10,10,10,0.8), rgba(0,0,0,0.5)); 75 | background-image:linear-gradient(top, rgba(10,10,10,0.8), rgba(0,0,0,0.5)); 76 | -webkit-transition-duration: 0.5s; 77 | -moz-transition-duration: 0.5s; 78 | -ms-transition-duration: 0.5s; 79 | -o-transition-duration: 0.5s; 80 | transition-duration: 0.5s; 81 | -webkit-transform-origin: top center; 82 | -moz-transform-origin: top center; 83 | -ms-transform-origin: top center; 84 | -o-transform-origin: top center; 85 | transform-origin: top center; 86 | -webkit-transform: perspective(1000px); 87 | -moz-transform: perspective(1000px); 88 | -ms-transform: perspective(1000px); 89 | -o-transform: perspective(1000px); 90 | transform: perspective(1000px); 91 | 92 | -webkit-transform: translateZ(999px); 93 | -moz-transform: translateZ(999px); 94 | -ms-transform: translateZ(999px); 95 | transform: translateZ(999px); 96 | 97 | z-index: 999; 98 | } 99 | 100 | #control-region[hidden]{ 101 | -webkit-transform: perspective(1000px) translateY(-200px) rotateX(90deg); 102 | -moz-transform: perspective(1000px) translateY(-200px) rotateX(-90deg); 103 | -ms-transform: perspective(1000px) translateY(-200px) rotateX(-90deg); 104 | -o-transform: perspective(1000px) translateY(-200px) rotateX(-90deg); 105 | transform: perspective(1000px) translateY(-200px) rotateX(-90deg); 106 | opacity: 0; 107 | } 108 | 109 | #control-region>div{ 110 | color: white; 111 | font-size: 2em; 112 | text-align: center; 113 | } 114 | 115 | #control-region table{ 116 | margin: 0 auto; 117 | text-align: left; 118 | background: rgba(0,0,0,0.7); 119 | padding: 15px; 120 | border-radius: 10px; 121 | box-shadow: inset 0 1px 10px 5px black, 0 1px 1px rgba(255, 255, 255, 0.5); 122 | text-shadow: -1px -1px 1px black; 123 | } 124 | 125 | #control-region td, #control-region th{ 126 | padding: 5px 10px; 127 | } 128 | 129 | .player-set-name{ 130 | word-wrap: normal; 131 | overflow: hidden; 132 | } 133 | 134 | #control-region h3{ 135 | background-image:-webkit-linear-gradient(left, rgba(0,0,0,0.5), #000, rgba(0,0,0,0.5)); 136 | background-image:-moz-linear-gradient(left, rgba(0,0,0,0.5), #000, rgba(0,0,0,0.5)); 137 | background-image:-ms-linear-gradient(left, rgba(0,0,0,0.5), #000, rgba(0,0,0,0.5)); 138 | background-image:-o-linear-gradient(left, rgba(0,0,0,0.5), #000, rgba(0,0,0,0.5)); 139 | background-image:linear-gradient(left, rgba(0,0,0,0.5), #000, rgba(0,0,0,0.5)); 140 | box-shadow: 0 0 10px black; 141 | padding: 5px; 142 | text-shadow: 0 0 8px white; 143 | } 144 | 145 | #control-region>button{ 146 | font-size: 2em; 147 | margin: 0 auto; 148 | padding: 5px 15px; 149 | margin-top: 1em; 150 | border-radius: 10px; 151 | border: none; 152 | background-color: transparent; 153 | background-image: -webkit-linear-gradient(top, rgba(60,60,60,0.7), rgba(0,0,0,0.7)); 154 | background-image: -moz-linear-gradient(top, rgba(60,60,60,0.7), rgba(0,0,0,0.7)); 155 | background-image: -ms-linear-gradient(top, rgba(60,60,60,0.7), rgba(0,0,0,0.7)); 156 | background-image: -o-linear-gradient(top, rgba(60,60,60,0.7), rgba(0,0,0,0.7)); 157 | background-image: linear-gradient(top, rgba(60,60,60,0.7), rgba(0,0,0,0.7)); 158 | color: white; 159 | box-shadow: 0 0 0 1px black, inset 0 1px 1px rgba(255, 255, 255, 0.3), 0 1px 3px rgba(255, 255, 255, 0.8); 160 | cursor: pointer; 161 | vertical-align: bottom; 162 | } 163 | 164 | #control-region>button:hover{ 165 | background-image: -webkit-linear-gradient(top, rgba(90,90,90,0.7), rgba(30,30,30,0.7)); 166 | background-image: -moz-linear-gradient(top, rgba(90,90,90,0.7), rgba(30,30,30,0.7)); 167 | background-image: -ms-linear-gradient(top, rgba(90,90,90,0.7), rgba(30,30,30,0.7)); 168 | background-image: -o-linear-gradient(top, rgba(90,90,90,0.7), rgba(30,30,30,0.7)); 169 | background-image: linear-gradient(top, rgba(90,90,90,0.7), rgba(30,30,30,0.7)); 170 | } 171 | 172 | #control-region>button:active{ 173 | background-image: -webkit-linear-gradient(top, rgba(0,0,0,0.7), rgba(50,50,50,0.7)); 174 | background-image: -moz-linear-gradient(top, rgba(0,0,0,0.7), rgba(50,50,50,0.7)); 175 | background-image: -ms-linear-gradient(top, rgba(0,0,0,0.7), rgba(50,50,50,0.7)); 176 | background-image: -o-linear-gradient(top, rgba(0,0,0,0.7), rgba(50,50,50,0.7)); 177 | background-image: linear-gradient(top, rgba(0,0,0,0.7), rgba(50,50,50,0.7)); 178 | box-shadow: 0 0 0 1px black, inset 0 1px 1px rgba(0, 0, 0, 0.3), 0 1px 3px rgba(255, 255, 255, 0.8); 179 | padding-top: 6px; 180 | padding-bottom: 4px; 181 | text-shadow: -1px -1px 1px black; 182 | } 183 | 184 | #main-button-group{ 185 | position: absolute; 186 | left: 0; 187 | bottom: 0; 188 | z-index: 10; 189 | } 190 | 191 | #main-button-group>button{ 192 | -webkit-appearance: none; 193 | font-size: 17px; 194 | cursor: pointer; 195 | color: white; 196 | text-align: center; 197 | padding: 5px; 198 | background: #111; 199 | background-image:-webkit-linear-gradient(top, #222, #000); 200 | background-image:-moz-linear-gradient(top, #222, #000); 201 | background-image:-ms-linear-gradient(top, #222, #000); 202 | background-image:-o-linear-gradient(top, #222, #000); 203 | background-image:linear-gradient(top, #222, #000); 204 | box-shadow: 0 0 5px #111; 205 | margin: 0; 206 | float: left; 207 | border: none; 208 | } 209 | 210 | #main-button-group>button:last-child{ 211 | border-top-right-radius: 10px; 212 | } 213 | 214 | #main-button-group>button:hover{ 215 | background-image:-webkit-linear-gradient(top, #444, #222); 216 | background-image:-moz-linear-gradient(top, #444, #222); 217 | background-image:-ms-linear-gradient(top, #444, #222); 218 | background-image:-o-linear-gradient(top, #444, #222); 219 | background-image:linear-gradient(top, #444, #222); 220 | box-shadow: 0 0 5px #333; 221 | } 222 | 223 | #main-button-group>button:active{ 224 | padding-top: 6px; 225 | padding-bottom: 4px; 226 | background: #111; 227 | } 228 | 229 | #newgame-but{ 230 | 231 | } 232 | 233 | #game-region{ 234 | position: absolute; 235 | top: 0; 236 | bottom: 0; 237 | left: 0; 238 | right: 0; 239 | background: lightblue; 240 | -webkit-perspective: 8000px; 241 | -moz-perspective: 8000px; 242 | -ms-perspective: 8000px; 243 | -o-perspective: 8000px; 244 | perspective: 8000px; 245 | background-image:-webkit-radial-gradient( 246 | 50% 38%, #53c230, #138300); 247 | background-image:-moz-radial-gradient( 248 | 50% 38%, #53c230, #138300); 249 | background-image:-o-radial-gradient( 250 | 50% 38%, #53c230, #138300); 251 | background-position:center; 252 | background-repeat:no-repeat; 253 | background-attachment:fixed; 254 | overflow: hidden; 255 | } 256 | 257 | #pass-arrow{ 258 | display: none; 259 | position: absolute; 260 | font-size: 200px; 261 | color: white; 262 | font-family: Arial; 263 | left: 50%; 264 | top: 50%; 265 | margin-top: -100px; 266 | margin-left: -100px; 267 | text-shadow: 0 0 10px white; 268 | opacity: 0; 269 | font-weight: bold; 270 | cursor: pointer; 271 | z-index: 1; 272 | -webkit-transition-duration: 0.2s; 273 | -moz-transition-duration: 0.2s; 274 | -ms-transition-duration: 0.2s; 275 | -o-transition-duration: 0.2s; 276 | transition-duration: 0.2s; 277 | } 278 | 279 | #pass-arrow.show{ 280 | display: block; 281 | opacity: 1; 282 | } 283 | 284 | #pass-arrow:hover{ 285 | text-shadow: 0 0 15px black; 286 | } 287 | 288 | #pass-arrow:active{ 289 | text-shadow: 0 0 1px black; 290 | } 291 | 292 | #play-button{ 293 | position: absolute; 294 | -webkit-appearance: none; 295 | font-size: 40px; 296 | line-height: 60px; 297 | font-family: Arial; 298 | bottom: 20px; 299 | right: 20px; 300 | width: 200px; 301 | text-shadow: 0 0 -1px black, 0 0 1px white; 302 | opacity: 0; 303 | cursor: pointer; 304 | height: 60px; 305 | border-radius: 15px; 306 | color: white; 307 | text-align: center; 308 | padding: 0; 309 | background-image:-webkit-linear-gradient(top, #222, #000); 310 | background-image:-moz-linear-gradient(top, #222, #000); 311 | background-image:-ms-linear-gradient(top, #222, #000); 312 | background-image:-o-linear-gradient(top, #222, #000); 313 | background-image:linear-gradient(top, #222, #000); 314 | border:1px solid #111; 315 | box-shadow: 316 | 0 2px 8px rgba(0,0,0,0.5), 317 | inset 0 1px rgba(255,255,255,0.3), 318 | inset 0 10px rgba(255,255,255,0.2), 319 | inset 0 10px 20px rgba(255,255,255,0.25), 320 | inset 0 -15px 30px rgba(0,0,0,0.3); 321 | -webkit-transition-duration: 0.1s; 322 | -moz-transition-duration: 0.1s; 323 | -ms-transition-duration: 0.1s; 324 | -o-transition-duration: 0.1s; 325 | transition-duration: 0.1s; 326 | 327 | -webkit-transform: translate3d(0, 90px, 0); 328 | -moz-transform: translate3d(0, 90px, 0); 329 | -ms-transform: translate3d(0, 90px, 0); 330 | -o-transform: translate3d(0, 90px, 0); 331 | transform: translate3d(0, 90px, 0); 332 | } 333 | 334 | #play-button.show{ 335 | opacity: 1; 336 | -webkit-transform: translate3d(0, 0, 0); 337 | -moz-transform: translate3d(0, 0, 0); 338 | -ms-transform: translate3d(0, 0, 0); 339 | -o-transform: translate3d(0, 0, 0); 340 | transform: translate3d(0, 0, 0); 341 | } 342 | 343 | #play-button:hover{ 344 | background-image:-webkit-linear-gradient(top, #333, #111); 345 | background-image:-moz-linear-gradient(top, #333, #111); 346 | background-image:-ms-linear-gradient(top, #333, #111); 347 | background-image:-o-linear-gradient(top, #333, #111); 348 | background-image:linear-gradient(top, #333, #111); 349 | } 350 | 351 | #play-button:active{ 352 | line-height: 63px; 353 | background-image:-webkit-linear-gradient(top, #000, #222); 354 | background-image:-moz-linear-gradient(top, #000, #222); 355 | background-image:-ms-linear-gradient(top, #000, #222); 356 | background-image:-o-linear-gradient(top, #000, #222); 357 | background-image:linear-gradient(top, #000, #222); 358 | box-shadow: 359 | inset 0 1px rgba(255,255,255,0.4), 360 | inset 0 10px rgba(255,255,255,0.01), 361 | inset 0 10px 20px rgba(255,255,255,0.1), 362 | inset 0 -15px 30px rgba(0,0,0,0.2); 363 | } 364 | 365 | #game-message{ 366 | position: absolute; 367 | margin-left: -210px; 368 | margin-top: -60px; 369 | top: 50%; 370 | left: 50%; 371 | width: 380px; 372 | padding: 20px; 373 | color: white; 374 | background: rgba(0,0,0,0.8); 375 | box-shadow: 0 0 20px 1px black; 376 | border-radius: 10px; 377 | font-size: 2em; 378 | font-family: Arial; 379 | display: none; 380 | z-index: 0; 381 | } 382 | 383 | #end-message{ 384 | position: absolute; 385 | font-size: 4em; 386 | width: 400px; 387 | height: 80px; 388 | line-height: 80px; 389 | text-align: center; 390 | top: 50%; 391 | left: 50%; 392 | margin-left: -200px; 393 | margin-top: -40px; 394 | font-family: Impact; 395 | z-index: 1000; 396 | color: white; 397 | text-shadow: 0 1px 1px #bbb, 398 | 0 2px 0 #999, 399 | 0 3px 0 #888, 400 | 0 4px 0 #777, 401 | 0 5px 0 #666, 402 | 0 6px 0 #555, 403 | 0 7px 0 #444, 404 | 0 8px 0 #333, 405 | 0 9px 7px #302314; 406 | 407 | -webkit-transform: scale3d(0, 0, 0); 408 | -moz-transform: scale3d(0, 0, 0); 409 | -ms-transform: scale3d(0, 0, 0); 410 | -o-transform: scale3d(0, 0, 0); 411 | transform: scale3d(0, 0, 0); 412 | } 413 | 414 | #end-message.show{ 415 | -webkit-transform: scale3d(1, 1, 0); 416 | -moz-transform: scale3d(1, 1, 0); 417 | -ms-transform: scale3d(1, 1, 0); 418 | -o-transform: scale3d(1, 1, 0); 419 | transform: scale3d(1, 1, 0); 420 | 421 | -webkit-transition: -webkit-transform 1s; 422 | -moz-transition: -moz-transform 1s; 423 | -ms-transition: -ms-transform 1s; 424 | -o-transition: -o-transform 1s; 425 | transition: transform 1s; 426 | } 427 | 428 | .info-board{ 429 | position: absolute; 430 | z-index: 200; 431 | display: inline-block; 432 | background: rgba(0, 0, 0, 0.8); 433 | box-shadow: 0 0 10px black; 434 | -webkit-transition-duration: 0.3s; 435 | -moz-transition-duration: 0.3s; 436 | -ms-transition-duration: 0.3s; 437 | -o-transition-duration: 0.3s; 438 | transition-duration: 0.3s; 439 | -webkit-transform: translateZ(300px); 440 | border-radius: 5px; 441 | background-image:-webkit-linear-gradient(top, rgba(30, 30, 30, 0.8), rgba(0,0,0, 0.8)); 442 | background-image:-moz-linear-gradient(top, rgba(30, 30, 30, 0.8), rgba(0,0,0, 0.8)); 443 | background-image:-ms-linear-gradient(top, rgba(30, 30, 30, 0.8), rgba(0,0,0, 0.8)); 444 | background-image:-o-linear-gradient(top, rgba(30, 30, 30, 0.8), rgba(0,0,0, 0.8)); 445 | background-image:linear-gradient(top, rgba(30, 30, 30, 0.8), rgba(0,0,0, 0.8)); 446 | } 447 | 448 | .info-board.highlight{ 449 | -webkit-animation: pulsing 1s alternate infinite; 450 | -moz-animation: pulsing 1s alternate infinite; 451 | -ms-animation: pulsing 1s alternate infinite; 452 | animation: pulsing 1s alternate infinite; 453 | } 454 | 455 | .info-board.board-0{ 456 | top: 70%; 457 | left: 50%; 458 | } 459 | 460 | .info-board.board-1{ 461 | left: 15%; 462 | top: 50%; 463 | } 464 | 465 | .info-board.board-2{ 466 | top: 30%; 467 | left: 50%; 468 | } 469 | 470 | .info-board.board-3{ 471 | left: 85%; 472 | top: 50%; 473 | } 474 | 475 | .info-board:not(.table):hover{ 476 | opacity: 0.1; 477 | } 478 | 479 | .info-board.table.human{ 480 | background-image: -webkit-linear-gradient( top, rgba(190, 190, 40, 0.8), rgba(100, 100, 40, 0.8)); 481 | background-image: -moz-linear-gradient( top, rgba(190, 190, 40, 0.8), rgba(100, 100, 40, 0.8)); 482 | background-image: -ms-linear-gradient( top, rgba(190, 190, 40, 0.8), rgba(100, 100, 40, 0.8)); 483 | background-image: -o-linear-gradient( top, rgba(190, 190, 40, 0.8), rgba(100, 100, 40, 0.8)); 484 | background-image: linear-gradient( top, rgba(190, 190, 40, 0.8), rgba(100, 100, 40, 0.8)); 485 | } 486 | 487 | .player-name, .player-score, .final-score{ 488 | padding: 10px; 489 | float: left; 490 | padding-bottom: 8px; 491 | margin: 10px; 492 | margin-top: 8px; 493 | display: inline-block; 494 | width: 90px; 495 | box-shadow: inset 0 2px 2px black, 0 1px 1px white; 496 | border-radius: 4px; 497 | text-align: center; 498 | font-family: Arial; 499 | background-image:-webkit-linear-gradient(top, rgba(220, 220, 220, 0.7), rgba(255,255,255, 0.7)); 500 | background-image:-moz-linear-gradient(top, rgba(220, 220, 220, 0.7), rgba(255,255,255, 0.7)); 501 | background-image:-ms-linear-gradient(top, rgba(220, 220, 220, 0.7), rgba(255,255,255, 0.7)); 502 | background-image:-o-linear-gradient(top, rgba(220, 220, 220, 0.7), rgba(255,255,255, 0.7)); 503 | background-image:linear-gradient(top, rgba(220, 220, 220, 0.7), rgba(255,255,255, 0.7)); 504 | } 505 | 506 | .final-score{ 507 | width: 0; 508 | padding-left: 0; 509 | padding-right: 0; 510 | overflow: hidden; 511 | margin-right: 0; 512 | margin-left: 0; 513 | -webkit-transition-duration: 0.3s; 514 | -moz-transition-duration: 0.3s; 515 | -ms-transition-duration: 0.3s; 516 | -o-transition-duration: 0.3s; 517 | transition-duration: 0.3s; 518 | } 519 | 520 | .final-score.show{ 521 | width: 90px; 522 | padding: 10px; 523 | padding-bottom: 8px; 524 | margin-right: 10px; 525 | } 526 | 527 | .player-score{ 528 | margin-left: 0; 529 | -webkit-transition: background 5s; 530 | -moz-transition: background 5s; 531 | -ms-transition: background 5s; 532 | -o-transition: background 5s; 533 | transition: background 5s; 534 | } 535 | 536 | .player-score.highlight{ 537 | -webkit-transition-duration: 0s; 538 | -moz-transition-duration: 0s; 539 | -ms-transition-duration: 0s; 540 | -o-transition-duration: 0s; 541 | transition-duration: 0s; 542 | background-color: red; 543 | } 544 | 545 | .card>.front, .card>.back{ 546 | border-radius: 8px; 547 | position:absolute; 548 | top:0; 549 | left:0; 550 | width:85px; 551 | height:130px; 552 | background-color:white; 553 | -webkit-backface-visibility: hidden; 554 | -moz-backface-visibility: hidden; 555 | -ms-backface-visibility: hidden; 556 | -o-backface-visibility: hidden; 557 | backface-visibility: hidden; 558 | } 559 | 560 | .card{ 561 | width:85px; 562 | height:130px; 563 | border-radius:8px; 564 | position:absolute; 565 | margin-left: -42px; 566 | margin-top: -65px; 567 | top: 50%; 568 | left: 50%; 569 | -webkit-transition: -webkit-transform 0.2s, box-shadow 0.2s; 570 | -moz-transition: -moz-transform 0.2s, box-shadow 0.2s; 571 | -ms-transition: -ms-transform 0.2s, box-shadow 0.2s; 572 | -o-transition: -o-transform 0.2s, box-shadow 0.2s; 573 | transition: transform 0.2s, box-shadow 0.2s; 574 | z-index: 1000; 575 | cursor:default; 576 | border-radius: 8px; 577 | box-shadow: 0 0 5px rgba(0,0,0,0.5); 578 | -webkit-transform-style: preserve-3d; 579 | -moz-transform-style: preserve-3d; 580 | -ms-transform-style: preserve-3d; 581 | -o-transform-style: preserve-3d; 582 | transform-style: preserve-3d; 583 | } 584 | 585 | .card>.front{ 586 | background-color:white; 587 | background-repeat:no-repeat; 588 | background-position:50% 75%; 589 | background-size:70px; 590 | } 591 | 592 | .card>.back{ 593 | background-image:url('img/flip.png'); 594 | background-repeat:repeat; 595 | box-sizing: border-box; 596 | moz-box-sizing: border-box; 597 | border: 6px solid white; 598 | -webkit-transform: rotateY(180deg); 599 | -moz-transform: rotateY(180deg); 600 | -ms-transform: rotateY(180deg); 601 | -o-transform: rotateY(180deg); 602 | transform: rotateY(180deg); 603 | } 604 | 605 | .card.heart, .card.diamond{ 606 | color:red; 607 | } 608 | 609 | .card.noShadow .front, .card.noShadow .back{ 610 | box-shadow:none; 611 | border:1px solid #777777; 612 | } 613 | 614 | .card.movable .front{ 615 | box-shadow: 0 0 25px lightblue; 616 | } 617 | 618 | .card.movable:hover .front{ 619 | box-shadow:0 0 20px 5px lightblue; 620 | } 621 | 622 | .card.noShadow .front, .card.noShadow .back{ 623 | box-shadow:none; 624 | } 625 | 626 | .card.heart .front, .card.heart .icon{ 627 | background-image:url('img/heart.png'); 628 | } 629 | 630 | .card.club .front, .card.club .icon{ 631 | background-image:url('img/club.png'); 632 | } 633 | 634 | .card.spade .front, .card.spade .icon{ 635 | background-image:url('img/spade.png'); 636 | } 637 | 638 | .card.diamond .front, .card.diamond .icon{ 639 | background-image:url('img/diamond.png'); 640 | } 641 | 642 | .card .icon{ 643 | background-size:contain; 644 | background-position:center; 645 | background-repeat:no-repeat; 646 | width:15px; 647 | height:15px; 648 | position:absolute; 649 | left:4px; 650 | top:19px; 651 | -webkit-backface-visibility: hidden; 652 | -moz-backface-visibility: hidden; 653 | -ms-backface-visibility: hidden; 654 | -o-backface-visibility: hidden; 655 | backface-visibility: hidden; 656 | } 657 | 658 | .card .num{ 659 | position:absolute; 660 | left:7px; 661 | top:4px; 662 | font-size: 15px; 663 | font-family: Arial; 664 | -webkit-backface-visibility: hidden; 665 | -moz-backface-visibility: hidden; 666 | -ms-backface-visibility: hidden; 667 | -o-backface-visibility: hidden; 668 | backface-visibility: hidden; 669 | } 670 | 671 | input[type="number"]{ 672 | background: transparent; 673 | font-size: inherit; 674 | padding: 0 0.5em; 675 | color: white; 676 | } -------------------------------------------------------------------------------- /js/game_old.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | var game = window.game = {}; 3 | 4 | var status = 'prepare', 5 | heartBroken = false, 6 | currentPlay = 0, 7 | players = [], 8 | rounds = -1, 9 | directions = ['left', 'right', 'opposite'], 10 | transferred = 0; 11 | 12 | game.players = players; 13 | 14 | var interface = game.interface = { 15 | arrow: window.isDebug ? null : document.createElement('div'), 16 | button: window.isDebug ? null : document.createElement('button'), 17 | message: window.isDebug ? null : document.createElement('div'), 18 | showMessage: function(msg){ 19 | if(window.isDebug) { 20 | console.log(msg); 21 | return; 22 | } 23 | this.message.innerHTML = msg; 24 | this.message.style.display = 'block'; 25 | }, 26 | hideMessage: function(){ 27 | if(window.isDebug) { 28 | return; 29 | } 30 | this.message.style.display = ''; 31 | }, 32 | playerBoards: [], 33 | endMessage: window.isDebug ? null : document.createElement('div') 34 | }; 35 | 36 | var board = game.board = { 37 | cards: [], 38 | isEmpty: function(){ 39 | return this.desk.cards.length === 0; 40 | }, 41 | desk: { 42 | cards: [], 43 | players: [], 44 | getPosFor: function(ind){ 45 | var pos = { 46 | x: 0, 47 | y: layout.cardHeight / 2 + layout.cardWidth / 2, 48 | z: ind + 52 49 | }; 50 | pos.rotation = this.cards[ind].pos.rotation; 51 | return pos; 52 | }, 53 | addCard: function(card, applying){ 54 | card.ind = this.cards.length; 55 | this.cards.push(card); 56 | if(!applying){ 57 | this.players.push(card.parent.playedBy); 58 | } 59 | card.parent = this; 60 | card.flip(false); 61 | }, 62 | adjustPos: function(){ 63 | this.cards.forEach(function(c){ 64 | c.adjustPos(); 65 | }); 66 | }, 67 | score: function(){ 68 | var max = 0; 69 | for(var i = 1; i < 4; i++){ 70 | if( this.cards[i].suit === this.cards[max].suit && (this.cards[i].num > this.cards[max].num)){ 71 | max = i; 72 | } 73 | } 74 | var p = this.players[max], 75 | self = this; 76 | var nextTime = 600, 77 | time = 800; 78 | if(window.isDebug){ 79 | nextTime = 0; 80 | time = 0; 81 | } 82 | setTimeout(function(){ 83 | currentPlay = p.id; 84 | p.waste.addCards(self.cards); 85 | self.players = []; 86 | self.cards = []; 87 | if(players[0].row.cards.length === 0){ 88 | setTimeout(function(){ 89 | end(); 90 | },nextTime); 91 | }else{ 92 | setTimeout(function(){ 93 | proceed(); 94 | },nextTime); 95 | } 96 | }, time); 97 | } 98 | } 99 | }; 100 | 101 | var layout = game.layout = { 102 | width: 500, 103 | height: 500, 104 | cardSep: 30, 105 | cardHeight: 130, 106 | cardWidth: 85, 107 | rowMargin: 10, 108 | boardHeight: 55, 109 | boardWidth: 250, 110 | adjust: function(){ 111 | if(window.isDebug) return; 112 | var region = $('#game-region')[0]; 113 | this.width = region.offsetWidth; 114 | this.height = region.offsetHeight; 115 | players.forEach(function(r){ 116 | r.row.adjustPos(); 117 | r.waste.adjustPos(); 118 | }); 119 | board.desk.adjustPos(); 120 | } 121 | }; 122 | 123 | var proceed = game.proceed = function(){ 124 | ({ 125 | 'prepare': function(){ 126 | tester.informNewGame(); 127 | if(!window.isDebug){ 128 | [].forEach.call($('.movable'), function(c){ 129 | c.classList.remove('movable'); 130 | }); 131 | interface.hideMessage(); 132 | interface.button.classList.remove('show'); 133 | } 134 | rounds++; 135 | players.forEach(function(p){ 136 | p.initForNewRound(); 137 | }); 138 | board.desk.cards.length = 0; 139 | board.desk.players.length = 0; 140 | board.cards.forEach(function(c){ 141 | c.parent = null; 142 | c.flip(true); 143 | }); 144 | heartBroken = false; 145 | layout.adjust(); 146 | function move(){ 147 | if(curI === board.cards.length){ 148 | players.forEach(function(v){v.row.sort();}); 149 | setTimeout(function(){ 150 | status = 'start'; 151 | proceed(); 152 | }, window.isDebug ? 0 : 300); 153 | return; 154 | } 155 | players[curI % 4].row.addCard(board.cards[carddeck[curI]]); 156 | players[curI % 4].row.adjustPos(); 157 | if(curI%4 === 0){ 158 | var pc = board.cards[carddeck[curI]]; 159 | } 160 | curI++; 161 | setTimeout(move, window.isDebug ? 0 : 10); 162 | } 163 | curI = 0; 164 | var carddeck=[]; 165 | var i; 166 | for(i=0;i<52;i++) { 167 | carddeck.push(i); 168 | } 169 | 170 | for(i = 0; i < 52; i++){ 171 | var ran=Math.floor(Math.random()*(52-i)); 172 | var tmp = carddeck[ran]; 173 | carddeck[ran]=carddeck[51-i]; 174 | carddeck[51 - i]=tmp; 175 | } 176 | if(!window.isDebug){ 177 | for(i = 51; i >= 0; i--){ 178 | var c = board.cards[carddeck[i]].display.style; 179 | c.zIndex = 200 - i * 3; 180 | c[vendorPrefix + 'Transform'] = 'translate3d(-' + (52-i)/4+'px,-' + (52-i)/4 + 'px, -' + i +'px) rotateY(180deg)'; 181 | } 182 | } 183 | setTimeout(function(){move();}, window.isDebug ? 0 : 300); 184 | }, 185 | 'start': function(){ 186 | players.forEach(function(p){ 187 | p.prepareTransfer(); 188 | }); 189 | transferred = 0; 190 | if(window.isDebug){ 191 | status = 'passing'; 192 | currentPlay = 0; 193 | proceed(); 194 | } 195 | }, 196 | 'passing': function(){ 197 | if(transferred === 4){ 198 | players.forEach(function(r){ 199 | r.row.sort(); 200 | }); 201 | if(window.isDebug){ 202 | status = "playing"; 203 | currentPlay = board.cards[26].parent.playedBy.id; 204 | setTimeout(proceed, 0); 205 | }else{ 206 | status = 'confirming'; 207 | players[0].myTurn(); 208 | } 209 | }else{ 210 | players[currentPlay].myTurn(); 211 | } 212 | }, 213 | 'confirming': function(){ 214 | if(!window.isDebug){ 215 | interface.button.classList.add('show'); 216 | } 217 | players[0].row.curShifted = []; 218 | players[0].row.adjustPos(); 219 | currentPlay = board.cards[26].parent.playedBy.id; 220 | setTimeout(function(){ 221 | status = 'playing'; 222 | proceed(); 223 | }, window.isDebug ? 0 : 100); 224 | }, 225 | 'playing': function(){ 226 | if(!window.isDebug){ 227 | interface.button.classList.remove('show'); 228 | } 229 | if(board.desk.cards.length === 4){ 230 | board.desk.score(); 231 | }else if(players[0].row.curShifted.length === 1){ 232 | interface.hideMessage(); 233 | var card = players[0].row.curShifted[0]; 234 | players[0].row.out(card.ind, true); 235 | game.informCardOut(players[0], card); 236 | players[0].next(); 237 | }else{ 238 | players[currentPlay].myTurn(); 239 | } 240 | }, 241 | 'allEnd': function(){ 242 | if(!window.isDebug){ 243 | interface.playerboards.foreach(function(p){ 244 | p.display.style[vendorprefix + 'transform'] = ""; 245 | }); 246 | interface.endMessage.classList.remove('show'); 247 | } 248 | players.forEach(function(p){ 249 | p.score = p.oldScore = 0; 250 | }); 251 | rounds = -1; 252 | if(!window.isDebug){ 253 | interface.playerBoards.forEach(function(p){ 254 | p.hideFinal(); 255 | p.display.classList.remove('table'); 256 | }); 257 | } 258 | newGame(); 259 | }, 260 | 'end': function(){ 261 | if(!window.isDebug){ 262 | interface.playerBoards.forEach(function(p){ 263 | p.hideFinal(); 264 | p.display.classList.remove('table'); 265 | }); 266 | } 267 | newRound(); 268 | } 269 | })[status](); 270 | }; 271 | 272 | game.informCardOut = function(player, card){ 273 | tester.log("place", player, card); 274 | players.forEach(function(p){ 275 | p.watch({ 276 | type: "out", 277 | player: player, 278 | card: card 279 | }); 280 | }); 281 | }; 282 | 283 | game.init = function(){ 284 | var frag; 285 | if(!window.isDebug){ 286 | frag = document.createDocumentFragment(); 287 | } 288 | var i; 289 | for(i=0;i<52;i++){ 290 | var c = new Card(i); 291 | board.cards.push(c); 292 | if(!window.isDebug){ 293 | frag.appendChild(c.display); 294 | } 295 | } 296 | for(i=0;i<4;i++){ 297 | var b = new PlayerBoard(i); 298 | interface.playerBoards.push(b); 299 | if(!window.isDebug){ 300 | frag.appendChild(b.display); 301 | } 302 | } 303 | if(!window.isDebug){ 304 | interface.playerBoards[0].display.classList.add('human'); 305 | } 306 | game.players = players = [ 307 | window.isDebug ? new Ai(0) : new Human(0), 308 | new Ai(1), 309 | new Ai(2), 310 | new Ai(3) 311 | ]; 312 | players.forEach(function(p, ind){ 313 | p.name = config.names[ind]; 314 | }); 315 | 316 | if(!window.isDebug){ 317 | interface.arrow.innerHTML = "←"; 318 | interface.arrow.id = 'pass-arrow'; 319 | interface.arrow.onmouseup = function(){ 320 | interface.hideMessage(); 321 | status = 'passing'; 322 | currentPlay = 0; 323 | players[0].transfer(players[0].row.curShifted); 324 | this.classList.remove('show'); 325 | }; 326 | 327 | interface.button.id = 'play-button'; 328 | interface.button.onmouseup = function(){ 329 | proceed(); 330 | this.classList.remove('show'); 331 | }; 332 | 333 | interface.message.id = 'game-message'; 334 | 335 | interface.endMessage.id = 'end-message'; 336 | 337 | frag.appendChild(game.interface.arrow); 338 | frag.appendChild(game.interface.button); 339 | frag.appendChild(game.interface.message); 340 | frag.appendChild(game.interface.endMessage); 341 | 342 | $('#game-region')[0].appendChild(frag); 343 | } 344 | }; 345 | 346 | var end = game.end = function(){ 347 | if(players.some(function(p){ 348 | return p.score === 26; 349 | })){ 350 | players.forEach(function(p){ 351 | if(p.score !== 26){ 352 | p.score = 26; 353 | }else{ 354 | p.score = 0; 355 | } 356 | }); 357 | } 358 | tester.recordScore(players.map(function(p){ 359 | return p.score; 360 | })); 361 | players.forEach(function(p){ 362 | p.oldScore += p.score; 363 | }); 364 | status = 'end'; 365 | var rank = players.map(function(c){ 366 | return c; 367 | }); 368 | rank.sort(function(a,b){ 369 | return a.oldScore - b.oldScore; 370 | }); 371 | rank.forEach(function(r,ind){ 372 | r.board.rank = ind; 373 | }); 374 | layout.adjust(); 375 | setTimeout(function(){ 376 | if(!window.isDebug){ 377 | interface.playerBoards.forEach(function(p){ 378 | p.showFinal(); 379 | }); 380 | } 381 | if(!window.isDebug){ 382 | if(players.some(function(p){ 383 | return p.oldScore > 100; 384 | })){ 385 | if(players[0].board.rank === 0){ 386 | if(!window.isDebug){ 387 | interface.endMessage.innerHTML = 'You Won!'; 388 | interface.endMessage.style.color = 'white'; 389 | interface.endMessage.classList.add('show'); 390 | } 391 | }else{ 392 | if(!window.isDebug){ 393 | interface.endmessage.innerhtml = 'you lost!'; 394 | interface.endmessage.style.color = 'grey'; 395 | interface.endMessage.classList.add('show'); 396 | } 397 | } 398 | status = 'allEnd'; 399 | if(!window.isDebug){ 400 | interface.playerBoards.forEach(function(p){ 401 | p.display.style[vendorPrefix + 'Transform'] = 402 | 'translate3d(0, -' + ((layout.boardHeight + 10) * 2 + 40) + 'px, 0)'; 403 | }); 404 | } 405 | } 406 | } 407 | if(!window.isDebug){ 408 | interface.button.innerHTML = 'Continue'; 409 | interface.button.classList.add('show'); 410 | }else{ 411 | setTimeout(proceed, 0); 412 | } 413 | }, window.isDebug ? 0 : 600); 414 | }; 415 | 416 | var newRound = function(){ 417 | status = 'prepare'; 418 | proceed(); 419 | }; 420 | 421 | var newGame = game.newGame = function(){ 422 | players.forEach(function(p){ 423 | p.oldScore = 0; 424 | }); 425 | rounds = 0; 426 | status = 'prepare'; 427 | proceed(); 428 | }; 429 | 430 | game.load = function(){ 431 | // game.state.apply(); 432 | // players.forEach(function(p){ 433 | // p.score = p.waste.cards.reduce(function(p, c){ 434 | // if(c.suit === 1){ 435 | // return p + 1; 436 | // }else if(c.suit === 0 && c.num === 11){ 437 | // return p + 13; 438 | // }else{ 439 | // return p; 440 | // } 441 | // }, 0); 442 | // }); 443 | // layout.adjust(); 444 | // proceed(); 445 | }; 446 | 447 | game.getStatus = function(){ 448 | return status; 449 | }; 450 | 451 | game.setStatus = function(val){ 452 | status = val; 453 | }; 454 | 455 | game.getRounds = function(){ 456 | return rounds; 457 | }; 458 | 459 | game.nextPlayer = function(id){ 460 | currentPlay = (id + 1) % 4; 461 | }; 462 | 463 | game.isHeartBroken = function(){ 464 | return heartBroken; 465 | }; 466 | 467 | game.transfer = function(player, cards){ 468 | tester.log("transfer", player, cards); 469 | transferred++; 470 | player.out(cards); 471 | var adds = [1, 3, 2]; 472 | players[(player.id + adds[rounds % 3]) % 4].takeIn(cards); 473 | player.next(); 474 | return (player.id + adds[rounds % 3]) % 4; 475 | }; 476 | 477 | game.showPassingMsg = function(){ 478 | if(!window.isDebug){ 479 | interface.showMessage("Pass three cards to the " + directions[rounds % 3]); 480 | [function(){ 481 | interface.arrow.style[vendorPrefix + 'Transform'] = 'rotate(0)'; 482 | },function(){ 483 | interface.arrow.style[vendorPrefix + 'Transform'] = 'rotate(180deg)'; 484 | },function(){ 485 | interface.arrow.style[vendorPrefix + 'Transform'] = 'rotate(90deg)'; 486 | }][rounds % 3](); 487 | } 488 | }; 489 | 490 | game.informHeartBroken = function(){ 491 | heartBroken = true; 492 | }; 493 | 494 | window.onresize = function(){ 495 | layout.adjust(); 496 | }; 497 | })(); 498 | 499 | 500 | // Object.defineProperty(this, 'score', { 501 | // get: function(){ 502 | // return this._score; 503 | // }, 504 | // set: function(v){ 505 | // if(!window.isDebug){ 506 | // if(v > this._score){ 507 | // var b = this.board.scoretext.classList; 508 | // b.add('highlight'); 509 | // setTimeout(function(){ 510 | // b.remove('highlight'); 511 | // },100); 512 | // } 513 | // if(game.getRounds() > 0){ 514 | // this.board.scoretext.innerHTML = this._oldScore + '+' + v; 515 | // }else{ 516 | // this.board.scoretext.innerHTML = v; 517 | // } 518 | // } 519 | // this._score = v; 520 | // } 521 | // }); 522 | // Object.defineProperty(this, 'name', { 523 | // get: function(){ 524 | // return this._name; 525 | // }, 526 | // set: function(v){ 527 | // this._name = v; 528 | // if(!window.isDebug){ 529 | // this.board.nametext.innerHTML = v; 530 | // } 531 | // } 532 | // }); 533 | // Object.defineProperty(this, 'oldScore', { 534 | // get: function(){ 535 | // return this._oldScore; 536 | // }, 537 | // set: function(v){ 538 | // this._oldScore = v; 539 | // if(!window.isDebug){ 540 | // this.board.finaltext.innerHTML = v; 541 | // } 542 | // } 543 | // }); 544 | 545 | // Row.prototype.adjustPos = function(){ 546 | // if(this.isVertical){ 547 | // this.distance = layout.width / 2 - layout.rowMargin - layout.cardHeight / 2; 548 | // this.playedBy.board.display.style.top = layout.height / 2 - layout.boardHeight / 2 + 'px'; 549 | // if(this.id === 1){ 550 | // this.playedBy.board.display.style.left = layout.rowMargin * 1.5 + 'px'; 551 | // }else{ 552 | // this.playedBy.board.display.style.left = layout.width - layout.rowMargin * 1.5 - layout.boardWidth + 'px'; 553 | // } 554 | // }else{ 555 | // this.distance = layout.height / 2 - layout.rowMargin - layout.cardHeight / 2; 556 | // this.playedBy.board.display.style.left = layout.width / 2 - layout.boardWidth / 2 + 'px'; 557 | // if(this.id === 0){ 558 | // this.playedBy.board.display.style.top = layout.height - 30 - layout.rowMargin * 1.5 - layout.boardHeight - layout.cardHeight + 'px'; 559 | // }else{ 560 | // this.playedBy.board.display.style.top = 30 + layout.rowMargin * 1.5 + 'px'; 561 | // } 562 | // } 563 | // this.left = -((this.cards.length - 1) * layout.cardSep) / 2; 564 | // this.playedBy.board.display.classList.remove('table'); 565 | // if(game.getStatus() === 'end'){ 566 | // var top = layout.height / 2 - 2 * (layout.boardHeight + 10), 567 | // left = layout.width / 2 - layout.boardWidth / 2; 568 | // var b = this.playedBy.board; 569 | // b.display.style.top = top + b.rank * (layout.boardHeight + 10) + 'px'; 570 | // b.display.style.left = left + 'px'; 571 | // b.display.classList.add('table'); 572 | // } 573 | // this.cards.forEach(function(c){ 574 | // c.adjustPos(); 575 | // }); 576 | // }; --------------------------------------------------------------------------------