Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> Lập Trình Bash

Thành Thạo Scripting Bash: Tự Xây Dựng Game Minesweeper Trên Terminal

Tôi không phải chuyên gia giảng dạy lập trình, nhưng khi muốn giỏi hơn ở cái gì, tôi cố gắng tìm cách vui vẻ với nó. Ví dụ, khi muốn giỏi hơn scripting shell, tôi quyết định thực hành bằng cách lập trình phiên bản game Minesweeper trong Bash.

Nếu bạn là lập trình viên Bash có kinh nghiệm và muốn mài giũa kỹ năng vừa vui vẻ, hãy theo dõi để viết phiên bản Minesweeper riêng trên terminal. Mã nguồn hoàn chỉnh tại kho GitHub này.

Chuẩn bị

Trước khi viết code, tôi phác thảo các thành phần cần thiết cho game:

  1. In ra màn hình mìn
  2. Tạo logic gameplay
  3. Tạo logic xác định mìn khả dụng
  4. Đếm mìn khả dụng và đã khám phá (đã khai thác)
  5. Tạo logic kết thúc game

Trong Minesweeper, thế giới game là mảng 2D (cột và hàng) các ô ẩn. Mỗi ô có thể chứa mìn nổ hay không. Mục tiêu người chơi là lộ các ô không chứa mìn, và không bao giờ lộ mìn. Phiên bản Bash dùng ma trận 10x10, triển khai bằng mảng Bash đơn giản.

Trước hết, gán một số biến ngẫu nhiên. Đây là vị trí mìn có thể đặt trên bàn cờ. Bằng cách giới hạn số vị trí, dễ dàng xây dựng thêm. Logic có thể tốt hơn, nhưng tôi muốn game đơn giản và hơi "non nớt". (Tôi viết cho vui, nhưng hoan nghênh đóng góp để làm đẹp hơn.)

Các biến dưới là biến mặc định, khai báo để gọi ngẫu nhiên cho vị trí trường, như biến a-g, chúng ta sẽ dùng để tính ô có thể khai thác từ mìn:

# variables
score=0 # sẽ dùng lưu điểm game
# variables below will be used to randomly get the extract-able cells/fields from our mine.
a="1 10 -10 -1"
b="-1 0 1"
c="0 1"
d="-1 0 1 -2 -3"
e="1 2 20 21 10 0 -10 -20 -23 -2 -1"
f="1 2 3 35 30 20 22 10 0 -10 -20 -25 -30 -35 -3 -2 -1"
g="1 4 6 9 10 15 20 25 30 -30 -24 -11 -10 -9 -8 -7"
#
# declarations
declare -a room # khai báo mảng room, đại diện cho mỗi ô/trường mìn.

Tiếp, in bàn cờ với cột (0-9) và hàng (a-j), tạo ma trận 10x10 làm màn hình mìn. (M[10][10] là mảng 100 giá trị chỉ số 0-99.) Nếu muốn biết thêm về mảng Bash, đọc You don't know Bash: An introduction to Bash arrays.

Gọi hàm plough, in header trước: hai dòng trống, tiêu đề cột, và dòng kẻ viền trên:

printf '\n\n'
printf '%s' " a b c d e f g h i j"
printf '\n %s\n' "-----------------------------------------"

Tiếp, thiết lập biến đếm r theo dõi số hàng ngang đã điền. Lưu ý, chúng ta sẽ dùng cùng biến đếm 'r' làm chỉ số mảng sau trong code game. Trong vòng lặp for Bash, dùng lệnh seq tăng từ 0 đến 9, in chữ số (d%) đại diện số hàng ($row, định nghĩa bởi seq):

r=0 # our counter
for row in $(seq 0 9); do
printf '%d ' "$row" # print the row numbers from 0-9

Trước khi tiến xa, kiểm tra điều đã làm. Chúng ta in dãy [a-j] ngang trước, rồi in số hàng trong khoảng [0-9], sẽ dùng hai khoảng này làm tọa độ đầu vào người dùng để định vị mìn khai thác.

Tiếp, Trong mỗi hàng, có giao điểm cột, nên mở vòng lặp for mới. Lần này quản lý mỗi cột, thực chất tạo từng ô trong sân chơi. Tôi thêm hàm helper bạn thấy định nghĩa đầy đủ trong source code. Với mỗi ô, cần thứ làm trông giống mìn, nên khởi tạo ô trống bằng chấm (.), dùng hàm tùy chỉnh is_null_field. Cũng cần biến mảng lưu giá trị mỗi ô, dùng biến mảng toàn cục room cùng biến chỉ số r. Khi r tăng, lặp qua các ô, đặt mìn dọc đường.

 for col in $(seq 0 9); do
((r+=1)) # increment the counter as we move forward in column sequence
is_null_field $r # assume a function which will check, if the field is empty, if so, initialize it with a dot(.)
printf '%s \e[33m%s\e[0m ' "|" "${room[$r]}" # finally print the separator, note that, the first value of ${room[$r]} will be '.', as it is just initialized.
#close col loop
done

