dev/bash/chess/chess.sh

337 lines
8.2 KiB
Bash
Raw Normal View History

2011-12-05 19:15:15 +04:00
#!/bin/bash
# Network chess by Evgeny Stepanischev http://bolknote.ru 2011
if [ $# -ne 2 ]; then
echo Usage: $0 host-of-opponent port
exit
fi
# Хост оппонента
HOST="$1"
# Общий порт
PORT="$2"
# Клавиатурные комбинации извстной длины
SEQLEN=(1b5b4. [2-7]. [cd]... [89ab].{5} f.{7})
# Фигуры
WHITE=(♙ ♙ ♙ ♙ ♙ ♙ ♙ ♙ ♖ ♘ ♗ ♕ ♔ ♗ ♘ ♖)
BLACK=(♜ ♞ ♝ ♛ ♚ ♝ ♞ ♜ ♟ ♟ ♟ ♟ ♟ ♟ ♟ ♟)
# Наш ход?
OURMOVE=
# Я чёрный или белый?
MYCOLOR=
# Доска
declare -a XY
# Курсор
CX=1 CY=7
TAKEN=
# Необходимые нам клавиатурные коды
KUP=1b5b41
KDOWN=1b5b42
KLEFT=1b5b44
KRIGHT=1b5b43
KSPACE=20
# Восстановление экрана
function Restore {
echo -ne "\033[5B\033[5B\033[?25h\033[m"
stty "$ORIG" 2>/dev/null
bind '"\r":accept-line'
}
trap Restore EXIT
# Выключаем Enter
bind -r '\r'
# Выключаем остальную клавиатуру
ORIG=`stty -g`
stty -echo
# Убирам курсор
echo -e "\033[?25l"
# Отдаём события клавиатуры в сеть
function ToNet {
echo $1 | nc "$HOST" "$PORT"
}
# Реакция на клавиши курсора
function React {
case $1 in
$KLEFT)
if [ $CX -gt 1 ]; then
CX=$(($CX-1))
PrintBoard
fi
;;
$KRIGHT)
if [ $CX -lt 8 ]; then
CX=$(($CX+1))
PrintBoard
fi
;;
$KUP)
if [ $CY -gt 1 ]; then
CY=$(($CY-1))
PrintBoard
fi
;;
$KDOWN)
if [ $CY -lt 8 ]; then
CY=$(($CY+1))
PrintBoard
fi
esac
# Отдаём события клавиатуры в сеть
[ "$OURMOVE" ] && ToNet $1
}
# Проверка совпадения с известной клавиатурной комбинацией
function CheckCons {
local i
for i in ${SEQLEN[@]}; do
if [[ $1 =~ ^$i ]]; then
return 0
fi
done
return 1
}
# Функция реакции на клавиатуру, вызывает React на каждую нажатую клавишу,
# кроме KSPACE — на неё возвращается управление
function PressEvents {
local real code action
# Цикл обработки клавиш, здесь считываются коды клавиш,
# по паузам между нажатиями собираются комбинации и известные
# обрабатываются сразу
while true; do
# измеряем время выполнения команды read и смотрим код нажатой клавиши
# akw NR==1||NR==4 забирает только строку №1 (там время real) и №4 (код клавиши)
eval $( (time -p read -r -s -n1 ch; printf 'code %d\n' "'$ch") 2>&1 |
awk 'NR==1||NR==4 {print $1 "=" $2}' | tr '\r\n' ' ')
# read возвращает пусто для Enter и пробела, присваиваем им код 20,
# а так же возвращаются отрицательные коды для UTF8
if [ "$code" = 0 ]; then
code=20
else
[ $code -lt 0 ] && code=$((256+$code))
code=$(printf '%02x' $code)
fi
if [ $code = $KSPACE ]; then
[ "$OURMOVE" ] && ToNet $KSPACE
SpaceEvent && return
continue
fi
# Если клавиши идут подряд (задержки по времени нет)
if [ $real = 0.00 ]; then
seq="$seq$code"
if CheckCons $seq; then
React $seq
seq=
fi
# Клавиши идут с задержкой (пользователь не может печатать с нулевой задержкой),
# значит последовательность собрана, надо начинать новую
else
[ "$seq" ] && React $seq
seq=$code
# возможно последовательность состоит из одного символа
if CheckCons $seq; then
React $seq
seq=
fi
fi
done
}
# Проверяем чёрная или белая фигура
function CheckColor {
echo -n ${1:0:1}
}
# Первичное заполнение доски
function FillBoard {
local x y ch
for y in {1..8}; do
for x in {1..8}; do
ch='S '
if [ $y -le 2 ]; then
ch=B${BLACK[$x+8*$y-9]}
else
if [ $y -ge 7 ]; then
ch=W${WHITE[$x+8*$y-57]}
fi
fi
XY[$x+100*$y]=$ch
done
done
}
# Вывод букв по краю доски
function PrintBoardLetters {
local letters=abcdefgh
[ -z "$OURMOVE" ] && echo -ne "\033[30m" || echo -ne "\033[0m"
echo -n ' '
for x in {0..7}; do
echo -n "${letters:$x:1} "
done
echo
}
# Вывод цифры по краю доски
function PrintBoardDigit {
[ -z "$OURMOVE" ] && echo -ne "\033[30m"
echo -en " $((9-$1))\033[0m "
}
# Вывод доски
function PrintBoard {
local x y c ch
local colors=('48;5;209;37;1' '48;5;94;37;1')
PrintBoardLetters
for y in {1..8}; do
PrintBoardDigit $y
for x in {1..8}; do
c=${colors[($x+$y) & 1]}
ch=${XY[$x+100*$y]}
if [[ $CX == $x && $CY == $y ]]; then
c="$c;7"
[ "$TAKEN" ] && ch=$TAKEN
[ $MYCOLOR == B ] && c="$c;38;5;16"
fi
[[ $(CheckColor "$ch") == "B" ]] && c="$c;38;5;16"
echo -en "\033[${c}m${ch:1:1} \033[m"
done
PrintBoardDigit $y
echo
done
PrintBoardLetters
echo -e "\033[11A"
}
# Приём событий
function NetListen {
nc -l -p $PORT
}
# Готовы слушать события сети
function NetEvents {
local code
while true; do
code=$(NetListen)
[[ "$code" == "$KSPACE" ]] && SpaceEvent && return
React $code
done
}
# Реакция на нажатие Space и Enter — взять или положить фигуру
function SpaceEvent {
local xy
# Проверяем, есть ли фигура под курсором
let xy="$CX+$CY*100"
# Фигуры нет
if [ "${XY[$xy]:-S }" = "S " ]; then
if [ -z "$TAKEN" ]; then
echo -en "\007"
else
# Положили фигуру
XY[$xy]=$TAKEN
TAKEN=
return 0
fi
# Фигура есть
else
# Мы не должны позволять «съесть» свою фигуру
if [[ $(CheckColor "$TAKEN") == $(CheckColor "${XY[$xy]}") ]]; then
echo -en "\007"
else
# Мы взяли фигуру
TAKEN=${XY[$xy]}
XY[$xy]="S "
fi
fi
return 1
}
# Очистка клавиатурного буфера
function ClearKeyboardBuffer {
# Быстро — через zsh
which -s zsh && (zsh -c 'while {} {read -rstk1 || break}'; return)
# Медленно — через bash
local delta
while true; do
delta=`(time -p read -rs -n1 -t1) 2>&1 | awk 'NR==1{print $2}'`
[[ "$delta" == "0.00" ]] || break
done
}
FillBoard
# Кто будет ходить первым
ToNet HI
[[ "$(NetListen)" == "HI" ]] && OURMOVE=1
ToNet ULOOSE
[ "$OURMOVE" ] && MYCOLOR=W || MYCOLOR=B
PrintBoard
# Основной цикл — обрабатываем события из сети или с клавиатуры
while true; do
if [ -n "$OURMOVE" ]; then
ClearKeyboardBuffer
PressEvents
OURMOVE=
else
NetEvents
OURMOVE=1
fi
PrintBoard
done