#!/usr/local/bin/bash
#
# Chess Bash
# a simple chess game written in an inappropriate language :)
#
# Copyright (c) 2015 by Bernhard Heinloth <[email protected]>
#
# 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"
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"
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