Computer >> Máy Tính >  >> Lập trình >> Lập trình BASH

Rèn luyện kỹ năng Bash nâng cao bằng cách xây dựng Minesweeper

Tôi không phải là chuyên gia về dạy lập trình, nhưng khi tôi muốn trở nên giỏi hơn ở một lĩnh vực nào đó, tôi cố gắng tìm cách để có được niềm vui với nó. Ví dụ:khi tôi muốn trở nên giỏi hơn trong việc viết kịch bản shell, tôi quyết định luyện tập bằng cách lập trình một phiên bản của trò chơi Minesweeper trong Bash.

Nếu bạn là một lập trình viên Bash có kinh nghiệm và muốn trau dồi kỹ năng của mình trong khi giải trí, hãy làm theo để viết phiên bản Minesweeper của riêng bạn trong thiết bị đầu cuối. Mã nguồn hoàn chỉnh được tìm thấy trong kho lưu trữ GitHub này.

Chuẩn bị sẵn sàng

Trước khi bắt đầu viết bất kỳ mã nào, tôi đã vạch ra những nguyên liệu cần thiết để tạo trò chơi của mình:

  1. In một bãi mìn
  2. Tạo logic trò chơi
  3. Tạo logic để xác định bãi mìn có sẵn
  4. Giữ số lượng các mỏ có sẵn và đã phát hiện (đã khai thác)
  5. Tạo logic trò chơi kết thúc

Trong Minesweeper, thế giới trò chơi là một mảng 2D (cột và hàng) gồm các ô được che giấu. Mỗi ô có thể có hoặc không có mìn nổ. Mục tiêu của người chơi là tiết lộ các ô không chứa mìn và không bao giờ để lộ mìn. Phiên bản Bash của trò chơi sử dụng ma trận 10x10, được triển khai bằng các mảng bash đơn giản.

Đầu tiên, tôi chỉ định một số biến ngẫu nhiên. Đây là những vị trí mà mìn có thể được đặt trên bảng. Bằng cách giới hạn số lượng địa điểm, sẽ dễ dàng xây dựng trên đầu trang này. Logic có thể tốt hơn, nhưng tôi muốn giữ cho trò chơi trông đơn giản và hơi non nớt. (Tôi viết cái này cho vui, nhưng tôi rất vui khi hoan nghênh những đóng góp của bạn để làm cho nó trông đẹp hơn.)

Các biến bên dưới là một số biến mặc định, được khai báo để gọi ngẫu nhiên cho vị trí trường, như biến a-g, chúng tôi sẽ sử dụng chúng để tính toán các mỏ có thể trích xuất của chúng tôi:

 # biến 
score =0 # sẽ được sử dụng để lưu trữ điểm của trò chơi
# biến dưới đây sẽ được sử dụng để lấy ngẫu nhiên các ô / trường có thể trích xuất từ ​​mỏ của chúng tôi.
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 "
#
# khai báo
/> khai báo -a room # khai báo một mảng phòng, nó sẽ đại diện cho từng ô / lĩnh vực của tôi.

Tiếp theo, tôi in bảng của mình với các cột (0-9) và các hàng (a-j), tạo thành một ma trận 10x10 để làm bãi mìn cho trò chơi. (M [10] [10] là mảng 100 giá trị với chỉ số 0-99.) Nếu bạn muốn biết thêm về mảng Bash, hãy đọc Bạn chưa biết Bash:Giới thiệu về mảng Bash.> .

Hãy gọi nó là một hàm, cày chúng tôi in tiêu đề trước:hai dòng trống, tiêu đề cột và một dòng để phác thảo phần trên cùng của sân chơi:

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

Tiếp theo, tôi thiết lập một biến bộ đếm, được gọi là r , để theo dõi có bao nhiêu hàng ngang đã được điền. Lưu ý rằng, chúng tôi sẽ sử dụng cùng một biến bộ đếm ' r 'như chỉ mục mảng của chúng tôi sau này trong mã trò chơi. Trong một Bash cho vòng lặp, sử dụng seq lệnh tăng từ 0 đến 9, tôi in một chữ số ( d% ) để đại diện cho số hàng ($ row, được xác định bởi seq ):

 r =0 # bộ đếm của chúng tôi 
cho hàng trong $ (seq 0 9); do
printf '% d' "$ row" # in các số hàng từ 0-9

Trước khi chúng ta tiếp tục từ đây, hãy kiểm tra những gì chúng ta đã làm cho đến bây giờ. Chúng tôi đã in trình tự [a-j] theo chiều ngang trước tiên và sau đó chúng tôi in số hàng trong một phạm vi [0-9] , chúng tôi sẽ sử dụng hai phạm vi này để hoạt động như người dùng của chúng tôi nhập tọa độ để xác định vị trí mỏ để khai thác.

