<?php

// Move validation for myChess
// ```````````````````````````

//TODO: Test whether the king castles out of/through check

namespace {
	require_once 'common.php';
}

namespace chess {	


	/*-----------------------*/
	/* Public validation api */
	/*-----------------------*/


	// --> [true | false]
	function canMoveStr($board_str, $from_str, $to_str, $turn) {
		
		// Parse board
		$board  = explode('|', $board_str);
		if (count($board) !== 64) return false;
		
		// Parse pieces
		$p_from = parseSquare($from_str);
		$p_to   = parseSquare($to_str);
		if ($p_from === false || $p_to === false) return false;
		
		// Validate turn
		if (getOwner($board[$p_from]) != $turn) return false;

		return canMove($board, $p_from, $p_to);
	}

	// --> [true | false]
	function canMove($board, $from, $to) {		
		if ( !valid($from) || !valid($to) ) return false;
		if ($from == $to) return false;		
		
		$owner = getOwner($board[$from]);
		if (getOwner($board[$to]) == $owner) return false;
		
		switch (strtolower($board[$from])) {
			case 'p': {
				if (! canPawnMove(  $board, $from, $to, $owner)) return false;
			} break;
			case 'n': {
				if (! canKnightMove($board, $from, $to, $owner)) return false;
			} break;
			case 'b': {
				if (! canBishopMove($board, $from, $to, $owner)) return false;
			} break;
			case 'r': {
				if (! canRookMove(  $board, $from, $to, $owner)) return false;
			} break;
			case 'q': {
				if (! canQueenMove( $board, $from, $to, $owner)) return false;
			} break;
			case 'k': {
				if (! canKingMove(  $board, $from, $to, $owner)) return false;
			} break;
			default: {
				return false;
			} break;
		}
		
		// Move the piece and then test for check
		$newBoard = applyMove($board, $from, $to);		
		if (inCheck($newBoard, getOwner($board[$from]))) return false;		
		
		return true;
	}
	
	// --> a string
	function formatMoveText($board_str, $from_str, $to_str) {
		$move_str = $from_str.' '.$to_str;
	
		// Parse board and destination
		$board  = explode('|', $board_str);
		$p_from = parseSquare($from_str);
		$p_to   = parseSquare($to_str);
		if (count($board) !== 64 || $p_to === false || $p_from === false) return $move_str;
		
		// Names for things
		static $pieces = array(
			'p' => 'Pawn', 'n' => 'Knight', 'b' => 'Bishop',
			'r' => 'Rook', 'q' => 'Queen',  'k' => 'King'
		);
		static $owners = array(
			PLAYER_W => 'white', PLAYER_B => 'black'
		);
		
		// Format source and destination
		if (isset($pieces[strtolower($board[$p_from])])) {
			$move_str = $pieces[strtolower($board[$p_from])].' '.$move_str;
		}
		if (isset($pieces[strtolower($board[$p_to])])) {			
			$move_str .= ' taking '.$owners[getOwner($board[$p_to])].' '.strtolower($pieces[strtolower($board[$p_to])]);
		}
		
		// Format if in check
		$newBoard = applyMove($board, $p_from, $p_to);
		if (inCheck($newBoard, opponent(getOwner($board[$p_from])))) $move_str = 'Check: '.$move_str;
		
		// Done
		return $move_str;
	}
	
	// --> a board_str
	function applyMoveStr($board_str, $from_str, $to_str) {
		$p_from = parseSquare($from_str);
		$p_to   = parseSquare($to_str);
		if ($p_from === false || $p_to === false) return $board_str;
		
		return implode('|', applyMove(explode('|', $board_str), $p_from, $p_to));
	}
	
