بیاین بازی ساده زیر رو بسازیم
در ابتدا به سایز ترمینال نیاز داریم
cols=$(tput cols)
lines=$(tput lines)
echo $cols
echo $lines
تیپات یک پکیجیه که با ncurses نصب میشه و روی اکثر سیستمها هست
دومین کارمون اینه که محیط یا همون فیلد بازی رو مشخص کنیم.
خب برای این کار باید بتونیم یک کاراکتری رو توی تمام خونه های ترمینال چاپ کنیم.
cols=$(tput cols)
lines=$(tput lines)
for ((i=0; i < cols; i++)); do
printf '-'
for ((j=0; j < lines j++)); do
printf '-'
done
done
از اونجایی که لاینها دارن یکم اسکرول میدن و ما این پایین ترمینالو میخوایم واسه اینکه بعدا میزان جون و تعداد حدس های درست رو نشون بدیم، تعداد لاینها رو کم میکنیم
cols=$(tput cols)
lines=$(tput lines)
for ((i=0; i < cols; i++)); do
printf '-'
for ((j=0; j < lines- 4; j++)); do <---- CHANGE HERE
printf '-'
done
done
در مرحله بعدی باید بتونیم یه سری ستاره جاهای رندم چاپ کنیم
یه فایل تست باز می کنیم
cols=$(tput cols)
lines=$(tput lines)
num_stars=20
declare -A star_positions
for ((n=0; n<num_stars; n++)); do
rand_col=$((RANDOM % cols))
rand_line=$((RANDOM % (lines - 4)))
star_positions["$rand_line,$rand_col"]=1
done
for ((i=0; i < lines - 4; i++)); do
for ((j=0; j < cols; j++)); do
if [[ ${star_positions["$i,$j"]} ]]; then
tput cup "$i" "$j"
printf '*'
fi
done
done
tput cup "$lines" 0
یک آرایه اسوشیتیو (آرایه کلید-مقدار) به نام `star_positions` ایجاد میکنیم.از این آرایه برای ذخیره کردن ستون و سطر رندم استفاده میکنیم.پس از آن در لوپ اول، برای هر ستاره، موقعیت تصادفن را در ترمینال تعیین میکنیم. موقعیت ستاره در آرایه `star_positions` با کلید `"$rand_line,$rand_col"` ذخیره میشود. بعدا یک تابع move cursor میسازیم ولی فعلا با تیپات کارمونو راه میندازیم
اگه بخوایم هم ستاره چاپ کنیم و هم خط فاصله میتونیم به سبک کد قبلی برگردیم.
حالا که دیدیم چطور میشه در جاهای رندم کاراکتری رو چاپ کرد،به شکل زیر عمل میکنیم.
for ((i=0; i < lines - 4; i++)); do
for ((j=0; j < cols; j++)); do
if [[ ${star_positions["$i,$j"]} ]]; then
printf '*'
else
printf '-'
fi
done
printf '\n'
done
پس تا اینجا کد ما به این شکل میشه
cols=$(tput cols)
lines=$(tput lines)
num_stars=20
declare -A star_positions
for ((n=0; n<num_stars; n++)); do
rand_col=$((RANDOM % cols))
rand_line=$((RANDOM % (lines - 4)))
star_positions["$rand_line,$rand_col"]=1
done
for ((i=0; i < lines - 4; i++)); do
for ((j=0; j < cols; j++)); do
if [[ ${star_positions["$i,$j"]} ]]; then
printf '*'
else
printf '-'
fi
done
printf '\n'
done
حالا خوبه که مکان مینهارو پنهان کنیم و واسه این کار تمام ترمینال رو با کاراکتر یونیکد بلوک پر میکنیم
برای وارد کردن کاراکتر یونیکد توی ویم در حالت insert به این شکل عمل میکنیم`C-v u2588`
یعنی کنترل+v و بعد کد یونیکد رو وارد میکنیم.
cols=$(tput cols)
lines=$(tput lines)
num_stars=20
declare -A star_positions
for ((n=0; n<num_stars; n++)); do
rand_col=$((RANDOM % cols))
rand_line=$((RANDOM % (lines - 4)))
star_positions["$rand_line,$rand_col"]=1
done
for ((i=0; i < lines - 4; i++)); do
for ((j=0; j < cols; j++)); do
if [[ ${star_positions["$i,$j"]} ]]; then
printf '█'
else
printf '█'
fi
done
printf '\n'
done
تا اینجای کار ما یک زمین بازی درست کردیم
حالا وقتشه که رنگ زمین بازیمون رو عوض کنیم
printf '\033[1;33m█\033[0m'
با استفاده از کد انسی بالا رنگ پیش زمینه ترمینال رو زرد میکنیم و بلوک هارو چاپ میکنیم
حالا باید راهی برای آزادانه حرکت دادن نشانگر پیدا کنیم. با جستجو در man 1 stty با قابلیت raw آشنا میشیم. همچنین با استفاده از دستور read میتونیم کلید های ورودی را بخونیم
# Enable raw input mode
stty -raw
# Clear the screen
clear
# Infinite loop to process key presses
while true; do
# Read the input
read -rsn1 key
# Check for escape sequences
if [[ $key == $'\x1b' ]]; then
# Read the next two bytes
read -rsn2 -t 0.1 key
case "$key" in
"[A") # Up arrow
echo -ne "\033[A"
;;
"[B") # Down arrow
echo -ne "\033[B"
;;
"[C") # Right arrow
echo -ne "\033[C"
;;
"[D") # Left arrow
echo -ne "\033[D"
;;
esac
fi
done
# Reset terminal settings when done (not reached in this loop)
stty echo icanon
قطعهی بالا بهخوبی کامنت شده تا متوجه بشید هر قسمت چه کاری انجام میده، اما به جزئیات بیشتری نمیپردازیم. اگه علاقهمند هستید، میتونید برای اطلاعات بیشتر به `man stty` و `man read` مراجعه کنید.
در سطح بعدی باید روشی پیدا کنیم تا بتونیم موقعیت ستارههای تصادفی رو ردیابی کنیم و بعد در صورتی که کاربر کلید Enter رو روی اونها فشار داد، اونها رو با چیزی جایگزین کنیم.
اما ابتدا کد خودمون رو سازماندهی میکنیم. تااینجا به یک تابع draw_grid، تابع replace_stars و یک حلقهی اصلی بازی (game_loop) نیاز داریم، که توی اون حالت raw را فعال میکنیم. همچنین یک تابع move_cursor و متغیرهای cursor_row و cursor_col رو تعریف میکنیم تا حرکت مکاننما رو آسونتر مدیریت کنیم. این کار به ما اجازه میده کدهای ANSI که مکاننما را در کد بالا جابجا میکردن، حذف کنیم.
cursor_row=$((lines - 5))
cursor_col=0
move_cursor() {
echo -ne "\033[${1};${2}H"
}
draw_grid() {
clear
for ((i=0; i < lines - 4; i++)); do
for ((j=0; j < cols; j++)); do
if [[ ${star_positions["$i,$j"]} ]]; then
printf '\033[1;33m█\033[0m'
else
printf '\033[1;33m█\033[0m'
fi
done
printf '\n'
done
}
make_stars() {
for ((n=0; n<num_stars; n++)); do
rand_col=$((RANDOM % cols))
rand_line=$((RANDOM % (lines - 4)))
star_positions["$rand_line,$rand_col"]=1
done
}
game_loop() {
while true; do
move_cursor $((cursor_row+1)) $((cursor_col+1))
read -rsn1 key
if [[ $key == $'\x1b' ]]; then
read -rsn2 -t 0.1 key
case "$key" in
"[A") # Up arrow
((cursor_row--))
((cursor_row < 0)) && cursor_row=0
;;
"[B") # Down arrow
((cursor_row++))
((cursor_row > lines - 5)) && cursor_row=$((lines - 5))
;;
"[C") # Right arrow
((cursor_col++))
((cursor_col >= cols)) && cursor_col=$((cols - 1))
;;
"[D") # Left arrow
((cursor_col--))
((cursor_col < 0)) && cursor_col=0
;;
esac
fi
done
}
جالا باید برخورد با ستارهها را پیادهسازی کنیم. باید بتونیم وقتی کاربر کلید Enter رو روی ستارهها فشار میده، اونها رو با کاراکتر دیگهای جایگزین کنیم.
replace_star() {
if [[ ${star_positions["$cursor_row,$cursor_col"]} ]]; then
# Clear the star and replace it with '#'
star_positions["$cursor_row,$cursor_col"]=0
move_cursor $((cursor_row+1)) $((cursor_col+1))
echo -n "#"
move_cursor $((cursor_row+1)) $((cursor_col+1))
fi
}
با هم
cols=$(tput cols)
lines=$(tput lines)
num_stars=10000
cursor_row=$((lines - 5))
cursor_col=0
move_cursor() {
echo -ne "\033[${1};${2}H"
}
draw_grid() {
clear
for ((i=0; i < lines - 4; i++)); do
for ((j=0; j < cols; j++)); do
if [[ ${star_positions["$i,$j"]} ]]; then
printf '\033[1;33m█\033[0m'
else
printf '\033[1;33m█\033[0m'
fi
done
printf '\n'
done
}
make_stars() {
for ((n=0; n<num_stars; n++)); do
rand_col=$((RANDOM % cols))
rand_line=$((RANDOM % (lines - 4)))
star_positions["$rand_line,$rand_col"]=1
done
}
replace_star() {
if [[ ${star_positions["$cursor_row,$cursor_col"]} ]]; then
# Clear the star and replace it with '#'
star_positions["$cursor_row,$cursor_col"]=0
move_cursor $((cursor_row+1)) $((cursor_col+1))
echo -n "#"
move_cursor $((cursor_row+1)) $((cursor_col+1))
fi
}
game_loop() {
while true; do
move_cursor $((cursor_row+1)) $((cursor_col+1))
read -rsn1 key
if [[ $key == "" ]]; then
replace_star
fi
if [[ $key == $'\x1b' ]]; then
read -rsn2 -t 0.1 key
case "$key" in
"[A") # Up arrow
((cursor_row--))
((cursor_row < 0)) && cursor_row=0
;;
"[B") # Down arrow
((cursor_row++))
((cursor_row > lines - 5)) && cursor_row=$((lines - 5))
;;
"[C") # Right arrow
((cursor_col++))
((cursor_col >= cols)) && cursor_col=$((cols - 1))
;;
"[D") # Left arrow
((cursor_col--))
((cursor_col < 0)) && cursor_col=0
;;
esac
fi
done
}
function main() {
draw_grid
make_stars
game_loop
}
main
حالا وقتش رسیده که انفجارها رو بسازیم. برای این کار، تابع replace_star خودمون رو اصلاح میکنیم.
replace_star() {
if [[ ${star_positions["$cursor_row,$cursor_col"]} ]]; then
star_positions["$cursor_row,$cursor_col"]=0
size=$((RANDOM % 5 + 3))
local start_row=$((cursor_row - size / 2))
local start_col=$((cursor_col - size / 2))
for ((i=0; i<size; i++)); do
move_cursor $((start_row + i)) $start_col
printf '\033[1;31m'
for ((j=0; j<size; j++)); do
echo -n '█'
done
printf '\033[0m'
done
move_cursor $cursor_row $cursor_col
echo -e '\033[1;31m#\033[0m' # Center character in red
move_cursor $((start_row + size + 1)) $start_col
else
move_cursor $((cursor_row+1)) $((cursor_col+1))
printf '\033[1;36m█\033[0m'
fi
}
تقریباً کارمون تموم شده. تنها کاری که باید انجام بدیم اینه که متغیرهایی برای سلامتی و مرحله اضافه کنیم و منطقهای کوچکی برای زمانی که کاربر برنده یا بازنده شده، اضافه کنیم. چاپ اطلاعات در پایین ترمینال مثل قبل آسونه و میتونیم از تابع move_cursor خودمون استفاده کنیم.
round=0;
hp=5;
win=0;
و مکان مشخصی برای پیام جون
health_msg=$((lines - 2))
move_cursor $health_msg 0
printf "HP: $hp"
دو عملیات ریاضی بعد انفجار اضافه میکنیم
((round++))
((hp--))
به این شکل میتونیم`round` رو کم کنیم و`hp` زیاد کنیم
باقی کد هم توی یک لاجیک ساده wrap میکنیم
if [[ $round -ge 6 ]]; then
tput reset
figlet "GAME OVER"
exit 0
else
((win++))
fi
و کد زیر رو قبل` fi `بالا قرار می دیم
if [[ $win -eq 20 ]]; then
tput reset
echo "Round Cleared!"
exit 0
fi
سپس به کاربر خوشامد میگیم
center_col=$((cols / 2 - 25))
center_line=$((lines / 2 - 2))
move_cursor $center_line $center_col
printf "Survive 20 Guesses, or DIE after 5 explosions"
sleep 2;
و برای بار آخر کد رو ریفاکتور میکنیم
#!/usr/bin/env bash
cols=$(tput cols)
lines=$(tput lines)
center_col=$((cols / 2 - 25))
center_line=$((lines / 2 - 2))
cursor_row=$((lines - 5))
cursor_col=0
round=0
hp=5
win=0
health_msg=$((lines - 2))
num_stars=10000
move_cursor() {
echo -ne "\033[${1};${2}H"
}
initialize_screen() {
move_cursor $center_line $center_col
printf "Survive 20 Guesses, or DIE after 5 explosions"
sleep 2
clear
move_cursor $health_msg 0
printf "HP: $hp"
}
generate_star_positions() {
declare -gA star_positions
for ((n=0; n<num_stars; n++)); do
rand_col=$((RANDOM % cols))
rand_line=$((RANDOM % (lines - 4)))
star_positions["$rand_line,$rand_col"]=1
done
}
draw_grid() {
clear
for ((i=0; i < lines - 4; i++)); do
for ((j=0; j < cols; j++)); do
if [[ ${star_positions["$i,$j"]} ]]; then
printf '\033[1;33m█\033[0m'
else
printf '\033[1;33m█\033[0m'
fi
done
printf '\n'
done
}
replace_star() {
if [[ ${star_positions["$cursor_row,$cursor_col"]} ]]; then
star_positions["$cursor_row,$cursor_col"]=0
size=$((RANDOM % 5 + 3))
local start_row=$((cursor_row - size / 2))
local start_col=$((cursor_col - size / 2))
for ((i=0; i<size; i++)); do
move_cursor $((start_row + i)) $start_col
printf '\033[1;31m'
for ((j=0; j<size; j++)); do
echo -n '█'
done
printf '\033[0m'
done
((round++))
((hp--))
move_cursor $cursor_row $cursor_col
echo -e '\033[1;31m#\033[0m'
move_cursor $((start_row + size + 1)) $start_col
if [[ $round -ge 5 ]]; then
tput reset
figlet "GAME OVER"
exit 0
else
move_cursor 1 1
printf '\033[1;37mYou Survive The Explosion!\033[0m'
sleep 1
move_cursor 1 1
size=26
printf '\033[1;33m'
for ((i=0; i<size; i++)); do
echo -n '█'
done
printf '\033[0m'
round_msg=$((lines - 3))
move_cursor "$round_msg" 0
echo "Round: $round / 5"
move_cursor $health_msg 0
printf "HP: $hp"
fi
else
move_cursor $((cursor_row+1)) $((cursor_col+1))
printf '\033[1;36m█\033[0m'
((win++))
win_msg=$((lines - 1))
move_cursor $win_msg 0
printf "Win: $win"
if [[ $win -eq 20 ]]; then
tput reset
figlet "Round Cleared!"
exit 0
fi
fi
}
game_loop() {
stty -echo -icanon time 0 min 0
while true; do
move_cursor $((cursor_row+1)) $((cursor_col+1))
read -rsn1 key
if [[ $key == "" ]]; then
replace_star
fi
if [[ $key == $'\x1b' ]]; then
read -rsn2 -t 0.1 key
case "$key" in
"[A") ((cursor_row--)); ((cursor_row < 0)) && cursor_row=0 ;;
"[B") ((cursor_row++)); ((cursor_row > lines - 5)) && cursor_row=$((lines - 5)) ;;
"[C") ((cursor_col++)); ((cursor_col >= cols)) && cursor_col=$((cols - 1)) ;;
"[D") ((cursor_col--)); ((cursor_col < 0)) && cursor_col=0 ;;
esac
fi
done
stty echo icanon
}
initialize_screen
generate_star_positions
draw_grid
game_loop
و به همین سادگی بازی ما تموم شد. قطعا جای خیلی زیادی برای ارتقای این بازی وجود داره، اما با اطلاعاتی که توی این نوشته یاد گرفتید، میتونین خودتون بینهایت این کد رو ارتفا بدین.