Tiếp theo, Trong mỗi hàng, có một giao điểm cột, vì vậy đã đến lúc mở cho mới vòng. Cái này quản lý từng cột, vì vậy về cơ bản nó tạo ra từng ô trong sân chơi. Tôi đã thêm một số chức năng trợ giúp mà bạn có thể xem định nghĩa đầy đủ trong mã nguồn. Đối với mỗi ô, chúng tôi cần một cái gì đó để làm cho trường trông giống như một cái mỏ, vì vậy chúng tôi khởi tạo các ô trống bằng dấu chấm (.), Sử dụng hàm tùy chỉnh có tên is_null_field . Ngoài ra, chúng tôi cần một biến mảng để lưu trữ giá trị cho mỗi ô, chúng tôi sẽ sử dụng biến mảng toàn cục được xác định trước room cùng với một biến chỉ mục r . Như r tăng dần, chúng tôi lặp lại các ô, thả mìn trên đường đi.

 cho col trong $ (seq 0 9); do 
((r + =1)) # tăng bộ đếm khi chúng ta di chuyển về phía trước trong chuỗi cột
is_null_field $ r # giả sử một hàm sẽ kiểm tra, nếu trường trống, nếu vậy, hãy khởi tạo nó bằng dấu chấm (.)
printf '% s \ e [33m% s \ e [0m' "|" "$ {room [$ r]}" # cuối cùng in dấu phân tách, lưu ý rằng, giá trị đầu tiên của $ {room [$ r]} sẽ là '.', vì nó vừa được khởi tạo.
#close col vòng lặp
thực hiện

Cuối cùng, tôi giữ cho bảng được xác định rõ ràng bằng cách bao quanh cuối mỗi hàng bằng một dòng, rồi đóng vòng lặp hàng:

 printf '% s \ n' "|" # print dấu phân cách cuối dòng 
printf '% s \ n' "-------------------------------- --------- "
# đóng hàng cho vòng lặp
xong
printf '\ n \ n'

Toàn bộ cày chức năng trông giống như:

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

Tôi đã mất một thời gian để quyết định có cần is_null_field , vì vậy chúng ta hãy xem xét kỹ hơn những gì nó làm. Chúng tôi cần một trạng thái đáng tin cậy ngay từ đầu trận đấu. Lựa chọn đó là tùy ý – nó có thể là một số hoặc bất kỳ ký tự nào. Tôi quyết định cho rằng mọi thứ được khai báo dưới dạng dấu chấm (.) Vì tôi tin rằng nó làm cho bảng điều khiển trông đẹp mắt. Đây là những gì trông giống như:

 is_null_field () 
{
local e =$ 1 #, chúng tôi đã sử dụng chỉ mục 'r' cho phòng mảng, hãy gọi nó là 'e'
nếu [[-z "$ {room [$ e]} "]]; thì
phòng [$ r] =". " # đây là nơi chúng tôi đặt dấu chấm (.) để khởi tạo ô / minefield
fi
}

