var debugging = false;
var Move = function(from,to,type)
{
    this.from = from;
    this.to = to;
    this.type = type;
}

Move.prototype.toString = function()
{
    return '{from:' + this.from + ',to:' + this.to + ',type:' + this.type + '}';
}




/* Castling: 'C' = king and rook not moved, castling may be possible.
   ' ' king and/or relevant rook moved, castling definitely not legal.
 */
var MECASTLELEFT = 64; /* castling long (white), short (black) */
var MECASTLERIGHT = 65; /* castling short (black), long (white) */
var YOUCASTLELEFT = 66; /* opponent castling left */
var YOUCASTLERIGHT = 67; /* opponent castling right */

 /* En passant state:
    'a'-'h': latest half-move was a double pawn move in this file.
    ' ': latest half-move was not a double pawn move.
 */
var ENPASSANTROW = 68;

var TOMOVE = 69; /* 'M' = my move, 'Y' = your move */
var WHOAMI = 70; /* 'W' or 'B' */

function StandardInitialPosition()
{
    var white = 'rnbqkbnrpppppppp';
    for(rowix=2; rowix<6; ++rowix)
	white += '        ';
    white += white.slice(8, 16).toUpperCase()
	+ white.slice(0, 8).toUpperCase();
    var black = _piece_invertCase(_reverseString(white));

    white += 'CCCC MW';
    black += 'CCCC YB';

    return new Position(white, black, white);
}
function BlankPosition()
{
    var allblank = '                                                                ';
    var white = allblank+'CCCC MW';
    var black = allblank+'CCCC YB';
    return new Position(white, black, white);
}

function Position(white, black, tomove)
{
    /* INV: tomove == (white.charAt(TOMOVE)=='M'? white : black) 
       && white.charAt(WHOAMI)=='W' && black.charAt(WHOAMI)=='B'
     */
    this.white = white; this.black = black; this.tomove = tomove;
}

function _reverseString(s)
{
    var res = [];
    for(var ix=0; ix<s.length; ++ix)
	res[ix] = s.charAt(s.length-1-ix);
    return res.join('')
}

var _piece_opposite_colour = {
    'K': 'k',
    'k': 'K',
    'Q': 'q',
    'q': 'Q',
    'R': 'r',
    'r': 'R',
    'B': 'b',
    'b': 'B',
    'N': 'n',
    'n': 'N',
    'P': 'p',
    'p': 'P',
    ' ': ' '
};

var _piece_score = {
    'k': 1000.0,
    'K': -1000.0,
    'q': 8.0,
    'Q': -8.0,
    'r': 5.0,
    'R': -5.0,
    'b': 3.0,
    'B': -3.0,
    'n': 3.0,
    'N': -3.0,
    'p': 1.0,
    'P': -1.0,
    ' ': 0.0
};
var _my_piece = {
    'K': false,
    'k': true,
    'Q': false,
    'q': true,
    'R': false,
    'r': true,
    'B': false,
    'b': true,
    'N': false,
    'n': true,
    'P': false,
    'p': true,
    ' ': false
};


function _piece_invertCase(s)
{
    var res = [];
    for(var ix=0; ix<s.length; ++ix)
    {
	var c = s.charAt(ix);
	res[res.length] = _piece_opposite_colour[c];
    }
    return res.join('');
}

Position.prototype.toString = function()
{
    var res = '<pre>\n+--------+\n';
    for(rowix=7; rowix>= 0; --rowix)
    {
	var row = this.white.substr(rowix*8, 8); // .replace(/ /g,'&nbsp;');
	res += '|' + row + '|';
	res += '\n';
    }
    res += '+--------+\n</pre>\n';
    return res;
}