Cuối, giữ bàn cờ rõ ràng bằng cách bao đáy mỗi hàng bằng dòng kẻ, và đóng vòng lặp hàng:

printf '%s\n' "|" # print the line end separator
printf ' %s\n' "-----------------------------------------"
# close row for loop
done
printf '\n\n'

Hàm plough đầy đủ trông như:

plough()
{
r=0
printf '\n\n'
printf '%s' " a b c d e f g h i j"
printf '\n %s\n' "-----------------------------------------"
for row in $(seq 0 9); do
printf '%d ' "$row"
for col in $(seq 0 9); do
((r+=1))
is_null_field $r
printf '%s \e[33m%s\e[0m ' "|" "${room[$r]}"
done
printf '%s\n' "|"
printf ' %s\n' "-----------------------------------------"
done
printf '\n\n'
}

Mất một chút thời gian quyết định cần is_null_field, nên xem kỹ nó làm gì. Chúng ta cần trạng thái đáng tin cậy từ đầu game. Lựa chọn tùy ý—có thể là số hoặc ký tự bất kỳ. Tôi quyết định giả định mọi thứ khai báo là chấm (.) vì tôi tin nó làm bàn cờ đẹp. Trông như sau:

is_null_field()
{
local e=$1 # we used index 'r' for array room already, let's call it 'e'
if [[ -z "${room[$e]}" ]];then
room[$r]="." # this is where we put the dot(.) to initialize the cell/minefield
fi
}

Bây giờ, đã khởi tạo tất cả ô mìn, tôi lấy số lượng mìn khả dụng bằng khai báo và gọi hàm đơn giản dưới:

get_free_fields()
{
free_fields=0 # initialize the variable
for n in $(seq 1 ${#room[@]}); do
if [[ "${room[$n]}" = "." ]]; then # check if the cells has initial value dot(.), then count it as a free field.
((free_fields+=1))
fi
done
}

Đây là màn hình mìn in ra, [a-j] là cột, và [0-9] là hàng.

Thành Thạo Scripting Bash: Tự Xây Dựng Game Minesweeper Trên Terminal

Tạo logic điều khiển người chơi

Logic người chơi đọc tùy chọn từ stdin làm tọa độ đến mìn và khai thác trường chính xác trên màn hình mìn. Nó dùng mở rộng tham số Bash trích xuất đầu vào cột và hàng, sau đó đưa cột vào switch trỏ đến ký hiệu số nguyên tương đương trên bàn cờ, để hiểu điều này, xem giá trị gán cho biến 'o' trong câu lệnh switch case dưới. Ví dụ, người chơi nhập c3, Bash tách thành hai ký tự: c và 3. Để đơn giản, tôi bỏ qua xử lý đầu vào không hợp lệ.

 colm=${opt:0:1} # get the first char, the alphabet
ro=${opt:1:1} # get the second char, the digit
case $colm in
a ) o=1;; # finally, convert the alphabet to its equivalent integer notation.
b ) o=2;;
c ) o=3;;
d ) o=4;;
e ) o=5;;
f ) o=6;;
g ) o=7;;
h ) o=8;;
i ) o=9;;
j ) o=10;;
esac

Sau đó tính chỉ số chính xác và gán chỉ số tọa độ đầu vào cho trường đó.

Cũng dùng nhiều lệnh shuf ở đây, shuf là tiện ích Linux thiết kế cung cấp hoán vị ngẫu nhiên thông tin nơi tùy chọn -i chỉ số hoặc khoảng có thể xáo trộn và -n chỉ số lượng tối đa đầu ra trả về. Dấu ngoặc kép cho phép đánh giá toán học trong Bash, và chúng ta sẽ dùng nhiều ở đây.

Giả sử ví dụ trước nhận c3 qua stdin. Khi đó, ro=3 và o=3 từ câu lệnh switch case chuyển c thành số nguyên tương đương, đưa vào công thức tính chỉ số cuối 'i'.

 i=$(((ro*10)+o)) # Follow BODMAS rule, to calculate final index. 
is_free_field $i $(shuf -i 0-5 -n 1) # call a custom function that checks if the final index value points to a an empty/free cell/field.

Duyệt qua toán học để hiểu chỉ số cuối 'i' tính như nào:

i=$(((ro*10)+o))
i=$(((3*10)+3))=$((30+3))=33

Giá trị chỉ số cuối là 33. Trên bàn cờ in ra, chỉ số cuối trỏ ô thứ 33 và đó nên là hàng thứ 3 (bắt đầu từ 0, nếu không phải hàng thứ 4) và cột thứ 3 (C).

Tạo logic xác định màn hình mìn khả dụng

