diff --git a/build/ludorum.js b/build/ludorum.js index c30630f..8919977 100644 --- a/build/ludorum.js +++ b/build/ludorum.js @@ -2162,7 +2162,7 @@ exports.utils.Scanner = declare({ stats.add({key:'defeat.length', game:game.name, role:role, player:p}, ply, game); } else if (r > 0) { stats.add({key:'victory.result', game:game.name, role:role, player:p}, r, game); - stats.add({key:'victory.result', game:game.name, role:role, player:p}, ply, game); + stats.add({key:'victory.length', game:game.name, role:role, player:p}, ply, game); } else { stats.add({key:'draw.length', game:game.name, role:role, player:p}, ply, game); } @@ -2727,11 +2727,11 @@ games.Mancala = declare(Game, { this.board = board || this.makeBoard(); }, - /** games.Mancala.makeBoard(seeds=3, houses=6): + /** games.Mancala.makeBoard(seeds=4, houses=6): Builds a board array to use as the game state. */ makeBoard: function makeBoard(seeds, houses){ - seeds = isNaN(seeds) ? 3 : +seeds; + seeds = isNaN(seeds) ? 4 : +seeds; houses = isNaN(houses) ? 6 : +houses; var result = []; for(var j = 0; j < 2; j++){ diff --git a/build/ludorum.min.js b/build/ludorum.min.js index 5a01e77..29e6890 100644 --- a/build/ludorum.min.js +++ b/build/ludorum.min.js @@ -1,5 +1,5 @@ //! ludorum 0.1.2 -"use strict";!function(a,b){"function"==typeof define&&define.amd?define(["creatartis-base"],b):"object"==typeof module&&module.exports?module.exports=b(require("creatartis-base")):a.ludorum=b(a.base)}(this,function __init__(base){var declare=base.declare,unimplemented=base.objects.unimplemented,obj=base.obj,copy=base.copy,raiseIf=base.raiseIf,Iterable=base.Iterable,iterable=base.iterable,Future=base.Future,Randomness=base.Randomness,initialize=base.initialize,Statistics=base.Statistics,Events=base.Events,exports={__name__:"ludorum",__init__:__init__};__init__.dependencies={"creatartis-base":base};var utils=exports.utils={},Game=exports.Game=declare({constructor:function(a){this.activePlayers=a?Array.isArray(a)?a:[a]:[this.players[0]]},name:"?",players:[],moves:unimplemented("Game","moves"),next:unimplemented("Game","next"),result:unimplemented("Game","result"),scores:function(){return this.results()},view:function(){return this},isActive:function(){for(var a=0;aa,"There is no active player."),raiseIf(a>1,"More than one player is active."),this.activePlayers[0]},opponents:function(a){return a=a||this.activePlayers,this.players.filter(function(b){return a.indexOf(b)<0})},opponent:function(a){var b=this.players.indexOf(a||this.activePlayer());return this.players[(b+1)%this.players.length]},perform:function(a,b){b=b||this.activePlayer();var c={};c[b]=a;for(var d=2;d+a?this.ply()+ +a:+a,this.history[0|a]},result:function(){return this.state().result()},decisions:function(a){a=a||this.state();var b=this,c=this.players,d=a.activePlayers;return Future.all(d.map(function(b){return c[b].decision(a,b)})).then(function(c){var e=iterable(d).zip(c).toObject();return b.onMove(a,e),e})},run:function(a){if(a=isNaN(a)?1/0:+a,1>a)return Future.when(this);var b,c=this.ply(),d=this.state();if(1>c&&this.onBegin(d),d=this.__advanceAleatories__(d),b=d.result())return this.onEnd(d,b),Future.when(this);var e=this;return this.decisions(d).then(function(b){return e.__advance__(d,b)?e.run(a-1):e})},__advanceAleatories__:function(a){for(var b;a instanceof Aleatory;a=b)b=a.next(),this.history.push(b),this.onNext(a,b);return a},__advance__:function(a,b){var c=this,d=a.activePlayers.filter(function(a){return b[a]instanceof Match.CommandQuit});if(d.length>0)return c.onQuit(a,d[0]),!1;var e=a.next(b);return this.history.push(e),this.onNext(a,e),!0},"static CommandQuit":function(){},onBegin:function(a){this.events.emit("begin",a,this),this.logger&&this.logger.info("Match begins with ",iterable(this.players).map(function(a){return a[1]+" as "+a[0]}).join(", "),"; for ",a,".")},onMove:function(a,b){this.events.emit("move",a,b,this),this.logger&&this.logger.info("Players move: ",JSON.stringify(b)," in ",a)},onNext:function(a,b){this.events.emit("next",a,b,this),this.logger&&this.logger.info("Match advances from ",a," to ",b)},onEnd:function(a,b){this.events.emit("end",a,b,this),this.logger&&this.logger.info("Match for ",a,"ends with ",JSON.stringify(b))},onQuit:function(a,b){this.events.emit("quit",a,b,this),this.logger&&this.logger.info("Match for ",a," aborted because player "+b+" quitted.")},toString:function(){return"Match("+this.game+", "+JSON.stringify(this.players)+")"}}),Tournament=exports.Tournament=declare({constructor:function(a,b){this.game=a,this.players=Array.isArray(b)?b:iterables.iterable(b).toArray(),this.statistics=new Statistics,this.events=new Events({events:["begin","beforeMatch","afterMatch","end"]})},__advance__:unimplemented("Tournament","__advance__"),run:function(){this.onBegin();var a=this;return Future.doWhile(function(){return Future.then(a.__advance__(),function(b){return b?(a.beforeMatch(b),a.__runMatch__(b).then(function(b){return a.account(b),a.afterMatch(b),b})):null})}).then(this.onEnd.bind(this))},__runMatch__:function(a){return a.run()},account:function(a){var b=this.game,c=a.result(),d=this.statistics;raiseIf(!c,"Match doesn't have results. Has it finished?"),iterable(a.players).forEach(function(e){var f=e[0],g=e[1],h=c[e[0]];d.add({key:"results",game:b.name,role:f,player:g.name},h),d.add({key:h>0?"victories":0>h?"defeats":"draws",game:b.name,role:f,player:g.name},h),d.add({key:"length",game:b.name,role:f,player:g.name},a.ply()),a.history.forEach(function(a){if("function"==typeof a.moves){var c=a.moves();c&&c.hasOwnProperty(f)&&c[f].length>0&&d.add({key:"width",game:b.name,role:f,player:g.name},c[f].length)}})})},onBegin:function(){this.events.emit("begin",this),this.logger&&this.logger.info("Tournament begins for game ",game.name,".")},beforeMatch:function(a){this.events.emit("beforeMatch",a,this),this.logger&&this.logger.debug("Beginning match with ",JSON.stringify(a.players),".")},afterMatch:function(a){this.events.emit("afterMatch",a,this),this.logger&&this.logger.debug("Finishing match with ",JSON.stringify(a.players),".")},onEnd:function(){this.events.emit("end",this.statistics,this),this.logger&&this.logger.info("Tournament ends for game ",game.name,":\n",this.statistics,"\n")}}),tournaments=exports.tournaments={},Aleatory=exports.Aleatory=declare({constructor:function(a,b){this.random=b||Randomness.DEFAULT,"function"==typeof a&&(this.next=a)},value:function a(){var a,b=random.random();if(iterable(this.distribution()).forEach(function(c){if(b-=c[1],0>=b)throw a=c[0],Iterable.STOP_ITERATION}),"undefined"==typeof a)throw new Error("Random value could not be obtained.");return a},next:unimplemented("Aleatory","next"),distribution:unimplemented("Aleatory","distribution")}),aleatories=exports.aleatories={};players.RandomPlayer=declare(Player,{constructor:function(a){Player.call(this,a),initialize(this,a).object("random",{defaultValue:Randomness.DEFAULT})},decision:function(a,b){return this.random.choice(this.__moves__(a,b))}}),players.TracePlayer=declare(Player,{constructor:function(a){Player.call(this,a),this.trace=iterable(a.trace),this.__iterator__=this.trace.__iter__(),this.__decision__=this.__iterator__()},decision:function(){try{this.__decision__=this.__iterator__()}catch(a){Iterable.prototype.catchStop(a)}return this.__decision__},__serialize__:function(){return["TracePlayer",{name:this.name,trace:this.trace.toArray()}]}});var HeuristicPlayer=players.HeuristicPlayer=declare(Player,{constructor:function(a){Player.call(this,a),initialize(this,a).object("random",{defaultValue:Randomness.DEFAULT}).func("heuristic",{ignore:!0})},moveEvaluation:function(a,b,c){return this.stateEvaluation(b.next(obj(c,a)),c)},stateEvaluation:function(a,b){var c=a.result();return c?c[b]:this.heuristic(a,b)},heuristic:function(){return this.random.random(-.5,.5)},bestMoves:function(a){return iterable(a).greater(function(a){return a[1]}).map(function(a){return a[0]})},selectMoves:function(a,b,c){var d=this,e=!1,f=a.map(function(a){var f=d.moveEvaluation(a,b,c);return f instanceof Future?(e=e||!0,f.then(function(b){return[a,b]})):[a,f]});return e?Future.all(f).then(this.bestMoves):this.bestMoves(f)},decision:function(a,b){var c=this,d=c.selectMoves(c.__moves__(a,b),a,b);return Future.then(d,function(a){return c.random.choice(a)})}}),MaxNPlayer=players.MaxNPlayer=declare(HeuristicPlayer,{constructor:function(a){HeuristicPlayer.call(this,a),initialize(this,a).integer("horizon",{defaultValue:3,coerce:!0})},stateEvaluation:function(a,b){return this.maxN(a,b,0)[b]},heuristics:function(a){var b={},c=this;return a.players.forEach(function(d){b[d]=c.heuristic(a,d)}),b},quiescence:function(a,b,c){var d=a.result();return d?d:c>=this.horizon?this.heuristics(a):null},maxN:function(a,b,c){var d=this.quiescence(a,b,c);if(!d){var e,f,g=a.activePlayer(),h=this.__moves__(a,g),d={};if(h.length<1)throw new Error("No moves for unfinished game "+a+".");for(var i=0;i(d[g]||-1/0)&&(d=e)}return d},toString:function(){return(this.constructor.name||"MaxNPlayer")+"("+JSON.stringify({name:this.name,horizon:this.horizon})+")"}}),MiniMaxPlayer=players.MiniMaxPlayer=declare(HeuristicPlayer,{constructor:function(a){HeuristicPlayer.call(this,a),initialize(this,a).integer("horizon",{defaultValue:4,coerce:!0})},stateEvaluation:function(a,b){return this.minimax(a,b,0)},quiescence:function(a,b,c){var d=a.result();return d?d[b]:c>=this.horizon?this.heuristic(a,b):0/0},minimax:function(a,b,c){var d=this.quiescence(a,b,c);if(isNaN(d)){var e,f,g=a.activePlayer(),h=this.__moves__(a,g);if(h.length<1)throw new Error("No moves for unfinished game "+a+".");g==b?(d=-1/0,e=Math.max):(d=+1/0,e=Math.min);for(var i=0;id&&(d=f):e>f&&(e=f),!(d>=e));k++);return i?d:e}}),players.MonteCarloPlayer=declare(HeuristicPlayer,{constructor:function(a){HeuristicPlayer.call(this,a),initialize(this,a).number("simulationCount",{defaultValue:30,coerce:!0}).number("timeCap",{defaultValue:1e3,coerce:!0}).object("agent",{defaultValue:null})},selectMoves:function(a,b,c){for(var d=this,e=Date.now()+this.timeCap,f=a.map(function(a){return{move:a,next:b.next(obj(c,a)),isFinal:!1,sum:0,count:0}}),g=0;g0?a.sum/a.count:0}).map(function(a){return a.move})},stateEvaluation:function(a,b){for(var c,d=0,e=this.simulationCount,f=0;e>f&&(c=this.simulation(a,b),d+=c.result[b],!(c.plies<1));++f);return e>0?d/e:0},simulation:function(a){var b,c,d,e=this;for(b=0;!0;++b)if(a instanceof Aleatory)a=a.next();else{if(d=a.moves(),!d)return{game:a,result:a.result(),plies:b};c={},a.activePlayers.forEach(function(b){c[b]=e.agent?e.agent.decision(a,b):e.random.choice(d[b])}),a=a.next(c)}},toString:function(){return(this.constructor.name||"MonteCarloPlayer")+"("+JSON.stringify({name:this.name,simulationCount:this.simulationCount,timeCap:this.timeCap})+")"}});var UserInterfacePlayer=players.UserInterfacePlayer=declare(Player,{constructor:function(a){Player.call(this,a)},participate:function(a,b){return this.role=b,this},decision:function(){return this.__future__&&this.__future__.isPending()&&this.__future__.resolve(new Match.CommandQuit),this.__future__=new Future},perform:function(a){var b=this.__future__;return b&&(this.__future__=null,b.resolve(a)),!!b}}),UserInterface=players.UserInterface=declare({constructor:function(a){this.onBegin=this.onBegin.bind(this),this.onNext=this.onNext.bind(this),this.onEnd=this.onEnd.bind(this),a.match&&this.show(a.match)},show:function(a){this.match&&(a.events.off("begin",this.onBegin),a.events.off("next",this.onNext),a.events.off("end",this.onEnd)),this.match=a,a.events.on("begin",this.onBegin),a.events.on("next",this.onNext),a.events.on("end",this.onEnd)},onBegin:function(a){this.display(a)},onNext:function(a,b){this.display(b)},onEnd:function(a,b){this.results=b,this.display(a)},display:function(){throw new Error("UserInterface.display is not defined. Please override.")},perform:function(a,b){iterable(this.match.players).forEach(function(c){var d=(c[0],c[1]);d instanceof UserInterfacePlayer&&(!b||d.role===b)&&d.perform(a)})}});UserInterface.BasicHTMLInterface=declare(UserInterface,{constructor:function(a){UserInterface.call(this,a),this.container=a.container,"string"==typeof this.container&&(this.container=document.getElementById(this.container))},display:function display(game){var ui=this,container=this.container;container.innerHTML=game.toHTML(),Array.prototype.slice.call(container.querySelectorAll("[data-ludorum]")).forEach(function(elem){var data=eval("({"+elem.getAttribute("data-ludorum")+"})");data.hasOwnProperty("move")&&(elem.onclick=ui.perform.bind(ui,data.move,data.activePlayer))})}});var WebWorkerPlayer=players.WebWorkerPlayer=declare(Player,{constructor:function(a){Player.call(this,a),initialize(this,a).object("worker"),this.worker.onmessage=base.Parallel.prototype.__onmessage__.bind(this)},"static createWorker":function(a){raiseIf("string function".indexOf(typeof a)<0,"Invalid player builder: "+a+"!");var b=new base.Parallel;return b.run("self.ludorum = ("+exports.__init__+')(self.base), "OK"').then(function(){return b.run("self.PLAYER = ("+a+').call(self), "OK"')}).then(function(){return b.worker})},"static create":function(a){var b=this;return b.createWorker(a.playerBuilder).then(function(a){return new b({name:name,worker:a})})},decision:function(a,b){return this.__future__&&this.__future__.isPending()&&this.__future__.resolve(Match.commandQuit),this.__future__=new Future,this.worker.postMessage("PLAYER.decision(ludorum.Game.fromJSON("+a.toJSON()+"), "+JSON.stringify(b)+")"),this.__future__}});aleatories.Dice=declare(Aleatory,{constructor:function(a,b,c){Aleatory.call(this,a,c),this.base=isNaN(b)?6:Math.max(2,+b)},value:function(){return this.random.randomInt(1,this.base+1)},distribution:function(){return this.__distribution__||(this.__distribution__=function(a){return Iterable.range(1,a+1).map(function(b){return[b,1/a]}).toArray()}(this.base))}});var Checkerboard=utils.Checkerboard=declare({constructor:function(a,b){isNaN(a)||(this.height=0|a),isNaN(b)||(this.width=0|b)},emptySquare:null,isValidCoord:function(a){return Array.isArray(a)&&!isNaN(a[0])&&!isNaN(a[1])&&a[0]>=0&&a[0]=0&&a[1]=b}).map(function(a){return Iterable.range(0,a.length-b+1).map(function(c){return a.slice(c,c+b)})}).flatten()},walk:function(a,b){var c=this;return new Iterable(function(){var d=a.slice();return function(){if(c.isValidCoord(d)){var a=d.slice();return d[0]+=b[0],d[1]+=b[1],a}throw Iterable.STOP_ITERATION}})},walks:function(a,b){var c=this;return b.map(function(b){return c.walk(a,b)})},"static DIRECTIONS":{HORIZONTAL:[[0,-1],[0,1]],VERTICAL:[[-1,0],[1,0]],ORTHOGONAL:[[0,-1],[0,1],[-1,0],[1,0]],DIAGONAL:[[-1,-1],[-1,1],[1,-1],[1,1]],EVERY:[[0,-1],[0,1],[-1,0],[1,0],[-1,-1],[-1,1],[1,-1],[1,1]]},clone:unimplemented("utils.Checkerboard","clone"),__place__:unimplemented("utils.Checkerboard","place"),place:function(a,b){return this.clone().__place__(a,b)},__move__:function(a,b,c){return this.__place__(b,this.square(a)).__place__(a,"undefined"==typeof c?this.emptySquare:c)},move:function(a,b,c){return this.clone().__move__(a,b,c)},__swap__:function(a,b){var c=this.square(b);return this.__place__(b,this.square(a)).__place__(a,c)},swap:function(a,b){return this.clone().__swap__(a,b)},asHTMLTable:function(a,b,c){var d=a.createElement("table");board.horizontals().reverse().foeEach(function(b){var e=a.createElement("tr");d.appendChild(e),b.forEach(function(b){var d=board.square(b),f=c(d,b),g=a.createElement("td");td_content=a.createTextNode(f.hasOwnProperty("content")?f.content:d),e.appendChild(g),g.id=f.id||"ludorum-square-"+b.join("-"),g.className=f.className||"ludorum-square",g.data_ludorum=f})})}}),CheckerboardFromString=utils.CheckerboardFromString=declare(Checkerboard,{constructor:function(a,b,c,d){if(Checkerboard.call(this,a,b),d&&(this.emptySquare=(d+this.emptySquare).charAt(0)),c&&c.length!==a*b)throw new Error("Given string "+JSON.stringify(c)+" does not match board dimensions.");this.string=c||this.emptySquare.repeat(a*b)},emptySquare:".",toString:function(){var a=this.string,b=this.height,c=this.width;return Iterable.range(b).map(function(d){return a.substr((b-d-1)*c,c)}).join("\n")},square:function(a,b){var c=a[0],d=a[1],e=this.width;return c>=0&&c=0&&e>d?this.string.charAt(c*e+d):b},asString:function(a){var b=this;return a.map(function(a){return b.square(a)}).join("")},asStrings:function(a){var b=this;return a.map(function(a){return b.asString(a)})},asRegExp:function(a,b,c){c=c||".";var d=this.width,e=Iterable.repeat(!1,d*this.height).toArray();a.forEach(function(a){e[a[0]*d+a[1]]=!0});for(var f,g="",h=0,i=0;ih?f?b:c:(f?b:c)+"{"+h+"}"}return g},asRegExps:function(a,b,c){var d=this;return a.map(function(a){return d.asRegExp(a,b,c)}).join("|")},clone:function(){return new this.constructor(this.height,this.width,this.string,this.hasOwnProperty("emptySquare")?this.emptySquare:void 0)},__place__:function(a,b){raiseIf(!this.isValidCoord(a),"Invalid coordinate ",a,"."),b=(b+this.emptySquare).charAt(0);var c=a[0]*this.width+a[1];return this.string=this.string.substr(0,c)+b+this.string.substr(c+1),this}});return exports.utils.Scanner=declare({constructor:function(a){initialize(this,a).object("game",{ignore:!0}).integer("maxWidth",{defaultValue:1e3,coerce:!0}).integer("maxLength",{defaultValue:50,coerce:!0}).object("random",{defaultValue:Randomness.DEFAULT}).object("statistics",{defaultValue:new Statistics})},scan:function(a){var b=this,c=arguments.length<2?this.game?[this.game]:[]:Array.prototype.slice.call(arguments,1),d=0;return Future.whileDo(function(){return c.length>0&&dg?(e.add({key:"defeat.result",game:b.name,role:f,player:h},g,b),e.add({key:"defeat.length",game:b.name,role:f,player:h},c,b)):g>0?(e.add({key:"victory.result",game:b.name,role:f,player:h},g,b),e.add({key:"victory.result",game:b.name,role:f,player:h},c,b)):e.add({key:"draw.length",game:b.name,role:f,player:h},c,b)}),!0;var f=b.moves();return iterable(b.activePlayers).forEach(function(a){e.add({key:"game.width",game:b.name,role:a},f[a].length)}),!1}}),games.Predefined=declare(Game,{constructor:function(a,b,c,d){b&&(this.__results__=b,this.players=Object.keys(b)),Game.call(this,a),this.height=isNaN(c)?5:+c,this.width=isNaN(d)?5:+d},name:"Predefined",players:["A","B"],__results__:{A:0,B:0},moves:function(){return this.height>0?obj(this.activePlayer(),Iterable.range(1,this.width+1).toArray()):void 0},result:function(){return this.height>0?null:this.__results__},next:function(){return new this.constructor(this.opponent(),this.__results__,this.height-1,this.width)},__serialize__:function(){return[this.name,this.activePlayer(),this.results,this.height,this.width]}}),games.Choose2Win=declare(Game,{constructor:function(a,b,c){Game.call(this,b),this.__turns__=isNaN(a)?1/0:+a,this.__winner__=c},name:"Choose2Win",players:["This","That"],moves:function(){return!this.__winner__&&this.__turns__>0?obj(this.activePlayer(),["win","lose","pass"]):void 0},result:function(){return this.__winner__?this.victory(this.__winner__):this.__turns__<1?this.draw():null},next:function(a){var b=this.activePlayer(),c=this.opponent(b);switch(a[b]){case"win":return new this.constructor(this.__turns__-1,c,b);case"lose":return new this.constructor(this.__turns__-1,c,c);case"pass":return new this.constructor(this.__turns__-1,c)}throw new Error("Invalid move "+a[b]+" for player "+b+".")},__serialize__:function(){return[this.name,this.__turns__,this.activePlayer(),this.__winner__]}}),games.ConnectionGame=declare(Game,{height:9,width:9,lineLength:5,constructor:function(a,b){Game.call(this,a),this.board=b instanceof CheckerboardFromString?b:new CheckerboardFromString(this.height,this.width,(b||".".repeat(this.height*this.width))+"")},name:"ConnectionGame",players:["First","Second"],__lines__:function(){function a(a,c,d){var e=a+"x"+c+"/"+d;if(!b.hasOwnProperty(e)){var f=new CheckerboardFromString(a,c,".".repeat(a*c));b[e]=f.lines().map(function(a){return a.toArray()},function(a){return a.length>=d}).toArray()}return b[e]}var b={};return a.CACHE=b,a}(),result:function(){if(this.hasOwnProperty("__result__"))return this.__result__;for(var a=this.lineLength,b=this.board.asStrings(this.__lines__(this.height,this.width,a)).join(" "),c=0;c=0)return this.__result__=this.victory([this.players[c]]);return this.__result__=b.indexOf(".")<0?this.draw():null},moves:function(){return this.hasOwnProperty("__moves__")?this.__moves__:this.__moves__=this.result()?null:obj(this.activePlayer(),iterable(this.board.string).filter(function(a){return"."===a},function(a,b){return b}).toArray())},next:function(a){var b=this.activePlayer(),c=this.players.indexOf(b),d=+a[b],e=d/this.width>>0,f=d%this.width;return new this.constructor((c+1)%this.players.length,this.board.place([e,f],c.toString(36)))},toHTML:function(){var a=this.moves(),b=this.activePlayer(),c=this.board,d=this.width;return a=a&&a[b],""+c.horizontals().reverse().map(function(e){return""+e.map(function(e){var f="",g=c.square(e),h=e[0]*d+e[1];return a&&a.indexOf(h)>=0&&(f=' data-ludorum="move: '+h+", activePlayer: '"+b+"'\""),"."===g?"":'"}).join("")+""}).join("")+"
 
"},__serialize__:function(){return[this.name,this.activePlayer(),this.board.string]}}),games.OddsAndEvens=declare(Game,{constructor:function(a,b){Game.call(this,this.players),this.turns=isNaN(a)?1:+a,this.points=b||{Evens:0,Odds:0}},name:"OddsAndEvens",players:["Evens","Odds"],moves:function(){return this.turns<1?null:{Evens:[1,2],Odds:[1,2]}},result:function(){var a=this.points.Evens-this.points.Odds;return this.turns>0?null:{Evens:+a,Odds:-a}},next:function(a){var b=!((a.Evens+a.Odds)%2);return new this.constructor(this.turns-1,{Evens:this.points.Evens+(b?1:0),Odds:this.points.Odds+(b?0:1)})},__serialize__:function(){return[this.name,this.turns,this.points]}}),games.TicTacToe=declare(Game,{constructor:function(a,b){Game.call(this,a),this.board=b||"_________"},name:"TicTacToe",players:["Xs","Os"],result:function(){return function(){return this.board.match(this.WIN_X)?this.victory(["Xs"]):this.board.match(this.WIN_O)?this.victory(["Os"]):this.board.indexOf("_")<0?this.draw():null}}(),moves:function(){if(this.result())return null;var a={};return a[this.activePlayer()]=iterable(this.board).filter(function(a){return"_"===a},function(a,b){return b}).toArray(),a},next:function(a){var b=this.activePlayer(),c=+a[b];if("_"!==this.board.charAt(c))throw new Error("Invalid move "+JSON.stringify(a)+" for board "+this.board+" (moves= "+JSON.stringify(a)+").");var d=this.board.substring(0,c)+b.charAt(0)+this.board.substring(c+1);return new this.constructor(this.opponent(b),d)},toString:function(){var a=this.board;return[a.substr(0,3).split("").join("|"),"-+-+-",a.substr(3,3).split("").join("|"),"-+-+-",a.substr(6,3).split("").join("|")].join("\n")},toHTML:function(){var a=this.activePlayer(),b=this.board.split("").map(function(b,c){return"_"===b?' ":""+b+""});return""+[b.slice(0,3).join(""),b.slice(3,6).join(""),b.slice(6,9).join("")].join("")+"
"},__serialize__:function(){return[this.name,this.activePlayer(),this.board]},"static heuristics":{heuristicFromWeights:function(a){function b(b,d){var e=d.charAt(0);return iterable(b.board).map(function(b,c){return"_"===b?0:a[c]*(b===e?1:-1)}).sum()/c}var c=iterable(a).map(Math.abs).sum();return b.weights=a,b}},"":function(){var a=new CheckerboardFromString(3,3,"_".repeat(9)),b=a.sublines(a.lines(),3);this.prototype.WIN_X=new RegExp(a.asRegExps(b,"X",".")),this.prototype.WIN_O=new RegExp(a.asRegExps(b,"O","."))}}),games.TicTacToe.heuristics.defaultHeuristic=games.TicTacToe.heuristics.heuristicFromWeights([2,1,2,1,5,1,2,1,2]),games.ToadsAndFrogs=declare(Game,{constructor:function b(a,c){Game.call(this,a),this.board=c||b.board()},"static board":function(a,b){return a=isNaN(a)?3:+a,b=isNaN(b)?2:+b,"T".repeat(a)+"_".repeat(b)+"F".repeat(a)},name:"ToadsAndFrogs",players:["Toads","Frogs"],result:function(){return this.moves()?null:this.defeat()},moves:function(){var a=this.activePlayer(),b={},c=b[a]=[];return this.board.replace(a==this.players[0]?/TF?_/g:/_T?F/g,function(a,b){return c.push(b),a}),c.length>0?b:null},next:function(a){var b=this.activePlayer(),c=a[b],d=(b.charAt(0),this.board);if("T_"==d.substr(c,2))d=d.substring(0,c)+"_T"+d.substring(c+2);else if("_F"==d.substr(c,2))d=d.substring(0,c)+"F_"+d.substring(c+2);else if("TF_"==d.substr(c,3))d=d.substring(0,c)+"_FT"+d.substring(c+3);else{if("_TF"!=d.substr(c,3))throw new Error("Invalid move ",c," for board <",d,">.");d=d.substring(0,c)+"FT_"+d.substring(c+3)}return new this.constructor(this.opponent(),d)},__serialize__:function(){return[this.name,this.activePlayer,this.board]}}),games.Mancala=declare(Game,{constructor:function(a,b){Game.call(this,a),this.board=b||this.makeBoard()},makeBoard:function(a,b){a=isNaN(a)?3:+a,b=isNaN(b)?6:+b;for(var c=[],d=0;2>d;d++){for(var e=0;b>e;e++)c.push(a);c.push(0)}return c},name:"Mancala",players:["North","South"],emptyCapture:!1,countRemainingSeeds:!0,store:function(a){switch(this.players.indexOf(a)){case 0:return this.board.length/2-1;case 1:return this.board.length-1;default:throw new Error("Invalid player "+a+".")}},houses:function(a){switch(this.players.indexOf(a)){case 0:return Iterable.range(0,this.board.length/2-1).toArray();case 1:return Iterable.range(this.board.length/2,this.board.length-1).toArray();default:throw new Error("Invalid player "+a+".")}},oppositeHouse:function(a,b){var c=this.houses(a),d=this.houses(this.opponent(a)),e=c.indexOf(b);return 0>e?e:d.reverse()[e]},nextSquare:function(a,b){do b=(b+1)%this.board.length;while(b===this.store(this.opponent(a)));return b},moves:function(){if(this.result())return null;var a=this.board,b={},c=this.activePlayer();return b[c]=this.houses(c).filter(function(b){return a[b]>0}),b[c].length>0?b:null},scores:function(){var a=this,b=this.board,c=this.players.map(function(c){return iterable(a.houses(c)).map(function(a){return b[a]}).sum()});if(c[0]>0&&c[1]>0)return null;var d={};return this.players.forEach(function(e,f){d[e]=b[a.store(e)]+a.countRemainingSeeds*c[f] +"use strict";!function(a,b){"function"==typeof define&&define.amd?define(["creatartis-base"],b):"object"==typeof module&&module.exports?module.exports=b(require("creatartis-base")):a.ludorum=b(a.base)}(this,function __init__(base){var declare=base.declare,unimplemented=base.objects.unimplemented,obj=base.obj,copy=base.copy,raiseIf=base.raiseIf,Iterable=base.Iterable,iterable=base.iterable,Future=base.Future,Randomness=base.Randomness,initialize=base.initialize,Statistics=base.Statistics,Events=base.Events,exports={__name__:"ludorum",__init__:__init__};__init__.dependencies={"creatartis-base":base};var utils=exports.utils={},Game=exports.Game=declare({constructor:function(a){this.activePlayers=a?Array.isArray(a)?a:[a]:[this.players[0]]},name:"?",players:[],moves:unimplemented("Game","moves"),next:unimplemented("Game","next"),result:unimplemented("Game","result"),scores:function(){return this.results()},view:function(){return this},isActive:function(){for(var a=0;aa,"There is no active player."),raiseIf(a>1,"More than one player is active."),this.activePlayers[0]},opponents:function(a){return a=a||this.activePlayers,this.players.filter(function(b){return a.indexOf(b)<0})},opponent:function(a){var b=this.players.indexOf(a||this.activePlayer());return this.players[(b+1)%this.players.length]},perform:function(a,b){b=b||this.activePlayer();var c={};c[b]=a;for(var d=2;d+a?this.ply()+ +a:+a,this.history[0|a]},result:function(){return this.state().result()},decisions:function(a){a=a||this.state();var b=this,c=this.players,d=a.activePlayers;return Future.all(d.map(function(b){return c[b].decision(a,b)})).then(function(c){var e=iterable(d).zip(c).toObject();return b.onMove(a,e),e})},run:function(a){if(a=isNaN(a)?1/0:+a,1>a)return Future.when(this);var b,c=this.ply(),d=this.state();if(1>c&&this.onBegin(d),d=this.__advanceAleatories__(d),b=d.result())return this.onEnd(d,b),Future.when(this);var e=this;return this.decisions(d).then(function(b){return e.__advance__(d,b)?e.run(a-1):e})},__advanceAleatories__:function(a){for(var b;a instanceof Aleatory;a=b)b=a.next(),this.history.push(b),this.onNext(a,b);return a},__advance__:function(a,b){var c=this,d=a.activePlayers.filter(function(a){return b[a]instanceof Match.CommandQuit});if(d.length>0)return c.onQuit(a,d[0]),!1;var e=a.next(b);return this.history.push(e),this.onNext(a,e),!0},"static CommandQuit":function(){},onBegin:function(a){this.events.emit("begin",a,this),this.logger&&this.logger.info("Match begins with ",iterable(this.players).map(function(a){return a[1]+" as "+a[0]}).join(", "),"; for ",a,".")},onMove:function(a,b){this.events.emit("move",a,b,this),this.logger&&this.logger.info("Players move: ",JSON.stringify(b)," in ",a)},onNext:function(a,b){this.events.emit("next",a,b,this),this.logger&&this.logger.info("Match advances from ",a," to ",b)},onEnd:function(a,b){this.events.emit("end",a,b,this),this.logger&&this.logger.info("Match for ",a,"ends with ",JSON.stringify(b))},onQuit:function(a,b){this.events.emit("quit",a,b,this),this.logger&&this.logger.info("Match for ",a," aborted because player "+b+" quitted.")},toString:function(){return"Match("+this.game+", "+JSON.stringify(this.players)+")"}}),Tournament=exports.Tournament=declare({constructor:function(a,b){this.game=a,this.players=Array.isArray(b)?b:iterables.iterable(b).toArray(),this.statistics=new Statistics,this.events=new Events({events:["begin","beforeMatch","afterMatch","end"]})},__advance__:unimplemented("Tournament","__advance__"),run:function(){this.onBegin();var a=this;return Future.doWhile(function(){return Future.then(a.__advance__(),function(b){return b?(a.beforeMatch(b),a.__runMatch__(b).then(function(b){return a.account(b),a.afterMatch(b),b})):null})}).then(this.onEnd.bind(this))},__runMatch__:function(a){return a.run()},account:function(a){var b=this.game,c=a.result(),d=this.statistics;raiseIf(!c,"Match doesn't have results. Has it finished?"),iterable(a.players).forEach(function(e){var f=e[0],g=e[1],h=c[e[0]];d.add({key:"results",game:b.name,role:f,player:g.name},h),d.add({key:h>0?"victories":0>h?"defeats":"draws",game:b.name,role:f,player:g.name},h),d.add({key:"length",game:b.name,role:f,player:g.name},a.ply()),a.history.forEach(function(a){if("function"==typeof a.moves){var c=a.moves();c&&c.hasOwnProperty(f)&&c[f].length>0&&d.add({key:"width",game:b.name,role:f,player:g.name},c[f].length)}})})},onBegin:function(){this.events.emit("begin",this),this.logger&&this.logger.info("Tournament begins for game ",game.name,".")},beforeMatch:function(a){this.events.emit("beforeMatch",a,this),this.logger&&this.logger.debug("Beginning match with ",JSON.stringify(a.players),".")},afterMatch:function(a){this.events.emit("afterMatch",a,this),this.logger&&this.logger.debug("Finishing match with ",JSON.stringify(a.players),".")},onEnd:function(){this.events.emit("end",this.statistics,this),this.logger&&this.logger.info("Tournament ends for game ",game.name,":\n",this.statistics,"\n")}}),tournaments=exports.tournaments={},Aleatory=exports.Aleatory=declare({constructor:function(a,b){this.random=b||Randomness.DEFAULT,"function"==typeof a&&(this.next=a)},value:function a(){var a,b=random.random();if(iterable(this.distribution()).forEach(function(c){if(b-=c[1],0>=b)throw a=c[0],Iterable.STOP_ITERATION}),"undefined"==typeof a)throw new Error("Random value could not be obtained.");return a},next:unimplemented("Aleatory","next"),distribution:unimplemented("Aleatory","distribution")}),aleatories=exports.aleatories={};players.RandomPlayer=declare(Player,{constructor:function(a){Player.call(this,a),initialize(this,a).object("random",{defaultValue:Randomness.DEFAULT})},decision:function(a,b){return this.random.choice(this.__moves__(a,b))}}),players.TracePlayer=declare(Player,{constructor:function(a){Player.call(this,a),this.trace=iterable(a.trace),this.__iterator__=this.trace.__iter__(),this.__decision__=this.__iterator__()},decision:function(){try{this.__decision__=this.__iterator__()}catch(a){Iterable.prototype.catchStop(a)}return this.__decision__},__serialize__:function(){return["TracePlayer",{name:this.name,trace:this.trace.toArray()}]}});var HeuristicPlayer=players.HeuristicPlayer=declare(Player,{constructor:function(a){Player.call(this,a),initialize(this,a).object("random",{defaultValue:Randomness.DEFAULT}).func("heuristic",{ignore:!0})},moveEvaluation:function(a,b,c){return this.stateEvaluation(b.next(obj(c,a)),c)},stateEvaluation:function(a,b){var c=a.result();return c?c[b]:this.heuristic(a,b)},heuristic:function(){return this.random.random(-.5,.5)},bestMoves:function(a){return iterable(a).greater(function(a){return a[1]}).map(function(a){return a[0]})},selectMoves:function(a,b,c){var d=this,e=!1,f=a.map(function(a){var f=d.moveEvaluation(a,b,c);return f instanceof Future?(e=e||!0,f.then(function(b){return[a,b]})):[a,f]});return e?Future.all(f).then(this.bestMoves):this.bestMoves(f)},decision:function(a,b){var c=this,d=c.selectMoves(c.__moves__(a,b),a,b);return Future.then(d,function(a){return c.random.choice(a)})}}),MaxNPlayer=players.MaxNPlayer=declare(HeuristicPlayer,{constructor:function(a){HeuristicPlayer.call(this,a),initialize(this,a).integer("horizon",{defaultValue:3,coerce:!0})},stateEvaluation:function(a,b){return this.maxN(a,b,0)[b]},heuristics:function(a){var b={},c=this;return a.players.forEach(function(d){b[d]=c.heuristic(a,d)}),b},quiescence:function(a,b,c){var d=a.result();return d?d:c>=this.horizon?this.heuristics(a):null},maxN:function(a,b,c){var d=this.quiescence(a,b,c);if(!d){var e,f,g=a.activePlayer(),h=this.__moves__(a,g),d={};if(h.length<1)throw new Error("No moves for unfinished game "+a+".");for(var i=0;i(d[g]||-1/0)&&(d=e)}return d},toString:function(){return(this.constructor.name||"MaxNPlayer")+"("+JSON.stringify({name:this.name,horizon:this.horizon})+")"}}),MiniMaxPlayer=players.MiniMaxPlayer=declare(HeuristicPlayer,{constructor:function(a){HeuristicPlayer.call(this,a),initialize(this,a).integer("horizon",{defaultValue:4,coerce:!0})},stateEvaluation:function(a,b){return this.minimax(a,b,0)},quiescence:function(a,b,c){var d=a.result();return d?d[b]:c>=this.horizon?this.heuristic(a,b):0/0},minimax:function(a,b,c){var d=this.quiescence(a,b,c);if(isNaN(d)){var e,f,g=a.activePlayer(),h=this.__moves__(a,g);if(h.length<1)throw new Error("No moves for unfinished game "+a+".");g==b?(d=-1/0,e=Math.max):(d=+1/0,e=Math.min);for(var i=0;id&&(d=f):e>f&&(e=f),!(d>=e));k++);return i?d:e}}),players.MonteCarloPlayer=declare(HeuristicPlayer,{constructor:function(a){HeuristicPlayer.call(this,a),initialize(this,a).number("simulationCount",{defaultValue:30,coerce:!0}).number("timeCap",{defaultValue:1e3,coerce:!0}).object("agent",{defaultValue:null})},selectMoves:function(a,b,c){for(var d=this,e=Date.now()+this.timeCap,f=a.map(function(a){return{move:a,next:b.next(obj(c,a)),isFinal:!1,sum:0,count:0}}),g=0;g0?a.sum/a.count:0}).map(function(a){return a.move})},stateEvaluation:function(a,b){for(var c,d=0,e=this.simulationCount,f=0;e>f&&(c=this.simulation(a,b),d+=c.result[b],!(c.plies<1));++f);return e>0?d/e:0},simulation:function(a){var b,c,d,e=this;for(b=0;!0;++b)if(a instanceof Aleatory)a=a.next();else{if(d=a.moves(),!d)return{game:a,result:a.result(),plies:b};c={},a.activePlayers.forEach(function(b){c[b]=e.agent?e.agent.decision(a,b):e.random.choice(d[b])}),a=a.next(c)}},toString:function(){return(this.constructor.name||"MonteCarloPlayer")+"("+JSON.stringify({name:this.name,simulationCount:this.simulationCount,timeCap:this.timeCap})+")"}});var UserInterfacePlayer=players.UserInterfacePlayer=declare(Player,{constructor:function(a){Player.call(this,a)},participate:function(a,b){return this.role=b,this},decision:function(){return this.__future__&&this.__future__.isPending()&&this.__future__.resolve(new Match.CommandQuit),this.__future__=new Future},perform:function(a){var b=this.__future__;return b&&(this.__future__=null,b.resolve(a)),!!b}}),UserInterface=players.UserInterface=declare({constructor:function(a){this.onBegin=this.onBegin.bind(this),this.onNext=this.onNext.bind(this),this.onEnd=this.onEnd.bind(this),a.match&&this.show(a.match)},show:function(a){this.match&&(a.events.off("begin",this.onBegin),a.events.off("next",this.onNext),a.events.off("end",this.onEnd)),this.match=a,a.events.on("begin",this.onBegin),a.events.on("next",this.onNext),a.events.on("end",this.onEnd)},onBegin:function(a){this.display(a)},onNext:function(a,b){this.display(b)},onEnd:function(a,b){this.results=b,this.display(a)},display:function(){throw new Error("UserInterface.display is not defined. Please override.")},perform:function(a,b){iterable(this.match.players).forEach(function(c){var d=(c[0],c[1]);d instanceof UserInterfacePlayer&&(!b||d.role===b)&&d.perform(a)})}});UserInterface.BasicHTMLInterface=declare(UserInterface,{constructor:function(a){UserInterface.call(this,a),this.container=a.container,"string"==typeof this.container&&(this.container=document.getElementById(this.container))},display:function display(game){var ui=this,container=this.container;container.innerHTML=game.toHTML(),Array.prototype.slice.call(container.querySelectorAll("[data-ludorum]")).forEach(function(elem){var data=eval("({"+elem.getAttribute("data-ludorum")+"})");data.hasOwnProperty("move")&&(elem.onclick=ui.perform.bind(ui,data.move,data.activePlayer))})}});var WebWorkerPlayer=players.WebWorkerPlayer=declare(Player,{constructor:function(a){Player.call(this,a),initialize(this,a).object("worker"),this.worker.onmessage=base.Parallel.prototype.__onmessage__.bind(this)},"static createWorker":function(a){raiseIf("string function".indexOf(typeof a)<0,"Invalid player builder: "+a+"!");var b=new base.Parallel;return b.run("self.ludorum = ("+exports.__init__+')(self.base), "OK"').then(function(){return b.run("self.PLAYER = ("+a+').call(self), "OK"')}).then(function(){return b.worker})},"static create":function(a){var b=this;return b.createWorker(a.playerBuilder).then(function(a){return new b({name:name,worker:a})})},decision:function(a,b){return this.__future__&&this.__future__.isPending()&&this.__future__.resolve(Match.commandQuit),this.__future__=new Future,this.worker.postMessage("PLAYER.decision(ludorum.Game.fromJSON("+a.toJSON()+"), "+JSON.stringify(b)+")"),this.__future__}});aleatories.Dice=declare(Aleatory,{constructor:function(a,b,c){Aleatory.call(this,a,c),this.base=isNaN(b)?6:Math.max(2,+b)},value:function(){return this.random.randomInt(1,this.base+1)},distribution:function(){return this.__distribution__||(this.__distribution__=function(a){return Iterable.range(1,a+1).map(function(b){return[b,1/a]}).toArray()}(this.base))}});var Checkerboard=utils.Checkerboard=declare({constructor:function(a,b){isNaN(a)||(this.height=0|a),isNaN(b)||(this.width=0|b)},emptySquare:null,isValidCoord:function(a){return Array.isArray(a)&&!isNaN(a[0])&&!isNaN(a[1])&&a[0]>=0&&a[0]=0&&a[1]=b}).map(function(a){return Iterable.range(0,a.length-b+1).map(function(c){return a.slice(c,c+b)})}).flatten()},walk:function(a,b){var c=this;return new Iterable(function(){var d=a.slice();return function(){if(c.isValidCoord(d)){var a=d.slice();return d[0]+=b[0],d[1]+=b[1],a}throw Iterable.STOP_ITERATION}})},walks:function(a,b){var c=this;return b.map(function(b){return c.walk(a,b)})},"static DIRECTIONS":{HORIZONTAL:[[0,-1],[0,1]],VERTICAL:[[-1,0],[1,0]],ORTHOGONAL:[[0,-1],[0,1],[-1,0],[1,0]],DIAGONAL:[[-1,-1],[-1,1],[1,-1],[1,1]],EVERY:[[0,-1],[0,1],[-1,0],[1,0],[-1,-1],[-1,1],[1,-1],[1,1]]},clone:unimplemented("utils.Checkerboard","clone"),__place__:unimplemented("utils.Checkerboard","place"),place:function(a,b){return this.clone().__place__(a,b)},__move__:function(a,b,c){return this.__place__(b,this.square(a)).__place__(a,"undefined"==typeof c?this.emptySquare:c)},move:function(a,b,c){return this.clone().__move__(a,b,c)},__swap__:function(a,b){var c=this.square(b);return this.__place__(b,this.square(a)).__place__(a,c)},swap:function(a,b){return this.clone().__swap__(a,b)},asHTMLTable:function(a,b,c){var d=a.createElement("table");board.horizontals().reverse().foeEach(function(b){var e=a.createElement("tr");d.appendChild(e),b.forEach(function(b){var d=board.square(b),f=c(d,b),g=a.createElement("td");td_content=a.createTextNode(f.hasOwnProperty("content")?f.content:d),e.appendChild(g),g.id=f.id||"ludorum-square-"+b.join("-"),g.className=f.className||"ludorum-square",g.data_ludorum=f})})}}),CheckerboardFromString=utils.CheckerboardFromString=declare(Checkerboard,{constructor:function(a,b,c,d){if(Checkerboard.call(this,a,b),d&&(this.emptySquare=(d+this.emptySquare).charAt(0)),c&&c.length!==a*b)throw new Error("Given string "+JSON.stringify(c)+" does not match board dimensions.");this.string=c||this.emptySquare.repeat(a*b)},emptySquare:".",toString:function(){var a=this.string,b=this.height,c=this.width;return Iterable.range(b).map(function(d){return a.substr((b-d-1)*c,c)}).join("\n")},square:function(a,b){var c=a[0],d=a[1],e=this.width;return c>=0&&c=0&&e>d?this.string.charAt(c*e+d):b},asString:function(a){var b=this;return a.map(function(a){return b.square(a)}).join("")},asStrings:function(a){var b=this;return a.map(function(a){return b.asString(a)})},asRegExp:function(a,b,c){c=c||".";var d=this.width,e=Iterable.repeat(!1,d*this.height).toArray();a.forEach(function(a){e[a[0]*d+a[1]]=!0});for(var f,g="",h=0,i=0;ih?f?b:c:(f?b:c)+"{"+h+"}"}return g},asRegExps:function(a,b,c){var d=this;return a.map(function(a){return d.asRegExp(a,b,c)}).join("|")},clone:function(){return new this.constructor(this.height,this.width,this.string,this.hasOwnProperty("emptySquare")?this.emptySquare:void 0)},__place__:function(a,b){raiseIf(!this.isValidCoord(a),"Invalid coordinate ",a,"."),b=(b+this.emptySquare).charAt(0);var c=a[0]*this.width+a[1];return this.string=this.string.substr(0,c)+b+this.string.substr(c+1),this}});return exports.utils.Scanner=declare({constructor:function(a){initialize(this,a).object("game",{ignore:!0}).integer("maxWidth",{defaultValue:1e3,coerce:!0}).integer("maxLength",{defaultValue:50,coerce:!0}).object("random",{defaultValue:Randomness.DEFAULT}).object("statistics",{defaultValue:new Statistics})},scan:function(a){var b=this,c=arguments.length<2?this.game?[this.game]:[]:Array.prototype.slice.call(arguments,1),d=0;return Future.whileDo(function(){return c.length>0&&dg?(e.add({key:"defeat.result",game:b.name,role:f,player:h},g,b),e.add({key:"defeat.length",game:b.name,role:f,player:h},c,b)):g>0?(e.add({key:"victory.result",game:b.name,role:f,player:h},g,b),e.add({key:"victory.length",game:b.name,role:f,player:h},c,b)):e.add({key:"draw.length",game:b.name,role:f,player:h},c,b)}),!0;var f=b.moves();return iterable(b.activePlayers).forEach(function(a){e.add({key:"game.width",game:b.name,role:a},f[a].length)}),!1}}),games.Predefined=declare(Game,{constructor:function(a,b,c,d){b&&(this.__results__=b,this.players=Object.keys(b)),Game.call(this,a),this.height=isNaN(c)?5:+c,this.width=isNaN(d)?5:+d},name:"Predefined",players:["A","B"],__results__:{A:0,B:0},moves:function(){return this.height>0?obj(this.activePlayer(),Iterable.range(1,this.width+1).toArray()):void 0},result:function(){return this.height>0?null:this.__results__},next:function(){return new this.constructor(this.opponent(),this.__results__,this.height-1,this.width)},__serialize__:function(){return[this.name,this.activePlayer(),this.results,this.height,this.width]}}),games.Choose2Win=declare(Game,{constructor:function(a,b,c){Game.call(this,b),this.__turns__=isNaN(a)?1/0:+a,this.__winner__=c},name:"Choose2Win",players:["This","That"],moves:function(){return!this.__winner__&&this.__turns__>0?obj(this.activePlayer(),["win","lose","pass"]):void 0},result:function(){return this.__winner__?this.victory(this.__winner__):this.__turns__<1?this.draw():null},next:function(a){var b=this.activePlayer(),c=this.opponent(b);switch(a[b]){case"win":return new this.constructor(this.__turns__-1,c,b);case"lose":return new this.constructor(this.__turns__-1,c,c);case"pass":return new this.constructor(this.__turns__-1,c)}throw new Error("Invalid move "+a[b]+" for player "+b+".")},__serialize__:function(){return[this.name,this.__turns__,this.activePlayer(),this.__winner__]}}),games.ConnectionGame=declare(Game,{height:9,width:9,lineLength:5,constructor:function(a,b){Game.call(this,a),this.board=b instanceof CheckerboardFromString?b:new CheckerboardFromString(this.height,this.width,(b||".".repeat(this.height*this.width))+"")},name:"ConnectionGame",players:["First","Second"],__lines__:function(){function a(a,c,d){var e=a+"x"+c+"/"+d;if(!b.hasOwnProperty(e)){var f=new CheckerboardFromString(a,c,".".repeat(a*c));b[e]=f.lines().map(function(a){return a.toArray()},function(a){return a.length>=d}).toArray()}return b[e]}var b={};return a.CACHE=b,a}(),result:function(){if(this.hasOwnProperty("__result__"))return this.__result__;for(var a=this.lineLength,b=this.board.asStrings(this.__lines__(this.height,this.width,a)).join(" "),c=0;c=0)return this.__result__=this.victory([this.players[c]]);return this.__result__=b.indexOf(".")<0?this.draw():null},moves:function(){return this.hasOwnProperty("__moves__")?this.__moves__:this.__moves__=this.result()?null:obj(this.activePlayer(),iterable(this.board.string).filter(function(a){return"."===a},function(a,b){return b}).toArray())},next:function(a){var b=this.activePlayer(),c=this.players.indexOf(b),d=+a[b],e=d/this.width>>0,f=d%this.width;return new this.constructor((c+1)%this.players.length,this.board.place([e,f],c.toString(36)))},toHTML:function(){var a=this.moves(),b=this.activePlayer(),c=this.board,d=this.width;return a=a&&a[b],""+c.horizontals().reverse().map(function(e){return""+e.map(function(e){var f="",g=c.square(e),h=e[0]*d+e[1];return a&&a.indexOf(h)>=0&&(f=' data-ludorum="move: '+h+", activePlayer: '"+b+"'\""),"."===g?"":'"}).join("")+""}).join("")+"
 
