885 lines
24 KiB
JavaScript
885 lines
24 KiB
JavaScript
/*
|
|
*
|
|
* Copyright (c) 2012 - Jocly - www.jocly.com
|
|
*
|
|
* This file is part of the Jocly game platform and cannot be used outside of this context without the written permission of Jocly.
|
|
*
|
|
*/
|
|
|
|
(function() {
|
|
|
|
var boardWidth,boardHeight,invertNotation=false;
|
|
|
|
function PosToString(pos) {
|
|
if(!invertNotation) {
|
|
var p1=boardWidth*boardHeight-pos-1;
|
|
var p2=p1%boardWidth;
|
|
return (p1-p2)+boardWidth-p2;
|
|
} else {
|
|
var col=pos%boardWidth;
|
|
var row=(pos-col)/boardWidth;
|
|
return row*boardWidth+boardWidth-col;
|
|
}
|
|
}
|
|
|
|
Model.Game.checkersPosToString = PosToString;
|
|
|
|
Model.Game.InitGameInfo = function() {
|
|
// overload to set game feature options
|
|
}
|
|
|
|
Model.Game.BuildGraphCoord = function() {
|
|
var WIDTH=this.mOptions.width;
|
|
var HEIGHT=this.mOptions.height;
|
|
|
|
var g=[];
|
|
var coord=[];
|
|
this.g.Graph=g;
|
|
this.g.Coord=coord;
|
|
}
|
|
|
|
/* Optional method.
|
|
* Called when the game is created.
|
|
*/
|
|
Model.Game.InitGame = function() {
|
|
var WIDTH=this.mOptions.width;
|
|
var HEIGHT=this.mOptions.height;
|
|
boardWidth=WIDTH;
|
|
boardHeight=HEIGHT;
|
|
invertNotation=this.mOptions.invertNotation || false;
|
|
|
|
this.g.compulsoryCatch=true;
|
|
this.g.canStepBack=true;
|
|
this.g.mustMoveForward=false;
|
|
this.g.mustMoveForwardStrict=false;
|
|
this.g.lastRowFreeze=false;
|
|
this.g.lastRowCrown=false;
|
|
this.g.captureLongestLine=false;
|
|
this.g.noMove="lose";
|
|
this.g.kingCaptureShort=false;
|
|
this.g.kingValue=5;
|
|
this.g.lastRowFactor=0;
|
|
this.g.canCaptureBackward=true;
|
|
this.g.captureInstantRemove=false;
|
|
this.g.longRangeKing=true;
|
|
this.g.drawKvsK=true;
|
|
this.g.drawKvs2K=true;
|
|
this.g.whiteStarts=true;
|
|
this.g.king180deg=false;
|
|
|
|
if(this.mOptions.variant)
|
|
for(var k in this.mOptions.variant)
|
|
this.g[k]=this.mOptions.variant[k];
|
|
|
|
this.BuildGraphCoord();
|
|
|
|
this.zobrist=new JocGame.Zobrist({
|
|
board: {
|
|
type: "array",
|
|
size: this.g.Graph.length,
|
|
values: ["1/0","1/1","-1/0","-1/1"],
|
|
}
|
|
});
|
|
}
|
|
|
|
/* Optional method.
|
|
* Called when the game is over.
|
|
*/
|
|
Model.Game.DestroyGame = function() {
|
|
}
|
|
|
|
Model.Game.CheckersDirections = 4;
|
|
Model.Game.Checkers2WaysDirections = [ 0,1,1,0 ];
|
|
|
|
// walk through neighbor positions
|
|
Model.Game.CheckersEachDirection = function(pos,fnt) {
|
|
for(var i=0;i<this.CheckersDirections;i++) {
|
|
var npos=this.g.Graph[pos][i];
|
|
if(npos!=null)
|
|
if(fnt(npos,i)==false)
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Constructs an instance of the Move object for the game.
|
|
* args is either an empty object ({}), or contains the data passed to the InitUI parameter.
|
|
*/
|
|
Model.Move.Init = function(args) {
|
|
//JocLog("Move.Init",args);
|
|
this.pos=[];
|
|
for(var i in args.pos)
|
|
this.pos.push(args.pos[i]);
|
|
this.capt=[];
|
|
for(var i in args.capt)
|
|
this.capt.push(args.capt[i]);
|
|
}
|
|
|
|
/* Optional method.
|
|
* Copy the given board data to self.
|
|
* Even if optional, it is better to implement the method for performance reasons.
|
|
*/
|
|
/*
|
|
Model.Move.CopyFrom = function(aMove) {
|
|
this.row=aMove.row;
|
|
this.col=aMove.col;
|
|
}
|
|
*/
|
|
|
|
/* Optional method.
|
|
* Verify move equality. If not defined, comparison is performed from JSON stringification of move objects.
|
|
*/
|
|
/*
|
|
Model.Move.Equals = function(move) {
|
|
return this.fc==move.fc && this.tc==move.tc && this.fr==move.fr && this.tr==move.tr;
|
|
}
|
|
*/
|
|
|
|
/* Optional method.
|
|
* Returns a string to represent the move for display to human. If not defined JSON is used.
|
|
*/
|
|
Model.Move.ToString = function(format) {
|
|
format = format || "natural";
|
|
var self = this;
|
|
|
|
function NaturalFormat() {
|
|
var posses=[PosToString(self.pos[0]),PosToString(self.pos[self.pos.length-1])];
|
|
var sep='-';
|
|
for(var i=1;i<self.capt.length;i++)
|
|
if(self.capt[i]!=null) {
|
|
sep='x';
|
|
if(self.capt.length>3)
|
|
posses.push(PosToString(self.capt[i]));
|
|
}
|
|
return posses.join(sep);
|
|
}
|
|
|
|
function HubFormat() {
|
|
var sep = "-";
|
|
var parts = [PosToString(self.pos[0]), PosToString(self.pos[self.pos.length - 1])];
|
|
if (self.capt[1]) {
|
|
sep = "x";
|
|
var capts = self.capt.slice(1).map(function(pos) {
|
|
return PosToString(pos);
|
|
});
|
|
capts.sort();
|
|
parts = parts.concat(capts);
|
|
}
|
|
return parts.join(sep);
|
|
}
|
|
|
|
function DxpFormat() {
|
|
var parts = [PosToString(self.pos[0]), PosToString(self.pos[self.pos.length - 1])];
|
|
if(self.capt[1]) {
|
|
parts.push(self.capt.length-1);
|
|
var capts = [];
|
|
for(var i=1;i<self.capt.length;i++)
|
|
capts.push(PosToString(self.capt[i]));
|
|
capts.sort();
|
|
parts = parts.concat(capts);
|
|
} else
|
|
parts.push(0);
|
|
return parts.map(function(num) {
|
|
return num<10 ? "0"+num : num;
|
|
}).join("");
|
|
}
|
|
|
|
switch(format) {
|
|
case "natural":
|
|
return NaturalFormat();
|
|
case "hub":
|
|
return HubFormat();
|
|
case "dxp":
|
|
return DxpFormat();
|
|
default:
|
|
return "??";
|
|
}
|
|
}
|
|
|
|
/* Board object constructor.
|
|
*/
|
|
Model.Board.Init = function(aGame) {
|
|
this.zSign=0;
|
|
}
|
|
|
|
Model.Board.InitialPosition = function(aGame) {
|
|
var WIDTH=aGame.mOptions.width;
|
|
var HEIGHT=aGame.mOptions.height;
|
|
var INITIAL=aGame.mOptions.initial;
|
|
|
|
this.board=[]; // access pieces by position
|
|
for(var r=0;r<HEIGHT;r++) {
|
|
for(var c=0;c<WIDTH;c++)
|
|
this.board[r*WIDTH+c]=-1;
|
|
}
|
|
|
|
this.pieces=[]; // access pieces by index
|
|
var index=0;
|
|
if(aGame.mInitial) {
|
|
this.pCount=[0,0];
|
|
this.spCount=[0,0];
|
|
this.kpCount=[0,0];
|
|
for(var i=0;i<aGame.mInitial.pieces.length;i++) {
|
|
var piece=aGame.mInitial.pieces[i];
|
|
this.board[piece.p]=i;
|
|
this.pieces.push({
|
|
s: piece.s,
|
|
p: piece.p,
|
|
l: -1,
|
|
t: piece.t,
|
|
plp: piece.p,
|
|
});
|
|
this.zSign=aGame.zobrist.update(this.zSign,"board",piece.s+"/"+piece.t,piece.p);
|
|
var index01=-(piece.s-1)/2;
|
|
this.pCount[index01]++;
|
|
if(piece.t==0)
|
|
this.spCount[index01]++;
|
|
else
|
|
this.kpCount[index01]++;
|
|
}
|
|
} else {
|
|
for(var i in INITIAL.a) {
|
|
var p=INITIAL.a[i];
|
|
var pos=p[0]*WIDTH+p[1];
|
|
var piece={
|
|
s: JocGame.PLAYER_A, // side
|
|
p: pos, // position
|
|
l: -1, // last position for this piece
|
|
t: 0, // piece type
|
|
plp: pos, // last position in move (for piece angle calculation)
|
|
};
|
|
this.pieces.push(piece);
|
|
this.board[pos]=index++;
|
|
this.zSign=aGame.zobrist.update(this.zSign,"board","1/0",pos);
|
|
}
|
|
for(var i in INITIAL.b) {
|
|
var p=INITIAL.b[i];
|
|
var pos=p[0]*WIDTH+p[1];
|
|
var piece={
|
|
s: JocGame.PLAYER_B, // side
|
|
p: pos, // position
|
|
l: -1, // last position for this piece
|
|
t: 0, // piece type
|
|
plp: pos, // last position in move (for piece angle calculation)
|
|
};
|
|
this.pieces.push(piece);
|
|
this.board[pos]=index++;
|
|
this.zSign=aGame.zobrist.update(this.zSign,"board","-1/0",pos);
|
|
}
|
|
this.pCount=[INITIAL.a.length,INITIAL.b.length];
|
|
this.spCount=[INITIAL.a.length,INITIAL.b.length];
|
|
this.kpCount=[INITIAL.a.length,INITIAL.b.length];
|
|
}
|
|
}
|
|
|
|
/* Push into the mMoves array, every possible move
|
|
*/
|
|
Model.Board.GenerateMoves = function(aGame) {
|
|
try {
|
|
this._GenerateMoves(aGame);
|
|
} catch(e) {
|
|
debugger;
|
|
}
|
|
}
|
|
|
|
Model.Board._GenerateMoves = function(aGame) {
|
|
var HEIGHT=aGame.mOptions.height;
|
|
var $this=this;
|
|
|
|
this.mMoves = [];
|
|
|
|
function EachPiece(fnt) {
|
|
for(var i in $this.pieces) {
|
|
var piece=$this.pieces[i];
|
|
if(piece && piece.s==$this.mWho)
|
|
fnt(i,piece.p);
|
|
}
|
|
}
|
|
|
|
|
|
function catchPieces(pos,poss,capts,dirs,king) {
|
|
while(true) {
|
|
var nextPoss=[];
|
|
var nextCapts=[];
|
|
var nextDirs=[];
|
|
aGame.CheckersEachDirection(pos,function(pos0,dir) {
|
|
var r;
|
|
if(aGame.g.canCaptureBackward==false)
|
|
r=aGame.g.Coord[pos][0];
|
|
var dir0=aGame.Checkers2WaysDirections[dir];
|
|
if(!king) {
|
|
if($this.board[pos0]>=0 && $this.pieces[$this.board[pos0]].s==-$this.mWho) {
|
|
var r0,forward;
|
|
if(aGame.g.canCaptureBackward==false) {
|
|
r0=aGame.g.Coord[pos0][0];
|
|
forward=false;
|
|
if(($this.mWho==JocGame.PLAYER_A && r0>=r) || ($this.mWho==JocGame.PLAYER_B && r0<=r))
|
|
forward=true;
|
|
}
|
|
if(aGame.g.canCaptureBackward || forward==true) {
|
|
var pos1=aGame.g.Graph[pos0][dir];
|
|
if(pos1!=null && ($this.board[pos1]==-1 || pos1==poss[0])) {
|
|
var keep=true;
|
|
for(var i=0;i<dirs.length;i++)
|
|
if((aGame.g.captureInstantRemove && capts[i]==pos0) ||
|
|
(aGame.g.captureInstantRemove==false && capts[i]==pos0 && dirs[i]==dir0)) {
|
|
keep=false;
|
|
break;
|
|
}
|
|
if(keep) {
|
|
nextPoss.push(pos1);
|
|
nextCapts.push(pos0);
|
|
nextDirs.push(dir0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else { // king
|
|
if(aGame.g.longRangeKing)
|
|
while($this.board[pos0]==-1 ||
|
|
(aGame.g.king180deg && pos0!=null && capts.indexOf(pos0)>=0))
|
|
pos0=aGame.g.Graph[pos0][dir];
|
|
if(pos0!=null) {
|
|
if($this.board[pos0]>=0 && $this.pieces[$this.board[pos0]].s==-$this.mWho) {
|
|
var caught=pos0;
|
|
pos0=aGame.g.Graph[pos0][dir];
|
|
if(aGame.g.kingCaptureShort) {
|
|
if($this.board[pos0]==-1 || pos0==poss[0]) {
|
|
var keep=true;
|
|
for(var i=0;i<dirs.length;i++)
|
|
if(!aGame.g.king180deg) {
|
|
if((aGame.g.captureInstantRemove && capts[i]==caught) ||
|
|
(aGame.g.captureInstantRemove==false && capts[i]==caught &&
|
|
dirs[i]==dir0)) {
|
|
keep=false;
|
|
break;
|
|
}
|
|
} else if(capts[i]==caught) {
|
|
keep=false;
|
|
break;
|
|
}
|
|
if(keep) {
|
|
nextPoss.push(pos0);
|
|
nextCapts.push(caught);
|
|
nextDirs.push(dir0);
|
|
}
|
|
pos0=aGame.g.Graph[pos0][dir];
|
|
}
|
|
} else {
|
|
while($this.board[pos0]==-1 || pos0==poss[0]) {
|
|
var keep=true;
|
|
for(var i=0;i<dirs.length;i++)
|
|
if((aGame.g.captureInstantRemove && capts[i]==caught) ||
|
|
(aGame.g.captureInstantRemove==false && capts[i]==caught && dirs[i]==dir0)) {
|
|
keep=false;
|
|
break;
|
|
}
|
|
if(keep) {
|
|
nextPoss.push(pos0);
|
|
nextCapts.push(caught);
|
|
nextDirs.push(dir0);
|
|
}
|
|
pos0=aGame.g.Graph[pos0][dir];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
});
|
|
if(nextPoss.length==0) {
|
|
if(poss.length>1)
|
|
$this.mMoves.push({ pos: poss, capt: capts });
|
|
break;
|
|
}
|
|
|
|
if(!aGame.g.compulsoryCatch && poss.length>1) {
|
|
var poss1=[];
|
|
for(var i=0;i<poss.length;i++)
|
|
poss1.push(poss[i]);
|
|
var capts1=[];
|
|
for(var i=0;i<capts.length;i++)
|
|
capts1.push(capts[i]);
|
|
$this.mMoves.push({ pos: poss1, capt: capts1 });
|
|
}
|
|
if(nextPoss.length==1) {
|
|
pos=nextPoss[0];
|
|
poss.push(pos);
|
|
capts.push(nextCapts[0]);
|
|
dirs.push(nextDirs[0]);
|
|
} else {
|
|
for(var i=0;i<nextPoss.length;i++) {
|
|
var poss1=[];
|
|
for(var j=0;j<poss.length;j++)
|
|
poss1.push(poss[j]);
|
|
poss1.push(nextPoss[i]);
|
|
var capts1=[];
|
|
for(var j=0;j<capts.length;j++)
|
|
capts1.push(capts[j]);
|
|
capts1.push(nextCapts[i]);
|
|
var dirs1=[];
|
|
for(var j=0;j<dirs.length;j++)
|
|
dirs1.push(dirs[j]);
|
|
dirs1.push(nextDirs[i]);
|
|
catchPieces(nextPoss[i],poss1,capts1,dirs1,king);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
EachPiece(function(index,pos) {
|
|
if($this.pieces[index].t==0)
|
|
catchPieces(pos,[pos],[null],[null],false);
|
|
else if($this.pieces[index].t==1)
|
|
catchPieces(pos,[pos],[null],[null],true);
|
|
});
|
|
if(aGame.g.compulsoryCatch==false || this.mMoves.length==0)
|
|
EachPiece(function(index,pos) {
|
|
var r;
|
|
if($this.pieces[index].t==0) {
|
|
if(aGame.g.mustMoveForward || aGame.g.mustMoveForwardStrict || aGame.lastRowFreeze)
|
|
r=aGame.g.Coord[pos][0];
|
|
aGame.CheckersEachDirection(pos,function(pos0,dir) {
|
|
var r0,forward,lastRow;
|
|
if(aGame.g.mustMoveForwardStrict) {
|
|
r0=aGame.g.Coord[pos0][0];
|
|
forward=false;
|
|
if(($this.mWho==JocGame.PLAYER_A && r0>r) || ($this.mWho==JocGame.PLAYER_B && r0<r))
|
|
forward=true;
|
|
} else if(aGame.g.mustMoveForward) {
|
|
r0=aGame.g.Coord[pos0][0];
|
|
forward=true;
|
|
if(($this.mWho==JocGame.PLAYER_A && r0<r) || ($this.mWho==JocGame.PLAYER_B && r0>r))
|
|
forward=false;
|
|
}
|
|
if(aGame.g.lastRowFreeze) {
|
|
lastRow=false;
|
|
if(($this.mWho==JocGame.PLAYER_A && r==HEIGHT-1) || ($this.mWho==JocGame.PLAYER_B && r==0))
|
|
lastRow=true;
|
|
}
|
|
if($this.board[pos0]==-1 && (aGame.g.canStepBack || pos0!=$this.pieces[index].l) &&
|
|
((aGame.g.mustMoveForward==false && aGame.g.mustMoveForwardStrict==false) || forward==true) &&
|
|
(aGame.g.lastRowFreeze==false || lastRow==false))
|
|
$this.mMoves.push({ pos: [ pos, pos0 ], capt: [ null, null ] });
|
|
return true;
|
|
});
|
|
} else if($this.pieces[index].t==1) {
|
|
aGame.CheckersEachDirection(pos,function(pos0,dir) {
|
|
if(aGame.g.longRangeKing) {
|
|
while($this.board[pos0]==-1) {
|
|
$this.mMoves.push({ pos: [ pos, pos0 ], capt: [ null, null ] });
|
|
pos0=aGame.g.Graph[pos0][dir];
|
|
}
|
|
} else {
|
|
if($this.board[pos0]==-1) {
|
|
$this.mMoves.push({ pos: [ pos, pos0 ], capt: [ null, null ] });
|
|
pos0=aGame.g.Graph[pos0][dir];
|
|
}
|
|
}
|
|
return true;
|
|
});
|
|
}
|
|
});
|
|
if(this.mMoves.length==0) {
|
|
switch(aGame.g.noMove) {
|
|
case "count":
|
|
this.mFinished=true;
|
|
if(this.pCount[0]<this.pCount[1])
|
|
this.mWinner=JocGame.PLAYER_B;
|
|
else if(this.pCount[1]<this.pCount[0])
|
|
this.mWinner=JocGame.PLAYER_A;
|
|
else
|
|
this.mWinner=JocGame.DRAW;
|
|
break;
|
|
case "lose":
|
|
this.mFinished=true;
|
|
this.mWinner=-this.mWho;
|
|
break;
|
|
default:
|
|
this.mFinished=true;
|
|
this.mWinner=JocGame.DRAW;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(aGame.g.captureLongestLine) {
|
|
var moves0=this.mMoves;
|
|
var moves1=[];
|
|
var bestLength=0;
|
|
for(var i in moves0) {
|
|
var move=moves0[i];
|
|
if(move.pos.length==bestLength)
|
|
moves1.push(move);
|
|
else if(move.pos.length>bestLength) {
|
|
moves1=[move];
|
|
bestLength=move.pos.length;
|
|
}
|
|
}
|
|
this.mMoves=moves1;
|
|
}
|
|
}
|
|
|
|
/* Optional method.
|
|
*/
|
|
/*
|
|
Model.Board.QuickEvaluate = function(aGame) {
|
|
return this.EvaluateChessBoard(aGame);
|
|
}
|
|
*/
|
|
|
|
/* The Evaluate method must:
|
|
* - detects whether the game is finished by setting mFinished to true, and determine the winner by assigning
|
|
* mWinner (to JocGame.PLAYER_A, JocGame.PLAYER_B, JocGame.DRAW).
|
|
* - calculate mEvaluation to indicate apparent advantage to PLAYER_A (higher positive evaluation) or to
|
|
* PLAYER_B (lower negative evaluation)
|
|
* Parameters:
|
|
* - aFinishOnly: it is safe to ignore this parameter value, but for better performance, the mEvaluation setting
|
|
* can be skipped if aFinishOnly is true (function caller is only interested if the game is finished).
|
|
* - aTopLevel: it is safe to ignore this parameter value. For convenience, if true, there is no performance involved
|
|
* so it is safe to make additional calculation and storing data, for instance to simplify the display of the last move.
|
|
*/
|
|
Model.Board.Evaluate = function(aGame,aFinishOnly,aTopLevel) {
|
|
if(aGame.mOptions.preventRepeat && aGame.GetRepeatOccurence(this)>2) {
|
|
this.mFinished=true;
|
|
this.mWinner=JocGame.DRAW;
|
|
return;
|
|
}
|
|
if(aGame.g.drawKvsK && this.spCount[0]==0 && this.spCount[1]==0 && this.kpCount[0]==1 && this.kpCount[1]==1) {
|
|
this.mFinished=true;
|
|
this.mWinner=JocGame.DRAW;
|
|
return;
|
|
}
|
|
if(aGame.g.drawKvs2K && this.spCount[0]==0 && this.spCount[1]==0 && this.kpCount[0]+this.kpCount[1]==3 &&
|
|
(this.kpCount[0]==1 || this.kpCount[0]==2)) {
|
|
this.mFinished=true;
|
|
this.mWinner=JocGame.DRAW;
|
|
return;
|
|
}
|
|
|
|
if(this.pCount[1]==0) {
|
|
this.mFinished=true;
|
|
this.mWinner=JocGame.PLAYER_A;
|
|
return;
|
|
}
|
|
if(this.pCount[0]==0) {
|
|
this.mFinished=true;
|
|
this.mWinner=JocGame.PLAYER_B;
|
|
return;
|
|
}
|
|
|
|
this.mEvaluation=(this.spCount[0]-this.spCount[1])*10
|
|
+(this.kpCount[0]-this.kpCount[1])*10*aGame.g.kingValue
|
|
+this.pCount[0]/this.pCount[1]-this.pCount[1]/this.pCount[0];
|
|
if(aGame.g.lastRowFactor!=0) {
|
|
var HEIGHT=aGame.mOptions.height;
|
|
var rowSumA=0;
|
|
var rowSumB=0;
|
|
for(var i in this.pieces) {
|
|
var piece=this.pieces[i];
|
|
if(piece && piece.t==0) {
|
|
if(this.mWho==JocGame.PLAYER_A)
|
|
rowSumA+=aGame.g.Coord[piece.p][0];
|
|
else
|
|
rowSumB+=HEIGHT-1-aGame.g.Coord[piece.p][0];
|
|
}
|
|
}
|
|
this.mEvaluation += (rowSumA-rowSumB) * aGame.g.lastRowFactor;
|
|
}
|
|
|
|
//JocLog("Evaluation",this.mEvaluation,this.pCount);
|
|
}
|
|
|
|
/*
|
|
* Copy the given board data to self.
|
|
* Even if optional, it is better to implement the method for performance reasons.
|
|
*/
|
|
|
|
Model.Board.CopyFrom = function(aBoard) {
|
|
this.board=[];
|
|
for(var i=0;i<aBoard.board.length;i++)
|
|
this.board[i]=aBoard.board[i];
|
|
this.pieces=[];
|
|
for(var i=0;i<aBoard.pieces.length;i++) {
|
|
var p=aBoard.pieces[i];
|
|
if(p==null)
|
|
this.pieces[i]=null;
|
|
else
|
|
this.pieces[i]={
|
|
s: p.s,
|
|
p: p.p,
|
|
l: p.l,
|
|
t: p.t,
|
|
plp: p.plp,
|
|
};
|
|
}
|
|
this.pCount=[aBoard.pCount[0],aBoard.pCount[1]];
|
|
this.spCount=[aBoard.spCount[0],aBoard.spCount[1]];
|
|
this.kpCount=[aBoard.kpCount[0],aBoard.kpCount[1]];
|
|
this.mWho=aBoard.mWho;
|
|
this.zSign=aBoard.zSign;
|
|
}
|
|
|
|
/* Modify the current board instance to apply the move.
|
|
*/
|
|
Model.Board.ApplyMove = function(aGame,move) {
|
|
var WIDTH=aGame.mOptions.width;
|
|
var HEIGHT=aGame.mOptions.height;
|
|
var pos0=move.pos[0];
|
|
var pIndex=this.board[pos0];
|
|
var piece=this.pieces[pIndex];
|
|
var player=piece.s;
|
|
piece.l=pos0;
|
|
var toBeRemoved={};
|
|
this.zSign=aGame.zobrist.update(this.zSign,"board",piece.s+"/"+piece.t,piece.p);
|
|
for(var i=1;i<move.pos.length;i++) {
|
|
var pos=move.pos[i];
|
|
this.board[piece.p]=-1;
|
|
piece.p=pos;
|
|
this.board[pos]=pIndex;
|
|
var caught=move.capt[i];
|
|
if(caught!=null) {
|
|
if(this.board[caught]>=0)
|
|
toBeRemoved[this.board[caught]]=true;
|
|
this.board[caught]=-1;
|
|
}
|
|
pos0=pos;
|
|
}
|
|
this.zSign=aGame.zobrist.update(this.zSign,"board",piece.s+"/"+piece.t,pos);
|
|
var plp=move.capt[move.capt.length-1]
|
|
piece.plp=plp?plp:move.pos[move.pos.length-2];
|
|
for(var index in toBeRemoved) {
|
|
var piece0=this.pieces[index];
|
|
var other=(1-piece0.s)/2;
|
|
this.pCount[other]--;
|
|
switch(piece0.t) {
|
|
case 0: this.spCount[other]--; break;
|
|
case 1: this.kpCount[other]--; break;
|
|
}
|
|
this.zSign=aGame.zobrist.update(this.zSign,"board",piece0.s+"/"+piece0.t,piece0.p);
|
|
this.pieces[index]=null;
|
|
}
|
|
if(aGame.g.lastRowCrown && this.pieces[pIndex].t==0) {
|
|
var r=aGame.g.Coord[move.pos[move.pos.length-1]][0];
|
|
if((player==JocGame.PLAYER_A && r==HEIGHT-1) || (player==JocGame.PLAYER_B && r==0)) {
|
|
var piece0=this.pieces[pIndex];
|
|
piece0.t=1;
|
|
var self=(1-player)/2;
|
|
this.spCount[self]--;
|
|
this.kpCount[self]++;
|
|
this.zSign=aGame.zobrist.update(this.zSign,"board",piece0.s+"/0",piece0.p);
|
|
this.zSign=aGame.zobrist.update(this.zSign,"board",piece0.s+"/1",piece0.p);
|
|
}
|
|
}
|
|
}
|
|
|
|
Model.Board.IsValidMove = function(aGame,move) {
|
|
return true;
|
|
}
|
|
|
|
Model.Board.GetSignature = function() {
|
|
return this.zSign;
|
|
}
|
|
|
|
Model.Game.Import = function(format,data) {
|
|
var turn=1, pieces=[];
|
|
var boardSize=this.mOptions.width*this.mOptions.height;
|
|
var boardWidth=this.mOptions.width;
|
|
function AddPiece(side,type,pos) {
|
|
pos=boardSize-pos;
|
|
var posCol=pos%boardWidth;
|
|
pos=pos-posCol+boardWidth-posCol-1;
|
|
pieces.push({
|
|
s: side,
|
|
p: pos,
|
|
l: -1,
|
|
t: type,
|
|
plp: pos,
|
|
});
|
|
}
|
|
|
|
if(format=='pjn') {
|
|
var result={
|
|
status: false,
|
|
error: 'parse',
|
|
}
|
|
var parts=data.split(':');
|
|
turn=parts[0]=='W'?1:-1;
|
|
for(var i=1;i<parts.length;i++) {
|
|
if(parts[i].length==0)
|
|
continue;
|
|
var color=parts[i].substr(0,1)=='W'?1:-1;
|
|
var parts2=parts[i].substr(1).split(',');
|
|
for(var j=0;j<parts2.length;j++) {
|
|
var p=parts2[j];
|
|
var type=0;
|
|
if(p.substr(0,1)=='K') {
|
|
type=1;
|
|
p=p.substr(1);
|
|
}
|
|
var m=/^([0-9]+)-([0-9]+)$/.exec(p);
|
|
if(m)
|
|
for(var pos=parseInt(m[1]);pos<=parseInt(m[2]);pos++)
|
|
AddPiece(color,type,pos);
|
|
else
|
|
AddPiece(color,type,parseInt(p));
|
|
}
|
|
}
|
|
pieces.sort(function(p1,p2) {
|
|
return p2.s-p1.s;
|
|
});
|
|
return {
|
|
status: true,
|
|
initial: {
|
|
pieces: pieces,
|
|
turn: turn,
|
|
}
|
|
}
|
|
}
|
|
return {
|
|
status: false,
|
|
error: 'unsupported',
|
|
}
|
|
}
|
|
|
|
var SuperGetBestMatchingMove = JocGame.prototype.GetBestMatchingMove;
|
|
|
|
Model.Game.GetBestMatchingMove = function(moveStr,candidateMoves) {
|
|
var prettyMoves=[];
|
|
var $this = this;
|
|
candidateMoves.forEach(function(m) {
|
|
if(typeof m.ToString=="function")
|
|
prettyMoves.push(m.ToString());
|
|
else
|
|
prettyMoves.push($this.CreateMove(m).ToString());
|
|
});
|
|
moveStr=moveStr.replace(/0([1-9])/g,"$1");
|
|
var dbm=/^([0-9]+)[\-x]([0-9]+)([\-x][0-9]+)*$/.exec(moveStr);
|
|
if(dbm) {
|
|
var re=new RegExp('^'+dbm[1]+'[\-x]'+dbm[2]+'([\-x][0-9]+)*$');
|
|
//console.log("re",re.toString())
|
|
var bestMatchesMap={};
|
|
candidateMoves.forEach(function(candidate,index) {
|
|
if(re.test(prettyMoves[index])) {
|
|
var key=prettyMoves[index];
|
|
var m=/^([0-9]+)x([0-9]+)x([0-9]+(x[0-9]+)*)/.exec(key);
|
|
if(m) {
|
|
var arr=m[3].split('x');
|
|
arr.sort(function(p1,p2) {
|
|
return parseInt(p1)-parseInt(p2);
|
|
});
|
|
key=m[1]+"x"+m[2]+"x"+arr.join("x");
|
|
}
|
|
bestMatchesMap[key]=candidate;
|
|
}
|
|
});
|
|
var bestMatches=[];
|
|
for(var i in bestMatchesMap)
|
|
bestMatches.push(bestMatchesMap[i]);
|
|
if(bestMatches.length>0)
|
|
candidateMoves=bestMatches;
|
|
}
|
|
return SuperGetBestMatchingMove.call(this,moveStr,candidateMoves);
|
|
}
|
|
|
|
Model.Board.ExportBoardState = function(aGame,format) {
|
|
format = format || "natural";
|
|
var self = this;
|
|
|
|
function FenFormat() {
|
|
var colors={};
|
|
var fenParts=[];
|
|
self.pieces.forEach(function(piece) {
|
|
if(piece && piece.p!=null) {
|
|
var color=piece.s==1?'W':'B';
|
|
if(colors[color]===undefined)
|
|
colors[color]={};
|
|
var abbrev=piece.t==1?'K':'';
|
|
if(colors[color][abbrev]===undefined)
|
|
colors[color][abbrev]={
|
|
group: piece.t==0,
|
|
pos: [],
|
|
}
|
|
colors[color][abbrev].pos.push(parseInt(PosToString(piece.p)));
|
|
}
|
|
});
|
|
for(var color in colors) {
|
|
var fenColorParts=[];
|
|
for(var abbrev in colors[color]) {
|
|
var pieceType=colors[color][abbrev];
|
|
if(pieceType.group) {
|
|
pieceType.pos.sort(function(pos1,pos2) {
|
|
return parseInt(pos1)-parseInt(pos2);
|
|
});
|
|
var last=-2, end=-1, start=-1;
|
|
pieceType.pos.forEach(function(pos) {
|
|
if(parseInt(pos)==last+1) {
|
|
end=pos;
|
|
} else {
|
|
if(end>=0) {
|
|
fenColorParts.push(abbrev+start+"-"+end);
|
|
end=-1;
|
|
} else {
|
|
if(start>=0)
|
|
fenColorParts.push(abbrev+start);
|
|
}
|
|
start=pos;
|
|
}
|
|
last=parseInt(pos);
|
|
});
|
|
if(end>=0)
|
|
fenColorParts.push(abbrev+start+"-"+end);
|
|
else if(start>=0)
|
|
fenColorParts.push(abbrev+start);
|
|
} else
|
|
pieceType.pos.forEach(function(pos) {
|
|
fenColorParts.push(abbrev+pos);
|
|
});
|
|
}
|
|
fenParts.push(color+fenColorParts.join(","));
|
|
}
|
|
var fen=fenParts.join(":");
|
|
return fen;
|
|
}
|
|
|
|
function DxpHubFormat(black) {
|
|
var poss = [];
|
|
for(var pos = 0; pos<self.board.length; pos++) {
|
|
var col=pos%boardWidth;
|
|
var row=(pos-col)/boardWidth;
|
|
var posIndex = (boardHeight - row -1)* boardWidth + col;
|
|
var pieceIndex = self.board[pos];
|
|
if(pieceIndex<0)
|
|
poss[posIndex] = "e";
|
|
else {
|
|
var piece = self.pieces[pieceIndex];
|
|
var t = piece.s == 1 ? 'w' : black;
|
|
if(piece.t==1)
|
|
t = t.toUpperCase();
|
|
poss[posIndex] = t;
|
|
}
|
|
}
|
|
return poss.join("");
|
|
}
|
|
|
|
function DxpFormat() {
|
|
return DxpHubFormat('z');
|
|
}
|
|
|
|
function HubFormat() {
|
|
return DxpHubFormat('z');
|
|
}
|
|
|
|
switch(format) {
|
|
case "natural":
|
|
case "fen":
|
|
return FenFormat();
|
|
case "dxp":
|
|
return DxpFormat();
|
|
case "hub":
|
|
return HubFormat();
|
|
default:
|
|
return JSON.stringify(this);
|
|
}
|
|
}
|
|
|
|
})();
|