1041 lines
25 KiB
JavaScript
1041 lines
25 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.
|
|
*
|
|
*/
|
|
|
|
/* Optional method.
|
|
* Called when the game is created.
|
|
*/
|
|
Model.Game.InitGame = function() {
|
|
var WIDTH=this.mOptions.width;
|
|
var HEIGHT=this.mOptions.height;
|
|
var g=[];
|
|
var coord=[];
|
|
for(var r=0;r<HEIGHT;r++) {
|
|
for(var c=0;c<WIDTH;c++) {
|
|
var pos=r*WIDTH+c;
|
|
coord[pos]=[r,c];
|
|
g[pos]=[];
|
|
for(var h=-1;h<2;h++)
|
|
for(var v=-1;v<2;v++) {
|
|
if(h!=0 || v!=0) {
|
|
if(r+h<HEIGHT && r+h>=0 && c+v<WIDTH && c+v>=0)
|
|
g[pos].push((r+h)*WIDTH+c+v);
|
|
else
|
|
g[pos].push(null);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
this.g.Graph=g;
|
|
this.g.Coord=coord;
|
|
this.zobrist=new JocGame.Zobrist({
|
|
board: {
|
|
type: "array",
|
|
size: this.g.Graph.length,
|
|
values: ["1","-1","-2"],
|
|
}
|
|
});
|
|
|
|
}
|
|
|
|
/* Optional method.
|
|
* Called when the game is over.
|
|
*/
|
|
Model.Game.DestroyGame = function() {
|
|
}
|
|
|
|
// walk through neighbor positions
|
|
Model.Game.ScrumEachDirection = function(pos,fnt) {
|
|
for(var i=0;i<8;i++) {
|
|
var npos=this.g.Graph[pos][i];
|
|
if(npos!=null)
|
|
fnt(npos,i);
|
|
}
|
|
}
|
|
|
|
/* 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);
|
|
if(typeof(args.seg)!="undefined") {
|
|
this.seg=[{
|
|
f: args.seg[0].f, // first segment, piece from
|
|
t: args.seg[0].t, // first segment, piece to
|
|
}];
|
|
if(typeof(args.seg[0].b)!="undefined")
|
|
this.seg[0].b=args.seg[0].b; // first segment, ball to
|
|
if(typeof(args.seg[1])!="undefined") {
|
|
this.seg[1]={
|
|
f: args.seg[1].f, // second segment, piece from
|
|
t: args.seg[1].t, // second segment, piece to
|
|
};
|
|
if(typeof(args.seg[1].b)!="undefined")
|
|
this.seg[1].b=args.seg[1].b; // second segment, ball to
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Optional method.
|
|
* Returns a string to represent the move for display to human. If not defined JSON is used.
|
|
*/
|
|
Model.Move.ToString = function() {
|
|
var str=this.seg[0].f+">"+this.seg[0].t;
|
|
if(typeof(this.seg[0].b)!="undefined")
|
|
str+=">"+this.seg[0].b;
|
|
if(typeof(this.seg[1])!="undefined") {
|
|
str+=" - "+this.seg[1].f+">"+this.seg[1].t;
|
|
if(typeof(this.seg[1].b)!="undefined")
|
|
str+=">"+this.seg[1].b;
|
|
}
|
|
return str;
|
|
}
|
|
|
|
/* 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.first=true;
|
|
|
|
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;
|
|
for(var i in INITIAL.a)
|
|
if(INITIAL.a.hasOwnProperty(i)) {
|
|
var p=INITIAL.a[i];
|
|
var pos=p[0]*WIDTH+p[1];
|
|
this.pieces.push({
|
|
s: JocGame.PLAYER_A, // side
|
|
p: pos, // position
|
|
a: 180,
|
|
sc: false,
|
|
});
|
|
this.board[pos]=index++;
|
|
this.zSign=aGame.zobrist.update(this.zSign,"board","1",pos);
|
|
}
|
|
for(var i in INITIAL.b)
|
|
if(INITIAL.b.hasOwnProperty(i)) {
|
|
var p=INITIAL.b[i];
|
|
var pos=p[0]*WIDTH+p[1];
|
|
this.pieces.push({
|
|
s: JocGame.PLAYER_B, // side
|
|
p: pos, // position
|
|
a: 0,
|
|
sc: false,
|
|
});
|
|
this.board[pos]=index++;
|
|
this.zSign=aGame.zobrist.update(this.zSign,"board","-1",pos);
|
|
}
|
|
this.ball=INITIAL.ball[0]*WIDTH+INITIAL.ball[1];
|
|
this.board[this.ball]=-2;
|
|
this.zSign=aGame.zobrist.update(this.zSign,"board","-2",this.ball);
|
|
this.scrum=false;
|
|
}
|
|
|
|
/* Push into the mMoves array, every possible move
|
|
*/
|
|
Model.Board.GenerateMoves = function(aGame) {
|
|
this.mMoves = [];
|
|
try {
|
|
this._GenerateMoves(aGame);
|
|
} catch(e) {
|
|
console.error("GenerateMoves: "+e);
|
|
}
|
|
}
|
|
|
|
Model.Board._GenerateMoves = function(aGame) {
|
|
// generating an exhaustive list of moves (~10K) would badly affect computer thinking
|
|
// only a subset of moves must be considered, hence the complexity of the GenerateMoves method
|
|
|
|
var HEIGHT=aGame.mOptions.height;
|
|
var $this=this;
|
|
var time0=new Date().getTime();
|
|
|
|
// === Utility functions ===
|
|
|
|
// get distance between 2 positions
|
|
// TODO measure impact of caching
|
|
var distCache={};
|
|
function Dist(p0,p1) {
|
|
if(p0>p1) {
|
|
var tmp=p0;
|
|
p0=p1;
|
|
p1=tmp;
|
|
}
|
|
var sign=""+p0+"/"+p1;
|
|
if(typeof(distCache[sign])=="undefined") {
|
|
var coord0=aGame.g.Coord[p0];
|
|
var coord1=aGame.g.Coord[p1];
|
|
var d=Math.max(Math.abs(coord0[0]-coord1[0]),Math.abs(coord0[1]-coord1[1]));
|
|
distCache[sign]=d;
|
|
}
|
|
return distCache[sign];
|
|
}
|
|
|
|
// walk through neighbor positions but directed to another position
|
|
function EachDirectionTo(pos,toPos,strict,fnt) {
|
|
var c0=aGame.g.Coord[pos];
|
|
var c=aGame.g.Coord[toPos];
|
|
var dirs=[0,1,2,3,4,5,6,7];
|
|
if(c[0]>c0[0]) {
|
|
dirs[0]=-1;
|
|
dirs[1]=-1;
|
|
dirs[2]=-1;
|
|
if(strict) {
|
|
dirs[3]=-1;
|
|
dirs[4]=-1;
|
|
}
|
|
} else if(c[0]<c0[0]) {
|
|
dirs[5]=-1;
|
|
dirs[6]=-1;
|
|
dirs[7]=-1;
|
|
if(strict) {
|
|
dirs[3]=-1;
|
|
dirs[4]=-1;
|
|
}
|
|
}
|
|
if(c[1]>c0[1]) {
|
|
dirs[0]=-1;
|
|
dirs[3]=-1;
|
|
dirs[5]=-1;
|
|
if(strict) {
|
|
dirs[1]=-1;
|
|
dirs[6]=-1;
|
|
}
|
|
} else if(c[1]<c0[1]) {
|
|
dirs[2]=-1;
|
|
dirs[4]=-1;
|
|
dirs[7]=-1;
|
|
if(strict) {
|
|
dirs[1]=-1;
|
|
dirs[6]=-1;
|
|
}
|
|
}
|
|
for(var i=0; i<dirs.length; i++) {
|
|
if(dirs[i]!=-1) {
|
|
var npos=aGame.g.Graph[pos][dirs[i]];
|
|
if(npos)
|
|
fnt(npos,i);
|
|
}
|
|
}
|
|
}
|
|
|
|
// walk through each of own pieces
|
|
function EachPiece(fnt) {
|
|
for(var i=0; i<$this.pieces.length; i++) {
|
|
var piece=$this.pieces[i];
|
|
if(piece.s==$this.mWho)
|
|
fnt(i,piece.p);
|
|
}
|
|
}
|
|
|
|
// count number of opponents in the neighborhood
|
|
var ballDataCountCache={};
|
|
function BallDataCount(pos) {
|
|
if(typeof(ballDataCountCache[pos])=="undefined") {
|
|
var opCount=0;
|
|
var selfCount=0;
|
|
var emptyCount=0;
|
|
var etab=[];
|
|
aGame.ScrumEachDirection(pos,function(npos) {
|
|
var cell=$this.board[npos];
|
|
if(cell<0) {
|
|
if(cell==-1) {
|
|
emptyCount++;
|
|
etab.push(npos);
|
|
}
|
|
} else if($this.pieces[cell].s==$this.mWho)
|
|
selfCount++;
|
|
else
|
|
opCount++;
|
|
});
|
|
var counters={
|
|
e: emptyCount,
|
|
etab: etab,
|
|
s: selfCount,
|
|
o: opCount,
|
|
}
|
|
ballDataCountCache[pos]=counters;
|
|
return counters;
|
|
} else
|
|
return ballDataCountCache[pos];
|
|
}
|
|
var ballDataCount=BallDataCount(this.ball);
|
|
|
|
// get level1 ball moves
|
|
var ballMoves1=[];
|
|
var ballRow=aGame.g.Coord[this.ball][0];
|
|
aGame.ScrumEachDirection(this.ball,function(pos) {
|
|
var cell=$this.board[pos];
|
|
if(cell==-1) {
|
|
ballMoves1.push({
|
|
p: pos,
|
|
c: BallDataCount(pos),
|
|
g: (aGame.g.Coord[pos][0]-ballRow)*$this.mWho
|
|
});
|
|
}
|
|
});
|
|
|
|
// === handling scrum ===
|
|
if(this.scrum) {
|
|
var scrumExit=-1;
|
|
var pieces=[];
|
|
aGame.ScrumEachDirection(this.ball,function(pos) {
|
|
var cell=$this.board[pos];
|
|
if(cell==-1)
|
|
scrumExit=pos;
|
|
else if(cell>=0 && $this.pieces[cell].s==$this.mWho)
|
|
pieces.push(cell);
|
|
});
|
|
if(scrumExit==-1) {
|
|
this.mFinished=true;
|
|
this.mWinner=JocGame.DRAW;
|
|
return;
|
|
}
|
|
for(var i=0; i<pieces.length; i++) {
|
|
var index=pieces[i];
|
|
var enclosed=true;
|
|
aGame.ScrumEachDirection(scrumExit,function(pos) {
|
|
var cell=$this.board[pos];
|
|
if(cell==-1 || (cell>=0 && pos==$this.pieces[index].p))
|
|
enclosed=false;
|
|
});
|
|
if(enclosed==false)
|
|
this.mMoves.push({seg:[{
|
|
f: $this.pieces[index].p,
|
|
t: $this.ball,
|
|
b: scrumExit,
|
|
}]});
|
|
}
|
|
if(pieces.length>0) {
|
|
//JocLog("Scrum moves",this.mMoves);
|
|
if(this.mMoves.length==0) {
|
|
this.mFinished=true;
|
|
this.mWinner=JocGame.DRAW;
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
// === building segments ===
|
|
|
|
var segments=[];
|
|
|
|
|
|
// segments moving the ball
|
|
aGame.ScrumEachDirection(this.ball,function(pos) {
|
|
var cell=$this.board[pos];
|
|
if(cell>=0 && $this.pieces[cell].s==$this.mWho) {
|
|
for(var i=0; i<ballMoves1.length; i++) {
|
|
var bm=ballMoves1[i];
|
|
var segment={
|
|
i: cell, // piece index
|
|
f: pos, // start piece position
|
|
t: $this.ball, // end piece position
|
|
b: bm.p, // ball position
|
|
c: bm.c, // # of opponent in ball neighborhood
|
|
g: bm.g, // ball row gain
|
|
d: 1, // distance from piece to ball
|
|
}
|
|
segments.push(segment);
|
|
}
|
|
}
|
|
});
|
|
|
|
// segments moving piece only
|
|
EachPiece(function(index,pos) {
|
|
|
|
function AddSegment(pos0,d0) {
|
|
var segment={
|
|
i: index,
|
|
f: pos,
|
|
t: pos0,
|
|
b: null,
|
|
c: ballDataCount,
|
|
g: 0,
|
|
d: d0,
|
|
}
|
|
segments.push(segment);
|
|
}
|
|
|
|
var dist=Dist(pos,$this.ball);
|
|
if(dist==1) {
|
|
aGame.ScrumEachDirection(pos,function(npos) {
|
|
var cell=$this.board[npos];
|
|
if(cell==-1) {
|
|
var d=Dist(npos,$this.ball);
|
|
AddSegment(npos,d);
|
|
}
|
|
});
|
|
} else if(dist<4) {
|
|
aGame.ScrumEachDirection(pos,function(npos) {
|
|
var cell=$this.board[npos];
|
|
if(cell==-1) {
|
|
var d=Dist(npos,$this.ball);
|
|
AddSegment(npos,d);
|
|
}
|
|
});
|
|
} else {
|
|
EachDirectionTo(pos,$this.ball,false,function(npos) {
|
|
var cell=$this.board[npos];
|
|
if(cell==-1) {
|
|
var d=Dist(npos,$this.ball);
|
|
AddSegment(npos,d);
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
// update segment weights
|
|
for(var i=0; i<segments.length; i++) {
|
|
var segment=segments[i];
|
|
var weight=
|
|
(8-segment.c.o)*0.125 * 1 +
|
|
(segment.c.s)*0.125 * 1 +
|
|
(segment.g+2)*0.25 * 4 +
|
|
(11-segment.d)*(1/11) * 2 ;
|
|
if(segment.e==0)
|
|
weight=-1000;
|
|
if(segment.e==1)
|
|
weight+=1000;
|
|
if(segment.e==2)
|
|
weight+=2;
|
|
segment.w=weight;
|
|
}
|
|
segments.sort(function(a,b) {
|
|
return b.w-a.w;
|
|
});
|
|
|
|
// generate moves
|
|
var moves=[];
|
|
var pmoves=[]; // provisioned moves
|
|
|
|
if(this.first) {
|
|
for(var i=0;i<aGame.mOptions.levelOptions.MIN_MOVES && i<segments.length;i++) {
|
|
var seg=segments[i];
|
|
var move={ seg:[{
|
|
f: seg.f,
|
|
t: seg.t,
|
|
}] }
|
|
if(seg.b!=null)
|
|
move.seg[0].b=seg.b;
|
|
moves.push(move);
|
|
}
|
|
this.mMoves=moves;
|
|
return;
|
|
}
|
|
|
|
var gotLevel1WinningSegment=false;
|
|
function AddMove(seg1,seg2) {
|
|
|
|
if(seg1.b!=null) {
|
|
// check if winning first segment
|
|
var ballRow=aGame.g.Coord[seg1.b][0];
|
|
if((ballRow==0 && $this.mWho==JocGame.PLAYER_B) || (ballRow==HEIGHT-1 && $this.mWho==JocGame.PLAYER_A)) {
|
|
var empty=0;
|
|
aGame.ScrumEachDirection(seg1.b,function(pos) {
|
|
if($this.board[pos]==-1)
|
|
empty++;
|
|
});
|
|
if(empty>0) {
|
|
if(gotLevel1WinningSegment==false) {
|
|
moves=[];
|
|
gotLevel1WinningSegment=true;
|
|
}
|
|
var move={seg:[{
|
|
f: seg1.f,
|
|
t: seg1.t,
|
|
b: seg1.b,
|
|
}]};
|
|
moves.push(move);
|
|
}
|
|
}
|
|
} else {
|
|
var ballRow=aGame.g.Coord[$this.ball][0];
|
|
if((ballRow==0 && $this.mWho==JocGame.PLAYER_B) || (ballRow==HEIGHT-1 && $this.mWho==JocGame.PLAYER_A)) {
|
|
var touch=false;
|
|
var empty=0;
|
|
aGame.ScrumEachDirection($this.ball,function(pos) {
|
|
if(pos==seg1.t)
|
|
touch=true;
|
|
else if($this.board[pos]==-1)
|
|
empty++;
|
|
});
|
|
if(touch && empty>0) {
|
|
if(gotLevel1WinningSegment==false) {
|
|
moves=[];
|
|
gotLevel1WinningSegment=true;
|
|
}
|
|
var move={seg:[{
|
|
f: seg1.f,
|
|
t: seg1.t,
|
|
}]};
|
|
moves.push(move);
|
|
}
|
|
}
|
|
}
|
|
if(gotLevel1WinningSegment)
|
|
return;
|
|
var empty=0;
|
|
var ball=$this.ball;
|
|
if(seg1.b!=null) ball=seg1.b;
|
|
if(seg2.b!=null) ball=seg2.b;
|
|
aGame.ScrumEachDirection(ball,function(pos) {
|
|
var cell=$this.board[pos];
|
|
if(cell<0)
|
|
empty++;
|
|
if(seg1.t==pos || seg2.t==pos)
|
|
empty--;
|
|
if(seg1.f==pos || seg2.f==pos)
|
|
empty++;
|
|
});
|
|
if(empty==0) return;
|
|
|
|
var move={seg:[{
|
|
f: seg1.f,
|
|
t: seg1.t,
|
|
},{
|
|
f: seg2.f,
|
|
t: seg2.t,
|
|
}]};
|
|
if(seg1.b!=null)
|
|
move.seg[0].b=seg1.b;
|
|
if(seg2.b!=null)
|
|
move.seg[1].b=seg2.b;
|
|
moves.push(move);
|
|
}
|
|
|
|
var iSegWT1=-1, iSegWT2=0;
|
|
function GetNextSegmentPair() {
|
|
while(true) {
|
|
iSegWT1++;
|
|
if(iSegWT1==iSegWT2) {
|
|
iSegWT2++;
|
|
iSegWT1=0;
|
|
}
|
|
if(iSegWT2<segments.length) {
|
|
var seg1=segments[iSegWT1];
|
|
var seg2=segments[iSegWT2];
|
|
if((seg1.b==null || seg2.b==null) && // filter: 2 level1 segments using same ball
|
|
(seg1.i!=seg2.i) && // playing same piece
|
|
(seg1.t!=seg2.t) && // same destination cell
|
|
(seg2.t!=seg1.b || seg2.b!=null ) && // moving piece to ball
|
|
(seg2.b!=seg1.t) // no ball to where first piece just move
|
|
)
|
|
return [ seg1 , seg2 ];
|
|
} else {
|
|
// hopefully should not happen
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
|
|
// get segments moving ball at level 2
|
|
var l2segmentsBallCache={}
|
|
function GetLevel2BallSegments(ball) {
|
|
if(typeof(l2segmentsBallCache[ball])=="undefined") {
|
|
l2segmentsBallCache[ball]=[];
|
|
var avail=[];
|
|
var pieces=[];
|
|
aGame.ScrumEachDirection(ball,function(pos) {
|
|
var cell=$this.board[pos];
|
|
if(cell==-1)
|
|
avail.push(pos);
|
|
else if(cell>=0 && $this.pieces[cell].s==$this.mWho) {
|
|
pieces.push(cell);
|
|
}
|
|
});
|
|
for(var i=0; i<pieces.length; i++) {
|
|
var index=pieces[i];
|
|
for(var j=0; j<avail.length; j++) {
|
|
l2segmentsBallCache[ball].push({
|
|
i: index,
|
|
f: $this.pieces[index].p,
|
|
t: ball,
|
|
b: avail[j],
|
|
});
|
|
}
|
|
}
|
|
}
|
|
return l2segmentsBallCache[ball];
|
|
}
|
|
|
|
// get segments moving to position
|
|
var l2segmentsPieceCache={}
|
|
function GetLevel2PieceSegments(ppos) {
|
|
if(typeof(l2segmentsPieceCache[ppos])=="undefined") {
|
|
l2segmentsPieceCache[ppos]=[];
|
|
aGame.ScrumEachDirection(ppos,function(pos) {
|
|
var cell=$this.board[pos];
|
|
if(cell>=0 && $this.pieces[cell].s==$this.mWho) {
|
|
l2segmentsPieceCache[ppos].push({
|
|
i: cell,
|
|
f: $this.pieces[cell].p,
|
|
t: ppos,
|
|
});
|
|
}
|
|
});
|
|
}
|
|
return l2segmentsPieceCache[ppos];
|
|
}
|
|
|
|
var segs;
|
|
while(moves.length<aGame.mOptions.levelOptions.MIN_MOVES) {
|
|
segs=GetNextSegmentPair();
|
|
if(segs) {
|
|
//JocLog("Move",m[0],m[1]);
|
|
//AddMove(m[0],m[1]);
|
|
var seg1=segs[0];
|
|
var seg2=segs[1];
|
|
AddMove(seg1,seg2);
|
|
if(seg1.b!=null && !seg1.explored) { // ball moved at level 1
|
|
seg1.explored=true;
|
|
// consider ball move at level 2
|
|
var segments2=GetLevel2BallSegments(seg1.b);
|
|
for(var i=0; i<segments2.length; i++) {
|
|
var segment2=segments2[i];
|
|
if(segment2.i!=seg1.i) {
|
|
AddMove(seg1,segment2);
|
|
}
|
|
}
|
|
// consider moving piece to where 1st piece was
|
|
var segments2=GetLevel2PieceSegments(seg1.f);
|
|
for(var i=0; i<segments2.length; i++) {
|
|
var segment2=segments2[i];
|
|
if(segment2.i!=seg1.i) {
|
|
AddMove(seg1,segment2);
|
|
}
|
|
}
|
|
}
|
|
} else
|
|
break;
|
|
}
|
|
|
|
// add moves where seg1 moves piece only and seg2 moves ball to where first piece was
|
|
var piecesAround=[];
|
|
aGame.ScrumEachDirection(this.ball,function(pos) {
|
|
var cell=$this.board[pos];
|
|
if(cell>=0 && $this.pieces[cell].s==$this.mWho)
|
|
piecesAround.push(cell);
|
|
});
|
|
var piecesAround1=[];
|
|
for(var i=0; i<piecesAround.length; i++) { // do not consider moves with ball behind
|
|
var row=aGame.g.Coord[this.pieces[piecesAround[i]].p][0];
|
|
if((this.mWho==JocGame.PLAYER_A && row>=ballRow) ||
|
|
(this.mWho==JocGame.PLAYER_B && row<=ballRow))
|
|
piecesAround1.push(piecesAround[i]);
|
|
}
|
|
var movesLength0=moves.length;
|
|
for(var i=0; i<piecesAround1.length; i++)
|
|
for(var j=0; j<piecesAround.length; j++) {
|
|
if(piecesAround1[i]!=piecesAround[j]) {
|
|
var piece1=this.pieces[piecesAround1[i]];
|
|
var piece2=this.pieces[piecesAround[j]];
|
|
aGame.ScrumEachDirection(piece1.p,function(pos) {
|
|
var cell=$this.board[pos];
|
|
if(cell==-1) {
|
|
var seg1={
|
|
i: piecesAround1[i],
|
|
f: piece1.p,
|
|
t: pos,
|
|
}
|
|
var seg2={
|
|
i: piecesAround[j],
|
|
f: piece2.p,
|
|
t: $this.ball,
|
|
b: piece1.p,
|
|
}
|
|
//JocLog("group2 move",seg1,seg2);
|
|
AddMove(seg1,seg2);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
var group2Moves=moves.length-movesLength0;
|
|
|
|
this.mMoves=moves;
|
|
|
|
if(this.mMoves.length==0) {
|
|
// always have one 'bad' but legal move possible to prevent no move found
|
|
function MakeLooseMove() {
|
|
var looseMove=null;
|
|
var looseData=[];
|
|
var piecesDist=[];
|
|
EachPiece(function(index,pos) {
|
|
var d=Dist(pos,$this.ball);
|
|
piecesDist.push({
|
|
i: index,
|
|
d: d,
|
|
});
|
|
});
|
|
piecesDist.sort(function(a,b) {
|
|
return b.d-a.d;
|
|
});
|
|
for(var i=0; i<piecesDist.length; i++) {
|
|
var piece=piecesDist[i];
|
|
var pos=$this.pieces[piece.i].p;
|
|
var d0=Dist(pos,$this.ball);
|
|
var abortEachDir=false;
|
|
aGame.ScrumEachDirection(pos,function(npos) {
|
|
if(abortEachDir)
|
|
return;
|
|
if($this.board[npos]==-1) {
|
|
var d=Dist(npos,$this.ball);
|
|
if(d>=d0) {
|
|
if(looseData.length==1 && npos==looseData[0].t) return;
|
|
looseData.push({
|
|
i: piece.i,
|
|
f: pos,
|
|
t: npos,
|
|
});
|
|
abortEachDir=true;
|
|
}
|
|
}
|
|
});
|
|
if(looseData.length==2) {
|
|
looseMove={seg:[{
|
|
f: looseData[0].f,
|
|
t: looseData[0].t,
|
|
},{
|
|
f: looseData[1].f,
|
|
t: looseData[1].t,
|
|
}]};
|
|
return looseMove;
|
|
}
|
|
}
|
|
}
|
|
this.mMoves.push(MakeLooseMove());
|
|
}
|
|
|
|
gmStats = (typeof(gmStats)!="undefined") ? gmStats : {
|
|
count: 0,
|
|
time: 0,
|
|
moves: 0,
|
|
segments: 0,
|
|
maxGroup2: 0,
|
|
}
|
|
var time1 = new Date().getTime();
|
|
var deltaTime = time1 - time0;
|
|
|
|
gmStats.count++;
|
|
gmStats.time+=deltaTime;
|
|
gmStats.moves+=moves.length;
|
|
gmStats.segments+=segments.length;
|
|
|
|
if(group2Moves>gmStats.maxGroup2) {
|
|
gmStats.maxGroup2=group2Moves;
|
|
//JocLog("Group2",group2Moves);
|
|
}
|
|
|
|
//JocLog("Duration",deltaTime,"ms",gmStats.time/gmStats.count,"moves",moves.length,gmStats.moves/gmStats.count,"segments",segments.length,gmStats.segments/gmStats.count);
|
|
|
|
//JocLog("segments",segments.length,segments);
|
|
//JocLog("moves",this.mMoves);
|
|
|
|
//JocLog("-----------------------------------------");
|
|
}
|
|
|
|
/* 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.GetRepeatOccurence(this)>2) {
|
|
this.mFinished=true;
|
|
this.mWinner=JocGame.DRAW;
|
|
return;
|
|
}
|
|
|
|
var HEIGHT=aGame.mOptions.height;
|
|
var $this=this;
|
|
|
|
var emptyCount=0;
|
|
var aCount=0;
|
|
var bCount=0;
|
|
|
|
aGame.ScrumEachDirection(this.ball,function(pos) {
|
|
var cell=$this.board[pos];
|
|
if(cell==-1)
|
|
emptyCount++;
|
|
else if(cell>=0) {
|
|
var piece=$this.pieces[cell];
|
|
if(piece.s==JocGame.PLAYER_A) {
|
|
aCount++;
|
|
} else {
|
|
bCount++;
|
|
}
|
|
if(ballRow==0 && piece.p==JocGame.PLAYER_B) {
|
|
$this.mFinished=true;
|
|
$this.mWinner=JocGame.PLAYER_B;
|
|
} else if(ballRow==HEIGHT-1 && piece.p==JocGame.PLAYER_A) {
|
|
$this.mFinished=true;
|
|
$this.mWinner=JocGame.PLAYER_A;
|
|
}
|
|
}
|
|
});
|
|
|
|
var ballRow=aGame.g.Coord[this.ball][0];
|
|
if(ballRow==0 && bCount>0) {
|
|
$this.mFinished=true;
|
|
$this.mWinner=JocGame.PLAYER_B;
|
|
}
|
|
if(ballRow==HEIGHT-1 && aCount>0) {
|
|
$this.mFinished=true;
|
|
$this.mWinner=JocGame.PLAYER_A;
|
|
}
|
|
|
|
var rowCloseA=ballRow/(HEIGHT-1);
|
|
var rowCloseB=1-rowCloseA;
|
|
var rowClose=rowCloseA-rowCloseB;
|
|
|
|
var neighbor=0;
|
|
if(bCount==0)
|
|
neighbor++;
|
|
if(aCount==0)
|
|
neighbor--;
|
|
|
|
var aBDist=0;
|
|
var bBDist=0;
|
|
var bCoord=aGame.g.Coord[this.ball];
|
|
for(var i=0; i<this.pieces.length; i++) {
|
|
var piece=this.pieces[i];
|
|
var coord=aGame.g.Coord[piece.p];
|
|
var d=Math.max(Math.abs(bCoord[0]-coord[0]),Math.abs(bCoord[1]-coord[1]));
|
|
if(piece.s==JocGame.PLAYER_A) {
|
|
aBDist+=d;
|
|
} else {
|
|
bBDist+=d;
|
|
}
|
|
}
|
|
|
|
this.mEvaluation=
|
|
(rowClose * aGame.mOptions.levelOptions.ROW_FACTOR) +
|
|
(neighbor * aGame.mOptions.levelOptions.NEIGHBOR_FACTOR) +
|
|
((bBDist-aBDist) * aGame.mOptions.levelOptions.BDIST_FACTOR)
|
|
;
|
|
|
|
//JocLog("Evaluate",this.mEvaluation,"neighbor",neighbor,"row",rowClose,"abdist",aBDist,"bbdist",bBDist);
|
|
}
|
|
|
|
/* Optional method.
|
|
* 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 pos=0;pos<aBoard.board.length;pos++)
|
|
this.board.push(aBoard.board[pos]);
|
|
this.pieces=[];
|
|
for(var i=0;i<aBoard.pieces.length;i++) {
|
|
var piece=aBoard.pieces[i];
|
|
this.pieces.push({
|
|
s: piece.s,
|
|
p: piece.p,
|
|
a: piece.a,
|
|
sc: piece.sc,
|
|
});
|
|
}
|
|
this.ball=aBoard.ball;
|
|
this.scrum=aBoard.scrum;
|
|
this.first=aBoard.first;
|
|
this.mWho=aBoard.mWho;
|
|
this.zSign=aBoard.zSign;
|
|
}
|
|
|
|
/* Modify the current board instance to apply the move.
|
|
*/
|
|
Model.Board.ApplyMove = function(aGame,move) {
|
|
//JocLog("ApplyMove "+JSON.stringify(move)+" "+JSON.stringify(this));
|
|
var $this=this;
|
|
|
|
var piece0=this.board[move.seg[0].f];
|
|
if(piece0<0) {
|
|
JocLog("!!! ApplyMove",move,this.board,"seg0: no piece at start");
|
|
return;
|
|
}
|
|
if(this.pieces[piece0].s!=this.mWho) {
|
|
console.error("!!! ApplyMove",move,this.board,"seg0: piece at start is not self");
|
|
return;
|
|
}
|
|
var pto0=this.board[move.seg[0].t];
|
|
if(pto0!=-1 && pto0!=-2) {
|
|
JocLog("!!! ApplyMove",move,this.board,"seg0: cell at dest not avail",pto0);
|
|
return;
|
|
}
|
|
if(pto0==-2) {
|
|
if(typeof(move.seg[0].b)=="undefined") {
|
|
JocLog("!!! ApplyMove",move,this.board,"seg0: to ball but no ball depl");
|
|
return;
|
|
}
|
|
}
|
|
if(pto0!=-2) {
|
|
if(typeof(move.seg[0].b)!="undefined") {
|
|
JocLog("!!! ApplyMove",move,this.board,"seg0: no ball but ball depl");
|
|
return;
|
|
}
|
|
}
|
|
var seg=move.seg[0];
|
|
var pIndex=this.board[seg.f];
|
|
this.zSign=aGame.zobrist.update(this.zSign,"board",this.mWho,seg.f);
|
|
this.board[seg.f]=-1;
|
|
this.zSign=aGame.zobrist.update(this.zSign,"board",this.mWho,seg.t);
|
|
this.board[seg.t]=pIndex;
|
|
this.pieces[piece0].p=seg.t;
|
|
if(seg.b!==undefined) {
|
|
this.zSign=aGame.zobrist.update(this.zSign,"board",-2,this.ball);
|
|
this.ball=seg.b;
|
|
this.zSign=aGame.zobrist.update(this.zSign,"board",-2,this.ball);
|
|
this.board[this.ball]=-2;
|
|
}
|
|
|
|
if(typeof(move.seg[1])!="undefined") {
|
|
var piece1=this.board[move.seg[1].f];
|
|
if(piece0==piece1) {
|
|
JocLog("!!! ApplyMove",move,this.board,"seg1: move same piece");
|
|
return;
|
|
}
|
|
if(piece1<0) {
|
|
JocLog("!!! ApplyMove",move,this.board,"seg1: no piece at start");
|
|
JocLog(move,this);
|
|
return;
|
|
}
|
|
if(this.pieces[piece1].s!=this.mWho) {
|
|
JocLog("!!! ApplyMove",move,this.board,"seg1: piece at start is not self");
|
|
return;
|
|
}
|
|
var pto1=this.board[move.seg[1].t];
|
|
if(pto1!=-1 && pto1!=-2) {
|
|
JocLog("!!! ApplyMove",move,this.board,"seg1: cell at dest not avail");
|
|
return;
|
|
}
|
|
if(pto1==-2) {
|
|
if(typeof(move.seg[1].b)=="undefined") {
|
|
JocLog("!!! ApplyMove",move,this.board,"seg1: to ball but no ball depl");
|
|
return;
|
|
}
|
|
}
|
|
if(pto1!=-2) {
|
|
if(typeof(move.seg[1].b)!="undefined") {
|
|
JocLog("!!! ApplyMove",move,this.board,"seg1: no ball but ball depl");
|
|
return;
|
|
}
|
|
}
|
|
var seg=move.seg[1];
|
|
var pIndex=this.board[seg.f];
|
|
this.zSign=aGame.zobrist.update(this.zSign,"board",this.mWho,seg.f);
|
|
this.board[seg.f]=-1;
|
|
this.zSign=aGame.zobrist.update(this.zSign,"board",this.mWho,seg.t);
|
|
this.board[seg.t]=pIndex;
|
|
this.pieces[piece1].p=seg.t;
|
|
if(seg.b!==undefined) {
|
|
this.zSign=aGame.zobrist.update(this.zSign,"board",-2,this.ball);
|
|
this.ball=seg.b;
|
|
this.zSign=aGame.zobrist.update(this.zSign,"board",-2,this.ball);
|
|
this.board[this.ball]=-2;
|
|
}
|
|
}
|
|
|
|
// detect scrum
|
|
var emptyCount=0;
|
|
aGame.ScrumEachDirection(this.ball,function(pos){
|
|
if($this.board[pos]==-1) {
|
|
emptyCount++;
|
|
$this.scrumExit=pos;
|
|
}
|
|
});
|
|
|
|
this.scrum=false;
|
|
if(emptyCount==1) {
|
|
this.scrum=true;
|
|
} else if(emptyCount==0) {
|
|
JocLog("!!! ApplyMove: ball enclosed");
|
|
}
|
|
|
|
var scrummers={};
|
|
if(this.scrum) {
|
|
aGame.ScrumEachDirection(this.ball,function(pos,dir) {
|
|
var pIndex=$this.board[pos];
|
|
if(pIndex>=0)
|
|
scrummers[pIndex]=dir;
|
|
});
|
|
}
|
|
for(var i=0;i<this.pieces.length;i++) {
|
|
var piece=this.pieces[i];
|
|
var angle=0;
|
|
if(scrummers[i]!==undefined) {
|
|
angle=[135,180,-135,90,-90,45,0,-45][scrummers[i]];
|
|
piece.a=angle;
|
|
piece.sc=true;
|
|
} else {
|
|
piece.a=angle+(piece.s==1?180:0);
|
|
piece.sc=false;
|
|
}
|
|
}
|
|
|
|
this.first=false;
|
|
|
|
//JocLog("Applied Move "+JSON.stringify(this));
|
|
}
|
|
|
|
Model.Board.IsValidMove = function(aGame,move) {
|
|
//JocLog("IsValidMove",move);
|
|
var $this=this;
|
|
var empty=0;
|
|
var ball=this.ball;
|
|
var seg1=move.seg[0];
|
|
var seg2=move.seg[1];
|
|
if(seg1.b) ball=seg1.b;
|
|
if(seg2 && seg2.b) ball=seg2.b;
|
|
aGame.ScrumEachDirection(ball,function(pos) {
|
|
var cell=$this.board[pos];
|
|
if(cell<0)
|
|
empty++;
|
|
if(seg1.t==pos || (seg2 && seg2.t==pos))
|
|
empty--;
|
|
if(seg1.f==pos || (seg2 && seg2.f==pos))
|
|
empty++;
|
|
});
|
|
if(empty==0) {
|
|
this.mInvalidMoveMessage=aGame.mStrings['no-surround'];
|
|
return false;
|
|
} else
|
|
return true;
|
|
}
|
|
|
|
Model.Board.GetSignature = function() {
|
|
return this.zSign;
|
|
}
|
|
|