"},__serialize__:function(){return[this.name,this.activePlayer(),this.board.string]}}),games.OddsAndEvens=declare(Game,{constructor:function(a,b){Game.call(this,this.players),this.turns=isNaN(a)?1:+a,this.points=b||{Evens:0,Odds:0}},name:"OddsAndEvens",players:["Evens","Odds"],moves:function(){return this.turns<1?null:{Evens:[1,2],Odds:[1,2]}},result:function(){var a=this.points.Evens-this.points.Odds;return this.turns>0?null:{Evens:+a,Odds:-a}},next:function(a){var b=!((a.Evens+a.Odds)%2);return new this.constructor(this.turns-1,{Evens:this.points.Evens+(b?1:0),Odds:this.points.Odds+(b?0:1)})},__serialize__:function(){return[this.name,this.turns,this.points]}}),games.TicTacToe=declare(Game,{constructor:function(a,b){Game.call(this,a),this.board=b||"_________"},name:"TicTacToe",players:["Xs","Os"],result:function(){return function(){return this.board.match(this.WIN_X)?this.victory(["Xs"]):this.board.match(this.WIN_O)?this.victory(["Os"]):this.board.indexOf("_")<0?this.draw():null}}(),moves:function(){if(this.result())return null;var a={};return a[this.activePlayer()]=iterable(this.board).filter(function(a){return"_"===a},function(a,b){return b}).toArray(),a},next:function(a){var b=this.activePlayer(),c=+a[b];if("_"!==this.board.charAt(c))throw new Error("Invalid move "+JSON.stringify(a)+" for board "+this.board+" (moves= "+JSON.stringify(a)+").");var d=this.board.substring(0,c)+b.charAt(0)+this.board.substring(c+1);return new this.constructor(this.opponent(b),d)},toString:function(){var a=this.board;return[a.substr(0,3).split("").join("|"),"-+-+-",a.substr(3,3).split("").join("|"),"-+-+-",a.substr(6,3).split("").join("|")].join("\n")},toHTML:function(){var a=this.activePlayer(),b=this.board.split("").map(function(b,c){return"_"===b?' ":""+b+""});return""+[b.slice(0,3).join(""),b.slice(3,6).join(""),b.slice(6,9).join("")].join("")+"
"},__serialize__:function(){return[this.name,this.activePlayer(),this.board]},"static heuristics":{heuristicFromWeights:function(a){function b(b,d){var e=d.charAt(0);return iterable(b.board).map(function(b,c){return"_"===b?0:a[c]*(b===e?1:-1)}).sum()/c}var c=iterable(a).map(Math.abs).sum();return b.weights=a,b}},"":function(){var a=new CheckerboardFromString(3,3,"_".repeat(9)),b=a.sublines(a.lines(),3);this.prototype.WIN_X=new RegExp(a.asRegExps(b,"X",".")),this.prototype.WIN_O=new RegExp(a.asRegExps(b,"O","."))}}),games.TicTacToe.heuristics.defaultHeuristic=games.TicTacToe.heuristics.heuristicFromWeights([2,1,2,1,5,1,2,1,2]),games.ToadsAndFrogs=declare(Game,{constructor:function b(a,c){Game.call(this,a),this.board=c||b.board()},"static board":function(a,b){return a=isNaN(a)?3:+a,b=isNaN(b)?2:+b,"T".repeat(a)+"_".repeat(b)+"F".repeat(a)},name:"ToadsAndFrogs",players:["Toads","Frogs"],result:function(){return this.moves()?null:this.defeat()},moves:function(){var a=this.activePlayer(),b={},c=b[a]=[];return this.board.replace(a==this.players[0]?/TF?_/g:/_T?F/g,function(a,b){return c.push(b),a}),c.length>0?b:null},next:function(a){var b=this.activePlayer(),c=a[b],d=(b.charAt(0),this.board);if("T_"==d.substr(c,2))d=d.substring(0,c)+"_T"+d.substring(c+2);else if("_F"==d.substr(c,2))d=d.substring(0,c)+"F_"+d.substring(c+2);else if("TF_"==d.substr(c,3))d=d.substring(0,c)+"_FT"+d.substring(c+3);else{if("_TF"!=d.substr(c,3))throw new Error("Invalid move ",c," for board <",d,">.");d=d.substring(0,c)+"FT_"+d.substring(c+3)}return new this.constructor(this.opponent(),d)},__serialize__:function(){return[this.name,this.activePlayer,this.board]}}),games.Mancala=declare(Game,{constructor:function(a,b){Game.call(this,a),this.board=b||this.makeBoard()},makeBoard:function(a,b){a=isNaN(a)?4:+a,b=isNaN(b)?6:+b;for(var c=[],d=0;2>d;d++){for(var e=0;b>e;e++)c.push(a);c.push(0)}return c},name:"Mancala",players:["North","South"],emptyCapture:!1,countRemainingSeeds:!0,store:function(a){switch(this.players.indexOf(a)){case 0:return this.board.length/2-1;case 1:return this.board.length-1;default:throw new Error("Invalid player "+a+".")}},houses:function(a){switch(this.players.indexOf(a)){case 0:return Iterable.range(0,this.board.length/2-1).toArray();case 1:return Iterable.range(this.board.length/2,this.board.length-1).toArray();default:throw new Error("Invalid player "+a+".")}},oppositeHouse:function(a,b){var c=this.houses(a),d=this.houses(this.opponent(a)),e=c.indexOf(b);return 0>e?e:d.reverse()[e]},nextSquare:function(a,b){do b=(b+1)%this.board.length;while(b===this.store(this.opponent(a)));return b},moves:function(){if(this.result())return null;var a=this.board,b={},c=this.activePlayer();return b[c]=this.houses(c).filter(function(b){return a[b]>0}),b[c].length>0?b:null},scores:function(){var a=this,b=this.board,c=this.players.map(function(c){return iterable(a.houses(c)).map(function(a){return b[a]}).sum()});if(c[0]>0&&c[1]>0)return null;var d={};return this.players.forEach(function(e,f){d[e]=b[a.store(e)]+a.countRemainingSeeds*c[f] }),d},result:function(){var a=this.scores(),b=this.players;return a&&this.zerosumResult(a[b[0]]-a[b[1]],b[0])},next:function(a){var b,c,d=this.activePlayer(),e=+a[d],f=this.board.slice(0),g=f[e],h=!1;for(raiseIf(1>g,"Invalid move ",e," for game ",this),f[e]=0;g>0;g--)e=this.nextSquare(d,e),f[e]++;return h=e==this.store(d),h||(c=this.oppositeHouse(d,e),c>=0&&1==f[e]&&f[c]>0&&(b=this.store(d),f[b]++,f[e]=0,this.emptyCapture||(f[b]+=f[c],f[c]=0))),new this.constructor(h?d:this.opponent(),f)},resultBounds:function(){var a=iterable(this.board).sum();return[-a,+a]},__serialize__:function(){return[this.name,this.activePlayer(),this.board.slice()]},identifier:function(){return this.activePlayer().charAt(0)+this.board.map(function(a){return("00"+a.toString(36)).substr(-2)}).join("")},toString:function(){var a=this,b=base.Text.lpad,c=this.players[0],d=this.houses(c).map(function(c){return b(""+a.board[c],2,"0")}).reverse(),e=b(""+this.board[this.store(c)],2,"0"),f=this.players[1],g=this.houses(f).map(function(c){return b(""+a.board[c],2,"0")}),h=b(""+this.board[this.store(f)],2,"0");return" "+d.join(" | ")+" \n"+e+" ".repeat(2*d.length+3*(d.length-1)+2)+h+"\n "+g.join(" | ")+" "},toHTML:function(){function a(a,c){return!b||!b[a]||!b[a].indexOf(c)<0?""+this.board[c]+"":'"+this.board[c]+""}var b=this.moves(),c=this.players[0],d=this.players[1];return'"+this.houses(c).map(a.bind(this,c)).reverse().join("")+'"+this.houses(d).map(a.bind(this,d)).join("")+"
'+this.board[this.store(c)]+"'+this.board[this.store(d)]+"
"},"static heuristics":{heuristicFromWeights:function(a){function b(b,d){var e,f=0;switch(b.players.indexOf(d)){case 0:e=1;break;case 1:e=-1;break;default:throw new Error("Invalid player "+d+".")}return iterable(b.board).map(function(b,c){return f+=b,b*a[c]}).sum()/c/f*e}var c=iterable(a).map(Math.abs).sum();return b.weights=a,b}},"":function(){this.makeBoard=this.prototype.makeBoard,this.heuristics.defaultHeuristic=this.heuristics.heuristicFromWeights([1,1,1,1,1,1,5,-1,-1,-1,-1,-1,-1,-5])}}),games.Pig=declare(Game,{constructor:function(a,b,c,d){Game.call(this,a),this.goal=isNaN(b)?100:+b,this.__scores__=c||iterable(this.players).zip([0,0]).toObject(),this.__rolls__=d||[]},name:"Pig",players:["One","Two"],moves:function(){if(!this.result()){var a=this.activePlayer(),b=this.__scores__[a]+iterable(this.__rolls__).sum();return obj(a,b=this.goal||b>=this.goal){var c={};return c[this.players[0]]=a-b,c[this.players[1]]=-c[this.players[0]],c}},next:function(a){var b=this.activePlayer(),c=a[b];if("hold"===c){var d=copy(this.__scores__);return d[b]+=iterable(this.__rolls__).sum(),new this.constructor(this.opponent(),this.goal,d,[])}if("roll"===c){var e=this;return new aleatories.Dice(function(a){return a=isNaN(a)?this.value():+a,a>1?new e.constructor(b,e.goal,e.__scores__,e.__rolls__.concat(a)):new e.constructor(e.opponent(),e.goal,e.__scores__,[])})}throw new Error("Invalid moves: "+JSON.stringify(a))},__serialize__:function(){return[this.name,this.activePlayer(),this.goal,this.__scores__,this.__rolls__]}}),games.ConnectFour=declare(games.ConnectionGame,{height:6,width:7,lineLength:4,name:"ConnectFour",players:["Yellow","Red"],moves:function(){var a=null;if(!this.result()){for(var b=[],c=this.board.string,d=(this.height-1)*this.width,e=0;e0&&(a={},a[this.activePlayer()]=b)}return a},next:function(a){for(var b=this.activePlayer(),c=this.board.string,d=+a[b],e=this.height,f=this.width,g=0;e>g;++g)if("."===c.charAt(g*f+d))return new this.constructor(this.opponent(),this.board.place([g,d],b===this.players[0]?"0":"1"));throw new Error("Invalid move "+JSON.stringify(a)+"!")},toHTML:function(){var a=this.moves(),b=this.activePlayer(),c=this.board;return a=a&&a[b],""+"".repeat(this.board.width)+""+c.horizontals().reverse().map(function(d){return""+d.map(function(d){var e="",f=c.square(d);return a&&a.indexOf(d[1])>=0&&(e=' data-ludorum="move: '+d[1]+", activePlayer: '"+b+"'\""),"."===f?"":'"}).join("")+""}).join("")+"
 