Position.prototype._validate = function(doingwhat)
{
    if(!debugging) return this;
    var bummer = null;
    if(this.white.length != 71) 
	bummer = "white is length " + this.white.length + ", 71 expected";
    else if(this.black.length != 71)
	bummer = "black is length " + this.black.length + ", 71 expected";
    else 
    {
	if(this.black.slice(0,64) != _piece_invertCase(_reverseString(this.white.slice(0,64))))
	    bummer = "white and black view differ";
	if(this.tomove === this.white)
	{
	    if(this.white.charAt(TOMOVE)!='M' || this.black.charAt(TOMOVE)!='Y')
		bummer = "seems to be white's move, but then again..";
	}
	else if(this.tomove === this.black)
	{
	    if(this.white.charAt(TOMOVE)!='Y' || this.black.charAt(TOMOVE)!='M')
		bummer = "seems to be black's move, but then again..";
	}
	else
	    bummer = "neither white's nor black's move??";
    }

    if(bummer !== null)
    {
	log.debug(bummer + " after " + doingwhat);
	_PaintView(this.white, 'board2', '../img/board/');
	_PaintView(this.black, 'board3', '../img/board/');
	log.debug("white: " + this.white.replace(/ /g,'*'));
	log.debug("bLACk: " + _piece_invertCase(_reverseString(this.black)).replace(/ /g,'*'));
	log.debug("black: " + this.black.replace(/ /g,'*'));
	alert("white and black view differ after " + doingwhat);
	throw bummer + " after " + doingwhat;
    }
    return this;
}

Position.prototype.white_to_move = function()
{
    return this.white.charAt(TOMOVE)=='M';
}

Position.prototype.black_to_move = function()
{
    return this.black.charAt(TOMOVE)=='M';
}

Position.prototype.move = function(mov)
{
    var white,black,tomove;
    if(this.white_to_move())
    {
	var bw = this._move_helper(mov, this.white, this.black);
	white = bw[0] + bw[2] + bw[3] + bw[4] + 'YW';
	black = bw[1] + bw[3] + bw[2] + bw[4] + 'MB';
	tomove = black;
    }
    else
    {
	var bw = this._move_helper(mov, this.black, this.white);
	black = bw[0] + bw[2] + bw[3] + bw[4] + 'YB';
	white = bw[1] + bw[3] + bw[2] + bw[4] + 'MW';
	tomove = white;
    }

    return new Position(white,black,tomove)._validate(this.PrintMove(mov));
}


Position.prototype._move_helper = function(mov, myside, yourside)
{
    var res = [null, /* my 64 squares */
	       null, /* opp. view 64 squares */
	       myside.substr(MECASTLELEFT,2),
	       myside.substr(YOUCASTLELEFT,2),
	       ' ', /* en passant flag */
	];
    var inv_to = 63-mov.to;
    var inv_from = 63-mov.from;
    var dist = mov.to-mov.from;
    if(mov.type == 'k')
    {
	if(dist == -2)
	{
	    res[2] = '  '; /* no castling after this */
	    if(mov.from == 4) /* white castling? */
	    {
		res[0] = '  kr ' + myside.slice(5, 64);
		res[1] = yourside.slice(0, 59) + ' RK  ';
	    }
	    else /* mov.from == 3: black castling */
	    {
		res[0] = ' kr ' + myside.slice(4, 64);
		res[1] = yourside.slice(0, 60) + ' RK ';
	    }
	    return res;
	}
	else if(dist == 2)
	{
	    res[2] = '  '; /* no castling after this */
	    if(mov.from == 4) /* white castling? */
	    {
		res[0] = myside.slice(0, 4) + ' rk ' + myside.slice(8, 64);
	    }
	    else /* mov.from == 3: black castling */
	    {
		res[0] = myside.slice(0, 3) + ' rk  ' + myside.slice(8, 64);
	    }
	    res[1] = yourside.slice(0, 56) + _reverseString(_piece_invertCase(res[0].slice(0,8)));
	    return res;
	}
    }
    else if(mov.type == 'r')
    {
	if(mov.from == 0)
	    res[2] = ' ' + res[2].charAt(1); /* no more castling left */
	else if(mov.from == 7)
	    res[2] = res[2].charAt(0) + ' '; /* no more castling right */
    }
    else if(mov.type == 'p')
    {
	if(dist == 16)
	{
	    /* to inidicate that en passant is now possible, store the
	       column name a-h from the opponents view */
	    res[4] = String.fromCharCode(104-mov.to%8);
	}
	else if((mov.to%8) != (mov.from%8) && myside.charAt(mov.to)==' ')
	{
	    /* En passant: Pawn move that changes file yet there seems to
	       be no piece to take.
	       Remove the taken piece. The generic
	    */
	    myside = myside.slice(0, mov.to-8) + ' ' + myside.slice(mov.to-7, 64);
	    yourside = yourside.slice(0, 71-mov.to) + ' ' + yourside.slice(72-mov.to, 64);
	}
    }

    if(mov.to > mov.from)
    {
	res[0] = myside.slice(0, mov.from) + ' ' 
	    + myside.slice(mov.from+1, mov.to) 
	    + mov.type + myside.slice(mov.to+1, 64);
    }
    else
    {
	res[0] = myside.slice(0, mov.to) + mov.type 
	    + myside.slice(mov.to+1, mov.from) 
	    + ' ' + myside.slice(mov.from+1, 64);
    }
    if(inv_to > inv_from)
    {
	res[1] = yourside.slice(0, inv_from) + ' ' 
	    + yourside.slice(inv_from+1, inv_to)
	    + _piece_opposite_colour[mov.type] + yourside.slice(inv_to+1, 64);
    }
    else
    {
	res[1] = yourside.slice(0, inv_to) 
	    + _piece_opposite_colour[mov.type] 
	    + yourside.slice(inv_to+1, inv_from) 
	    + ' ' + yourside.slice(inv_from+1, 64);
    }
    return res;
}