Bây giờ, tôi đã khởi tạo tất cả các ô trong mỏ của chúng tôi, tôi nhận được số lượng tất cả các ô có sẵn bằng cách khai báo và sau đó gọi một hàm đơn giản được hiển thị bên dưới:

 get_free_fields () 
{
free_fields =0 # khởi tạo biến
cho n trong $ (seq 1 $ {# room [@]}); làm
nếu [["$ {room [$ n]}" ="." ]]; sau đó # kiểm tra xem các ô có giá trị ban đầu là dấu chấm (.) hay không, sau đó tính nó là trường tự do.
((free_fields + =1))
fi
done
}
trước>

Đây là bãi mìn đã in, nơi [ a-j] là các cột và [ 0-9 ] là các hàng.

Rèn luyện kỹ năng Bash nâng cao bằng cách xây dựng Minesweeper

Tạo logic để điều khiển trình phát

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

 colm =$ {opt:0:1} # lấy ký tự đầu tiên, bảng chữ cái 
ro =$ {opt:1:1} # lấy ký tự thứ hai, chữ số
trường hợp $ colm trong
a) o =1;; # cuối cùng, chuyển bảng chữ cái sang ký hiệu số nguyên tương đương.
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 đó, nó tính toán chỉ số chính xác và chỉ định chỉ số của tọa độ đầu vào cho trường đó.

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

Giả sử ví dụ trước của chúng tôi nhận được c3 qua stdin. Sau đó, ro =3 o =3 từ bên trên chuyển đổi câu lệnh trường hợp chuyển đổi c thành số nguyên tương đương của nó, hãy đặt nó vào công thức của chúng tôi để tính chỉ số cuối cùng ' i'.

 i =$ (((ro * 10) + o)) # Tuân theo quy tắc BODMAS, để tính chỉ số cuối cùng. 
is_free_field $ i $ (shuf -i 0-5 -n 1) # gọi một hàm tùy chỉnh để kiểm tra xem giá trị chỉ mục cuối cùng có trỏ đến một ô / trường trống / trống không.

Dạo qua bài toán này để hiểu cách chỉ số cuối cùng ' i 'được tính:

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

Giá trị chỉ mục cuối cùng là 33. Trên bảng của chúng tôi, được in ở trên, chỉ mục cuối cùng trỏ đến ô thứ 33 và đó phải là hàng thứ 3 (bắt đầu từ 0, nếu không thì thứ 4) và cột thứ 3 (C).

Tạo logic để xác định bãi mìn có sẵn

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

Trong mã này, một ô có sẵn nếu nó chứa một dấu chấm (. ) tính cách. Giả sử nó có sẵn, giá trị trong ô được đặt lại và điểm số được cập nhật. Nếu một ô không khả dụng vì nó không chứa dấu chấm, thì một biến not_allowed được thiết lập. Để ngắn gọn, tôi để bạn xem mã nguồn của trò chơi để biết nội dung của câu lệnh cảnh báo trong logic trò chơi.

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

Rèn luyện kỹ năng Bash nâng cao bằng cách xây dựng Minesweeper

Nếu tọa độ được nhập có sẵn, mỏ sẽ được phát hiện, như hình dưới đây. Khi h6 được cung cấp dưới dạng đầu vào, một số giá trị được điền ngẫu nhiên trên các bãi mỏ của chúng tôi, những giá trị này được thêm vào điểm số của người dùng sau khi trích xuất vài phút.

Rèn luyện kỹ năng Bash nâng cao bằng cách xây dựng Minesweeper

Bây giờ hãy nhớ các biến mà chúng ta đã khai báo lúc đầu, [a-g], bây giờ tôi sẽ sử dụng chúng ở đây để trích xuất các mỏ ngẫu nhiên gán giá trị của chúng cho biến m sử dụng hướng dẫn Bash. Vì vậy, tùy thuộc vào tọa độ đầu vào, chương trình chọn một tập hợp ngẫu nhiên các số bổ sung ( m ) để tính toán các trường bổ sung sẽ được điền (như được hiển thị ở trên) bằng cách thêm chúng vào tọa độ đầu vào ban đầu, được biểu thị ở đây bằng i ( được tính ở trên ) .

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

 m =$ (shuf -e a b c d e f g X -n 1) # thêm một ký tự X bổ sung vào trò trộn, khi m =X, GAMEOVER 
của nó nếu [["$ m"! ="X"]]; thì # X sẽ là lần kích hoạt mìn nổ (GAME-OVER) của chúng tôi
với giới hạn tính bằng $ {! m}; do #! m đại diện cho giá trị value của m
trường =$ (shuf -i 0-5 -n 1) # lại nhận được một số ngẫu nhiên và
index =$ ((i + giới hạn)) # thêm các giá trị của m vào chỉ mục của chúng tôi và tính toán một chỉ mục mới cho đến khi m đạt đến phần tử cuối cùng của nó.
is_free_field $ index $ field
xong

Tôi muốn tất cả các ô được tiết lộ tiếp giáp với ô mà người chơi đã chọn.

Rèn luyện kỹ năng Bash nâng cao bằng cách xây dựng Minesweeper

Giữ số lượng các mỏ hiện có và đã khai thác

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

 get_free_fields () 
{
free_fields =0
for n in $ (seq 1 $ {# room [@]}); làm
nếu [["$ {room [$ n]}" ="." ]]; sau đó
((free_fields + =1))
fi
xong
}

Chờ đã, điều gì sẽ xảy ra nếu free_fields =0 ? Điều đó có nghĩa là người dùng của chúng tôi đã khai thác tất cả các mỏ. Vui lòng xem mã chính xác để hiểu rõ hơn.

 if [[$ free_fields -eq 0]]; thì # tốt có nghĩa là bạn đã trích xuất tất cả các mỏ. 
printf '\ n \ n \ t% s:% s% d \ n \ n' "Bạn Giành chiến thắng" "bạn đã ghi được" "$ score"
Thoát 0
fi

Tạo logic cho Gameover

Đối với tình huống Gameover, chúng tôi in ở giữa terminal bằng cách sử dụng một số logic tiện lợi mà tôi để nó cho người đọc khám phá cách nó hoạt động.

 if [["$ m" ="X"]]; then 
g =0 # để sử dụng nó trong phòng
mở rộng tham số [$ i] =X # ghi đè chỉ mục và in X
cho j trong {42..49}; làm # ở giữa các bãi mìn,
out ="gameover"
k =$ {out:$ g:1} # in một bảng chữ cái trong mỗi ô
phòng [$ j] =$ {k ^^}
((g + =1))
xong
fi

Cuối cùng, chúng tôi có thể in hai dòng được chờ đợi nhất.

 if [["$ m" ="X"]]; thì 
printf '\ n \ n \ t% s:% s% d \ n' "GAMEOVER" "bạn đã ghi được" "$ score"
printf '\ n \ n \ t% s \ n \ n '"Bạn vừa mới khai thác được $ free_fields."
thoát khỏi 0
fi

Rèn luyện kỹ năng Bash nâng cao bằng cách xây dựng Minesweeper

Vậy đó, các bạn! Nếu bạn muốn biết thêm, hãy truy cập mã nguồn của trò chơi Minesweeper này và các trò chơi khác trong Bash từ repo GitHub của tôi. Tôi hy vọng nó mang lại cho bạn một số cảm hứng để tìm hiểu thêm về Bash và vui vẻ khi làm như vậy.