"},__serialize__:function(){return[this.name,this.activePlayer(),this.board.string]}}),games.Mutropas=declare(Game,{allPieces:Iterable.range(9).toArray(),name:"Mutropas",players:["Left","Right"],constructor:function(a){Game.call(this,this.players),a=a||{},this._pieces=a.pieces||this.dealtPieces(a.random),this._scores=a.scores||obj(this.players[0],0,this.players[1],0)},result:function(){var a=this.players[0];return this._pieces[a].length<1?copy({},this._scores):null},moves:function(){var a=this.players[0],b=this.players[1];return this.result()?null:obj(a,this._pieces[a].slice(),b,this._pieces[b].slice())},next:function(a){var b=this.players[0],c=this.players[1],d=a[b],e=a[c];raiseIf(this._pieces[b].indexOf(d)<0,"Invalid move "+JSON.stringify(d)+" for player "+b+"! (moves= "+JSON.stringify(a)+")"),raiseIf(this._pieces[c].indexOf(e)<0,"Invalid move "+JSON.stringify(e)+" for player "+c+"! (moves= "+JSON.stringify(a)+")");var f=this.moveResult(d,e),g=this._pieces[b].slice(),h=this._pieces[c].slice();return g.splice(g.indexOf(d),1),h.splice(h.indexOf(e),1),new this.constructor({pieces:obj(b,g,c,h),scores:obj(b,this._scores[b]+f,c,this._scores[c]-f)})},__serialize__:function(){return[this.name,{pieces:this._pieces,scores:this._scores}]},dealtPieces:function(a){var a=a||Randomness.DEFAULT,b=this.allPieces.length>>1,c=a.split(b,this.allPieces),d=a.split(b,c[1]);return obj(this.players[0],c[0],this.players[1],d[0])},moveResult:function(a,b){var c=iterable(this.allPieces).max(0)+1;return b>a?c>>1>=b-a?1:-1:a>b?a-b>=(c>>1)+1?1:-1:0}}),games.Othello=declare(Game,{constructor:function(a,b){if(Game.call(this,a),this.board=this.makeBoard.apply(this,b||[]),!this.moves()){var c=this.opponent();this.moves(c)&&(this.activePlayers=[c])}},makeBoard:function(a,b,c){return a=isNaN(a)?8:+a,b=isNaN(b)?8:+b,raiseIf(4>a||4>b||a%2||b%2,"An Othello board must have even dimensions greater than 3."),"string"==typeof c?new CheckerboardFromString(a,b,c):new CheckerboardFromString(a,b).__place__([a/2,b/2-1],"W").__place__([a/2-1,b/2],"W").__place__([a/2,b/2],"B").__place__([a/2-1,b/2-1],"B")},name:"Othello",players:["Black","White"],lines:new utils.Checkerboard(8,8).lines().map(function(a){return a.toArray()},function(a){return a.length>2}).toArray(),__MOVE_REGEXPS__:{Black:[/\.W+B/g,/BW+\./g],White:[/\.B+W/g,/WB+\./g]},moves:function(a){if(!a&&this.__moves__)return this.__moves__;a=a||this.activePlayer();var b=this.board,c={},d=this.__MOVE_REGEXPS__[a];this.lines.forEach(function(a){d.forEach(function(d){b.asString(a).replace(d,function(b,d){var e="."===b.charAt(0)?a[d]:a[b.length-1+d];return c[e]=e,b})})});var e=[];for(var f in c)e.push(c[f]);return this.__moves__=e.length>0?obj(a,e):null},result:function(){if(this.moves())return null;var a={W:-1,B:1},b=iterable(this.board.string).map(function(b){return a[b]||0}).sum();return this.zerosumResult(b,"Black")},next:function(a){var b,c,d=this.board.clone(),e=this.activePlayer();if(!a.hasOwnProperty(e)||!d.isValidCoord(a[e]))throw new Error("Invalid moves "+JSON.stringify(a)+"!");return e==this.players[0]?(b="B",c=/^W+B/):(b="W",c=/^B+W/),d.walks(a[e],Checkerboard.DIRECTIONS.EVERY).forEach(function(a){var e=c.exec(d.asString(a).substr(1));e&&a.toArray().slice(0,e[0].length).forEach(function(a){d.__place__(a,b)})}),new this.constructor(this.opponent(),[d.height,d.width,d.string])},__serialize__:function(){var a=this.board;return[this.name,this.activePlayer(),[a.height,a.width,a.string]]},toHTML:function(){var a=this.moves(),b=this.activePlayer(),c=this.board;return a=a&&a[b].map(JSON.stringify),""+c.horizontals().reverse().map(function(d){return""+d.map(function(d){switch(c.square(d)){case"B":return'';case"W":return'';default:var e=JSON.stringify(d);return a&&a.indexOf(e)>=0?'":''}}).join("")+""}).join("")+"
    
