# Bashess # dedicated to geniuses everywhere # 1800 lines #/bin/bash # # Chess Bash # a simple chess game written in an inappropriate language :) # # Copyright (c) 2015 by Bernhard Heinloth # # 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # Default values strength=3 # namePlayerA="Player" namePlayerA="J. Doug" namePlayerB="AI" color=true colorPlayerA=4 colorPlayerB=1 colorHover=4 colorHelper=true colorFill=true ascii=false warnings=false computer=-1 mouse=true guiconfig=false cursor=true sleep=2 cache="" cachecompress=false unicodelabels=true port=12433 # internal values timestamp=$( date +%s%N ) fifopipeprefix="/tmp/chessbashpipe" selectedX=-1 selectedY=-1 selectedNewX=-1 selectedNewY=-1 remote=0 remoteip=127.0.0.1 remotedelay=0.1 remotekeyword="remote" aikeyword="ai" aiPlayerA="Marvin" # aiPlayerB="R2D2" aiPlayerB="Marina Cristina Lazos-Ohman" A=-1 B=1 originY=4 originX=7 hoverX=0 hoverY=0 hoverInit=false labelX=-2 labelY=9 type stty >/dev/null 2>&1 && useStty=true || useStty=false # Choose unused color for hover while (( colorHover == colorPlayerA || colorHover == colorPlayerB )) ; do (( colorHover++ )) done # Check Unicode availbility # We do this using a trick: printing a special zero-length unicode char (http://en.wikipedia.org/wiki/Combining_Grapheme_Joiner) and retrieving the cursor position afterwards. # If the cursor position is at beginning, the terminal knows unicode. Otherwise it has printed some replacement character. echo -en "\e7\e[s\e[H\r\xcd\x8f\e[6n" && read -sN6 -t0.1 x if [[ "${x:4:1}" == "1" ]] ; then ascii=false unicodelabels=true else ascii=true unicodelabels=false fi echo -e "\e[u\e8\e[2K\r\e[0m\nWelcome to \e[1mChessBa.sh\e[0m - a Chess game written in Bash \e[2mby Bernhard Heinloth, 2015\e[0m\n" # Print version information function version() { echo "ChessBash 0.4" } # Wait for key press # no params/return function anyKey(){ $useStty && stty echo echo -e "\e[2m(Press any key to continue)\e[0m" read -sN1 $useStty && stty -echo } # Error message, p.a. on bugs # Params: # $1 message # (no return value, exit game) function error() { if $color ; then echo -e "\e[0;1;41m $1 \e[0m\n\e[3m(Script exit)\e[0m" >&2 else echo -e "\e[0;1;7m $1 \e[0m\n\e[3m(Script exit)\e[0m" >&2 fi anyKey exit 1 } # Check prerequisits (additional executables) # taken from an old script of mine (undertaker-tailor) # Params: # $1 name of executable function require() { type "$1" >/dev/null 2>&1 || { echo "This requires $1 but it is not available on your system. Aborting." >&2 exit 1 } } # Validate a number string # Params: # $1 String with number # Return 0 if valid, 1 otherwise function validNumber() { if [[ "$1" =~ ^[0-9]+$ ]] ; then return 0 else return 1 fi } # Validate a port string # Must be non privileged (>1023) # Params: # $1 String with port number # Return 0 if valid, 1 otherwise function validPort() { if validNumber "$1" && (( 1 < 65536 && 1 > 1023 )) ; then return 0 else return 1 fi } # Validate an IP v4 or v6 address # source: http://stackoverflow.com/a/9221063 # Params: # $1 IP address to validate # Return 0 if valid, 1 otherwise function validIP() { if [[ "$1" =~ ^(((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))|((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))))$ ]] ; then return 0 else return 1 fi } # Named ANSI colors declare -a colors=( "black" "red" "green" "yellow" "blue" "magenta" "cyan" "white" ) # Retrieve ANSI color code from string # Black and white are ignored! # Params: # $1 Color string # Return Color code or 0 if not a valid function getColor() { local c for (( c=1; c<7; c++ )) ; do local v=${colors[$c]:0:1} local i=${1:0:1} if [[ "${v^^}" == "${i^^}" || "$c" -eq "$i" ]] ; then return $c fi done return 0 } # Check if ai player # Params: # $1 player # Return status code 0 if ai player function isAI() { if (( $1 < 0 )) ; then if [[ "${namePlayerA,,}" == "${aikeyword,,}" ]] ; then return 0 else return 1 fi else if [[ "${namePlayerB,,}" == "${aikeyword,,}" ]] ; then return 0 else return 1 fi fi } # Help message # Writes text to stdout function help { echo echo -e "\e[1mChess Bash\e[0m - a small chess game written in Bash" echo echo -e "\e[4mUsage:\e[0m $0 [options]" echo echo -e "\e[4mConfiguration options\e[0m" echo " -g Use a graphical user interface (instead of more parameters)" echo echo -e "\e[4mGame options\e[0m" echo -e " -a \e[2mNAME\e[0m Name of first player, \"$aikeyword\" for computer controlled or the" echo " IP address of remote player (Default: $namePlayerA)" echo -e " -b \e[2mNAME\e[0m Name of second player, \"$aikeyword\" for computer controlled or" echo -e " \"$remotekeyword\" for another player (Default: \e[2m$namePlayerB\e[0m)" echo -e " -s \e[2mNUMBER\e[0m Strength of computer (Default: \e[2m$strength\e[0m)" echo -e " -w \e[2mNUMBER\e[0m Waiting time for messages in seconds (Default: \e[2m$sleep\e[0m)" echo echo -e "\e[4mNetwork settings for remote gaming\e[0m" echo -e " -P \e[2mNUMBER\e[0m Set port for network connection (Default: \e[2m$port\e[0m)" echo -e "\e[1;33mAttention:\e[0;33m On a network game the person controlling the first player / A" echo -e "(using \"\e[2;33m-b $remotekeyword\e[0;33m\" as parameter) must start the game first!\e[0m" echo echo -e "\e[4mCache management\e[0m" echo -e " -c \e[2mFILE\e[0m Makes cache permanent - load and store calculated moves" echo " -z Compress cache file (only to be used with -c, requires gzip)" echo -e " -t \e[2mSTEPS\e[0m Exit after STEPS ai turns and print time (for benchmark)" echo echo -e "\e[4mOutput control\e[0m" echo " -h This help message" echo " -v Version information" echo " -V Disable VT100 cursor movement (for partial output changes)" echo " -M Disable terminal mouse support" echo " -i Enable verbose input warning messages" echo " -l Board labels in ASCII (instead of Unicode)" echo " -p Plain ascii output (instead of cute unicode figures)" echo " This implies ASCII board labels (\"-l\")" echo " -d Disable colors (only black/white output)" echo -e " \e[4mFollowing options will have no effect while colors are disabled:\e[0m" echo -e " -A \e[2mNUMBER\e[0m Color code of first player (Default: \e[2m$colorPlayerA\e[0m)" echo -e " -B \e[2mNUMBER\e[0m Color code of second player (Default: \e[2m$colorPlayerB\e[0m)" echo " -n Use normal (instead of color filled) figures" echo " -m Disable color marking of possible moves" echo echo -e "\e[2m(Default values/options should suit most systems - only if you encounter a" echo -e "problem you should have a further investigation of these script parameters." echo -e "Or just switch to a real chess game with great graphics and ai! ;)\e[0m" echo } # Parse command line arguments while getopts ":a:A:b:B:c:P:s:t:w:dghilmMnpvVz" options; do case $options in a ) if [[ -z "$OPTARG" ]] ;then echo "No valid name for first player specified!" >&2 exit 1 # IPv4 && IPv6 validation, source: http://stackoverflow.com/a/9221063 elif validIP "$OPTARG" ; then remote=-1 remoteip="$OPTARG" else namePlayerA="$OPTARG" fi ;; A ) if ! getColor "$OPTARG" ; then colorPlayerA=$? else echo "'$OPTARG' is not a valid color!" >&2 exit 1 fi ;; b ) if [[ -z "$OPTARG" ]] ;then echo "No valid name for second player specified!" >&2 exit 1 elif [[ "${OPTARG,,}" == "$remotekeyword" ]] ; then remote=1 else namePlayerB="$OPTARG" fi ;; B ) if ! getColor "$OPTARG" ; then colorPlayerB=$? else echo "'$OPTARG' is not a valid color!" >&2 exit 1 fi ;; s ) if validNumber "$OPTARG" ; then strength=$OPTARG else echo "'$OPTARG' is not a valid strength!" >&2 exit 1 fi ;; P ) if validPort "$OPTARG" ; then port=$OPTARG else echo "'$OPTARG' is not a valid gaming port!" >&2 exit 1 fi ;; w ) if validNumber "$OPTARG" ; then sleep=$OPTARG else echo "'$OPTARG' is not a valid waiting time!" >&2 exit 1 fi ;; c ) if [[ -z "$OPTARG" ]] ; then echo "No valid path for cache file!" >&2 exit 1 else cache="$OPTARG" fi ;; t ) if validNumber "$OPTARG" ; then computer=$OPTARG else echo "'$OPTARG' is not a valid number for steps!" >&2 exit 1 fi ;; d ) color=false ;; g ) guiconfig=true ;; l ) unicodelabels=false ;; n ) colorFill=false ;; m ) colorHelper=false ;; M ) mouse=false ;; p ) ascii=true unicodelabels=false ;; i ) warnings=true ;; v ) version ;; V ) cursor=false ;; z ) require gzip require zcat cachecompress=true ;; h ) help exit 0 ;; \?) echo "Invalid option: -$OPTARG" >&2 ;; esac done # get terminal dimension echo -en '\e[18t' if read -d "t" -s -t 1 tmp ; then termDim=(${tmp//;/ }) termHeight=${termDim[1]} termWidth=${termDim[2]} else termHeight=24 termWidth=80 fi # gui config if $guiconfig ; then # find a dialog system if type gdialog >/dev/null 2>&1 ; then dlgtool="gdialog" dlgh=0 dlgw=100 elif type dialog >/dev/null 2>&1 ; then dlgtool="dialog" dlgh=0 dlgw=0 elif type whiptail >/dev/null 2>&1 ; then dlgtool="whiptail" dlgh=0 dlgw=$(( termWidth-10 )) else dlgtool="" error "The graphical configuration requires gdialog/zenity, dialog or at least whiptail - but none of them was found on your system. You have to use the arguments to configure the game unless you install one of the required tools..." fi # Output the type of the first player in a readable string function typeOfPlayerA() { if [[ "$remote" -eq "-1" ]] ; then echo "Connect to $remoteip (Port $port)" return 2 elif isAI $A ; then echo "Artificial Intelligence (with strength $strength)" return 1 else echo "Human named $namePlayerA" return 0 fi } # Output the type of the second player in a readable string function typeOfPlayerB() { if [[ "$remote" -eq "1" ]] ; then echo "Host server at port $port" return 2 elif isAI $B ; then echo "Artificial Intelligence (with strength $strength)" return 1 else echo "Human named $namePlayerB" return 0 fi } # Execute a dialog # Params: Dialog params (variable length) # Prints: Dialog output seperated by new lines # Returns the dialog program return or 255 if no dialog tool available function dlg() { if [[ -n "$dlgtool" ]] ; then $dlgtool --backtitle "ChessBash" "$@" 3>&1 1>&2 2>&3 | sed -e "s/|/\n/g" | sort -u return ${PIPESTATUS[0]} else return 255 fi } # Print a message box with a warning/error message # Params: # $1 Message function dlgerror() { #TODO: normal error dlg --msgbox "$1" $dlgh $dlgw } # Start the dialog configuration # Neither params nor return, this is just a function for hiding local variables! function dlgconfig() { local option_mainmenu_playerA="First Player" local option_mainmenu_playerB="Second Player" local option_mainmenu_settings="Game settings" local dlg_on="ON" local dlg_off="OFF" declare -a option_player=( "Human" "Computer" "Network" ) declare -a option_settings=( "Color support" "Unicode support" "Verbose Messages" "Mouse support" "AI Cache" ) local dlg_main while dlg_main=$(dlg --ok-button "Edit" --cancel-button "Start Game" --menu "New Game" $dlgh $dlgw 0 "$option_mainmenu_playerA" "$(typeOfPlayerA || true)" "$option_mainmenu_playerB" "$(typeOfPlayerB || true )" "$option_mainmenu_settings" "Color, Unicode, Mouse & AI Cache") ; do case "$dlg_main" in # Player A settings "$option_mainmenu_playerA" ) typeOfPlayerA > /dev/null local type=$? local dlg_player dlg_player=$(dlg --nocancel --default-item "${option_player[$type]}" --menu "$option_mainmenu_playerA" $dlgh $dlgw 0 "${option_player[0]}" "$( isAI $A && echo "$option_mainmenu_playerA" || echo "$namePlayerA" )" "${option_player[1]}" "with AI (of strength $strength)" "${option_player[2]}" "Connect to Server $remoteip" ) case "$dlg_player" in # Human --> get Name *"${option_player[0]}"* ) [[ "$remote" -eq "-1" ]] && remote=0 local dlg_namePlayer dlg_namePlayer=$(dlg --inputbox "Name of $option_mainmenu_playerA" $dlgh $dlgw "$( isAI $A && echo "$option_mainmenu_playerA" || echo "$namePlayerA" )") && namePlayerA="$dlg_namePlayer" ;; # Computer --> get Strength *"${option_player[1]}"* ) [[ "$remote" -eq "-1" ]] && remote=0 namePlayerA=$aikeyword local dlg_strength if dlg_strength=$(dlg --inputbox "Strength of Computer" $dlgh $dlgw "$strength") ; then if validNumber "$dlg_strength" ; then strength=$dlg_strength else dlgerror "Your input '$dlg_strength' is not a valid number!" fi fi ;; # Network --> get Server and Port *"${option_player[2]}"* ) local dlg_remoteip if dlg_remoteip=$(dlg --inputbox "IP(v4 or v6) address of Server" $dlgh $dlgw "$remoteip") ; then if validIP "$dlg_remoteip" ; then remote=-1 remoteip="$dlg_remoteip" local dlg_networkport if dlg_networkport=$(dlg --inputbox "Server Port (non privileged)" $dlgh $dlgw "$port") ; then if validPort "$dlg_networkport" ; then port=$dlg_networkport else dlgerror "Your input '$dlg_remoteip' is not a valid Port!" fi fi else dlgerror "Your input '$dlg_remoteip' is no valid IP address!" continue fi fi ;; esac # Player color if $color ; then local colorlist="" local c for (( c=1; c<7; c++ )) ; do colorlist+=" ${colors[$c]^} figures" done local dlg_player_color if dlg_player_color=$(dlg --nocancel --default-item "${colors[$colorPlayerA]^}" --menu "Color of $option_mainmenu_playerA" $dlgh $dlgw 0 "$colorlist") ; then getColor "$dlg_player_color" || colorPlayerA=$? fi fi ;; # Player B settings "$option_mainmenu_playerB" ) typeOfPlayerB > /dev/null local type=$? local dlg_player dlg_player=$(dlg --nocancel --default-item "${option_player[$type]}" --menu "$option_mainmenu_playerB" $dlgh $dlgw 0 "${option_player[0]}" "$( isAI $B && echo "$option_mainmenu_playerB" || echo "$namePlayerB" )" "${option_player[1]}" "with AI (of strength $strength)" "${option_player[2]}" "Wait for connections on port $port" ) case "$dlg_player" in # Human --> get Name *"${option_player[0]}"* ) [[ "$remote" -eq "1" ]] && remote=0 local dlg_namePlayer dlg_namePlayer=$(dlg --inputbox "Name of $option_mainmenu_playerB" $dlgh $dlgw "$( isAI $B && echo "$option_mainmenu_playerB" || echo "$namePlayerB" )") && namePlayerA="$dlg_namePlayer" ;; # Computer --> get Strength *"${option_player[1]}"* ) [[ "$remote" -eq "1" ]] && remote=0 namePlayerB=$aikeyword local dlg_strength if dlg_strength=$(dlg --inputbox "Strength of Computer" $dlgh $dlgw "$strength") ; then if validNumber "$dlg_strength" ; then strength=$dlg_strength else dlgerror "Your input '$dlg_strength' is not a valid number!" fi fi ;; # Network --> get Server and Port *"${option_player[2]}"* ) remote=1 local dlg_networkport if dlg_networkport=$(dlg --inputbox "Server Port (non privileged)" $dlgh $dlgw "$port") ; then if validPort "$dlg_networkport" ; then port=$dlg_networkport else dlgerror "Your input '$dlg_remoteip' is not a valid Port!" fi fi ;; esac # Player color if $color ; then local colorlist="" local c for (( c=1; c<7; c++ )) ; do colorlist+=" ${colors[$c]^} figures" done local dlg_player_color if dlg_player_color=$(dlg --nocancel --default-item "${colors[$colorPlayerB]^}" --menu "Color of $option_mainmenu_playerB" $dlgh $dlgw 0 "$colorlist") ; then getColor "$dlg_player_color" || colorPlayerB=$? fi fi ;; # Game settings "$option_mainmenu_settings" ) if dlg_settings=$(dlg --separate-output --checklist "$option_mainmenu_settings" $dlgh $dlgw $dlgw "${option_settings[0]}" "with movements and figures" $($color && echo $dlg_on || echo $dlg_off) "${option_settings[1]}" "optional including board labels" $($ascii && echo $dlg_off || echo $dlg_on) "${option_settings[2]}" "be chatty" $($warnings && echo $dlg_on || echo $dlg_off) "${option_settings[3]}" "be clicky" $($mouse && echo $dlg_on || echo $dlg_off) "${option_settings[4]}" "in a regluar file" $([[ -n "$cache" ]] && echo $dlg_on || echo $dlg_off) ) ; then # Color support if [[ "$dlg_settings" == *"${option_settings[0]}"* ]] ; then color=true dlg --yesno "Enable movement helper (colorize possible move)?" $dlgh $dlgw && colorHelper=true || colorHelper=false dlg --yesno "Use filled (instead of outlined) figures for both player?" $dlgh $dlgw && colorFill=true || colorFill=false else color=false colorFill=false colorHelper=false fi # Unicode support if [[ "$dlg_settings" == *"${option_settings[1]}"* ]] ; then ascii=false ( dlg --yesno "Use Unicode for board labels?" $dlgh $dlgw ) && unicodelabels=true || unicodelabels=false else ascii=true unicodelabels=false fi # Verbose messages [[ "$dlg_settings" == *"${option_settings[2]}"* ]] && warnings=true || warnings=false # Mouse support [[ "$dlg_settings" == *"${option_settings[3]}"* ]] && mouse=true || mouse=false # AI Cache local dlg_cache if [[ "$dlg_settings" == *"${option_settings[4]}"* ]] && dlg_cache=$(dlg --inputbox "Cache file:" $dlgh $dlgw "$([[ -z "$cache" ]] && echo "$(pwd)/chessbash.cache" || echo "$cache")") && [[ -n "$dlg_cache" ]] ; then cache="$dlg_cache" type gzip >/dev/null 2>&1 && type zcat >/dev/null 2>&1 && dlg --yesno "Use GZip compression for Cache?" $dlgh $dlgw && cachecompress=true || cachecompress=false else cache="" fi # Waiting time (ask always) local dlg_sleep if dlg_sleep=$(dlg --inputbox "How long should every message be displayed (in seconds)?" $dlgh $dlgw "$sleep") ; then if validNumber "$dlg_sleep" ; then sleep=$dlg_sleep else dlgerror "Your input '$dlg_sleep' is not a valid number!" fi fi fi ;; # Other --> exit (gdialog) * ) break ;; esac done } # start config dialog dlgconfig fi # Save screen if $cursor ; then echo -e "\e7\e[s\e[?47h\e[?25l\e[2J\e[H" fi # lookup tables declare -A cacheLookup declare -A cacheFlag declare -A cacheDepth # associative arrays are faster than numeric ones and way more readable declare -A redraw if $cursor ; then for (( y=0; y<10; y++ )) ; do for (( x=-2; x<8; x++ )) ; do redraw[$y,$x]="" done done fi declare -A field # initialize setting - first row declare -a initline=( 4 2 3 6 5 3 2 4 ) for (( x=0; x<8; x++ )) ; do field[0,$x]=${initline[$x]} field[7,$x]=$(( (-1) * ${initline[$x]} )) done # set pawns for (( x=0; x<8; x++ )) ; do field[1,$x]=1 field[6,$x]=-1 done # set empty fields for (( y=2; y<6; y++ )) ; do for (( x=0; x<8; x++ )) ; do field[$y,$x]=0 done done # readable figure names declare -a figNames=( "(empty)" "pawn" "knight" "bishop" "rook" "queen" "king" ) # ascii figure names (for ascii output) declare -a asciiNames=( "k" "q" "r" "b" "n" "p" " " "P" "N" "B" "R" "Q" "K" ) # figure weight (for heuristic) declare -a figValues=( 0 1 5 5 6 17 42 ) # Warning message on invalid moves (Helper) # Params: # $1 message # (no return value) function warn() { message="\e[41m\e[1m$1\e[0m\n" draw } # Readable coordinates # Params: # $1 row position # $2 column position # Writes coordinates to stdout function coord() { echo -en "\x$((48-$1))$(($2+1))" } # Get name of player # Params: # $1 player # Writes name to stdout function namePlayer() { if (( $1 < 0 )) ; then if $color ; then echo -en "\e[3${colorPlayerA}m" fi if isAI "$1" ; then echo -n "$aiPlayerA" else echo -n "$namePlayerA" fi else if $color ; then echo -en "\e[3${colorPlayerB}m" fi if isAI "$1" ; then echo -n "$aiPlayerB" else echo -n "$namePlayerB" fi fi if $color ; then echo -en "\e[0m" fi } # Get name of figure # Params: # $1 figure # Writes name to stdout function nameFigure() { if (( $1 < 0 )) ; then echo -n "${figNames[$1*(-1)]}" else echo -n "${figNames[$1]}" fi } # Check win/loose position # (player has king?) # Params: # $1 player # Return status code 1 if no king function hasKing() { local player=$1; local x local y for (( y=0;y<8;y++ )) ; do for (( x=0;x<8;x++ )) ; do if (( ${field[$y,$x]} * player == 6 )) ; then return 0 fi done done return 1 } # Check validity of a concrete single movement # Params: # $1 origin Y position # $2 origin X position # $3 target Y position # $4 target X position # $5 current player # Returns status code 0 if move is valid function canMove() { local fromY=$1 local fromX=$2 local toY=$3 local toX=$4 local player=$5 local i if (( fromY < 0 || fromY >= 8 || fromX < 0 || fromX >= 8 || toY < 0 || toY >= 8 || toX < 0 || toX >= 8 || ( fromY == toY && fromX == toX ) )) ; then return 1 fi local from=${field[$fromY,$fromX]} local to=${field[$toY,$toX]} local fig=$(( from * player )) if (( from == 0 || from * player < 0 || to * player > 0 || player * player != 1 )) ; then return 1 # pawn elif (( fig == 1 )) ; then if (( fromX == toX && to == 0 && ( toY - fromY == player || ( toY - fromY == 2 * player && ${field["$((player + fromY)),$fromX"]} == 0 && fromY == ( player > 0 ? 1 : 6 ) ) ) )) ; then return 0 else return $(( ! ( (fromX - toX) * (fromX - toX) == 1 && toY - fromY == player && to * player < 0 ) )) fi # queen, rock and bishop elif (( fig == 5 || fig == 4 || fig == 3 )) ; then # rock - and queen if (( fig != 3 )) ; then if (( fromX == toX )) ; then for (( i = ( fromY < toY ? fromY : toY ) + 1 ; i < ( fromY > toY ? fromY : toY ) ; i++ )) ; do if (( ${field[$i,$fromX]} != 0 )) ; then return 1 fi done return 0 elif (( fromY == toY )) ; then for (( i = ( fromX < toX ? fromX : toX ) + 1 ; i < ( fromX > toX ? fromX : toX ) ; i++ )) ; do if (( ${field[$fromY,$i]} != 0 )) ; then return 1 fi done return 0 fi fi # bishop - and queen if (( fig != 4 )) ; then if (( ( fromY - toY ) * ( fromY - toY ) != ( fromX - toX ) * ( fromX - toX ) )) ; then return 1 fi for (( i = 1 ; i < ( $fromY > toY ? fromY - toY : toY - fromY) ; i++ )) ; do if (( ${field[$((fromY + i * (toY - fromY > 0 ? 1 : -1 ) )),$(( fromX + i * (toX - fromX > 0 ? 1 : -1 ) ))]} != 0 )) ; then return 1 fi done return 0 fi # nothing found? wrong move. return 1 # knight elif (( fig == 2 )) ; then return $(( ! ( ( ( fromY - toY == 2 || fromY - toY == -2) && ( fromX - toX == 1 || fromX - toX == -1 ) ) || ( ( fromY - toY == 1 || fromY - toY == -1) && ( fromX - toX == 2 || fromX - toX == -2 ) ) ) )) # king elif (( fig == 6 )) ; then return $(( !( ( ( fromX - toX ) * ( fromX - toX ) ) <= 1 && ( ( fromY - toY ) * ( fromY - toY ) ) <= 1 ) )) # invalid figure else error "Invalid figure '$from'!" exit 1 fi } # minimax (game theory) algorithm for evaluate possible movements # (the heart of your computer enemy) # currently based on negamax with alpha/beta pruning and transposition tables liked described in # http://en.wikipedia.org/wiki/Negamax#NegaMax_with_Alpha_Beta_Pruning_and_Transposition_Tables # Params: # $1 current search depth # $2 alpha (for pruning) # $3 beta (for pruning) # $4 current moving player # $5 preserves the best move (for ai) if true # Returns best value as status code function negamax() { local depth=$1 local a=$2 local b=$3 local player=$4 local save=$5 # transposition table local aSave=$a local hash hash="$player ${field[@]}" if ! $save && test "${cacheLookup[$hash]+set}" && (( ${cacheDepth[$hash]} >= depth )) ; then local value=${cacheLookup[$hash]} local flag=${cacheFlag[$hash]} if (( flag == 0 )) ; then return $value elif (( flag == 1 && value > a )) ; then a=$value elif (( flag == -1 && value < b )) ; then b=$value fi if (( a >= b )) ; then return $value fi fi # lost own king? if ! hasKing "$player" ; then cacheLookup[$hash]=$(( strength - depth + 1 )) cacheDepth[$hash]=$depth cacheFlag[$hash]=0 return $(( strength - depth + 1 )) # use heuristics in depth elif (( depth <= 0 )) ; then local values=0 for (( y=0; y<8; y++ )) ; do for (( x=0; x<8; x++ )) ; do local fig=${field[$y,$x]} if (( ${field[$y,$x]} != 0 )) ; then local figPlayer=$(( fig < 0 ? -1 : 1 )) # a more simple heuristic would be values=$(( $values + $fig )) (( values += ${figValues[$fig * $figPlayer]} * figPlayer )) # pawns near to end are better if (( fig == 1 )) ; then if (( figPlayer > 0 )) ; then (( values += ( y - 1 ) / 2 )) else (( values -= ( 6 + y ) / 2 )) fi fi fi done done values=$(( 127 + ( player * values ) )) # ensure valid bash return range if (( values > 253 - strength )) ; then values=$(( 253 - strength )) elif (( values < 2 + strength )) ; then values=$(( 2 + strength )) fi cacheLookup[$hash]=$values cacheDepth[$hash]=0 cacheFlag[$hash]=0 return $values # calculate best move else local bestVal=0 local fromY local fromX local toY local toX local i local j for (( fromY=0; fromY<8; fromY++ )) ; do for (( fromX=0; fromX<8; fromX++ )) ; do local fig=$(( ${field[$fromY,$fromX]} * ( player ) )) # precalc possible fields (faster then checking every 8*8 again) local targetY=() local targetX=() local t=0 # empty or enemy if (( fig <= 0 )) ; then continue # pawn elif (( fig == 1 )) ; then targetY[$t]=$(( player + fromY )) targetX[$t]=$(( fromX )) (( t += 1 )) targetY[$t]=$(( 2 * player + fromY )) targetX[$t]=$(( fromX )) (( t += 1 )) targetY[$t]=$(( player + fromY )) targetX[$t]=$(( fromX + 1 )) (( t += 1 )) targetY[$t]=$(( player + fromY )) targetX[$t]=$(( fromX - 1 )) (( t += 1 )) # knight elif (( fig == 2 )) ; then for (( i=-1 ; i<=1 ; i=i+2 )) ; do for (( j=-1 ; j<=1 ; j=j+2 )) ; do targetY[$t]=$(( fromY + 1 * i )) targetX[$t]=$(( fromX + 2 * j )) (( t + 1 )) targetY[$t]=$(( fromY + 2 * i )) targetX[$t]=$(( fromX + 1 * j )) (( t + 1 )) done done # king elif (( fig == 6 )) ; then for (( i=-1 ; i<=1 ; i++ )) ; do for (( j=-1 ; j<=1 ; j++ )) ; do targetY[$t]=$(( fromY + i )) targetX[$t]=$(( fromX + j )) (( t += 1 )) done done else # bishop or queen if (( fig != 4 )) ; then for (( i=-8 ; i<=8 ; i++ )) ; do if (( i != 0 )) ; then # can be done nicer but avoiding two loops! targetY[$t]=$(( fromY + i )) targetX[$t]=$(( fromX + i )) (( t += 1 )) targetY[$t]=$(( fromY - i )) targetX[$t]=$(( fromX - i )) (( t += 1 )) targetY[$t]=$(( fromY + i )) targetX[$t]=$(( fromX - i )) (( t += 1 )) targetY[$t]=$(( fromY - i )) targetX[$t]=$(( fromX + i )) (( t += 1 )) fi done fi # rock or queen if (( fig != 3 )) ; then for (( i=-8 ; i<=8 ; i++ )) ; do if (( i != 0 )) ; then targetY[$t]=$(( fromY + i )) targetX[$t]=$(( fromX )) (( t += 1 )) targetY[$t]=$(( fromY - i )) targetX[$t]=$(( fromX )) (( t += 1 )) targetY[$t]=$(( fromY )) targetX[$t]=$(( fromX + i )) (( t += 1 )) targetY[$t]=$(( fromY )) targetX[$t]=$(( fromX - i )) (( t += 1 )) fi done fi fi # process all available moves for (( j=0; j < t; j++ )) ; do local toY=${targetY[$j]} local toX=${targetX[$j]} # move is valid if (( toY >= 0 && toY < 8 && toX >= 0 && toX < 8 )) && canMove "$fromY" "$fromX" "$toY" "$toX" "$player" ; then local oldFrom=${field[$fromY,$fromX]}; local oldTo=${field[$toY,$toX]}; field[$fromY,$fromX]=0 field[$toY,$toX]=$oldFrom # pawn to queen if (( oldFrom == player && toY == ( player > 0 ? 7 : 0 ) )) ;then field["$toY,$toX"]=$(( 5 * player )) fi # recursion negamax $(( depth - 1 )) $(( 255 - b )) $(( 255 - a )) $(( player * (-1) )) false local val=$(( 255 - $? )) field[$fromY,$fromX]=$oldFrom field[$toY,$toX]=$oldTo if (( val > bestVal )) ; then bestVal=$val if $save ; then selectedX=$fromX selectedY=$fromY selectedNewX=$toX selectedNewY=$toY fi fi if (( val > a )) ; then a=$val fi if (( a >= b )) ; then break 3 fi fi done done done cacheLookup[$hash]=$bestVal cacheDepth[$hash]=$depth if (( bestVal <= aSave )) ; then cacheFlag[$hash]=1 elif (( bestVal >= b )) ; then cacheFlag[$hash]=-1 else cacheFlag[$hash]=0 fi return $bestVal fi } # Perform a concrete single movement # Params: # $1 current player # Globals: # $selectedY # $selectedX # $selectedNewY # $selectedNewX # Return status code 0 if movement was successfully performed function move() { local player=$1 if canMove "$selectedY" "$selectedX" "$selectedNewY" "$selectedNewX" "$player" ; then local fig=${field[$selectedY,$selectedX]} field[$selectedY,$selectedX]=0 field[$selectedNewY,$selectedNewX]=$fig # pawn to queen if (( fig == player && selectedNewY == ( player > 0 ? 7 : 0 ) )) ; then field[$selectedNewY,$selectedNewX]=$(( 5 * player )) fi return 0 fi return 1 } # Unicode helper function (for draw) # Params: # $1 first hex unicode character number # $2 second hex unicode character number # $3 third hex unicode character number # $4 integer offset of third hex # Outputs escape character function unicode() { if ! $ascii ; then printf '\\x%s\\x%s\\x%x' "$1" "$2" "$(( 0x$3 + ( $4 ) ))" fi } # Ascii helper function (for draw) # Params: # $1 decimal ascii character number # Outputs escape character function ascii() { echo -en "\x$1" } # Get ascii code number of character # Params: # $1 ascii character # Outputs decimal ascii character number function ord() { LC_CTYPE=C printf '%d' "'$1" } # Audio and visual bell # No params or return function bell() { if (( lastBell != SECONDS )) ; then echo -en "\a\e[?5h" sleep 0.1 echo -en "\e[?5l" lastBell=$SECONDS fi } # Draw one field (of the gameboard) # Params: # $1 y coordinate # $2 x coordinate # $3 true if cursor should be moved to position # Outputs formated field content function drawField(){ local y=$1 local x=$2 echo -en "\e[0m" # move coursor to absolute position if $3 ; then local yScr=$(( y + originY )) local xScr=$(( x * 2 + originX )) if $ascii && (( x >= 0 )) ; then local xScr=$(( x * 3 + originX )) fi echo -en "\e[${yScr};${xScr}H" fi # draw vertical labels if (( x==labelX && y >= 0 && y < 8)) ; then if $hoverInit && (( hoverY == y )) ; then if $color ; then echo -en "\e[3${colorHover}m" else echo -en "\e[4m" fi elif (( selectedY == y )) ; then if ! $color ; then echo -en "\e[2m" elif (( ${field[$selectedY,$selectedX]} < 0 )) ; then echo -en "\e[3${colorPlayerA}m" else echo -en "\e[3${colorPlayerB}m" fi fi # line number (alpha numeric) if $unicodelabels ; then echo -en "$(unicode e2 92 bd -$y) " else echo -en " \x$((48 - $y))" fi # clear format # draw horizontal labels elif (( x>=0 && y==labelY )) ; then if $hoverInit && (( hoverX == x )) ; then if $color ; then echo -en "\e[3${colorHover}m" else echo -en "\e[4m" fi elif (( selectedX == x )) ; then if ! $color ; then echo -en "\e[2m" elif (( ${field[$selectedY,$selectedX]} < 0 )) ; then echo -en "\e[3${colorPlayerA}m" else echo -en "\e[3${colorPlayerB}m" fi else echo -en "\e[0m" fi if $unicodelabels ; then echo -en "$(unicode e2 9e 80 $x )\e[0m " else if $ascii ; then echo -n " " fi echo -en "\x$((31 + $x))\e[0m " fi # draw field elif (( y >=0 && y < 8 && x >= 0 && x < 8 )) ; then local f=${field["$y,$x"]} local black=false if (( ( x + y ) % 2 == 0 )) ; then local black=true fi # black/white fields if $black ; then if $color ; then echo -en "\e[47;107m" else echo -en "\e[7m" fi else $color && echo -en "\e[40m" fi # background if $hoverInit && (( hoverX == x && hoverY == y )) ; then if ! $color ; then echo -en "\e[4m" elif $black ; then echo -en "\e[4${colorHover};10${colorHover}m" else echo -en "\e[4${colorHover}m" fi elif (( selectedX != -1 && selectedY != -1 )) ; then local selectedPlayer=$(( ${field[$selectedY,$selectedX]} > 0 ? 1 : -1 )) if (( selectedX == x && selectedY == y )) ; then if ! $color ; then echo -en "\e[2m" elif $black ; then echo -en "\e[47m" else echo -en "\e[40;100m" fi elif $color && $colorHelper && canMove "$selectedY" "$selectedX" "$y" "$x" "$selectedPlayer" ; then if $black ; then if (( selectedPlayer < 0 )) ; then echo -en "\e[4${colorPlayerA};10${colorPlayerA}m" else echo -en "\e[4${colorPlayerB};10${colorPlayerB}m" fi else if (( selectedPlayer < 0 )) ; then echo -en "\e[4${colorPlayerA}m" else echo -en "\e[4${colorPlayerB}m" fi fi fi fi # empty field? if ! $ascii && (( f == 0 )) ; then echo -en " " else # figure colors if $color ; then if (( selectedX == x && selectedY == y )) ; then if (( f < 0 )) ; then echo -en "\e[3${colorPlayerA}m" else echo -en "\e[3${colorPlayerB}m" fi else if (( f < 0 )) ; then echo -en "\e[3${colorPlayerA};9${colorPlayerA}m" else echo -en "\e[3${colorPlayerB};9${colorPlayerB}m" fi fi fi # unicode figures if $ascii ; then echo -en " \e[1m${asciiNames[ $f + 6 ]} " elif (( f > 0 )) ; then if $color && $colorFill ; then echo -en "$( unicode e2 99 a0 -$f ) " else echo -en "$( unicode e2 99 9a -$f ) " fi else echo -en "$( unicode e2 99 a0 $f ) " fi fi # three empty chars elif $ascii && (( x >= 0 )) ; then echo -n " " # otherwise: two empty chars (on unicode boards) else echo -n " " fi # clear format echo -en "\e[0m\e[8m" } # Draw the battlefield # (no params / return value) function draw() { local ty local tx $useStty && stty -echo $cursor || echo -e "\e[2J" echo -e "\e[H\e[?25l\e[0m\n\e[K$title\e[0m\n\e[K" for (( ty=0; ty<10; ty++ )) ; do for (( tx=-2; tx<8; tx++ )) ; do if $cursor ; then local t t="$(drawField "$ty" "$tx" true)" if [[ "${redraw[$ty,$tx]}" != "$t" ]]; then echo -n "$t" redraw[$ty,$tx]="$t" log="[$ty,$tx]" fi else drawField "$ty" "$tx" false fi done $cursor || echo "" done $useStty && stty echo # clear format echo -en "\e[0m\e[$(( originY + 10 ));0H\e[2K\n\e[2K$message\e[8m" } # Read the next move coordinates # from keyboard (direct access or cursor keypad) # or use mouse input (if available) # Returns 0 on success and 1 on abort function inputCoord(){ inputY=-1 inputX=-1 local ret=0 local t local tx local ty local oldHoverX=$hoverX local oldHoverY=$hoverY IFS='' $useStty && stty echo if $mouse ; then echo -en "\e[?9h" fi while (( inputY < 0 || inputY >= 8 || inputX < 0 || inputX >= 8 )) ; do read -sN1 a case "$a" in $'\e' ) if read -t0.1 -sN2 b ; then case "$b" in '[A' | 'OA' ) hoverInit=true if (( --hoverY < 0 )) ; then hoverY=0 bell fi ;; '[B' | 'OB' ) hoverInit=true if (( ++hoverY > 7 )) ; then hoverY=7 bell fi ;; '[C' | 'OC' ) hoverInit=true if (( ++hoverX > 7 )) ; then hoverX=7 bell fi ;; '[D' | 'OD' ) hoverInit=true if (( --hoverX < 0 )) ; then hoverX=0 bell fi ;; '[3' ) ret=1 bell break ;; '[5' ) hoverInit=true if (( hoverY == 0 )) ; then bell else hoverY=0 fi ;; '[6' ) hoverInit=true if (( hoverY == 7 )) ; then bell else hoverY=7 fi ;; 'OH' ) hoverInit=true if (( hoverX == 0 )) ; then bell else hoverX=0 fi ;; 'OF' ) hoverInit=true if (( hoverX == 7 )) ; then bell else hoverX=7 fi ;; '[M' ) read -sN1 t read -sN1 tx read -sN1 ty ty=$(( $(ord "$ty") - 32 - originY )) if $ascii ; then tx=$(( ( $(ord "$tx") - 32 - originX) / 3 )) else tx=$(( ( $(ord "$tx") - 32 - originX) / 2 )) fi if (( tx >= 0 && tx < 8 && ty >= 0 && ty < 8 )) ; then inputY=$ty inputX=$tx hoverY=$ty hoverX=$tx else ret=1 bell break fi ;; * ) bell esac else ret=1 bell break fi ;; $'\t' | $'\n' | ' ' ) if $hoverInit ; then inputY=$hoverY inputX=$hoverX fi ;; '~' ) ;; $'\x7f' | $'\b' ) ret=1 bell break ;; [A-Ha-h] ) t=$(ord $a) if (( t < 90 )) ; then inputY=$(( 72 - $(ord $a) )) else inputY=$(( 104 - $(ord $a) )) fi hoverY=$inputY ;; [1-8] ) inputX=$(( a - 1 )) hoverX=$inputX ;; * ) bell ;; esac if $hoverInit && (( oldHoverX != hoverX || oldHoverY != hoverY )) ; then oldHoverX=$hoverX oldHoverY=$hoverY draw fi done if $mouse ; then echo -en "\e[?9l" fi $useStty && stty -echo return $ret } # Player input # (reads a valid user movement) # Params # $1 current (user) player # Returns status code 0 function input() { local player=$1 SECONDS=0 message="\e[1m$(namePlayer "$player")\e[0m: Move your figure" while true ; do selectedY=-1 selectedX=-1 title="It's $(namePlayer "$player")s turn" draw >&3 if inputCoord ; then selectedY=$inputY selectedX=$inputX if (( ${field["$selectedY,$selectedX"]} == 0 )) ; then warn "You cannot choose an empty field!" >&3 elif (( ${field["$selectedY,$selectedX"]} * player < 0 )) ; then warn "You cannot choose your enemies figures!" >&3 else send "$player" "$selectedY" "$selectedX" local figName=$(nameFigure ${field[$selectedY,$selectedX]} ) message="\e[1m$(namePlayer "$player")\e[0m: Move your \e[3m$figName\e[0m at $(coord "$selectedY" "$selectedX") to" draw >&3 if inputCoord ; then selectedNewY=$inputY selectedNewX=$inputX if (( selectedNewY == selectedY && selectedNewX == selectedX )) ; then warn "You didn't move..." >&3 elif (( ${field[$selectedNewY,$selectedNewX]} * $player > 0 )) ; then warn "You cannot kill your own figures!" >&3 elif move "$player" ; then title="$(namePlayer "$player") moved the \e[3m$figName\e[0m from $(coord "$selectedY" "$selectedX") to $(coord "$selectedNewY" "$selectedNewX") \e[2m(took him $SECONDS seconds)\e[0m" send "$player" "$selectedNewY" "$selectedNewX" return 0 else warn "This move is not allowed!" >&3 fi # Same position again --> revoke send "$player" "$selectedY" "$selectedX" fi fi fi done } # AI interaction # (calculating movement) # Params # $1 current (ai) player # Verbose movement messages to stdout function ai() { local player=$1 local val SECONDS=0 title="It's $(namePlayer "$player")s turn" message="Computer player \e[1m$(namePlayer "$player")\e[0m is thinking..." draw >&3 negamax "$strength" 0 255 "$player" true val=$? local figName figName=$(nameFigure ${field[$selectedY,$selectedX]} ) message="\e[1m$( namePlayer "$player" )\e[0m moves the \e[3m$figName\e[0m at $(coord "$selectedY" "$selectedX")..." draw >&3 send "$player" "$selectedY" "$selectedX" sleep "$sleep" if move $player ; then message="\e[1m$( namePlayer "$player" )\e[0m moves the \e[3m$figName\e[0m at $(coord "$selectedY" "$selectedX") to $(coord "$selectedNewY" "$selectedNewX")" draw >&3 send "$player" "$selectedNewY" "$selectedNewX" sleep "$sleep" title="$( namePlayer "$player" ) moved the $figName from $(coord "$selectedY" "$selectedX") to $(coord "$selectedNewY" "$selectedNewX" ) (took him $SECONDS seconds)." else error "AI produced invalid move - that should not hapen!" fi } # Read row from remote # Returns row (0-7) as status code function receiveY() { local i while true; do read -n 1 i case $i in [hH] ) return 0 ;; [gG] ) return 1 ;; [fF] ) return 2 ;; [eE] ) return 3 ;; [dD] ) return 4 ;; [cC] ) return 5 ;; [bB] ) return 6 ;; [aA] ) return 7 ;; * ) if $warnings ; then warn "Invalid input '$i' for row from network (character between 'A' and 'H' required)!" fi esac done } # Read column from remote # Returns column (0-7) as status code function receiveX() { local i while true; do read -n 1 i case $i in [1-8] ) return $(( i - 1 )) ;; * ) if $warnings ; then warn "Invalid input '$i' for column from network (character between '1' and '8' required)!" fi esac done } # receive movement from connected player # (no params/return value) function receive() { local player=$remote SECONDS=0 title="It's $(namePlayer "$player")s turn" message="Network player \e[1m$(namePlayer "$player")\e[0m is thinking... (or sleeping?)" draw >&3 while true ; do receiveY selectedY=$? receiveX selectedX=$? local figName figName=$(nameFigure ${field[$selectedY,$selectedX]} ) message"\e[1m$( namePlayer "$player" )\e[0m moves the \e[3m$figName\e[0m at $(coord $selectedY $selectedX)..." draw >&3 receiveY selectedNewY=$? receiveX selectedNewX=$? if (( selectedNewY == selectedY && selectedNewX == selectedX )) ; then selectedY=-1 selectedX=-1 selectedNewY=-1 selectedNewX=-1 message="\e[1m$( namePlayer "$player" )\e[0m revoked his move... okay, that'll be time consuming" draw >&3 else break fi done if move $player ; then message="\e[1m$( namePlayer "$player" )\e[0m moves the \e[3m$figName\e[0m at $(coord $selectedY $selectedX) to $(coord $selectedNewY $selectedNewX)" draw >&3 sleep "$sleep" title="$( namePlayer $player ) moved the $figName from $(coord $selectedY $selectedX) to $(coord $selectedNewY $selectedNewX) (took him $SECONDS seconds)." else error "Received invalid move from network - that should not hapen!" fi } # Write coordinates to network # Params: # $1 player # $2 row # $3 column # (no return value/exit code) function send() { local player=$1 local y=$2 local x=$3 if (( remote == player * (-1) )) ; then sleep "$remotedelay" coord "$y" "$x" echo sleep "$remotedelay" fi } # Import transposition tables # by reading serialised cache from stdin # (no params / return value) function importCache() { while IFS=$'\t' read hash lookup depth flag ; do cacheLookup["$hash"]=$lookup cacheDepth["$hash"]=$depth cacheFlag["$hash"]=$flag done } # Export transposition tables # Outputs serialised cache (to stdout) # (no params / return value) function exportCache() { for hash in "${!cacheLookup[@]}" ; do echo -e "$hash\t${cacheLookup[$hash]}\t${cacheDepth[$hash]}\t${cacheFlag[$hash]}" done } # Trap function for exporting cache # (no params / return value) function exitCache() { # permanent cache: export if [[ -n "$cache" ]] ; then echo -en "\r\n\e[2mExporting cache..." >&3 if $cachecompress ; then exportCache | gzip > "$cache" else exportCache > "$cache" fi echo -e " done!\e[0m" >&3 fi } # Perform necessary tasks for exit # like deleting files and measuring runtime # (no params / return value) function end() { # remove pipe if [[ -n "$fifopipe" && -p "$fifopipe" ]] ; then rm "$fifopipe" fi # disable mouse if $mouse ; then echo -en "\e[?9l" fi # enable input stty echo # restore screen if $cursor ; then echo -en "\e[2J\e[?47l\e[?25h\e[u\e8" fi # exit message duration=$(( $( date +%s%N ) - timestamp )) seconds=$(( duration / 1000000000 )) echo -e "\r\n\e[2mYou've wasted $seconds,$(( duration -( seconds * 1000000000 ))) seconds of your lifetime playing with a Bash script.\e[0m\n" } # Exit trap trap "end" 0 # setting up requirements for network piper="cat" fifopipe="/dev/fd/1" initializedGameLoop=true if (( remote != 0 )) ; then require nc require mknod initializedGameLoop=false if (( remote == 1 )) ; then fifopipe="$fifopipeprefix.server" piper="nc -l $port" else fifopipe="$fifopipeprefix.client" piper="nc $remoteip $port" echo -e "\e[1mWait!\e[0mPlease make sure the Host (the other Player) has started before continuing.\e[0m" anyKey fi if [[ ! -e "$fifopipe" ]] ; then mkfifo "$fifopipe" fi if [[ ! -p "$fifopipe" ]] ; then echo "Could not create FIFO pipe '$fifopipe'!" >&2 fi fi # print welcome title title="Welcome to ChessBa.sh" if isAI "1" || isAI "-1" ; then title="$title - your room heater tool!" fi # permanent cache: import if [[ -n "$cache" && -f "$cache" ]] ; then echo -en "\n\n\e[2mImporting cache..." if $cachecompress ; then importCache < <( zcat "$cache" ) else importCache < "$cache" fi echo -e " done\e[0m" fi # main game loop { p=1 while true ; do # initialize remote connection on first run if ! $initializedGameLoop ; then # set cache export trap trap "exitCache" 0 warn "Waiting for the other network player to be ready..." >&3 # exchange names if (( remote == -1 )) ; then read namePlayerA < $fifopipe echo "$namePlayerB" echo "connected with first player." >&3 elif (( remote == 1 )) ; then echo "$namePlayerA" read namePlayerB < $fifopipe echo "connected with second player." >&3 fi # set this loop initialized initializedGameLoop=true fi # reset global variables selectedY=-1 selectedX=-1 selectedNewY=-1 selectedNewX=-1 # switch current player (( p *= (-1) )) # check check (or: if the king is lost) if hasKing "$p" ; then if (( remote == p )) ; then receive < $fifopipe elif isAI "$p" ; then if (( computer-- == 0 )) ; then echo "Stopping - performed all ai steps" >&3 exit 0 fi ai "$p" else input "$p" fi else title="Game Over!" message="\e[1m$(namePlayer $(( p * (-1) )) ) wins the game!\e[1m\n" draw >&3 anyKey exit 0 fi done | $piper > "$fifopipe" # check exit code netcatExit=$? gameLoopExit=${PIPESTATUS[0]} if (( netcatExit != 0 )) ; then error "Network failure!" elif (( gameLoopExit != 0 )) ; then error "The game ended unexpected!" fi } 3>&1