#!/usr/bin/tclsh
# OpenVerse Server Program
# Modified by Andy Goth <unununium@openverse.org>
# Now it runs a GothChess room!
# 
#
# Module Name		- GothChess board handling procs
# Current Maintainter 	- Andy Goth <unununium@openverse.org>
# Sourced By		- gothchess.tcl
#
# Copyright (C) 1999 David Gale <cruise@openverse.org>
# For more information visit http://OpenVerse.org/
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
# USA.

global chess MVS

# reveal_pieces
#
# Correctly set all piece image names
proc reveal_pieces {} {
    global chess

    for {set owner 1} {$owner < 3} {incr owner} {
	for {set index 0} {$index < 16} {incr index} {
	    if {$chess(pieces.$owner.$index.in_play) == 0} {
		set img "mini_"
	    } else {
		set img ""
	    }
	    if {$owner == 1} {
		append img "white_"
	    } else {
		append img "black_"
	    }
	    switch -exact -- $chess(pieces.$owner.$index.type) {
		"rook" {
		    append img "rook.gif"
		}
		"knight" {
		    append img "knight.gif"
		}
		"bishop" {
		    append img "bishop.gif"
		}
		"king" {
		    append img "king.gif"
		}
		"queen" {
		    append img "queen.gif"
		}
		"pawn" {
		    append img "pawn.gif"
		}
		default {
		    # Bad piece type (use blank)
		    log_it "(reveal_pieces): Piece type of $chess(pieces.$owner.$index.type) (shouldn't happen)"
		    set img "blank.gif"
		}
	    }
	    # Set the chess image
	    set chess(pieces.$owner.$index.img) $img
	}
    }

    unset owner index img
}

# initialize_board
#
# Place all the pieces on the board, ready for a new game
proc initialize_board {} {
    global chess

    # Place all the pieces
    initialize_board_helper 1 "rook" 0 0 0
    initialize_board_helper 1 "knight" 1 0 1
    initialize_board_helper 1 "bishop" 2 0 2
    initialize_board_helper 1 "king" 3 0 3
    initialize_board_helper 1 "queen" 4 0 4
    initialize_board_helper 1 "bishop" 5 0 5
    initialize_board_helper 1 "knight" 6 0 6
    initialize_board_helper 1 "rook" 7 0 7

    for {set x 0} {$x < 8} {incr x} {
	initialize_board_helper 1 "pawn" $x 1 [expr $x + 8]
	initialize_board_helper 2 "pawn" $x 6 $x
    }

    initialize_board_helper 2 "rook" 0 7 8
    initialize_board_helper 2 "knight" 1 7 9
    initialize_board_helper 2 "bishop" 2 7 10
    initialize_board_helper 2 "king" 3 7 11
    initialize_board_helper 2 "queen" 4 7 12
    initialize_board_helper 2 "bishop" 5 7 13
    initialize_board_helper 2 "knight" 6 7 14
    initialize_board_helper 2 "rook" 7 7 15

    # Mark the empty squares as empty
    for {set y 2} {$y < 6} {incr y} {
	for {set x 0} {$x < 8} {incr x} {
	    set chess(board.$x.$y.owner) 0
	    set chess(board.$x.$y.index) -1
	}
    }

    unset x y
}

# initialize_board_helper owner type x y index
#
# Places a piece of $type type owned by $owner at $x, $y (index of $index)
proc initialize_board_helper {owner type x y index} {
    global chess

    # Set up the piece list
    set chess(pieces.$owner.$index.type) $type
    if {$owner == 1} {
	set chess(pieces.$owner.$index.img) "white_$type"
	append chess(pieces.$owner.$index.img) ".gif"
    } else {
	set chess(pieces.$owner.$index.img) "black_$type"
	append chess(pieces.$owner.$index.img) ".gif"
    }
    set chess(pieces.$owner.$index.in_play) 1
    set chess(pieces.$owner.$index.x) $x
    set chess(pieces.$owner.$index.y) $y
    set chess(pieces.$owner.$index.has_moved) 0

    # Set up the board grid
    set chess(board.$x.$y.owner) $owner
    set chess(board.$x.$y.index) $index
}