Position.prototype.IsInCheck = function()
{
    var kingsq = this.tomove.indexOf('K');
    return _attacks_square(this.tomove, kingsq);
}

Position.prototype.null_move = function()
{
    var white,black,tomove;
    if(this.white_to_move())
    {
	white = this.white.slice(0,68)+' YW';
	black = this.black.slice(0,68)+' MB';
	tomove = black;
    }
    else
    {
	white = this.white.slice(0,68)+' MW';
	black = this.black.slice(0,68)+' YB';
	tomove = white;
    }
    return new Position(white,black,tomove)._validate('null move');
}

Position.prototype.LegalMoves = function()
{
    var res = [];
    var pos = this.tomove;
    for(var fromsq=0; fromsq<64; ++fromsq)
    {
	var piece = pos.charAt(fromsq);
	if(_my_piece[piece])
	{
	    var fromfile = fromsq%8;
	    var rawmoves = _rawmove_table[piece+fromsq];
	    for(var seqix=0; seqix<rawmoves.length; ++seqix)
	    { 
		var seq = rawmoves[seqix];
		for(var tosqix=0; tosqix<seq.length; ++tosqix)
		{
		    var tosq = seq[tosqix];
		    var dist = tosq - fromsq;
		    var displacepiece = pos.charAt(tosq);
		    if(displacepiece == ' ')
		    {
			if(piece == 'p')
			{
			    var tofile = tosq%8;
			    if(tofile != fromfile)
			    {
				/* pawn changing file must capture
				   piece, however if it's an
				   en passant capture, the piece will
				   not be at the destination square. */
				var epfilecharcode = pos.charCodeAt(ENPASSANTROW);
				if(epfilecharcode != 32)
				{
				    if(tofile == epfilecharcode-97)
				    {
					var torank = (tosq-tofile)/8;
					if(torank != 5)
					    continue;
					/*
					  else: en passant, you may continue 
					*/
				    }
				    else continue; /* wrong file for the e.p. */
				}
				else continue; /* no pending e.p. */
			    }
			}

			else if(piece == 'k')
			{
			    if(dist==-2)
			    {
				if(pos.charAt(fromsq-1) != ' ')
				    break;
				if(pos.charAt(MECASTLELEFT)!='C')
				    break;
				var opponents_view = this.white_to_move()? this.black : this.white;
				if(_attacks_square(opponents_view, 63-fromsq)
				   || _attacks_square(opponents_view, 63-(fromsq-1)))
				    break; /* no castle if in or passing over check */
			    }
			    else if(dist == 2)
			    {
				if(pos.charAt(fromsq+1) != ' ')
				    break;
				if(pos.charAt(MECASTLERIGHT)!='C')
				    break;
				var opponents_view = this.white_to_move()? this.black : this.white;
				if(_attacks_square(opponents_view, 63-fromsq)
				   || _attacks_square(opponents_view, 63-(fromsq+1)))
				    break; /* no castle if in or passing over check */
			    }
			}
		    }

		    else if(displacepiece==displacepiece.toUpperCase())
		    {
			/* Capture; tosq is not empty and contains one
			   of the opponents pieces. */
			if(piece == 'p')
			{
			    if(fromsq%8 == tosq%8)
				break; /* pawn forward cannot take piece */
			}
			else if(piece == 'k')
			{
			    if(dist==-2 || dist==2)
				break; /* no castling into a piece */
			}
		    }

		    else 
			/* displacepiece is one of my own pieces */
			break; /* can't capture that */

		    /*
		      As there has been no break, the move is approved
		      may be entered into.
		    */

		    if(piece == 'p' && tosq>=56)
		    {
			res[res.length] = new Move(fromsq, tosq, 'q');
			res[res.length] = new Move(fromsq, tosq, 'r');
			res[res.length] = new Move(fromsq, tosq, 'b');
			res[res.length] = new Move(fromsq, tosq, 'n');
		    }
		    else
			res[res.length] = new Move(fromsq, tosq, piece);


		    /*
		      Make sure the move doesn't leave own king in check.

		      this can be done much more efficiently, by
		      computing just once at the beginning of this
		      functin

		      1) whether the king is in check.
		      2) the set of "x-ray pieces", which is any piece
		         that is the only one in a raw-moves line
		         before it encounters the king.

		      then the test can be skipped if it is not the
		      king being moved, the king was not in check and
		      the from-square is not in the xray set.

		      add to that
		      3) the threat set, which is the set of positions
		         that enemy pieces have aim at, including the
		         first met of the enemy's own pieces.

		      then any king moves to somewhere not in the
		      threat set can be accepted, and any king moves
		      to somewhere in the threat set can be rejected.
		    */
		    var new_pos = this.move(res[res.length-1]);
		    var kingsq = new_pos.tomove.indexOf('K');
		    if(_attacks_square(new_pos.tomove, kingsq))
		    {
			if(piece == 'p' && tosq>=56)
			    res.length -= 4;
			else
			    --res.length;
		    }

		    if(displacepiece != ' ') 
			break; /* skip remaining chinese chess xray-moves */
		}
	    }
	}
    }
    return res;
}

