Загрузка данных
#!/usr/bin/env bash
# Быстрый режим без пауз: FAST=1 bash 21.sh
setup_terminal() {
ui=0
RESET='' GOLD='' CYAN='' GREEN='' RED='' DIM=''
FACE='' RED_FACE='' BACK=''
if [[ -t 0 && -t 1 && ${TERM:-dumb} != dumb ]]; then
ui=1
RESET=$'\033[0m'
GOLD=$'\033[1;33m'
CYAN=$'\033[1;36m'
GREEN=$'\033[1;32m'
RED=$'\033[1;31m'
DIM=$'\033[2m'
FACE=$'\033[0;30;47m'
RED_FACE=$'\033[0;31;47m'
BACK=$'\033[0;97;44m'
printf '\033[?25l'
fi
trap cleanup EXIT
trap 'exit 130' INT
trap 'exit 143' TERM
}
cleanup() {
if (( ui )); then
printf '\033[0m\033[?25h\n'
fi
}
pause() {
if (( ui )) && [[ ${FAST:-0} != 1 ]]; then
sleep "$1"
fi
}
# Спиннер и полоса загрузки.
animate() {
local text=$1 steps=${2:-12}
local i j filled bar
local frames=('|' '/' '-' '\')
if (( ! ui )) || [[ ${FAST:-0} == 1 ]]; then
return
fi
for ((i = 0; i <= steps; i++)); do
filled=$((i * 16 / steps))
bar=''
for ((j = 0; j < 16; j++)); do
if (( j < filled )); then
bar+='█'
else
bar+='░'
fi
done
printf '\r\033[2K %s%s [%s] %s%s' \
"$CYAN" "${frames[i % 4]}" "$bar" "$text" "$RESET"
pause 0.07
done
printf '\r\033[2K'
}
read_command() {
printf '\n %s ' "$1"
if (( ui )); then
printf '\033[?25h'
fi
if ! IFS= read -r REPLY; then
exit 0
fi
if (( ui )); then
printf '\033[?25l'
fi
}
# Колода из 52 уникальных карт.
new_deck() {
local i j temp
deck=()
for ((i = 0; i < 52; i++)); do
deck+=("$i")
done
# Перемешивание Фишера — Йетса.
for ((i = 51; i > 0; i--)); do
j=$((RANDOM % (i + 1)))
temp=${deck[i]}
deck[i]=${deck[j]}
deck[j]=$temp
done
deck_position=0
}
draw_card() {
DRAWN=${deck[deck_position]}
deck_position=$((deck_position + 1))
}
# Подсчёт очков: туз стоит 11 или 1.
score() {
local card rank sum=0 aces=0
for card in "$@"; do
rank=$((card % 13))
if (( rank == 0 )); then
sum=$((sum + 11))
aces=$((aces + 1))
elif (( rank >= 10 )); then
sum=$((sum + 10))
else
sum=$((sum + rank + 1))
fi
done
while (( sum > 21 && aces > 0 )); do
sum=$((sum - 10))
aces=$((aces - 1))
done
printf '%s' "$sum"
}
card_name() {
local names=(
'Туз' '2' '3' '4' '5' '6' '7'
'8' '9' '10' 'Валет' 'Дама' 'Король'
)
local suits=('пик' 'червей' 'бубен' 'треф')
printf '%s %s' "${names[$1 % 13]}" "${suits[$1 / 13]}"
}
# Рисуем карты по четыре в строке.
# hide=1 скрывает вторую карту дилера.
render_hand() {
local hide=$1
shift
local cards=("$@")
local ranks=('A' '2' '3' '4' '5' '6' '7' '8' '9' '10' 'J' 'Q' 'K')
local suits=('♠' '♥' '♦' '♣')
local start end row index card rank suit ink
if (( ${#cards[@]} == 0 )); then
printf ' %sПока нет карт%s\n' "$DIM" "$RESET"
return
fi
for ((start = 0; start < ${#cards[@]}; start += 4)); do
end=$((start + 4))
if (( end > ${#cards[@]} )); then
end=${#cards[@]}
fi
for ((row = 0; row < 5; row++)); do
printf ' '
for ((index = start; index < end; index++)); do
card=${cards[index]}
rank=${ranks[card % 13]}
suit=${suits[card / 13]}
ink=$FACE
if (( card / 13 == 1 || card / 13 == 2 )); then
ink=$RED_FACE
fi
if (( hide && index == 1 )); then
ink=$BACK
fi
printf '%s' "$ink"
if (( row == 0 )); then
printf '┌─────────┐'
elif (( row == 4 )); then
printf '└─────────┘'
elif (( hide && index == 1 )); then
printf '│░░░░░░░░░│'
else
case "$row" in
1) printf '│%-9s│' "$rank" ;;
2) printf '│ %s │' "$suit" ;;
3) printf '│%9s│' "$rank" ;;
esac
fi
printf '%s ' "$RESET"
done
printf '\n'
done
done
}
table_text() {
printf '\n %s♠ ♥ Д В А Д Ц А Т Ь О Д Н О ♦ ♣%s\n' \
"$GOLD" "$RESET"
printf ' ──────────────────────────────────────────────────────\n'
printf ' Партия: %-3s Победы: %s Поражения: %s Ничьи: %s\n' \
"$round" "$wins" "$losses" "$ties"
printf ' Карт в колоде: %s\n\n' "$((52 - deck_position))"
if (( dealer_hidden )); then
printf ' %sДИЛЕР%s — очки скрыты\n' "$CYAN" "$RESET"
else
printf ' %sДИЛЕР%s — очки: %s\n' \
"$CYAN" "$RESET" "$(score "${dealer[@]}")"
fi
render_hand "$dealer_hidden" "${dealer[@]}"
printf '\n %sВЫ%s — очки: %s\n' \
"$GOLD" "$RESET" "$(score "${player[@]}")"
render_hand 0 "${player[@]}"
printf '\n ──────────────────────────────────────────────────────\n'
printf ' Вы ходите первым. Затем дилер добирает до 17 очков.\n'
printf ' Туз = 1 или 11. J/Q/K = 10. Блэкджек = 21 с двух карт.\n'
printf '\n %s%s%s\n\n' "$notice_color" "$notice" "$RESET"
}
# Подготавливаем экран целиком, затем выводим одним кадром.
render() {
local screen
screen=$(table_text)
if (( ui )); then
printf '\033[H\033[2J%s\n\n' "$screen"
else
printf '\n%s\n\n' "$screen"
fi
}
give_card() {
local who=$1
animate 'Раздаём карту…' 7
draw_card
if [[ $who == player ]]; then
player+=("$DRAWN")
notice="Ваша карта: $(card_name "$DRAWN")."
else
dealer+=("$DRAWN")
if (( dealer_hidden && ${#dealer[@]} == 2 )); then
notice='Вторая карта дилера лежит рубашкой вверх.'
else
notice="Карта дилера: $(card_name "$DRAWN")."
fi
fi
render
pause 0.4
}
play_round() {
local p d
player=()
dealer=()
dealer_hidden=1
notice_color=$CYAN
notice='Новая партия. Готовим колоду…'
new_deck
render
animate 'Перемешиваем 52 карты…' 18
give_card player
give_card player
give_card dealer
give_card dealer
p=$(score "${player[@]}")
d=$(score "${dealer[@]}")
# Блэкджек при начальной раздаче завершает партию сразу.
if (( p == 21 || d == 21 )); then
animate 'Проверяем блэкджек…' 12
dealer_hidden=0
if (( p == 21 && d == 21 )); then
outcome=tie
notice='У обоих блэкджек. Ничья!'
elif (( p == 21 )); then
outcome=win
notice='Блэкджек! Вы выиграли!'
else
outcome=loss
notice='У дилера блэкджек. Дилер выиграл.'
fi
return
fi
notice='Ваш ход: берите карты или остановитесь.'
while (( p < 21 )); do
render
read_command '1 / д — карту | 2 / н — хватит | q — выход:'
case "$REPLY" in
1|д|Д|да|Да|ДА|y|Y)
give_card player
p=$(score "${player[@]}")
;;
2|н|Н|нет|Нет|НЕТ|n|N)
break
;;
q|Q|й|Й)
exit 0
;;
*)
notice='Введите 1, 2 или q и нажмите Enter.'
;;
esac
done
if (( p > 21 )); then
dealer_hidden=0
outcome=loss
notice="У вас $p — перебор. Дилер выиграл."
return
fi
# Дилер ходит автоматически после игрока.
# При 17 и выше останавливается, даже если у игрока больше.
animate 'Дилер открывает вторую карту…' 12
dealer_hidden=0
notice="Ваш итог: $p. Теперь ход дилера."
render
pause 0.7
while (( d < 17 )); do
animate 'Дилер думает…' 9
give_card dealer
d=$(score "${dealer[@]}")
done
if (( d > 21 )); then
outcome=win
notice="У дилера $d — перебор. Вы выиграли!"
elif (( p > d )); then
outcome=win
notice="Вы выиграли! Ваши очки: $p. У дилера: $d."
elif (( p < d )); then
outcome=loss
notice="Дилер выиграл. Ваши очки: $p. У дилера: $d."
else
outcome=tie
notice="Ничья! У обоих по $p очков."
fi
}
main() {
setup_terminal
wins=0
losses=0
ties=0
round=0
while true; do
round=$((round + 1))
play_round
animate 'Подводим итоги…' 10
case "$outcome" in
win)
wins=$((wins + 1))
notice_color=$GREEN
;;
loss)
losses=$((losses + 1))
notice_color=$RED
;;
tie)
ties=$((ties + 1))
notice_color=$GOLD
;;
esac
render
while true; do
read_command 'Enter — ещё партия | q — выход:'
case "$REPLY" in
''|1|д|Д|да|Да|ДА|y|Y)
break
;;
q|Q|й|Й|2|н|Н|нет|Нет|НЕТ|n|N)
printf '\n Спасибо за игру! Побед: %s. Поражений: %s. Ничьих: %s.\n' \
"$wins" "$losses" "$ties"
return
;;
*)
printf ' Нажмите Enter или введите q.\n'
;;
esac
done
done
}
if [[ ${BASH_SOURCE[0]} == "$0" ]]; then
main
fi