	// --> a board
	function applyMove($board, $from, $to) {
		$ret = $board;
		
		// Move the piece
		$ret[$to] = $board[$from];
		$ret[$from] = '';
		
		// Pawn promotion
		if ($board[$from] == 'P' && row($to) == 8) {
			$ret[$to] = 'Q';
		} else if ($board[$from] == 'p' && row($to) == 1) {
			$ret[$to] = 'q';
		}
		
		// Castling
		if (isCastleAttempt($board, $from, $to, getOwner($board[$from]))) {			
			if (getOwner($board[$from]) == PLAYER_W) {				
				if (col($to) == 6) {					
					$ret[makePos(1, 7)] = '';
					$ret[makePos(1, 5)] = 'R';
				} else if (col($to) == 2) {
					$ret[makePos(1, 0)] = '';
					$ret[makePos(1, 3)] = 'R';
				}
			} else {
				if (col($to) == 6) {
					$ret[makePos(8, 7)] = '';
					$ret[makePos(8, 5)] = 'r';
				} else if (col($to) == 2) {
					$ret[makePos(8, 0)] = '';
					$ret[makePos(8, 3)] = 'r';
				}
			}
		}
		
		return $ret;
	}
	
	
	/*-----------*/
	/* Semantics */
	/*-----------*/
	
		
	function row($position) {
		return 8 - intval($position / 8);
	}
	
	function col($position) {
		return $position % 8;
	}
	
	function makePos($row, $col) {
		return ((8-$row) * 8) + $col;
	}
	
	// [true | false]
	function valid($position) {
		if ($position !== intval($position)) return false;
		return ($position >= 0 && $position <= 63);
	}
	
	// target piece --> [FALSE | PLAYER_W | PLAYER_B]
	function getOwner($target) {
		if ($target == '') return false;
		if ($target === strtoupper($target)) return PLAYER_W;
		if ($target === strtolower($target)) return PLAYER_B;
		return false;
	}
	
	// player --> [FALSE | PLAYER_W | PLAYER_B];
	function opponent($player) {
		if ($player == PLAYER_W) return PLAYER_B;
		if ($player == PLAYER_B) return PLAYER_W;
		return false;
	}
	
	// two-character string --> [FALSE | a position 0..63]
	function parseSquare($position) {
		$col = -1;
		
		if (strlen($position)!= 2) return false;
		switch ($position[0]) {
			case 'a': $col = 0; break;
			case 'b': $col = 1; break;
			case 'c': $col = 2; break;
			case 'd': $col = 3; break;
			case 'e': $col = 4; break;
			case 'f': $col = 5; break;
			case 'g': $col = 6; break;
			case 'h': $col = 7; break;
			default: {
				return false;
			}
		}
		
		$row = intval($position[1]);
		return makePos($row, $col);
	}

	
	/*-----------------*/
	/* Chess functions */
	/*-----------------*/
	
	
	// --> [FALSE | an index 0..63 of $player's king in $board]
	function findKing($board, $player) {
		$target = ($player == PLAYER_W) ? 'K' : 'k';
		for ($i = 0; $i < 64; $i++) if ($board[$i] == $target) return $i;
		return false;
	}
	
	// Returns true or false if $board is in check for $player
	function inCheck($board, $player) {
		
		// Find where the king is
		$kingpos = findKing($board, $player);
		if ($kingpos === false) return true; // Game's over
		
		// See if any enemy pieces can move to where the king is
		$opponent = opponent($player);
		for ($i = 0; $i < 64; $i++) {
			if (getOwner($board[$i]) == $opponent) {
				if (canMove($board, $i, $kingpos)) return true; // check
			}
		}
		
		// Nothing threatens the king
		return false;
	}
	
	
	/*----------------------*/
	/* Per-piece move rules */
	/*----------------------*/
	
	// --> [true | false]
	// n.b. not completely accurate as it doesn't have any game history
	// TODO: Test whether the king passes through check
	function isCastleAttempt($board, $from, $to, $player) {
		if (strtolower($board[$from]) != 'k') return false;
		$d_row = row($from) - row($to);
		$d_col = col($from) - col($to);
		
		if ($player == PLAYER_W) {
			if (row($from) == 1 && $d_row == 0 && col($from) == 4 && abs($d_col) == 2) {
				if (col($to) == 6) {
					if ( ($board[makePos(1, 5)] == '') &&
						 ($board[makePos(1, 6)] == '') &&
						 ($board[makePos(1, 7)] == 'R')
					) {
						return true;
					}
				} else { // $col($to)==2
					if ( ($board[makePos(1, 3)] == '') &&
						 ($board[makePos(1, 2)] == '') &&
						 ($board[makePos(1, 1)] == '') &&
						 ($board[makePos(1, 0)] == 'R')
					) {
						return true;
					}
				}
			}
		} else { // PLAYER_B
			if (row($from) == 8 && $d_row == 0 && col($from) == 4 && abs($d_col) == 2) {
				if (col($to) == 6) {
					if ( ($board[makePos(8, 5)] == '') &&
						 ($board[makePos(8, 6)] == '') &&
						 ($board[makePos(8, 7)] == 'r')
					) {
						return true;
					}
				} else { // $col($to)==2
					if ( ($board[makePos(8, 3)] == '') &&
						 ($board[makePos(8, 2)] == '') &&
						 ($board[makePos(8, 1)] == '') &&
						 ($board[makePos(8, 0)] == 'r')
					) {
						return true;
					}
				}
			}
		}		
		return false;
	}
	