function _PaintView(view, domname, pieceimage_prefix)
{
    var isblackpow = view.charAt(WHOAMI)=='B';
    var container = document.getElementById(domname);
    var imgs = container.getElementsByTagName('IMG');
    for(var imgix=0; imgix<imgs.length; ++imgix)
    {
	var img = imgs[imgix];
	var imgid = img.name;
	var sqno = _coord_to_sqno(imgid);
	if(sqno !== null)
	{
	    var imgfn = _image_for(isblackpow, (sqno%2)!=(Math.floor(sqno/8)%2), view.charAt(sqno));
	    img.src = pieceimage_prefix + imgfn;
	}
    }
}


Position.prototype.Paint = function(blackview, domname, pieceimage_prefix)
{
    _PaintView(blackview? this.black : this.white, domname, pieceimage_prefix);
}


function _image_for(black_pow, white_bg, piece)
{
    var bgcol = white_bg? 'w':'b';
    var piecelower;
    var piececol;
    if(piece == ' ')
    {
	piecelower = 'f';
	piececol = 'e';
    }
    else
    {
	piecelower = piece.toLowerCase();
	piececol = ((piece != piecelower)==black_pow)? 'w' : 'b';
    }
    return piececol + piecelower + bgcol + '.gif';
}

Position.prototype.Evaluation = function()
{
    var pieceeval = 0;
    for(var sq=0; sq<64; ++sq)
    {
	var piece = this.tomove.charAt(sq);
	pieceeval += _piece_score[piece];
    }
    return pieceeval;

    var mymoves = this.LegalMoves();
    var opponent_moves = this.null_move().LegalMoves();

    return pieceeval + (mymoves.length - opponent_moves.length)
    /(mymoves.length+opponent_moves.length+1);
}


