The script: ttt
#!/bin/bash # Sat Jul 12 16:52:30 EDT 2003 # NAME: ttt # Copyright 2003, Chris F.A. Johnson # Released under the terms of the GNU General Public License ESC=$'\e' tictactoe() { [ $verbose -ge 1 ] && echo tictactoe $* >&2 board=`printf "%9.9s" " "` mvnum=1 remainder=" 1 2 3 4 5 6 7 8 9 " show "$board" while : do status eval \$player$P [ $? -ne 0 ] && return put $_MOVE "$board" board=$_PUT # show "$board" mkbold "$_MOVE" printat 0 0 $mvnum printat $prompt sleep $pause & mlist="$mlist $_MOVE" remainder=${remainder/ $_MOVE / } checkboard "$board" && { mkbold $win printat $prompt "$cle" eval winner \$player$P return } [ $mvnum -eq 9 ] && { B=$U mkbold "$_MOVE" winner "Neither player" return } wait B=$U mkbold "$_MOVE" if [ $P = O ] then P=X OP=O else P=O OP=X fi mvnum=$(( $mvnum + 1 )) done } winner() { [ $verbose -ge 1 ] && echo winner $* >&2 printat $prompt "$cle $* wins\n\n" } status() { [ $verbose -ge 1 ] && echo status $* >&2 if [ $verbose -ge 1 ] then printat 4 0 printf "${cle}Move: %s Player: %s Board: \"%s\" Last move: %s Move list: %s" \ "$mvnum" "$P" "${board// /-}" "$_MOVE" "$mlist" else false fi } show() { [ $verbose -ge 1 ] && echo show $* >&2 printat 0 0 $mvnum printat 0 $top ##$(( ($LINES - 5) / 2 )) printf " %${margin}.${margin}s | %1s | %1s\n" "${1:0:1}" "${1:1:1}" "${1:2:1}" printf "%${margin}.${margin}s---+---+---\n" " " printf " %${margin}.${margin}s | %1s | %1s\n" "${1:3:1}" "${1:4:1}" "${1:5:1}" printf "%${margin}.${margin}s---+---+---\n" " " printf " %${margin}.${margin}s | %1s | %1s\n" "${1:6:1}" "${1:7:1}" "${1:8:1}" } put() { [ $verbose -ge 1 ] && echo put $1 \""$2"\" >&2 local n=$(( $1 - 1 )) _PUT=${2:-$board} [ "${_PUT:$n:1}" = " " ] && _PUT=${_PUT:0:$n}$P${_PUT:$1} } checkboard() { [ $verbose -ge 1 ] && echo checkboard \"$*\" >&2 case $1 in $P??$P??$P??) win="1 4 7" ;; ?$P??$P??$P?) win="2 5 8" ;; ??$P??$P??$P) win="3 6 9" ;; ??$P?$P?$P??) win="3 5 7" ;; $P???$P???$P) win="1 5 9" ;; $P$P$P??????) win="1 2 3" ;; ???$P$P$P???) win="4 5 6" ;; ??????$P$P$P) win="7 8 9" ;; *) false ;; esac } mkbold() { [ $verbose -ge 1 ] && echo mkbold $* >&2 for sq in $* do row=$(( ($sq - 1) / 3 )) col=$(( ($sq - 1) % 3 + 1 )) printat $(( ($col * 4) + $margin - 2 )) $(( ($row * 2) + $top )) printf "${B}$P${U}" done } human() { [ $verbose -ge 1 ] && echo human $* >&2 while : do key "Select ($remainder)" # case $_MOVE in # "$ESC") read -sn1; read -sn1 kp; continue ;; # esac _MOVE=$_KEY case $_MOVE in 1|2|3|4|5|6|7|8|9) [ "${board:_MOVE-1:1}" = " " ] && break ;; q|Q|x|X) echo; return 5 ;; p) printat $prompt KEYECHO= KEYMAX=$(( $COLUMNS - 10 )) key "Enter delay in seconds" pause=$_KEY ;; "") set -- $remainder _MOVE=$1 [ $verbose -ge 1 ] && echo "|$_MOVE|" >&2 break ;; *) printat $prompt printf "\r%s%s\r" " "$cle" $_MOVE: Invalid move" sleep 1 printf "%s\r" "$cle" ;; esac done } randy() { [ $verbose -ge 1 ] && echo randy $* >&2 randstr $remainder _MOVE=$_RANDSTR } key() { [ $verbose -ge 1 ] && echo key $* >&2 printat $prompt "$cle$CVIS$beep" read -${KEYECHO}n$KEYMAX -p "${1:-PAK}: " _KEY 2>&1 printf "$CINV" } getkey() { local OKchars=${1:-${remainder// /}} local kp2 kp3 stty -echo while : do prompt="==>" prompt IFS= read -r -sn1 -p " " kp 2>&1 || exit 2 #cleanup case $OKchars in *"$kp"*) case $kp in $ESC) read -st1 -n1 kp2 case $kp2 in \[) read -st1 -n1 kp3 case $kp3 in A) kp=$UP; break ;; B) kp=$DN; break ;; C) kp=$RT; break ;; D) kp=$LF; break ;; esac ;; esac ;; *) break ;; esac ;; esac done _KEY=$kp } randstr() { [ $verbose -ge 1 ] && echo randstr $* >&2 # [ -n "$1" ] || return 1 n=$(( ($RANDOM % $#) + 1 )) eval _RANDSTR=\${$n} } isblock() { [ $verbose -ge 1 ] && echo isblock $* >&2 win= block= for _MOVE in $remainder do P=$OP put $_MOVE P=$OP checkboard "$_PUT" && return done _MOVE= } block() { [ $verbose -ge 1 ] && echo block $* >&2 block= for _MOVE in $remainder do put $_MOVE checkboard "$_PUT" && return P=$OP put $_MOVE P=$OP checkboard "$_PUT" && block=$_MOVE done [ "$block" ] && { _MOVE=$block; return; } randy } computer() { [ $verbose -ge 1 ] && echo computer $* >&2 local r c case $mvnum in 1) randstr 1 3 7 9 _MOVE=$_RANDSTR ;; 2) if [ "${board:4:1}" = " " ] then _MOVE=5 else randstr 1 3 7 9 _MOVE=$_RANDSTR fi ;; 3) r=${mlist// /} c='1 3 7 9' randstr ${c//[$r]/} # randstr ${remainder//[$r]/} _MOVE=$_RANDSTR ;; 4) isblock [ "$_MOVE" ] && return r=${mlist// /} c='2 4 6 8' randstr ${remainder//[$r]/} _MOVE=$_RANDSTR ;; *) block ;; esac } printat() { #== print arguments 3-... at Y=$2 X=$1 [ $verbose -ge 1 ] && echo printat $* >&2 [ $# -lt 2 ] && return 1 local y=$2 local x=$1 shift 2 msg="$*" printf "\e[%d;%dH%b" $y $x "$msg" } version() { echo " $progname, version $version Copyright $copyright, $author $email This is free software, released under the terms of the GNU General Public License. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. " } usage() { echo " ${B}NAME${U}: $progname - Tic-Tac-Toe ${B}USAGE${U}: $progname [OPTIONS] ${B}OPTIONS${U}: -x player1 - specify first player -o player2 - specify second player -c player1,player2 - specify both players -p seconds - time to pause between moves -h, --help - help: print this message -H, --help_long - help: print more detailed message (if available) -v, --verbose - sends messsages to \$HOME/tttlog -V, --version - print version information Copyright 2003, Chris F.A. Johnson " } verbose=0 longusage=0 version="1.0" copyright=2003 author="Chris F.A. Johnson" progname=${0##*/} P=X OP=O playerX=human playerO=computer co= li= if tput ce >/dev/null 2>&1 then ## e.g. FreeBSD co=co li=li elif tput el 2>/dev/null then ## e.g. Linux co=cols li=lines fi UNBOLD=$'\E[0m' standout=$'\E[0;1;7m' cle=$'\E[K' clb=$'\E[1K' ULINE=$'\E[0;4m' REVERSE=$'\E[0;7m' BLINK=$'\E[0;5m' BOLD=$'\E[0;1m' CINV=$'\E[0;8m' R=$REVERSE U=$UNBOLD B=$BOLD BR=$B$R Cl=$'\f' if [ "$co" ] then tput reset COLUMNS=${COLUMNS:=`tput $co`} else COLUMNS=${COLUMNS:-80} fi top=6 margin=11 prompt="3 $(( $top + 9 ))" beep= #$'\a' pause=1 KEYMAX=1 KEYECHO=s while getopts vVhH-:xoX:O:c:p: var do case "$var" in x) playerX=human playerO-computer ;; o) playerO=human playerX=computer ;; X) playerX=$OPTARG ;; O) playerO=$OPTARG ;; c|C) playerO=${OPTARG#*,} playerX=${OPTARG%,*} ;; p) pause=$OPTARG ;; -) case $OPTARG in help) usage; exit ;; verbose) verbose=$(( $verbose + 1 )) ;; version) version; exit ;; esac ;; h) usage; exit ;; H) longusage=1; usage; exit ;; v) verbose=$(( $verbose + 1 )) ;; V) version; exit ;; *);; esac done shift $(( $OPTIND - 1 )) if [ "$co" ] then COLUMNS=`tput $co` else COLUMNS=${COLUMNS:-80} fi margin=11 ##$(( ($COLUMNS - 11) / 2 )) clear if [ $verbose -ge 1 ] then exec 2>$HOME/tttlog set -x fi tictactoe printat $prompt "\n$CVIS\n"