Để khai thác mìn, sau khi tọa độ giải mã và chỉ số tìm thấy, chương trình kiểm tra trường đó có khả dụng không. Nếu không, chương trình hiển thị cảnh báo, người chơi chọn tọa độ khác.

Trong code này, ô khả dụng nếu chứa ký tự chấm (.). Giả sử khả dụng, giá trị trong ô được reset và điểm cập nhật. Nếu ô không khả dụng vì không chứa chấm, biến not_allowed được đặt. Để ngắn gọn, tôi để bạn xem source code game cho nội dung câu cảnh báo trong logic game.

is_free_field()
{
local f=$1
local val=$2
not_allowed=0
if [[ "${room[$f]}" = "." ]]; then
room[$f]=$val
score=$((score+val))
else
not_allowed=1
fi
}

Thành Thạo Scripting Bash: Tự Xây Dựng Game Minesweeper Trên Terminal

Nếu tọa độ nhập vào khả dụng, mìn được khám phá, như dưới. Khi h6 được cung cấp làm đầu vào, một số giá trị ngẫu nhiên điền trên màn hình mìn, các giá trị này cộng vào điểm người dùng sau khi mìn được khai thác.

Thành Thạo Scripting Bash: Tự Xây Dựng Game Minesweeper Trên Terminal

Nhớ biến khai báo ở đầu, [a-g], tôi sẽ dùng chúng ở đây để khai thác mìn ngẫu nhiên gán giá trị cho biến m dùng gián tiếp Bash. Vì vậy, tùy thuộc vào tọa độ đầu vào, chương trình chọn tập số thêm ngẫu nhiên (m) tính các trường thêm sẽ điền (như trên) bằng cách cộng vào tọa độ đầu vào gốc, ở đây là i (tính ở trên).

Lưu ý ký tự X trong đoạn code dưới, là trigger GAME-OVER duy nhất, chúng ta thêm nó vào danh sách xáo trộn để xuất hiện ngẫu nhiên, với vẻ đẹp của lệnh shuf, nó có thể xuất hiện sau bất kỳ số cơ hội nào hoặc có thể không xuất hiện cho người chơi may mắn thắng.

m=$(shuf -e a b c d e f g X -n 1) # add an extra char X to the shuffle, when m=X, its GAMEOVER
if [[ "$m" != "X" ]]; then # X will be our explosive mine(GAME-OVER) trigger
for limit in ${!m}; do # !m represents the value of value of m
field=$(shuf -i 0-5 -n 1) # again get a random number and
index=$((i+limit)) # add values of m to our index and calculate a new index till m reaches its last element.
is_free_field $index $field
done

Tôi muốn tất cả ô lộ ra đều liền kề ô người chơi chọn.

Thành Thạo Scripting Bash: Tự Xây Dựng Game Minesweeper Trên Terminal

Đếm mìn khả dụng và đã khai thác

Chương trình cần theo dõi ô khả dụng trong màn hình mìn; nếu không, nó cứ hỏi người chơi nhập mãi sau khi tất cả ô đã lộ. Để triển khai, tạo biến free_fields, ban đầu 0. Trong vòng lặp for định nghĩa bởi số ô khả dụng còn lại trong màn hình mìn. Nếu ô chứa chấm (.), thì đếm free_fields tăng.

get_free_fields()
{
free_fields=0
for n in $(seq 1 ${#room[@]}); do
if [[ "${room[$n]}" = "." ]]; then
((free_fields+=1))
fi
done
}

Đợi đã, nếu free_fields=0? Nghĩa là người dùng đã khai thác hết mìn. Tự do xem code chính xác để hiểu rõ hơn.

if [[ $free_fields -eq 0 ]]; then # well that means you extracted all the mines.
printf '\n\n\t%s: %s %d\n\n' "You Win" "you scored" "$score"
exit 0
fi

Tạo logic Gameover

Với tình huống Gameover, in ra giữa terminal dùng logic khéo léo tôi để người đọc khám phá cách nó hoạt động.

if [[ "$m" = "X" ]]; then
g=0 # to use it in parameter expansion
room[$i]=X # override the index and print X
for j in {42..49}; do # in the middle of the minefields,
out="gameover"
k=${out:$g:1} # print one alphabet in each cell
room[$j]=${k^^}
((g+=1))
done
fi

Cuối cùng, in hai dòng mong đợi nhất.

if [[ "$m" = "X" ]]; then
printf '\n\n\t%s: %s %d\n' "GAMEOVER" "you scored" "$score"
printf '\n\n\t%s\n\n' "You were just $free_fields mines away."
exit 0
fi

Thành Thạo Scripting Bash: Tự Xây Dựng Game Minesweeper Trên Terminal

Xong rồi! Nếu muốn biết thêm, truy cập source code game Minesweeper này và game Bash khác từ repo GitHub của tôi. Hy vọng nó cho bạn cảm hứng học thêm Bash và vui vẻ khi làm!