Position.prototype.PrintMove = function(move) 
{
    var piece = this.tomove.charAt(move.from);
    var captures = this.tomove.charAt(move.to);
    var capturetext = captures==' '? '':'x';
    var invert = this.white_to_move();
    var from = invert? move.from : (63-move.from);
    var to = invert? move.to : (63-move.to);
    var piecetext = (piece=='p'? '' : piece.toUpperCase());
    return piecetext + _sqno_to_coord(from) + capturetext + _sqno_to_coord(to);
}


Position.prototype.IdentifyMove = function(moveastext)
{
    if(moveastext.length < 2) return null;

    var lastChar = moveastext.slice(-1);
    var is_check = lastChar == '+';
    if(is_check) moveastext = moveastext.slice(0, -1);
    var is_mate = lastChar == '#';
    if(is_mate) moveastext = moveastext.slice(0, -1);
    var firstChar = moveastext.charAt(0);
    if(firstChar >= 'i' && firstChar <= 'z')
	moveastext = firstChar.toUpperCase() + moveastext.slice(1);
    
    var promoteto = moveastext.slice(-1);
    if('QRBNq'.indexOf(promoteto) >= 0)
	moveastext = moveastext.slice(0,-1);
    else
	promoteto = null;

    var moves = this.LegalMoves();
    var best_move = null;
    var best_ix = null;
    for(var moveix in moves)
    {
	var move = moves[moveix];
	var candtexts = this.CandidateTextsForMove(move);
	for(var candix in candtexts)
	{
	    var candtext = candtexts[candix];
	    if(candtext == moveastext)
	    {
		if(best_ix === null || candix < best_ix)
		{
		    best_ix = candix;
		    best_move = move;
		}
		break;
	    }
	}
    }

    if(best_move === null)
    {
	/* xx debug */
	for(var moveix in moves)
	{
	    var move = moves[moveix];
	    var candtexts = this.CandidateTextsForMove(move);
	    // log.debug('Move: ' + move + ' prints as: ' + candtexts.join(', '));
	}
	
    }

    return best_move;
}
		
	

Position.prototype.CandidateTextsForMove = function(move)
/*
  Returns a list of reasonable ways to print the given move (assumed
  legal in the position).  "e.p." and promotion and "+" for check or
  "#" for mate excluded.
*/
{
    var invert = this.black_to_move();
    var from = invert? (63-move.from) : move.from;
    var to = invert? (63-move.to) : move.to;
    var fromrank =  String.fromCharCode(49+(from-from%8)/8);
    var fromfile = String.fromCharCode(97+(from%8));
    var torank =  String.fromCharCode(49+(to-to%8)/8);
    var tofile = String.fromCharCode(97+(to%8));
    var totext = tofile+torank;
    var fromtext = fromfile+fromrank;
    var piece = this.tomove.charAt(move.from);
    var piecetext = piece.toUpperCase();
    var captures = this.tomove.charAt(move.to);
    var is_capture = (captures!=' ');
    var capturetext = is_capture? '':'x';
    var dist = to-from;

    if(piece == 'p')
    {
	if(is_capture || tofile != fromfile)
	{
	    return [fromfile+'x'+totext,
		    fromtext+'x'+totext,
		    fromfile+'x'+tofile,
		    fromfile+tofile,
		    piecetext+fromtext+'x'+totext,
		    fromtext+totext,
		    piecetext+fromtext+totext,
		];
	}
	else
	{
	    return [totext,
		    fromtext+totext,
		    piecetext+fromtext+totext,
		    piecetext+totext,
		];
	}
    }
    else if(piece == 'k' && (dist==2 || dist==-2))
    {
	if((from==3) == (dist==2))
	    return ['O-O-O', '0-0-0'];
	else
	    return ['O-O', '0-0'];
    } 
    else
    {
	if(is_capture)
	{
	    return [
		piecetext + 'x' + totext,
		piecetext + fromtext + 'x' + totext,
		fromtext + 'x' + totext,
		fromtext + totext,
		piecetext + fromfile + 'x' + totext,
		piecetext + fromrank + 'x' + totext,
		piecetext + fromtext + totext,
		];
	}
	else
	{
	    return [
		piecetext + totext,
		piecetext + fromtext + totext,
		piecetext + fromfile + totext,
		piecetext + fromrank + totext,
		fromtext + totext,
		];
	}
    }
}

