ai.js
changeset 169 5a17638f0dca
parent 164 cdde49008500
parent 168 df8e645c3f36
child 171 6fce1391c2c0
equal deleted inserted replaced
166:cde69b258e85 169:5a17638f0dca
   182 }
   182 }
   183 
   183 
   184 
   184 
   185 
   185 
   186 ////////////////////////////////////////////////////////////////
   186 ////////////////////////////////////////////////////////////////
   187 // 1 step deep with linear weight function on score, max value,
   187 // 1 step deep with linear utility function on score, max value,
   188 // bonuses for max value stay at corner or edge and bonuses
   188 // bonuses for max value stay at corner or edge and bonuses
   189 // for each free field.
   189 // for each free field.
   190 ////////////////////////////////////////////////////////////////
   190 ////////////////////////////////////////////////////////////////
   191 
   191 
   192 /**
   192 /**
   193  * Defines coefficient for linear resulted weight function.
   193  * Defines coefficient for linear resulted utility function.
   194  * @name ai.OneStepAhead.cfg
   194  * @name ai.OneStepAhead.cfg
   195  * @namespace
   195  * @namespace
   196  * @property {number} scoreCoef    multiplicator for score
   196  * @property {number} scoreCoef    multiplicator for score
   197  * @property {number} maxValCoef   multiplicator for max value
   197  * @property {number} maxValCoef   multiplicator for max value
   198  * @property {number} cornerBonus  bonus for max value at board corner
   198  * @property {number} cornerBonus  bonus for max value at board corner
   208     this.brd = brd;
   208     this.brd = brd;
   209     this.cfg = ai.copyObj(ai.OneStepAhead.bestCfg);
   209     this.cfg = ai.copyObj(ai.OneStepAhead.bestCfg);
   210     ai.copyObj(cfg, this.cfg);
   210     ai.copyObj(cfg, this.cfg);
   211 }
   211 }
   212 ai.OneStepAhead.bestCfg = {scoreCoef: 1, maxValCoef: 0, cornerBonus: 0, edgeBonus: 0, freeBonus: 0};
   212 ai.OneStepAhead.bestCfg = {scoreCoef: 1, maxValCoef: 0, cornerBonus: 0, edgeBonus: 0, freeBonus: 0};
   213 ai.OneStepAhead.prototype.weight = function(brd) {
   213 ai.OneStepAhead.prototype.utility = function(brd) {
   214     var weight = 0;
   214     var utility = 0;
   215     if (this.cfg.scoreCoef > 0)
   215     if (this.cfg.scoreCoef > 0)
   216         weight += this.cfg.scoreCoef * brd.score();
   216         utility += this.cfg.scoreCoef * brd.score();
   217     var max = brd.maxVal();
   217     var max = brd.maxVal();
   218     if (this.cfg.maxValCoef > 0)
   218     if (this.cfg.maxValCoef > 0)
   219         weight += this.cfg.maxValCoef * max;
   219         utility += this.cfg.maxValCoef * max;
   220     if (this.cfg.cornerBonus > 0 && brd.atCorner(max))
   220     if (this.cfg.cornerBonus > 0 && brd.atCorner(max))
   221         weight += this.cfg.cornerBonus;
   221         utility += this.cfg.cornerBonus;
   222     if (this.cfg.edgeBonus > 0 && brd.atEdge(max))
   222     if (this.cfg.edgeBonus > 0 && brd.atEdge(max))
   223         weight += this.cfg.edgeBonus;
   223         utility += this.cfg.edgeBonus;
   224     if (this.cfg.freeBonus > 0)
   224     if (this.cfg.freeBonus > 0)
   225         weight += this.cfg.freeBonus * brd.freeCnt();
   225         utility += this.cfg.freeBonus * brd.freeCnt();
   226     return weight;
   226     return utility;
   227 }
   227 }
   228 /** Select best direction for next step. */
   228 /** Select best direction for next step. */
   229 ai.OneStepAhead.prototype.analyse = function(brd2d) {
   229 ai.OneStepAhead.prototype.analyse = function(brd2d) {
   230     var origBrd = new this.brd(brd2d);
   230     var origBrd = new this.brd(brd2d);
   231     var nextBrd = new this.brd();
   231     var nextBrd = new this.brd();
   232     var maxWeight = -1;
   232     var maxUtility = -1;
   233     var bestDir;
   233     var bestDir;
   234     for (var i = 0; i < ai.dirs.length; i++) {
   234     for (var i = 0; i < ai.dirs.length; i++) {
   235         var dir = ai.dirs[i];
   235         var dir = ai.dirs[i];
   236         if (origBrd[dir](nextBrd)) {
   236         if (origBrd[dir](nextBrd)) {
   237             var weight = this.weight(nextBrd);
   237             var utility = this.utility(nextBrd);
   238             if (maxWeight < weight) {
   238             if (maxUtility < utility) {
   239                 bestDir = dir;
   239                 bestDir = dir;
   240                 maxWeight = weight;
   240                 maxUtility = utility;
   241             }
   241             }
   242         }
   242         }
   243     }
   243     }
   244     return bestDir;
   244     return bestDir;
   245 }
   245 }
   251 ////////////////////////////////////////////////////////////////
   251 ////////////////////////////////////////////////////////////////
   252 // N level deep on score value without random simulation.
   252 // N level deep on score value without random simulation.
   253 ////////////////////////////////////////////////////////////////
   253 ////////////////////////////////////////////////////////////////
   254 
   254 
   255 /**
   255 /**
   256  * Defines coefficient for linear resulted weight function.
   256  * Defines coefficient for linear resulted utility function.
   257  * @name ai.StaticDeepMerges.cfg
   257  * @name ai.StaticDeepMerges.cfg
   258  * @namespace
   258  * @namespace
   259  * @property {number} scoreCoef    multiplicator for score
   259  * @property {number} scoreCoef    multiplicator for score
   260  * @property {number} maxValCoef   multiplicator for max value
   260  * @property {number} maxValCoef   multiplicator for max value
   261  * @property {number} cornerBonus  bonus for max value at board corner
   261  * @property {number} cornerBonus  bonus for max value at board corner
   270 ai.StaticDeepMerges = function(brd, cfg) {
   270 ai.StaticDeepMerges = function(brd, cfg) {
   271     this.brd = brd;
   271     this.brd = brd;
   272     this.cfg = ai.copyObj(ai.OneStepAhead.bestCfg);
   272     this.cfg = ai.copyObj(ai.OneStepAhead.bestCfg);
   273     ai.copyObj(cfg, this.cfg);
   273     ai.copyObj(cfg, this.cfg);
   274 }
   274 }
   275 ai.StaticDeepMerges.bestCfg = {scoreCoef: 1, maxValCoef: 0, cornerBonus: 0, edgeBonus: 0, freeBonus: 0, weightThreshold: 10};
   275 ai.StaticDeepMerges.bestCfg = {scoreCoef: 1, maxValCoef: 0, cornerBonus: 0, edgeBonus: 0, freeBonus: 0, utilityThreshold: 10};
   276 ai.StaticDeepMerges.prototype.weight = function(brd) {
   276 ai.StaticDeepMerges.prototype.utility = function(brd) {
   277     var weight = 0;
   277     var utility = 0;
   278     if (this.cfg.scoreCoef > 0)
   278     if (this.cfg.scoreCoef > 0)
   279         weight += this.cfg.scoreCoef * brd.score();
   279         utility += this.cfg.scoreCoef * brd.score();
   280     var max = brd.maxVal();
   280     var max = brd.maxVal();
   281     if (this.cfg.maxValCoef > 0)
   281     if (this.cfg.maxValCoef > 0)
   282         weight += this.cfg.maxValCoef * max;
   282         utility += this.cfg.maxValCoef * max;
   283     if (this.cfg.cornerBonus > 0 && brd.atCorner(max))
   283     if (this.cfg.cornerBonus > 0 && brd.atCorner(max))
   284         weight += this.cfg.cornerBonus;
   284         utility += this.cfg.cornerBonus;
   285     if (this.cfg.edgeBonus > 0 && brd.atEdge(max))
   285     if (this.cfg.edgeBonus > 0 && brd.atEdge(max))
   286         weight += this.cfg.edgeBonus;
   286         utility += this.cfg.edgeBonus;
   287     if (this.cfg.freeBonus > 0)
   287     if (this.cfg.freeBonus > 0)
   288         weight += this.cfg.freeBonus * brd.freeCnt();
   288         utility += this.cfg.freeBonus * brd.freeCnt();
   289     return weight;
   289     return utility;
   290 }
   290 }
   291 /** Select best direction for next step. */
   291 /** Select best direction for next step. */
   292 ai.StaticDeepMerges.prototype.analyse = function(brd2d) {
   292 ai.StaticDeepMerges.prototype.analyse = function(brd2d) {
   293     var origBrd = new this.brd(brd2d);
   293     var origBrd = new this.brd(brd2d);
   294     var nextBrd = new this.brd();
   294     var nextBrd = new this.brd();
   295     var prevScore = -1, nextScore = -1;
   295     var prevScore = -1, nextScore = -1;
   296     var maxWeight = -1;
   296     var maxUtility = -1;
   297     var bestDir;
   297     var bestDir;
   298     for (var i = 0; i < ai.dirs.length; i++) {
   298     for (var i = 0; i < ai.dirs.length; i++) {
   299         var dir = ai.dirs[i];
   299         var dir = ai.dirs[i];
   300         if (origBrd[dir](nextBrd)) {
   300         if (origBrd[dir](nextBrd)) {
   301             var weight = this.evalFn(nextBrd);
   301             var utility = this.evalFn(nextBrd);
   302             var ok = (weight - maxWeight) > this.cfg.weightThreshold;
   302             var ok = (utility - maxUtility) > this.cfg.utilityThreshold;
   303             if ( ! ok && maxWeight <= weight) {
   303             if ( ! ok && maxUtility <= utility) {
   304                 nextScore = this.weight(nextBrd);
   304                 nextScore = this.utility(nextBrd);
   305                 ok = prevScore < nextScore;
   305                 ok = prevScore < nextScore;
   306             }
   306             }
   307             if (ok) {
   307             if (ok) {
   308                 prevScore = nextScore;
   308                 prevScore = nextScore;
   309                 maxWeight = weight;
   309                 maxUtility = utility;
   310                 bestDir = dir;
   310                 bestDir = dir;
   311             }
   311             }
   312         }
   312         }
   313     }
   313     }
   314     return bestDir;
   314     return bestDir;
   315 }
   315 }
   316 ai.StaticDeepMerges.prototype.evalFn = function(brd) {
   316 ai.StaticDeepMerges.prototype.evalFn = function(brd) {
   317     var currScore = brd.score();
   317     var currScore = brd.score();
   318     var maxWeight = currScore;
   318     var maxUtility = currScore;
   319     var nextBrd = new this.brd();
   319     var nextBrd = new this.brd();
   320     for (var i = 0; i < ai.dirs.length; i++) {
   320     for (var i = 0; i < ai.dirs.length; i++) {
   321         if (brd[ai.dirs[i]](nextBrd)) {
   321         if (brd[ai.dirs[i]](nextBrd)) {
   322             var score = nextBrd.score();
   322             var score = nextBrd.score();
   323             if (score > currScore)
   323             if (score > currScore)
   324                 maxWeight = Math.max(maxWeight, this.evalFn(nextBrd));
   324                 maxUtility = Math.max(maxUtility, this.evalFn(nextBrd));
   325         }
   325         }
   326     }
   326     }
   327     return maxWeight;
   327     return maxUtility;
   328 }
   328 }
   329 /* Mark that next board will be unrelated to previous, so any stored precompution can be cleared. */
   329 /* Mark that next board will be unrelated to previous, so any stored precompution can be cleared. */
   330 ai.StaticDeepMerges.prototype.cleanup = function() { }
   330 ai.StaticDeepMerges.prototype.cleanup = function() { }
   331 
   331 
   332 
   332 
   334 ////////////////////////////////////////////////////////////////
   334 ////////////////////////////////////////////////////////////////
   335 // N level deep with random simulation.
   335 // N level deep with random simulation.
   336 ////////////////////////////////////////////////////////////////
   336 ////////////////////////////////////////////////////////////////
   337 
   337 
   338 /**
   338 /**
   339  * Defines coefficient for linear resulted weight function.
   339  * Defines coefficient for linear resulted utility function.
   340  * @name ai.expectimax.cfg
   340  * @name ai.expectimax.cfg
   341  * @namespace
   341  * @namespace
   342  * @property {number} scoreCoef    multiplicator for score
   342  * @property {number} scoreCoef    multiplicator for score
   343  * @property {number} maxValCoef   multiplicator for max value
   343  * @property {number} maxValCoef   multiplicator for max value
   344  * @property {number} cornerBonus  bonus for max value at board corner
   344  * @property {number} cornerBonus  bonus for max value at board corner
   360         this.cfg.balance = 1;
   360         this.cfg.balance = 1;
   361     if (!this.cfg.depth || this.cfg.depth < 0 || 9 <= this.cfg.depth)
   361     if (!this.cfg.depth || this.cfg.depth < 0 || 9 <= this.cfg.depth)
   362         this.cfg.depth = ai.expectimax.bestCfg.depth;
   362         this.cfg.depth = ai.expectimax.bestCfg.depth;
   363 }
   363 }
   364 ai.expectimax.bestCfg = {balance: .9, depth: 5, scoreCoef: 1, maxValCoef: 0, cornerBonus: 0, edgeBonus: 0, freeBonus: 0};
   364 ai.expectimax.bestCfg = {balance: .9, depth: 5, scoreCoef: 1, maxValCoef: 0, cornerBonus: 0, edgeBonus: 0, freeBonus: 0};
   365 ai.expectimax.prototype.weight = function(brd) {
   365 ai.expectimax.prototype.utility = function(brd) {
   366     var score = 0;
   366     var score = 0;
   367     var cfg = this.cfg;
   367     var cfg = this.cfg;
   368     if (cfg.scoreCoef > 0)
   368     if (cfg.scoreCoef > 0)
   369         score += cfg.scoreCoef * brd.score();
   369         score += cfg.scoreCoef * brd.score();
   370     if (cfg.maxValCoef > 0 || cfg.cornerBonus > 0 || cfg.edgeBonus > 0) {
   370     if (cfg.maxValCoef > 0 || cfg.cornerBonus > 0 || cfg.edgeBonus > 0) {
   407     this.cleanup();
   407     this.cleanup();
   408     return bestDir;
   408     return bestDir;
   409 }
   409 }
   410 ai.expectimax.prototype.evalFn = function(brd, depth) {
   410 ai.expectimax.prototype.evalFn = function(brd, depth) {
   411     if (depth >= this.depthLimit)
   411     if (depth >= this.depthLimit)
   412         return this.weight(brd);
   412         return this.utility(brd);
   413     var wCached = this.brdCache.get(brd);
   413     var wCached = this.brdCache.get(brd);
   414     if (wCached)
   414     if (wCached)
   415         return wCached;
   415         return wCached;
   416     var wMin = +Infinity;
   416     var wMin = +Infinity;
   417     var randBoard = new this.brd();
   417     var randBoard = new this.brd();
   451 ////////////////////////////////////////////////////////////////
   451 ////////////////////////////////////////////////////////////////
   452 // Survive as long as possible.
   452 // Survive as long as possible.
   453 ////////////////////////////////////////////////////////////////
   453 ////////////////////////////////////////////////////////////////
   454 
   454 
   455 /**
   455 /**
   456  * Defines coefficient for linear resulted weight function.
   456  * Defines coefficient for linear resulted utility function.
   457  * @name ai.survive.cfg
   457  * @name ai.survive.cfg
   458  * @namespace
   458  * @namespace
   459  * @property {number} scoreCoef    multiplicator for score
   459  * @property {number} scoreCoef    multiplicator for score
   460  * @property {number} maxValCoef   multiplicator for max value
   460  * @property {number} maxValCoef   multiplicator for max value
   461  * @property {number} cornerBonus  bonus for max value at board corner
   461  * @property {number} cornerBonus  bonus for max value at board corner
   476     if (!this.cfg.maxDepth || this.cfg.maxDepth < 0 || 20 <= this.cfg.maxDepth)
   476     if (!this.cfg.maxDepth || this.cfg.maxDepth < 0 || 20 <= this.cfg.maxDepth)
   477         this.cfg.maxDepth = ai.survive.bestCfg.maxDepth;
   477         this.cfg.maxDepth = ai.survive.bestCfg.maxDepth;
   478     this.cfg.altAI = new ai.StaticDeepMerges(brd, ai.survive.altAICfg);
   478     this.cfg.altAI = new ai.StaticDeepMerges(brd, ai.survive.altAICfg);
   479 }
   479 }
   480 ai.survive.bestCfg = {freeCells: 8, maxDepth: 5};
   480 ai.survive.bestCfg = {freeCells: 8, maxDepth: 5};
   481 ai.survive.altAICfg = {scoreCoef: 1, maxValCoef: 0, cornerBonus: 0, edgeBonus: 0, freeBonus: 0, weightThreshold: 0};
   481 ai.survive.altAICfg = {scoreCoef: 1, maxValCoef: 0, cornerBonus: 0, edgeBonus: 0, freeBonus: 0, utilityThreshold: 0};
   482 /** Select best direction for next step. */
   482 /** Select best direction for next step. */
   483 ai.survive.prototype.analyse = function(brd2d) {
   483 ai.survive.prototype.analyse = function(brd2d) {
   484     var origBrd = new this.brd(brd2d);
   484     var origBrd = new this.brd(brd2d);
   485     var nextBrd = new this.brd();
   485     var nextBrd = new this.brd();
   486     var bestW = -2;
   486     var bestW = -2;
   571 ai.MonteCarlo.bestCfg = {simulations: 1000, maxDepth: 20, cornerBonus: 0, edgeBonus: 0, freeBonus: 0};
   571 ai.MonteCarlo.bestCfg = {simulations: 1000, maxDepth: 20, cornerBonus: 0, edgeBonus: 0, freeBonus: 0};
   572 /** Select best direction for next step. */
   572 /** Select best direction for next step. */
   573 ai.MonteCarlo.prototype.analyse = function(brd2d) {
   573 ai.MonteCarlo.prototype.analyse = function(brd2d) {
   574     var origBrd = new this.brd(brd2d);
   574     var origBrd = new this.brd(brd2d);
   575     var nextBrd = new this.brd();
   575     var nextBrd = new this.brd();
   576     var bestW = - this.cfg.simulations;
   576     var bestUtility = - this.cfg.simulations;
   577     var bestDir;
   577     var bestDir;
   578     var freeCnt = origBrd.freeCnt();
   578     var freeCnt = origBrd.freeCnt();
   579     for (var i = 0; i < ai.dirs.length; i++) {
   579     for (var i = 0; i < ai.dirs.length; i++) {
   580         var dir = ai.dirs[i];
   580         var dir = ai.dirs[i];
   581         if (origBrd[dir](nextBrd)) {
   581         if (origBrd[dir](nextBrd)) {
   582             var w = 0;
   582             var utility = 0;
   583             for (var gameCnt = this.cfg.simulations; gameCnt > 0; gameCnt--) {
   583             for (var gameCnt = this.cfg.simulations; gameCnt > 0; gameCnt--) {
   584                 var tmpBrd = nextBrd.copy();
   584                 var tmpBrd = nextBrd.copy();
   585                 w += this.play(tmpBrd, this.cfg.maxDepth);
   585                 utility += this.play(tmpBrd, this.cfg.maxDepth);
   586             }
   586             }
   587             if (w > bestW) {
   587             if (utility > bestUtility) {
   588                 bestW = w;
   588                 bestUtility = utility;
   589                 bestDir = dir;
   589                 bestDir = dir;
   590             }
   590             }
   591         }
   591         }
   592     }
   592     }
   593     return bestDir;
   593     return bestDir;
   606         }
   606         }
   607     }
   607     }
   608     return -1;
   608     return -1;
   609 }
   609 }
   610 ai.MonteCarlo.prototype.evalFn = function(brd) {
   610 ai.MonteCarlo.prototype.evalFn = function(brd) {
   611     var w = 0;
   611     var utility = 0;
   612     if (this.cfg.freeBonus > 0)
   612     if (this.cfg.freeBonus > 0)
   613         w += this.cfg.freeBonus * brd.freeCnt();
   613         utility += this.cfg.freeBonus * brd.freeCnt();
   614     var max = brd.maxVal();
   614     var max = brd.maxVal();
   615     if (max > 7) {
   615     if (max > 7) {
   616         if (this.cfg.cornerBonus > 0 && brd.atCorner(max))
   616         if (this.cfg.cornerBonus > 0 && brd.atCorner(max))
   617             w += this.cfg.cornerBonus;
   617             utility += this.cfg.cornerBonus;
   618         if (this.cfg.edgeBonus > 0 && brd.atEdge(max))
   618         if (this.cfg.edgeBonus > 0 && brd.atEdge(max))
   619             w += this.cfg.edgeBonus;
   619             utility += this.cfg.edgeBonus;
   620     }
   620     }
   621     return w;
   621     return utility;
   622 }
   622 }
   623 /* Mark that next board will be unrelated to previous, so any stored precompution can be cleared. */
   623 /* Mark that next board will be unrelated to previous, so any stored precompution can be cleared. */
   624 ai.MonteCarlo.prototype.cleanup = function() {
   624 ai.MonteCarlo.prototype.cleanup = function() {
   625 }
   625 }
   626 
   626