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 } |
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 |
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) { |
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 |