function _coord_to_sqno(coord)
{
    if(coord.length != 2) 
	return null;

    var cfile = coord.charCodeAt(0)-97;
    var crank = coord.charCodeAt(1)-49;
    if(crank < 0 || crank > 7)
	return null;
    if(cfile < 0 || cfile > 7)
	return null;
    return crank*8+cfile;
}
function _sqno_to_coord(sqno)
{
    return String.fromCharCode(97+(sqno%8)) + String.fromCharCode(49+(sqno-sqno%8)/8);
}


function _parse_move(movetxt, white_to_move)
{
    var fromsqno = _coord_to_sqno(movetxt.slice(0,2));
    var tosqno = _coord_to_sqno(movetxt.slice(2,4));
    var piece = movetxt[4];
    if(white_to_move)
	return new Move(fromsqno, tosqno, piece.toLowerCase());
    else
	return new Move(63-fromsqno, 63-tosqno, piece.toLowerCase());
}

function Game(initial_board, current_move, movelist, domname, pieceimage_prefix)
{
    this.tags = {};
    this.domname = domname;
    this.pieceimage_prefix = pieceimage_prefix;
    this.blackview = false;

    this.initial_board = initial_board;
    if(current_move === null)
	this.current_move = 0;
    else
	this.current_move = current_move;
    
    this.moves = [];
    this.position_before_move = [initial_board];

    var white_to_move = initial_board.white_to_move();
    for(var movix in movelist)
    {
	var movetxt = movelist[movix];
	var newmove = _parse_move(movetxt, white_to_move);
	this.AppendMove(newmove);
	white_to_move = !white_to_move;
    }
}

Game.prototype.AppendMove = function(newmove)
{
    var last_position = this.position_before_move[this.moves.length];
    this.moves[this.moves.length] = newmove;
    this.position_before_move[this.moves.length] = last_position.move(newmove);
}

Game.prototype.CurrentPosition = function()
{
    return this.PositionAtMove(this.current_move);
}

Game.prototype.FinalPosition = function()
{
    return this.PositionAtMove(this.moves.length);
}


Game.prototype.PositionAtMove = function(moveno)
{
    if(moveno > this.moves.length)
	throw "The game is only " + this.moves.length + " ply; cannot show ply=" + moveno;
    if(moveno >= this.position_before_move.length)
	alert("Position at " + moveno + " but only up to " + this.position_before_move.length + " available??");
    return this.position_before_move[moveno];
}

Game.prototype.Paint = function()
{
    this.CurrentPosition().Paint(this.blackview, this.domname, this.pieceimage_prefix);
}


Game.prototype.UIPrev = function()
{
    if(this.current_move > 0) --this.current_move;
    this.Paint();
}
Game.prototype.UINext = function()
{
    if(this.current_move < this.moves.length)
	++this.current_move;
    this.Paint();
}
Game.prototype.UIFlip = function(flipbutton)
{
    this.blackview = !this.blackview;
    flipbutton.innerHTML = this.blackview? '&#9675;' : '&#9679;';
    this.Paint();
}
Game.prototype.UIBegin = function()
{
    this.current_move = 0;
    this.Paint();
}
Game.prototype.UIEnd = function()
{
    this.current_move = this.moves.length;
    this.Paint();
}

