jocly/src/games/scrum/scrum-model.js
2017-05-11 15:30:28 +02:00

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;
}