# is_clear x1 y1 x2 y2
#
# Returns 1 if there are no pieces between ($x1, $y1) and ($x2, $y2) (exclusive), 0 if not clear, or -1 if moving a weird angle or not at all
proc is_clear {x1 y1 x2 y2} {
    global chess

    if {($x1 != $x2) && ($y1 != $y2)} {
	# Moving diagonally
	if {abs([expr $x2 - $x1]) != abs([expr $y2 - $y1])} {
	    # Moving a funky (i.e. non-45 degree) angle
	    unset x1 y1 x2 y2

	    return -1
	}
    }

    set result 1

    if {$x1 < $x2} {
	if {$y1 < $y2} {
	    # $x1 < $x2
	    # $y1 < $y2
	    for {set x [expr $x1 + 1]; set y [expr $y1 + 1]} {$x < $x2} {incr x; incr y} {
		if {$chess(board.$x.$y.owner) > 0} {
		    set result 0
		    break
		}
	    }
	    unset x y
	} elseif {$y1 > $y2} {
	    # $x1 < $x2
	    # $y1 > $y2
	    for {set x [expr $x1 + 1]; set y [expr $y1 - 1]} {$x < $x2} {incr x; incr y -1} {
		if {$chess(board.$x.$y.owner) > 0} {
		    set result 0
		    break
		}
	    }
	    unset x y
	} else {
	    # $x1 < $x2
	    # $y1 == $y2
	    for {set x [expr $x1 + 1]} {$x < $x2} {incr x} {
		if {$chess(board.$x.$y2.owner) > 0} {
		    set result 0
		    break
		}
	    }
	    unset x
	}
    } elseif {$x1 > $x2} {
	if {$y1 < $y2} {
	    # $x1 > $x2
	    # $y1 < $y2
	    for {set x [expr $x1 - 1]; set y [expr $y1 + 1]} {$x > $x2} {incr x -1; incr y} {
		if {$chess(board.$x.$y.owner) > 0} {
		    set result 0
		    break
		}
	    }
	    unset x y
	} elseif {$y1 > $y2} {
	    # $x1 > $x2
	    # $y1 > $y2
	    for {set x [expr $x1 - 1]; set y [expr $y1 - 1]} {$x > $x2} {incr x -1; incr y -1} {
		if {$chess(board.$x.$y.owner) > 0} {
		    set result 0
		    break
		}
	    }
	    unset x y
	} else {
	    # $x1 > $x2
	    # $y1 == $y2
	    for {set x [expr $x1 - 1]} {$x > $x2} {incr x -1} {
		if {$chess(board.$x.$y2.owner) > 0} {
		    set result 0
		    break
		}
	    }
	    unset x
	}
    } else {
	if {$y1 < $y2} {
	    # $x1 == $x2
	    # $y1 < $y2
	    for {set y [expr $y1 + 1]} {$y < $y2} {incr y} {
		if {$chess(board.$x2.$y.owner) > 0} {
		    set result 0
		    break
		}
	    }
	    unset y
	} elseif {$y1 > $y2} {
	    # $x1 == $x2
	    # $y1 > $y2
	    for {set y [expr $y1 - 1]} {$y > $y2} {incr y -1} {
		if {$chess(board.$x2.$y.owner) > 0} {
		    set result 0
		    break
		}
	    }
	    unset y
	} else {
	    # $x1 == $x2
	    # $y1 == $y2
	    set result -1
	}
    }

    unset x1 y1 x2 y2

    return $result
}

# is_threatened name x y
#
# Returns 1 if one of $name's pieces would be threatened at $x, $y
proc is_threatened {name x y} {
    global chess

    set result 0

    # Scan through all of opponent's pieces and see if they can move to $x, $y
    set name [expr 3 - $name]

    for {set index 0} {$index < 16} {incr index} {
	if {$chess(pieces.$name.$index.in_play) == 1} {
	    switch -exact -- [type_of_move $name $index $x $y] {
		unknown {
		    # Safe
		}
		illegal {
		    # Safe
		}
		default {
		    # Unsafe
		    set result 1
		    break
		}
	    }
	}
    }

    unset name x y index

    return $result
}

# Possible move types:
#
# unknown            - synonymous with illegal
# illegal            - invalid move
# move               - simply moving
# capture            - move and capture
# pawn_promotion     - move and pawn promotion
# capture_promotion  - move, capture, and pawn promotion
# en_passant_move    - a move that creates an en passant opportunity
# en_passant_capture - en passant capture
# em_passant_promo   - a move that creates an an passant opportunity and a pawn promotion
# queenside_castle   - queenside castling
# kingside_castle    - kingside castling

# type_of_move owner index x y
#
# Returns the type of move that $owner would make if he moved his $index'th piece to $x, $y
proc type_of_move {owner index x y} {
    global chess

    switch -exact -- $chess(pieces.$owner.$index.type) {
	"pawn" {
	    set move_type [pawn_move $owner $index $chess(pieces.$owner.$index.x) $chess(pieces.$owner.$index.y) $x $y]
	}
	"rook" {
	    set move_type [rook_move $owner $index $chess(pieces.$owner.$index.x) $chess(pieces.$owner.$index.y) $x $y]
	}
	"knight" {
	    set move_type [knight_move $owner $index $chess(pieces.$owner.$index.x) $chess(pieces.$owner.$index.y) $x $y]
	}
	"bishop" {
	    set move_type [bishop_move $owner $index $chess(pieces.$owner.$index.x) $chess(pieces.$owner.$index.y) $x $y]
	}
	"king" {
	    set move_type [king_move $owner $index $chess(pieces.$owner.$index.x) $chess(pieces.$owner.$index.y) $x $y]
	}
	"queen" {
	    set move_type [queen_move $owner $index $chess(pieces.$owner.$index.x) $chess(pieces.$owner.$index.y) $x $y]
	}
    }

    unset owner index x y

    return $move_type
}