function _ComputeRawMoves(piece, sqno)
{
    var res;
    var s;

    switch(piece)
    {
    case 'p':
    {
	var forward;
	if(sqno <= 16)
	    forward = [sqno+8, sqno+16];
	else if(sqno <= 56)
	    forward = [sqno+8];
	else
	    return [];
	if(sqno % 8 != 0)
	    left = [sqno+7];
	else
	    left = [];
	if(sqno % 8 != 7)
	    right = [sqno+9];
	else
	    right = [];
	res = [right, forward, left];
    }
    break;

    case 'b': case 'q':
    {
	var file = sqno%8;
	var northwest = [];
	var northeast = [];
	var southwest = [];
	var southeast = [];

	for(s=sqno+9; s<64 && s%8 > file; s += 9)
	    northeast[northeast.length] = s;
	for(s=sqno+7; s<64 && s%8 < file; s += 7)
	    northwest[northwest.length] = s;
	for(s=sqno-9; s>=0 && s%8 < file; s -= 9)
	    southwest[southwest.length] = s;
	for(s=sqno-7; s>=0 && s%8 > file; s -= 7)
	    southeast[southeast.length] = s;

	if(piece == 'b')
	{
	    res = [northeast, northwest, southwest, southeast];
	    break;
	}
    }
    /* case 'q' falls thru to 'r' */

    case 'r':
    {
	var east = [];
	var north = [];
	var west = [];
	var south = [];
	
	for(s=sqno+1; s%8 != 0; ++s)
	    east[east.length] = s;
	for(s=sqno+8; s<64; s+=8)
	    north[north.length] = s;
	for(s=sqno-1; s>=(sqno-sqno%8); --s)
	    west[west.length] = s;
	for(s=sqno-8; s>=0; s-=8)
	    south[south.length] = s;

	if(piece == 'r')
	    res = [east, north, west, south];
	else
	    /* piece = 'q': Queen */
	    res = [east, northeast, north, northwest, west, southwest, south, southeast];
    }
    break;

    case 'n':
    {
	var file = sqno % 8;
	var rank = (sqno-file) / 8;

	res = [];
	if(file < 6 && rank < 7)
	    res[res.length] = [sqno+10];
	if(file < 7 && rank < 6)
	    res[res.length] = [sqno+17];
	if(file >= 1 && rank < 6)
	    res[res.length] = [sqno+15];
	if(file >= 2 && rank < 7)
	    res[res.length] = [sqno+6];
	if(file >= 2 && rank >= 1)
	    res[res.length] = [sqno-10];
	if(file >= 1 && rank >= 2)
	    res[res.length] = [sqno-17];
	if(file < 7 && rank >= 2)
	    res[res.length] = [sqno-15];
	if(file < 6 && rank >= 1)
	    res[res.length] = [sqno-6];
    }
    break;

    case 'k':
    {
	var file = sqno % 8;
	var rank = (sqno-file) / 8;
	res = [];
	if(file < 7)
	{
	    res[res.length] = [sqno+1];
	    if(rank == 0 && (file == 3 || file == 4))
		res[res.length] = [sqno+2];
	}
	if(file < 7 && rank < 7)
	    res[res.length] = [sqno+9];
	if(rank < 7)
	    res[res.length] = [sqno+8];
	if(file > 0 && rank < 7)
	    res[res.length] = [sqno+7];
	if(file > 0)
	{
	    res[res.length] = [sqno-1];
	    if(rank == 0 && (file == 3 || file == 4))
		res[res.length] = [sqno-2];
	}
	if(file > 0 && rank > 0)
	    res[res.length] = [sqno-9];
	if(rank > 0)
	    res[res.length] = [sqno-8];
	if(file < 7 && rank > 0)
	    res[res.length] = [sqno-7];
    }
    break;
    
    default: 
	log.debug(piece + ', huh, what kind of piece is that? one of the opponents, maybe?');
    }
    return res;
}