	// --> [true | false]
	function canPawnMove($board, $from, $to, $player) {
		$d_row = row($from) - row($to);
		$d_col = col($from) - col($to);
		
		// Are we capturing?
		if (abs($d_col) == 1) {
			//die('hi');
			if ($player == PLAYER_W && $d_row == -1) return true;
			if ($player == PLAYER_B && $d_row ==  1) return true;
			return false;
		}
		
		// Not capturing. Limit to own column and make sure target is free
		if ($d_col != 0) return false;
		
		if ($board[$to] !== '') return false;
		
		// Allow two steps on home row and one otherwise
		if ($player == PLAYER_W) {
			return ($d_row == -1 || (row($from) == 2 && $d_row == -2));	
		} else {
			return ($d_row == 1  || (row($from) == 7 && $d_row == 2 ));
		}
	}
	
	// --> [true | false]
	function canKingMove($board, $from, $to, $player) {
		$d_row = row($from) - row($to);
		$d_col = col($from) - col($to);

		if (isCastleAttempt($board, $from, $to, $player)) return true;
		
		// Allow one single step in any of 8 directions
		if (abs($d_row) > 1 || abs($d_col) > 1) return false;	
		return true;
	}
	
	// --> [true | false]
	function canKnightMove($board, $from, $to, $player) {	
		$d_row = abs(row($from) - row($to));
		$d_col = abs(col($from) - col($to));
		
		// Allow any single L-shaped move
		return ($d_row == 1 || $d_col == 1) && ($d_row + $d_col == 3);
	}
	
	// --> [true | false]
	function canRookMove($board, $from, $to, $player) {
		$d_row = row($from) - row($to);
		$d_col = col($from) - col($to);
		
		// Allow only moving in straight lines
		if ($d_row != 0 && $d_col != 0) return false;
		if ($d_row == 0 && $d_col == 0) return false;
		
		// Determine whether anything blocks the path, allowing taking an
		//   enemy piece at the final square
		$change = 0;
		if ($d_row) {
			// Moving rows (vertical)
			$lower  = (row($from) > row($to)) ? row($to)  : row($from);
			$higher = (row($from) > row($to)) ? row($from): row($to);
			for ($i = $lower + 1; $i < $higher; $i++) {
				if (strlen( $board[ makePos($i, col($from)) ] )) return false;
			}
		} else {
			// Moving columns (horizontal)
			$lower  = (col($from) > col($to)) ? col($to)  : col($from);
			$higher = (col($from) > col($to)) ? col($from): col($to);
			for ($i = $lower + 1; $i < $higher; $i++) {
				if (strlen( $board[ makePos(row($from), $i) ] )) return false;
			}
		}
		
		// Nothing blocks the path and we're moving in straight lines
		return true;		
	}
	
	// --> [true | false]
	function canBishopMove($board, $from, $to, $player) {
		$d_row = row($from) - row($to);
		$d_col = col($from) - col($to);
		
		// Allow only moving in straight diagonals
		if (abs($d_row) != abs($d_col)) return false;
		
		// Determine whether anything blocks the path
		$xdiff = (col($from) > col($to)) ? -1 : 1;
		$ydiff = (row($from) > row($to)) ? -1 : 1;
		
		$check = $from;
		for ($i = col($from); $i != (col($to)-$xdiff); $i += $xdiff) {
			$check = makePos(row($check)+$ydiff, col($check) + $xdiff);
			if (strlen($board[$check])) return false;
		}
		
		return true;
	}
	
	// --> [true | false]
	function canQueenMove($board, $from, $to, $player) {
		return (
			canBishopMove($board, $from, $to, $player) ||
			canRookMove($board, $from, $to, $player)
		);
	}
			
}