# pawn_move owner index x1 y1 x2 y2
#
# Returns the type of move that $x1, $y1 to $x2, $y2 would be (piece at $x1, $y1 is $owner's $index'th piece, and piece is a pawn)
proc pawn_move {owner index x1 y1 x2 y2} {
    global chess

    set move_type "unknown"

    if {$owner == 1} {
	# Player one
	if {$x1 == $x2} {
	    # Not moving laterally
	    if {[expr $y1 + 1] == $y2} {
		# Moving one space down
		if {$chess(board.$x2.$y2.owner) == 0} {
		    # Moving to an empty spot
		    if {$y2 == 7} {
			# Moving to the end of the board
			set move_type "pawn_promotion"
		    } else {
			# Moving to the middle of the board
			set move_type "move"
		    }
		}
	    } elseif {[expr $y1 + 2] == $y2} {
		# Moving two spaces down
		if {$chess(pieces.$owner.$index.has_moved) == 0} {
		    # Has not moved previously
		    if {$chess(board.$x2.$y2.owner) == 0} {
			# Moving to an empty spot
			if {$chess(board.$x2.[expr $y1 + 1].owner) == 0} {
			    # Is not jumping a piece
			    if {$y2 == 7} {
				# Moving to the end of the board
				set move_type "pawn_promotion"
			    } else {
				# Moving to the middle of the board
				set move_type "move"
			    }

			    # Test for the creation of an en passant opportunity
			    if {$x2 > 0} {
				# Not along the left side of the board
				if {$chess(board.[expr $x2 - 1].$y2.owner) == [expr 3 - $owner]} {
				    # Moved next to an enemy piece
				    if {[string compare $chess(pieces.[expr 3 - $owner].$chess(board.[expr $x2 - 1].$y2.index).type) "pawn"] == 0} {
					# Moved past a pawn
					if {$y2 == 7} {
					    # Moving to the end of the board
					    set move_type "en_passant_promo"
					} else {
					    # Moving to the middle of the board
					    set move_type "en_passant_move"
					}
					# Record en passant details
					set chess(en_passant_index) $index
					set chess(en_passant_x) $x2
					set chess(en_passant_y) [expr $y1 + 1]
				    }
				}
			    }
			    if {$x2 < 7} {
				# Not along the left side of the board
				if {$chess(board.[expr $x2 + 1].$y2.owner) == [expr 3 - $owner]} {
				    # Moved next to an enemy piece
				    if {[string compare $chess(pieces.[expr 3 - $owner].$chess(board.[expr $x2 + 1].$y2.index).type) "pawn"] == 0} {
					# Moved past a pawn
					if {$y2 == 7} {
					    # Moving to the end of the board
					    set move_type "en_passant_promo"
					} else {
					    # Moving to the middle of the board
					    set move_type "en_passant_move"
					}
					# Record en passant details
					set chess(en_passant_index) $index
					set chess(en_passant_x) $x2
					set chess(en_passant_y) [expr $y1 + 1]
				    }
				}
			    }
			}
		    }
		}
	    }
	} elseif {[expr $y1 + 1] == $y2} {
	    # Moving one space down
	    if {[expr $x1 + 1] == $x2} {
		# Moving one space to the right
		if {$chess(board.$x2.$y2.owner) == [expr 3 - $owner]} {
		    # Moving onto an enemy piece
		    if {$y2 == 7} {
			# Moving to the end of the board
			set move_type "capture_promotion"
		    } else {
			# Moving to the middle of the board
			set move_type "capture"
		    }
		} elseif {($x2 == $chess(en_passant_x)) && ($y2 == $chess(en_passant_y))} {
		    # Performing an en passant capture
		    set move_type "en_passant_capture"
		}
	    } elseif {[expr $x1 - 1] == $x2} {
		# Moving one space to the left
		if {$chess(board.$x2.$y2.owner) == [expr 3 - $owner]} {
		    # Moving onto an enemy piece
		    if {$y2 == 7} {
			# Moving to the end of the board
			set move_type "capture_promotion"
		    } else {
			# Moving to the middle of the board
			set move_type "capture"
		    }
		} elseif {($x2 == $chess(en_passant_x)) && ($y2 == $chess(en_passant_y))} {
		    # Performing an en passant capture
		    set move_type "en_passant_capture"
		}
	    }
	}
    } else {
	# Player two
	if {$x1 == $x2} {
	    # Not moving laterally
	    if {[expr $y1 - 1] == $y2} {
		# Moving one space up
		if {$chess(board.$x2.$y2.owner) == 0} {
		    # Moving onto an empty spot
		    if {$y2 == 0} {
			# Moving to the end of the board
			set move_type "pawn_promotion"
		    } else {
			# Moving to the middle of the the board
			set move_type "move"
		    }
		}
	    } elseif {[expr $y1 - 2] == $y2} {
		# Moving two spaces up
		if {$chess(pieces.$owner.$index.has_moved) == 0} {
		    # Has not moved previously
		    if {$chess(board.$x2.$y2.owner) == 0} {
			# Moving onto an empty spot
			if {$chess(board.$x2.[expr $y1 - 1].owner) == 0} {
			    # Is not jumping a piece
			    if {$y2 == 0} {
				# Moving to the end of the board
				set move_type "pawn_promotion"
			    } else {
				# Moving to the middle of the board
				set move_type "move"
			    }

			    # Test for the creation of an en passant opportunity
			    if {$x2 > 0} {
				# Not along the left side of the board
				if {$chess(board.[expr $x2 - 1].$y2.owner) == [expr 3 - $owner]} {
				    # Moved next to an enemy piece
				    if {[string compare $chess(pieces.[expr 3 - $owner].$chess(board.[expr $x2 - 1].$y2.index).type) "pawn"] == 0} {
					# Moved past a pawn
					if {$y2 == 0} {
					    # Moving to the end of the board
					    set move_type "en_passant_promotion"
					} else {
					    # Moving to the middle of the board
					    set move_type "en_passant_move"
					}
					# Record en passant details
					set chess(en_passant_index) $index
					set chess(en_passant_x) $x2
					set chess(en_passant_y) [expr $y1 - 1]
				    }
				}
			    }
			    if {$x2 < 7} {
				# Not along the left side of the board
				if {$chess(board.[expr $x2 + 1].$y2.owner) == [expr 3 - $owner]} {
				    # Moved next to an enemy piece
				    if {[string compare $chess(pieces.[expr 3 - $owner].$chess(board.[expr $x2 + 1].$y2.index).type) "pawn"] == 0} {
					# Moved past a pawn
					if {$y2 == 0} {
					    # Moving to the end of the board
					    set move_type "en_passant_promotion"
					} else {
					    # Moving to the middle of the board
					    set move_type "en_passant_move"
					}
					# Record en passant details
					set chess(en_passant_index) $index
					set chess(en_passant_x) $x2
					set chess(en_passant_y) [expr $y1 - 1]
				    }
				}
			    }
			}
		    }
		}
	    }
	} elseif {[expr $y1 - 1] == $y2} {
	    # Moving one space up
	    if {[expr $x1 + 1] == $x2} {
		# Moving one space to the right
		if {$chess(board.$x2.$y2.owner) == [expr 3 - $owner]} {
		    # Moving onto an enemy piece
		    if {$y2 == 0} {
			# Moving to the end of the board
			set move_type "capture_promotion"
		    } else {
			# Moving to the middle of the board
			set move_type "capture"
		    }
		} elseif {($x2 == $chess(en_passant_x)) && ($y2 == $chess(en_passant_y))} {
		    # Performing an en passant capture
		    set move_type "en_passant_capture"
		}
	    } elseif {[expr $x1 - 1] == $x2} {
		# Moving one space to the left
		if {$chess(board.$x2.$y2.owner) == [expr 3 - $owner]} {
		    # Moving onto an enemy piece
		    if {$y2 == 0} {
			# Moving to the end of the board
			set move_type "capture_promotion"
		    } else {
			# Moving to the middle of the board
			set move_type "capture"
		    }
		} elseif {($x2 == $chess(en_passant_x)) && ($y2 == $chess(en_passant_y))} {
		    # Performing an en passant capture
		    set move_type "en_passant_capture"
		}
	    }
	}
    }

    unset owner index x1 y1 x2 y2

    return $move_type
}