function _attacks_square(posview, sq)
{
    var lines = _attacklines(posview, sq);
    for(var lineix in lines)
    {
	var line = lines[lineix];
	var empty = true;
	for(var posix in line)
	{
	    var pos = line[posix];
	    if(posview.charAt(pos) != ' ')
	    {
		empty = false;
		break;
	    }
	}
	if(empty) return true;
    }
    return false;
}


function RankedMoves(position, method)
{
    var p = position;
    var legal_moves = p.LegalMoves();
    var moveevals = [];
    for(var movix=0; movix<legal_moves.length; ++movix)
    {
	var move = legal_moves[movix];
	var new_p = p.move(move);
	var score = (method>0)? (-new_p.Evaluation()) : null;
	moveevals[moveevals.length] = {score: score, move: move, movetxt: p.PrintMove(move)};
    }
    if(method > 0)
	moveevals.sort(function(a,b) { return b.score - a.score; });
    return moveevals;
}

function MoveRank(position, move, method)
{
    var ranked = RankedMoves(position, method);
    for(var ix in ranked)
    {
	var rmove = ranked[ix].move;
	if(rmove.to == move.to && rmove.from == move.from && rmove.type == move.type)
	    return { rank: ix, outof: ranked.length };
    }
    log.debug('RankedMoves: ' + ranked.join(', '));
    log.debug('Move: ' + move);
    throw "Attempt to rank an illegal move";
}

function SuggestMove(position)
{
    var p = position;
    var moveevals = RankedMoves(p, 1);
    for(var mix=0; mix<moveevals.length; ++mix)
    {
	var moveeval = moveevals[mix];
	log.debug(moveeval.movetxt + ' score: ' + moveeval.score.toFixed(3));
    }
    return moveevals[0];
}
    

_attackline_cache = {}

function _attacklines(posview, sq)
/*
  The "attacklines" for a position and a square.

  Each entry is a list of squares, such that is all squares are empty,
  then some piece is attacking the 'sq' square.
*/
{
    var res = [];
    for(var atsq=0; atsq<63; ++atsq)
    {
	var attacker = posview.charAt(atsq);
	var cacheid = atsq+attacker+sq;
	var cache_attack = _attackline_cache[cacheid];
	if(cache_attack !== null)
	{
	    if(cache_attack === undefined)
	    {
		cache_attack = _attackline(atsq, attacker, sq);
		_attackline_cache[cacheid] = cache_attack;
		if(cache_attack !== null)
		    res[res.length] = cache_attack;
	    }
	    else
		res[res.length] = cache_attack;
	}
    }
    return res;
}



function _attackline(atsq, attacker, sq)
{
    if(_my_piece[attacker])
    {
	var rawmoves = _rawmove_table[attacker+atsq];
	for(var seqix=0; seqix<rawmoves.length; ++seqix)
	{ 
	    var seq = rawmoves[seqix];
	    for(var tosqix=0; tosqix<seq.length; ++tosqix)
	    {
		if(seq[tosqix] == sq)
		{
		    if((attacker != 'k' || sq-atsq!=-2 || sq-atsq!=2)
		       && (attacker != 'p' || sq-atsq==7 || sq-atsq==9))
		    {
			return seq.slice(0, tosqix);
		    }
		}
	    }
	}
    }
    return null;
}

_rawmove_table = {}
function _Fill_Rawmove_Table(piece)
{
    for(var sq=0; sq<64; ++sq)
    {
	_rawmove_table[piece+sq] = _ComputeRawMoves(piece, sq);
    }
}
_Fill_Rawmove_Table('k');
_Fill_Rawmove_Table('q');
_Fill_Rawmove_Table('r');
_Fill_Rawmove_Table('b');
_Fill_Rawmove_Table('n');
_Fill_Rawmove_Table('p');