"}}),games.Othello.makeBoard=games.Othello.prototype.makeBoard,games.Othello.heuristics={},games.Bahab=declare(Game,{initialBoard:["BBABB","BBBBB",".....","bbbbb","bbabb"].join(""),name:"Bahab",players:["Uppercase","Lowercase"],constructor:function(a,b){Game.call(this,a),this.board=b instanceof CheckerboardFromString?b:new CheckerboardFromString(5,5,b||this.initialBoard)},__PLAYER_PIECES_RE__:{Uppercase:/[AB]/g,Lowercase:/[ab]/g},result:function(){var a=this.board.string;return a.match(/^[.bAB]+$|[A].{0,4}$/)?this.defeat(this.players[1]):a.match(/^[.Bab]+$|^.{0,4}[a]/)?this.defeat(this.players[0]):null},moves:function(){var a=this.activePlayer(),b=this.__PLAYER_PIECES_RE__[a],c=this.board,d=[];return c.string.replace(b,function(a,e){var f,g=[e/5|0,e%5];switch(a){case"A":f=[[1,-1],[1,0],[1,1]];break;case"B":f=[[1,-1],[1,1]];break;case"a":f=[[-1,-1],[-1,0],[-1,1]];break;case"b":f=[[-1,-1],[-1,1]]}return iterable(f).forEachApply(function(e,f){var h=[g[0]+e,g[1]+f],i=c.square(h);!c.isValidCoord(h)||i.match(b)||"b"==a.toLowerCase()&&"a"==i.toLowerCase()||d.push([g,h])}),a}),d.length>0?obj(a,d):null},next:function(a){if(!a)throw new Error("Invalid moves "+a+"!");var b=this.activePlayer(),c=a[b];if(!Array.isArray(a[b]))throw new Error("Invalid moves "+JSON.stringify(a)+"!");return new this.constructor(this.opponent(),this.board.move(c[0],c[1]))},__serialize__:function(){return[this.name,this.activePlayer(),this.board.string]},toHTML:function(){var a=(this.moves(),this.activePlayer(),this.board);return""+a.horizontals().reverse().map(function(b){return""+b.map(function(b){var c=a.square(b);switch(c){case"A":case"B":return'";case"a":case"b":return'";default:return''}}).join("")+""}).join("")+"
'+c+"'+c+" 
"}}),tournaments.RoundRobin=declare(Tournament,{constructor:function(a,b,c){Tournament.call(this,a,b),this.matchCount=isNaN(c)?a.players.length:+c,this.__advance__=this.__matches__().chain(Iterable.repeat(null)).__iter__()},__matches__:function(){var a=this.game,b=iterable(this.players);return b=b.product.apply(b,Iterable.repeat(this.players,a.players.length-1).toArray()),b.filter(function(a){for(var b=1;bc;c++)if(a[b]===a[c])return!1;return!0}).product(Iterable.range(this.matchCount)).map(function(b){return new Match(a,b[0])})}}),tournaments.Measurement=declare(Tournament,{constructor:function(a,b,c,d){Tournament.call(this,a,Array.isArray(b)?b:[b]),this.opponents=Array.isArray(c)?c:[c],raiseIf(this.opponents.length2?c.product.apply(c,Iterable.repeat(this.opponents,b-2).toArray()):c.map(function(a){return[a]}),iterable(this.players).product(Iterable.range(b),c,Iterable.range(this.matchCount)).map(function(b){var c=b[2].slice(0);return c.splice(b[1],0,b[0]),new Match(a,c)})}}),tournaments.Elimination=declare(Tournament,{constructor:function(a,b,c){Tournament.call(this,a,b),this.matchCount=isNaN(c)?1:+c>>0},__bracket__:function(a){var b=this.game,c=this.matchCount,d=this.game.players.length;return a=a||this.players,a.length 0) { stats.add({key:'victory.result', game:game.name, role:role, player:p}, r, game); - stats.add({key:'victory.result', game:game.name, role:role, player:p}, ply, game); + stats.add({key:'victory.length', game:game.name, role:role, player:p}, ply, game); } else { stats.add({key:'draw.length', game:game.name, role:role, player:p}, ply, game); }