# rook_move owner index x1 y1 x2 y2
#
# Returns the type of move that $x1, $y1 to $x2, $y2 would be (piece at $x1, $y1 is $owner's $index'th piece, and piece is a rook)
proc rook_move {owner index x1 y1 x2 y2} {
    global chess

    set move_type "unknown"

    if {[is_clear $x1 $y1 $x2 $y2] == 1} {
	# Moving a decent angle and not jumping anything
	if {($x1 == $x2) || ($y1 == $y2)} {
	    # Not moving diagonally
	    if {$chess(board.$x2.$y2.owner) == [expr 3 - $owner]} {
		# Moving onto an enemy piece
		set move_type "capture"
	    } else {
		# Moving onto an empty spot
		set move_type "move"
	    }
	}
    }

    unset owner index x1 y1 x2 y2

    return $move_type
}

# knight_move owner index x1 y1 x2 y2
#
# Returns the type of move that $x1, $y1 to $x2, $y2 would be (piece at $x1, $y1 is $owner's $index'th piece, and piece is a knight)
proc knight_move {owner index x1 y1 x2 y2} {
    global chess

    set move_type "unknown"

    set x_delta [expr abs($x2 - $x1)]
    set y_delta [expr abs($y2 - $y1)]

    if {(($x_delta == 2) && ($y_delta == 1)) || (($x_delta == 1) && ($y_delta == 2))} {
	# Moving in correct L pattern
	if {$chess(board.$x2.$y2.owner) == 0} {
	    # Moving to an empty spot
	    set move_type "move"
	} else {
	    # Moving onto an enemy piece
	    set move_type "capture"
	}
    }

    unset owner index x1 y1 x2 y2 x_delta y_delta

    return $move_type
}

# bishop_move owner index x1 y1 x2 y2
#
# Returns the type of move that $x1, $y1 to $x2, $y2 would be (piece at $x1, $y1 is $owner's $index'th piece, and piece is a bishop)
proc bishop_move {owner index x1 y1 x2 y2} {
    global chess

    set move_type "unknown"

    if {[is_clear $x1 $y1 $x2 $y2] == 1} {
	# Moving a decent angle and not jumping anything
	if {($x1 != $x2) && ($y1 != $y2)} {
	    # Moving diagonally
	    if {$chess(board.$x2.$y2.owner) == [expr 3 - $owner]} {
		# Moving onto an enemy piece
		set move_type "capture"
	    } else {
		# Moving onto an empty spot
		set move_type "move"
	    }
	}
    }

    unset owner index x1 y1 x2 y2

    return $move_type
}

