
(function() {
	
	Model.Game.cbBoardGeometryCubic = function(width,height,floors,fences) {

		var boardSize = 2*width*height+2*width*floors+2*height*floors;
		var planes = [{
			cols:width,
			rows:height,
			start:0,
		},{
			cols:height,
			rows:floors,
			start:width*height,
		},{
			cols:width,
			rows:floors,
			start:width*height+height*floors,
		},{
			cols:width,
			rows:floors,
			start:width*height+height*floors+width*floors,
		},{
			cols:height,
			rows:floors,
			start:width*height+height*floors+2*width*floors,
		},{
			cols:width,
			rows:height,
			start:width*height+2*height*floors+2*width*floors,
		}];
		
		function C(pos) {
			return pos%width;
		}
		function R(pos) {
			return Math.floor((pos%(width*height))/width);
		}
		function F(pos) {
			return Math.floor(pos/(width*height));
		}
		function P(pos) {
			for(var i=1;i<6;i++)
				if(pos<planes[i].start)
					return i-1;
			return 5;
		}
		function POS(c,r,f) {
			return planes[f].start+r*planes[f].cols+c;
		}
		function Graph(pos,delta) {
			var c0=C(pos);
			var r0=R(pos);
			var c=c0+delta[0]
			while(c<0)
				c+=width;
			c%=width;
			var r=r0+delta[1];
			if(r<0 || r>=height)
				return null;
			return POS(c,r);
		}
		
		var distance={};
		for(var pos=0;pos<boardSize;pos++) {
			distance[pos]={};
			
			for(var pos1=0;pos1<boardSize;pos1++)
				distance[pos][pos1]=1;

			distance[pos][pos]=0;
		}
		/*
		var steps=[[1,-1],[1,0],[1,1],[0,-1],[0,1],[-1,-1],[-1,0],[-1,-1]];
		var modifs=true;
		while(modifs) {
			modifs=false;
			for(var pos=0;pos<boardSize;pos++) {
				steps.forEach(function(delta) {
					var pos1=Graph(pos,delta);
					if(pos1==null)
						return;
					if(distance[pos][pos1]!=1) {
						distance[pos][pos1]=1;
						distance[pos1][pos]=1;
						modifs=true;
					} 
					for(var pos2=0;pos2<boardSize;pos2++) {
						if(pos2==pos)
							continue;
						if(distance[pos1][pos2]===undefined && distance[pos][pos2]!==undefined) {
							distance[pos1][pos2]=distance[pos][pos2]+1;
							distance[pos2][pos1]=distance[pos][pos2]+1;
							modifs=true;
						} else if(distance[pos1][pos2]!==undefined && distance[pos][pos2]!==undefined && distance[pos1][pos2]>distance[pos][pos2]+1) {
							distance[pos1][pos2]=distance[pos][pos2]+1;
							distance[pos2][pos1]=distance[pos][pos2]+1;
							modifs=true;
						}
					}
				});
			}
		}
		*/
		
		var distEdges={};
		for(var pos=0;pos<boardSize;pos++)
			distEdges[pos]=1;
		/*
		var modifs=true;
		while(modifs) {
			modifs=false;
			for(var pos=0;pos<boardSize;pos++) {
				if(pos in distEdges)
					continue;
				steps.forEach(function(delta) {
					var pos1=Graph(pos,delta);
					if(pos1==null)
						distEdges[pos]=1;
					else if(pos1 in distEdges) {
						if(!(pos in distEdges) || distEdges[pos]>distEdges[pos1]+1) {
							distEdges[pos]=distEdges[pos1]+1;
							modifs=true;
						}
					}
				});
			}
		}
		*/
		
		function PosName(pos) {
			 return String.fromCharCode(("a".charCodeAt(0))+C(pos)) + (R(pos)+1);
		}
		function PosByName(str) {
			var m=/^([a-z])([0-9]+)$/.exec(str);
			if(!m)
				return -1;
			var c=m[1].charCodeAt(0)-"a".charCodeAt(0);
			var r=parseInt(m[2])-1;
			return POS(c,r);
		}
		function CompactCrit(pos,index) {
			if(index==0)
				return String.fromCharCode(("a".charCodeAt(0))+C(pos));
			else if(index==1)
				return (R(pos)+1);
			else
				return null;
		}
		function GetDistances() {
			return distance;
		}
		
		return {
			boardSize: boardSize,
			width: width,
			height: height,
			depth: floors,
			C: C,
			R: R,
			F: F,
			P: P,
			POS: POS,
			Graph: Graph, 
			PosName: PosName,
			PosByName: PosByName,
			CompactCrit: CompactCrit,
			GetDistances: GetDistances,
			distEdge: distEdges,
			corners: null,
			planes: planes,
			fences: fences || [],
		};
	}

	Model.Game.cbPawnGraph = function(geometry,side) {
		var $this=this;
		var graph={};
		for(var pos=0;pos<geometry.boardSize;pos++) {
			var directions=[];
			var pos1=geometry.Graph(pos,[0,side]);
			if(pos1!=null)
				directions.push($this.cbTypedArray([pos1 | $this.cbConstants.FLAG_MOVE]));
			[-1,1].forEach(function(dc) {
				var pos2=geometry.Graph(pos,[dc,side]);
				if(pos2!=null)
					directions.push($this.cbTypedArray([pos2 | $this.cbConstants.FLAG_CAPTURE]));				
			});
			graph[pos]=directions;
		}
		return graph;
	}
		
	Model.Game.cbInitialPawnGraph = function(geometry,side) {
		var $this=this;
		var graph={};
		for(var pos=0;pos<geometry.boardSize;pos++) {
			var directions=[];
			var pos1=geometry.Graph(pos,[0,side]);
			if(pos1!=null) {
				var direction=[pos1 | $this.cbConstants.FLAG_MOVE];
				var pos2=geometry.Graph(pos1,[0,side]);
				if(pos2!=null)
					direction.push(pos2 | $this.cbConstants.FLAG_MOVE);
				directions.push($this.cbTypedArray(direction));
			}
			[-1,1].forEach(function(dc) {
				var pos2=geometry.Graph(pos,[dc,side]);
				if(pos2!=null)
					directions.push($this.cbTypedArray([pos2 | $this.cbConstants.FLAG_CAPTURE]));				
			});
			graph[pos]=directions;
		}
		return graph;
	}

	Model.Game.cbKingGraph = function(geometry) {
		return this.cbShortRangeGraph(geometry,[[-1,-1],[-1,0],[-1,1],[0,-1],[0,1],[1,-1],[1,0],[1,1]]);
	}

	Model.Game.cbKnightGraph = function(geometry) {
		return this.cbShortRangeGraph(geometry,[[2,-1],[2,1],[-2,-1],[-2,1],[-1,2],[-1,-2],[1,2],[1,-2]]);
	}

	Model.Game.cbHorseGraph = function(geometry) {
		var $this=this;
		var graph={};
		for(var pos=0;pos<geometry.boardSize;pos++) {
			graph[pos]=[];
			[[1,0,2,-1],[1,0,2,1],[-1,0,-2,-1],[-1,0,-2,1],[0,1,-1,2],[0,-1,-1,-2],[0,1,1,2],[0,-1,1,-2]].forEach(function(desc) {
				var pos1=geometry.Graph(pos,[desc[0],desc[1]]);
				if(pos1!=null) {
					var pos2=geometry.Graph(pos,[desc[2],desc[3]]);
					if(pos2!=null)
						graph[pos].push($this.cbTypedArray([pos1 | $this.cbConstants.FLAG_STOP, pos2 | $this.cbConstants.FLAG_MOVE | $this.cbConstants.FLAG_CAPTURE]));
				}
			});
		}
		return graph;
	}

	
	Model.Game.cbRookGraph = function(geometry) {
		return this.cbLongRangeGraph(geometry,[[0,-1],[0,1],[-1,0],[1,0]]);
	}
	
	Model.Game.cbBishopGraph = function(geometry) {
		return this.cbLongRangeGraph(geometry,[[1,-1],[1,1],[-1,1],[-1,-1]]);
	}
	
	Model.Game.cbQueenGraph = function(geometry) {
		return this.cbLongRangeGraph(geometry,[[0,-1],[0,1],[-1,0],[1,0],[1,-1],[1,1],[-1,1],[-1,-1]]);
	}

	Model.Game.cbXQGeneralGraph = function(geometry,confine) {
		var $this=this;
		var graph={};
		for(var pos=0;pos<geometry.boardSize;pos++) {
			graph[pos]=[];
			[[-1,0,false],[0,-1,true],[0,1,true],[1,0,false]].forEach(function(delta) {
				var direction=[];
				var pos1=geometry.Graph(pos,delta);
				if(pos1!=null) {
					if(!confine || (pos1 in confine))
					direction.push(pos1 | $this.cbConstants.FLAG_MOVE | $this.cbConstants.FLAG_CAPTURE);
					if(delta[2]) {
						var pos2=geometry.Graph(pos1,delta);
						while(pos2!=null) {
							if(!confine || (pos2 in confine))
								direction.push(pos2 | $this.cbConstants.FLAG_CAPTURE | $this.cbConstants.FLAG_CAPTURE_KING);
							else
								direction.push(pos2 | $this.cbConstants.FLAG_STOP);
							pos2=geometry.Graph(pos2,delta);
						}
					}
				}
				if(direction.length>0)
					graph[pos].push($this.cbTypedArray(direction));
			});
		}
		return graph;
	}
	
	Model.Game.cbXQSoldierGraph = function(geometry,side) {
		return this.cbShortRangeGraph(geometry,[[0,side]]);
	}

	Model.Game.cbXQPromoSoldierGraph = function(geometry,side) {
		return this.cbShortRangeGraph(geometry,[[0,side],[-1,0],[1,0]]);
	}

	Model.Game.cbXQAdvisorGraph = function(geometry,confine) {
		return this.cbShortRangeGraph(geometry,[[1,1],[-1,1],[1,-1],[-1,-1]],confine);
	}

	Model.Game.cbXQCannonGraph = function(geometry) {
		return this.cbLongRangeGraph(geometry,[[0,-1],[0,1],[-1,0],[1,0]],null,this.cbConstants.FLAG_MOVE | this.cbConstants.FLAG_SCREEN_CAPTURE);
	}
	
	Model.Game.cbXQElephantGraph = function(geometry,confine) {
		var $this=this;
		var graph={};
		for(var pos=0;pos<geometry.boardSize;pos++) {
			graph[pos]=[];
			if(confine && !(pos in confine))
				continue;
			[[1,1,2,2],[1,-1,2,-2],[-1,1,-2,2],[-1,-1,-2,-2]].forEach(function(desc) {
				var pos1=geometry.Graph(pos,[desc[0],desc[1]]);
				if(pos1!=null) {
					var pos2=geometry.Graph(pos,[desc[2],desc[3]]);
					if(pos2!=null && (!confine || (pos2 in confine)))
						graph[pos].push($this.cbTypedArray([pos1 | $this.cbConstants.FLAG_STOP, pos2 | $this.cbConstants.FLAG_MOVE | $this.cbConstants.FLAG_CAPTURE]));
				}
			});
		}
		return graph;
	}
	
	Model.Game.cbSilverGraph = function(geometry,side) {
		return this.cbShortRangeGraph(geometry,[[0,side],[-1,-1],[-1,1],[1,-1],[1,1]]);
	}
	
	Model.Game.cbFersGraph = function(geometry,side) {
		return this.cbShortRangeGraph(geometry,[[-1,-1],[-1,1],[1,-1],[1,1]]);
	}	

	Model.Game.cbElephantGraph = function(geometry,side) {
		return this.cbShortRangeGraph(geometry,[[-2,-2],[-2,2],[2,-2],[2,2]]);
	}	

	Model.Game.cbSchleichGraph = function(geometry,side) {
		return this.cbShortRangeGraph(geometry,[[-1,0],[1,0],[0,-1],[0,1]]);
	}	
	
	Model.Game.cbAlfilGraph = function(geometry,side) {
		return this.cbShortRangeGraph(geometry,[[-2,-2],[-2,2],[2,2],[2,-2]]);
	}	

	Model.Game.cbCubicRookGraph = function(geometry) {
		return this.cbLongRangeGraph(geometry,[[0,-1],[0,1],[-1,0],[1,0]],null,null,Math.max(geometry.width,geometry.height)-1);
	}

	Model.Game.cbCircularPawnGraph = function(geometry,cc,range) {
		var moveGraph = this.cbLongRangeGraph(geometry,cc?[[1,0]]:[[-1,0]],null,this.cbConstants.FLAG_MOVE,range);
		var captGraph = this.cbShortRangeGraph(geometry,cc?[[1,1],[1,-1]]:[[-1,1],[-1,-1]],null,this.cbConstants.FLAG_CAPTURE);
		return this.cbMergeGraphs(geometry,moveGraph,captGraph);
	}
	
})();