# king_move owner index x1 y1 x2 y2
#
# Returns the type of move that $x1, $y1 to $x2, $y2 would be (piece at $x1, $y1 is $owner's $index'th piece, and piece is a king)
proc king_move {owner index x1 y1 x2 y2} {
    global chess

    set move_type "unknown"

    if {[expr abs($x2 - $x1)] < 2} {
	# Moving zero or one square left and right
	if {[expr abs($y2 - $y1)] < 2} {
	    # Moving zero or one square up and down
	    if {$chess(board.$x2.$y2.owner) == 0} {
		# Moving to an empty spot
		set move_type "move"
	    } else {
		# Moving onto an enemy piece
		set move_type "capture"
	    }
	}
    } elseif {([expr $x1 + 2] == $x2) && ($y1 == $y2)} {
	# Moving exactly two squares to the right
	if {$owner == 1} {
	    # Player 1
	    if {$chess(board.7.0.owner) == 1} {
		# His piece is in the upper-right corner
		if {[string compare $chess(pieces.1.$chess(board.7.0.index).type) "rook"] == 0} {
		    # He has a rook in the upper-right corner
		    if {$chess(pieces.1.$chess(board.7.0.index).has_moved) == 0} {
			# His rook hasn't moved previously
			set move_type "queenside_castle"
		    }
		}
	    }
	} else {
	    # Player 2
	    if {$chess(board.7.7.owner) == 2} {
		# His piece is in the lower-right corner
		if {[string compare $chess(pieces.2.$chess(board.7.7.index).type) "rook"] == 0} {
		    # He has a rook in the lower-right corner
		    if {$chess(pieces.2.$chess(board.7.7.index).has_moved) == 0} {
			# His rook hasn't moved previously
			set move_type "queenside_castle"
		    }
		}
	    }
	}
    } elseif {([expr $x1 - 2] == $x2) && ($y1 == $y2)} {
	# Moving exactly two squares to the left
	if {$owner == 1} {
	    # Player 1
	    if {$chess(board.0.0.owner) == 1} {
		# His piece is in the upper-left corner
		if {[string compare $chess(pieces.1.$chess(board.0.0.index).type) "rook"] == 0} {
		    # He has a rook in the upper-left corner
		    if {$chess(pieces.1.$chess(board.0.0.index).has_moved) == 0} {
			# His rook hasn't moved previously
			set move_type "kingside_castle"
		    }
		}
	    }
	} else {
	    # Player 2
	    if {$chess(board.0.7.owner) == 2} {
		# His piece is in the lower-left corner
		if {[string compare $chess(pieces.2.$chess(board.0.7.index).type) "rook"] == 0} {
		    # He has a rook in the lower-right corner
		    if {$chess(pieces.2.$chess(board.0.7.index).has_moved) == 0} {
			# His rook hasn't moved previously
			set move_type "kingside_castle"
		    }
		}
	    }
	}
    }

    unset owner index x1 y1 x2 y2

    return $move_type
}

# queen_move owner index x1 y1 x2 y2
#
# Returns the type of move that $x1, $y1 to $x2, $y2 would be (piece at $x1, $y1 is $owner's $index'th piece, and piece is a queen)
proc queen_move {owner index x1 y1 x2 y2} {
    global chess

    set move_type "unknown"

    if {[is_clear $x1 $y1 $x2 $y2] == 1} {
	# Moving a decent angle and not jumping anything
	if {$chess(board.$x2.$y2.owner) == [expr 3 - $owner]} {
	    # Moving onto an enemy piece
	    set move_type "capture"
	} else {
	    # Moving onto an empty spot
	    set move_type "move"
	}
    }

    unset owner index x1 y1 x2 y2

    return $move_type
}

# do_move owner index x y
#
# If legal, moves $owner's $index'th piece to $x, $y
proc do_move {owner index x y} {
    global chess MVS

    # What kind of move?
    set move_type [type_of_move $owner $index $x $y]

    # Calculate the names of the initial and final locations
    set x_old $chess(pieces.$owner.$index.x)
    set y_old $chess(pieces.$owner.$index.y)
    set loc_1 [lindex {A B C D E F G H} $y_old]
    append loc_1 [expr $x_old + 1]
    set loc_2 [lindex {A B C D E F G H} $y]
    append loc_2 [expr $x + 1]

    # If the move is invalid, warn the player and let him try again
    if {([string compare $move_type "illegal"] == 0) || ([string compare $move_type "unknown"] == 0)} {
	log_it "(do_move): Player $owner tried to illegally move $chess(pieces.$owner.$index.type) from ($chess(pieces.$owner.$index.x), $chess(pieces.$owner.$index.y)) to ($x, $y)"
	show_message $chess(players.$owner.sock) "red" "$loc_1 to $loc_2 is illegal"

	unset owner index x y move_type loc_1 loc_2
	return
    }

    # Save backup information
    push $chess(board.$chess(pieces.$owner.$index.x).$chess(pieces.$owner.$index.y).owner) $chess(board.$chess(pieces.$owner.$index.x).$chess(pieces.$owner.$index.y).index) $chess(pieces.$owner.$index.x) $chess(pieces.$owner.$index.y) $chess(pieces.$owner.$index.has_moved) $chess(board.$x.$y.owner) $chess(board.$x.$y.index)

    if {$chess(board.$x.$y.owner) > 0} {
	set capture_index $chess(board.$x.$y.index)
    } else {
	set capture_index -1
    }

    # Update the board and piece information
    set chess(board.$chess(pieces.$owner.$index.x).$chess(pieces.$owner.$index.y).owner) 0
    set chess(board.$chess(pieces.$owner.$index.x).$chess(pieces.$owner.$index.y).index) -1
    set chess(pieces.$owner.$index.x) $x
    set chess(pieces.$owner.$index.y) $y
    set chess(pieces.$owner.$index.has_moved) 1
    set chess(board.$x.$y.owner) $owner
    set chess(board.$x.$y.index) $index

    switch -exact -- $move_type {
	"move" {
	    # Move the piece

	    # See if the king is threatened
	    set threatened 0
	    for {set king_index 0} {$king_index < 16} {incr king_index} {
		if {[string compare $chess(pieces.$owner.$king_index.type) "king"] == 0} {
		    # Found the king
		    if {[is_threatened $owner $chess(pieces.$owner.$king_index.x) $chess(pieces.$owner.$king_index.y)] == 1} {
			# He's threatened; the move is illegal
			set threatened 1
		    }
		    break
		}
	    }
	    unset king_index

	    if {$threatened == 0} {
		# The move is valid
		show_message "all" "white" "$loc_1 to $loc_2"
		log_it "(do_move): Player $owner moves from ($x_old, $y_old) to ($x, $y)"

		# Remove the selection circle
		set square "square_$x_old"
		append square "_$y_old"
		if {[expr ($x_old & 1) ^ ($y_old & 1)] == 0} {
		    set img "light_default.gif"
		} else {
		    set img "dark_default.gif"
		}
		send_to_user $chess(players.$owner.sock) "IMAGE $square [expr $x_old * 60 + 30] [expr $y_old * 60 + 30] $img [file size "$MVS(images)/$img"] 0"
		unset square img

		set piece_name "piece_$owner"
		append piece_name "_$index"
		slide_object "all" $piece_name [expr 60 * $x_old + 30] [expr 60 * $y_old + 30] [expr $x * 60 + 30] [expr $y * 60 + 30] 30 10
		unset piece_name

		# Clear the selection
		set chess(piece_index) -1

		# Clear any en passant opportunity
		set chess(en_passant_index) -1
		set chess(en_passant_x) -1
		set chess(en_passant_y) -1

		end_turn
	    }
	}
	"capture" {
	    # Move the piece and capture another one

	    # Move the victim off the board
	    push $chess(pieces.[expr 3 - $owner].$capture_index.in_play)
	    set chess(pieces.[expr 3 - $owner].$capture_index.in_play) 0

	    # See if the king is threatened
	    set threatened 0
	    for {set king_index 0} {$king_index < 16} {incr king_index} {
		if {[string compare $chess(pieces.$owner.$king_index.type) "king"] == 0} {
		    # Found the king
		    if {[is_threatened $owner $chess(pieces.$owner.$king_index.x) $chess(pieces.$owner.$king_index.y)] == 1} {
			# He's threatened; the move is illegal
			set threatened 1
		    }
		    break
		}
	    }
	    unset king_index

	    if {$threatened == 0} {
		# The move is valid
		show_message "all" "white" "$loc_1 to $loc_2 (capture)"
		log_it "(do_move): Player $owner moves from ($x_old, $y_old) to ($x, $y) and captures"

		# Remove the selection circle
		set square "square_$x_old"
		append square "_$y_old"
		if {[expr ($x_old & 1) ^ ($y_old & 1)] == 0} {
		    set img "light_default.gif"
		} else {
		    set img "dark_default.gif"
		}
		send_to_user $chess(players.$owner.sock) "IMAGE $square [expr $chess(pieces.$owner.$index.x) * 60 + 30] [expr $chess(pieces.$owner.$index.y) * 60 + 30] $img [file size "$MVS(images)/$img"] 0"
		unset square img

		# Show the piece capture animation
		capture_piece [expr 3 - $owner] $capture_index

		# Show the piece slide animation
		set piece_name "piece_$owner"
		append piece_name "_$index"
		slide_object "all" $piece_name [expr 60 * $x_old + 30] [expr 60 * $y_old + 30] [expr $x * 60 + 30] [expr $y * 60 + 30] 30 10
		unset piece_name

		# Clear the selection
		set chess(piece_index) -1

		# Clear any en passant opportunity
		set chess(en_passant_index) -1
		set chess(en_passant_x) -1
		set chess(en_passant_y) -1

		end_turn
	    } else {
		# Return the victim to the board
		set chess(pieces.[expr 3 - $owner].$capture_index.in_play) [pop]
	    }
	}
	"pawn_promotion" {
	    # Move the piece and wait for the user to select a new piece type for the pawn

	    # See if the king is threatened
	    set threatened 0
	    for {set king_index 0} {$king_index < 16} {incr king_index} {
		if {[string compare $chess(pieces.$owner.$king_index.type) "king"] == 0} {
		    # Found the king
		    if {[is_threatened $owner $chess(pieces.$owner.$king_index.x) $chess(pieces.$owner.$king_index.y)] == 1} {
			# He's threatened; the move is illegal
			set threatened 1
		    }
		    break
		}
	    }
	    unset king_index

	    if {$threatened == 0} {
		# Move is valid
		show_message "all" "white" "$loc_1 to $loc_2 (promotion)"
		log_it "(do_move): Player $owner moves from ($x_old, $y_old) to ($x, $y); pawn promotion pending"

		# Remove the selection circle
		set square "square_$x_old"
		append square "_$y_old"
		if {[expr ($x_old & 1) ^ ($y_old & 1)] == 0} {
		    set img "light_default.gif"
		} else {
		    set img "dark_default.gif"
		}
		send_to_user $chess(players.$owner.sock) "IMAGE $square [expr $x_old * 60 + 30] [expr $y_old * 60 + 30] $img [file size "$MVS(images)/$img"] 0"
		unset square img

		# Show the piece slide animation
		set piece_name "piece_$owner"
		append piece_name "_$index"
		slide_object "all" $piece_name [expr 60 * $x_old + 30] [expr 60 * $y_old + 30] [expr $x * 60 + 30] [expr $y * 60 + 30] 30 10
		unset piece_name

		# Take note of the pending promotion
		set chess(promotion) $index

		# Clear any en passant opportunity
		set chess(en_passant_index) -1
		set chess(en_passant_x) -1
		set chess(en_passant_y) -1

		# Clear the selection
		set chess(piece_index) -1
	    }
	}
	"capture_promotion" {
	    # Move the piece, capture another one, and then wait for the user to select a new piece type for the pawn

	    # Move the victim off the board
	    push $chess(pieces.[expr 3 - $owner].$capture_index.in_play)
	    set chess(pieces.[expr 3 - $owner].$capture_index.in_play) 0

	    # See if the king is threatened
	    set threatened 0
	    for {set king_index 0} {$king_index < 16} {incr king_index} {
		if {[string compare $chess(pieces.$owner.$king_index.type) "king"] == 0} {
		    # Found the king
		    if {[is_threatened $owner $chess(pieces.$owner.$king_index.x) $chess(pieces.$owner.$king_index.y)] == 1} {
			# He's threatened; the move is illegal
			set threatened 1
		    }
		    break
		}
	    }
	    unset king_index

	    if {$threatened == 0} {
		# The move is valid
		show_message "all" "white" "$loc_1 to $loc_2 (capt./promo.)"
		log_it "(do_move): Player $owner moves from ($x_old, $y_old) to ($x, $y) and captures; pawn promotion pending"

		# Remove the selection circle
		set square "square_$x_old"
		append square "_$y_old"
		if {[expr ($x_old & 1) ^ ($y_old & 1)] == 0} {
		    set img "light_default.gif"
		} else {
		    set img "dark_default.gif"
		}
		send_to_user $chess(players.$owner.sock) "IMAGE $square [expr $x_old * 60 + 30] [expr $y_old * 60 + 30] $img [file size "$MVS(images)/$img"] 0"
		unset square img

		# Show the capture animation
		capture_piece [expr 3 - $owner] $capture_index

		# Show the piece slide animation
		set piece_name "piece_$owner"
		append piece_name "_$index"
		slide_object "all" $piece_name [expr 60 * $x_old + 30] [expr 60 * $y_old + 30] [expr $x * 60 + 30] [expr $y * 60 + 30] 30 10
		unset piece_name

		# Take note of the pending pawn promotion
		set chess(promotion) $index

		# Clear any en passant opportunity
		set chess(en_passant_index) -1
		set chess(en_passant_x) -1
		set chess(en_passant_y) -1

		# Clear the selection
		set chess(piece_index) -1
	    } else {
		# Return the victim to the board
		set chess(pieces.[expr 3 - $owner].$capture_index.in_play) [pop]
	    }
	}
	"en_passant_move" {
	    # Move the piece but do not clear en passants

	    # See if the king is threatened
	    set threatened 0
	    for {set king_index 0} {$king_index < 16} {incr king_index} {
		if {[string compare $chess(pieces.$owner.$king_index.type) "king"] == 0} {
		    # Found the king
		    if {[is_threatened $owner $chess(pieces.$owner.$king_index.x) $chess(pieces.$owner.$king_index.y)] == 1} {
			# He's threatened; the move is illegal
			set threatened 1
		    }
		    break
		}
	    }
	    unset king_index

	    if {$threatened == 0} {
		# The move is valid
		show_message "all" "white" "$loc_1 to $loc_2"
		log_it "(do_move): Player $owner moves from ($x_old, $y_old) to ($x, $y)"

		# Remove the selection circle
		set square "square_$x_old"
		append square "_$y_old"
		if {[expr ($x_old & 1) ^ ($y_old & 1)] == 0} {
		    set img "light_default.gif"
		} else {
		    set img "dark_default.gif"
		}
		send_to_user $chess(players.$owner.sock) "IMAGE $square [expr $x_old * 60 + 30] [expr $y_old * 60 + 30] $img [file size "$MVS(images)/$img"] 0"
		unset square img

		set piece_name "piece_$owner"
		append piece_name "_$index"
		slide_object "all" $piece_name [expr 60 * $x_old + 30] [expr 60 * $y_old + 30] [expr $x * 60 + 30] [expr $y * 60 + 30] 30 10
		unset piece_name

		# Clear the selection
		set chess(piece_index) -1

		end_turn
	    }
	}
	"en_passant_promo" {
	    # Move the piece and wait for the user to select a new piece type for the pawn; also, do not clear en passants

	    # See if the king is threatened
	    set threatened 0
	    for {set king_index 0} {$king_index < 16} {incr king_index} {
		if {[string compare $chess(pieces.$owner.$king_index.type) "king"] == 0} {
		    # Found the king
		    if {[is_threatened $owner $chess(pieces.$owner.$king_index.x) $chess(pieces.$owner.$king_index.y)] == 1} {
			# He's threatened; the move is illegal
			set threatened 1
		    }
		    break
		}
	    }
	    unset king_index

	    if {$threatened == 0} {
		# Move is valid
		show_message "all" "white" "$loc_1 to $loc_2 (promotion)"
		log_it "(do_move): Player $owner moves from ($x_old, $y_old) to ($x, $y); pawn promotion pending"

		# Remove the selection circle
		set square "square_$x_old"
		append square "_$y_old"
		if {[expr ($x_old & 1) ^ ($y_old & 1)] == 0} {
		    set img "light_default.gif"
		} else {
		    set img "dark_default.gif"
		}
		send_to_user $chess(players.$owner.sock) "IMAGE $square [expr $x_old * 60 + 30] [expr $y_old * 60 + 30] $img [file size "$MVS(images)/$img"] 0"
		unset square img

		# Show the piece slide animation
		set piece_name "piece_$owner"
		append piece_name "_$index"
		slide_object "all" $piece_name [expr 60 * $x_old + 30] [expr 60 * $y_old + 30] [expr $x * 60 + 30] [expr $y * 60 + 30] 30 10
		unset piece_name

		# Take note of the pending promotion
		set chess(promotion) $index

		# Clear the selection
		set chess(piece_index) -1
	    }
	}
	"en_passant_capture" {
	    # Move the pawn and capture a piece in another square

	    # Wipe the victim from the board
	    push $chess(board.$chess(pieces.[expr 3 - $owner].$chess(en_passant_index).x).$chess(pieces.[expr 3 - $owner].$chess(en_passant_index).y).owner) $chess(board.$chess(pieces.[expr 3 - $owner].$chess(en_passant_index).x).$chess(pieces.[expr 3 - $owner].$chess(en_passant_index).y).index)
	    set chess(board.$chess(pieces.[expr 3 - $owner].$chess(en_passant_index).x).$chess(pieces.[expr 3 - $owner].$chess(en_passant_index).y).owner) 0
	    set chess(board.$chess(pieces.[expr 3 - $owner].$chess(en_passant_index).x).$chess(pieces.[expr 3 - $owner].$chess(en_passant_index).y).index) -1

	    # See if the king is threatened
	    set threatened 0
	    for {set king_index 0} {$king_index < 16} {incr king_index} {
		if {[string compare $chess(pieces.$owner.$king_index.type) "king"] == 0} {
		    # Found the king
		    if {[is_threatened $owner $chess(pieces.$owner.$king_index.x) $chess(pieces.$owner.$king_index.y)] == 1} {
			# He's threatened; the move is illegal
			set threatened 1
		    }
		    break
		}
	    }
	    unset king_index

	    if {$threatened == 0} {
		# The move is valid
		show_message "all" "white" "$loc_1 to $loc_2 (en passant)"
		log_it "(do_move): Player $owner moves from ($x_old, $y_old) to ($x, $y) and captures via en passant"

		# Remove the selection circle
		set square "square_$x_old"
		append square "_$y_old"
		if {[expr ($x_old & 1) ^ ($y_old & 1)] == 0} {
		    set img "light_default.gif"
		} else {
		    set img "dark_default.gif"
		}
		send_to_user $chess(players.$owner.sock) "IMAGE $square [expr $chess(pieces.$owner.$index.x) * 60 + 30] [expr $chess(pieces.$owner.$index.y) * 60 + 30] $img [file size "$MVS(images)/$img"] 0"
		unset square img

		# Show the piece capture animation
		capture_piece [expr 3 - $owner] $chess(en_passant_index)

		# Show the piece slide animation
		set piece_name "piece_$owner"
		append piece_name "_$index"
		slide_object "all" $piece_name [expr 60 * $x_old + 30] [expr 60 * $y_old + 30] [expr $x * 60 + 30] [expr $y * 60 + 30] 30 10
		unset piece_name

		# Clear any en passant opportunity
		set chess(en_passant_index) -1
		set chess(en_passant_x) -1
		set chess(en_passant_y) -1

		# Clear the selection
		set chess(piece_index) -1

		end_turn
	    } else {
		# Restore the victim's square (he's safe after all!)
		set chess(board.$chess(pieces.[expr 3 - $owner].$chess(en_passant_index).x).$chess(pieces.[expr 3 - $owner].$chess(en_passant_index).y).index) [pop]
		set chess(board.$chess(pieces.[expr 3 - $owner].$chess(en_passant_index).x).$chess(pieces.[expr 3 - $owner].$chess(en_passant_index).y).owner) [pop]
	    }
	}
	"queenside_castle" {
	}
	"kingside_castle" {
	}
    }

    if {$threatened == 1} {
	# The king is threatened; cancel the move
	set chess(board.$x.$y.index) [pop]
	set chess(board.$x.$y.owner) [pop]
	set chess(pieces.$owner.$index.has_moved) [pop]
	set chess(pieces.$owner.$index.y) [pop]
	set chess(pieces.$owner.$index.x) [pop]
	set chess(board.$chess(pieces.$owner.$index.x).$chess(pieces.$owner.$index.y).index) [pop]
	set chess(board.$chess(pieces.$owner.$index.x).$chess(pieces.$owner.$index.y).owner) [pop]

	log_it "(do_move): Player $owner tried to leave his king in danger"
	show_message $chess(players.$owner.sock) "red" "Your king is threatened"
    }

    clear_undo_stack

    unset owner index x y loc_1 loc_2 x_old y_old capture_index
}
