Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> การเขียนโปรแกรม Bash

เชี่ยวชาญ Bash Scripting: สร้างเกม Minesweeper ใน Terminal เอง

ผมไม่ใช่ Expert สอน Programming แต่วันอยากเก่งเรื่องไหน พยายามหาวิธีสนุกกับมัน เช่น อยากเก่ง Shell Scripting ตัดสินใจฝึกโดยเขียน Minesweeper ใน Bash

หากคุณ Bash Programmer ที่มีประสบการณ์ และอยากคัดลอกทักษะพร้อมสนุก ติดตามมาสร้าง Minesweeper เวอร์ชันตัวเองใน Terminal Source Code ครบใน GitHub Repository นี้

เตรียมตัว

ก่อนเขียนโค้ด ร่าง Ingredients ที่ต้องการสร้างเกม:

  1. Print Minefield
  2. Create Gameplay Logic
  3. Create Logic กำหนด Available Minefield
  4. นับ Available และ Discovered (Extracted) Mines
  5. Create Endgame Logic

ใน Minesweeper Game World เป็น 2D Array (Columns และ Rows) ของ Concealed Cells แต่ละ Cell อาจมีหรือไม่มี Explosive Mine เป้าหมาย Player คือ Reveal Cells ที่ไม่มี Mine และอย่า Reveal Mine เวอร์ชัน Bash ใช้ Matrix 10x10 ใช้ Bash Arrays ธรรมดา

ก่อนอื่น Assign Random Variables บางตัว นี่คือ Locations ที่ Mines อาจวางบน Board โดยจำกัดจำนวน Locations จะง่ายต่อการ Build On Top Logic อาจดีกว่านี้ได้ แต่อยากเก็บ Game ให้ Simple และ Immature เล็กน้อย (เขียนเพื่อสนุก แต่วelcome Contributions ทำให้ดูดีขึ้น)

Variables ด้านล่างเป็น Default Variables Declared เรียก Random สำหรับ Field Placement เช่น Variables a-g ใช้คำนวณ Extractable Mines:

# variables
score=0 # will be used to store the score of the 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 # declare an array room, it will represent each cell/field of our mine.

ต่อไป Print Board พร้อม Columns (0-9) และ Rows (a-j) สร้าง Matrix 10x10 เป็น Minefield สำหรับเกม (M[10][10] คือ Array 100 ค่า Index 0-99) หากอยากรู้ Bash Arrays อ่าน You don't know Bash: An introduction to Bash arrays

เรียก Function plough พิมพ์ Header ก่อน: 2 Blank Lines, Column Headings, และ Line Outline ส่วนบน Playing Field:

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

ต่อมา Counter Variable ชื่อ r ติดตามว่ามี Horizontal Rows กี่แถวที่ Populated แล้ว สังเกต ใช้ Counter Variable เดียว 'r' เป็น Array Index ต่อใน Game Code ใน Bash for Loop ใช้ seq Increment 0 ถึง 9 พิมพ์ Digit (d%) แทน Row Number ($row ที่ seq Define):

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

ก่อนไปต่อ เช็คว่าทำอะไรได้บ้าง พิมพ์ Sequence [a-j] แนวนอนก่อน แล้วพิมพ์ Row Numbers Range [0-9] จะใช้ทั้งสอง Range เป็น User Input Coordinates หา Mine Extract

ต่อไป ในแต่ละ Row มี Column Intersection เปิด for Loop ใหม่ Loop นี้จัดการ Column แต่ละตัว สร้าง Cell แต่ละ Cell ใน Playing Field เพิ่ม Helper Functions ดู Definition เต็มใน Source Code สำหรับแต่ละ Cell ต้องการอะไรทำให้ Field ดูเหมือน Mine Initialize Empty Ones ด้วย Dot (.) ใช้ Custom Function is_null_field ต้องการ Array Variable เก็บค่าแต่ละ Cell ใช้ Global Array Variable room พร้อม Index Variable r เมื่อ r Increment Iterate ผ่าน Cells วาง Mines ตลอดทาง

 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

สุดท้าย เก็บ Board ให้ Well-defined โดยปิดท้ายแต่ละ Row ด้วย Line แล้วปิด Row Loop:

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

Full plough Function:

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'
}

ใช้เวลาตัดสินใจต้องการ is_null_field มาแล้ว ใกล้ชิดดูมันทำอะไร ต้องการ Dependable State ตั้งแต่ต้นเกม Choice นี้ Arbitrary อาจเป็น Number หรือ Character ใดๆ ตัดสินใจ Assume ทุกอย่าง Declared เป็น Dot (.) เพราะเชื่อว่า Board ดูสวย นี่คือรูปแบบ:

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
}

ตอนนี้ Cell ทั้งหมดใน Mine Initialized แล้ว นับ Available Mines ทั้งหมด โดย Declare และ Call Function เรียบง่ายด้านล่าง:

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
}

นี่คือ Minefield ที่พิมพ์ออกมา [a-j] คือ Columns และ [0-9] คือ Rows

เชี่ยวชาญ Bash Scripting: สร้างเกม Minesweeper ใน Terminal เอง

สร้าง Logic ขับเคลื่อน Player

Player Logic อ่าน Option จาก Stdin เป็น Coordinate ไป Mines และ Extract Field ที่แน่นอนบน Minefield ใช้ Bash Parameter Expansion Extract Column และ Row Inputs แล้ว Feed Column ไป Switch Point ไป Integer Notation เทียบเท่าบน Board ดูได้จาก Values Assign ให้ Variable 'o' ใน Switch Case Statement ด้านล่าง เช่น Player Enter c3 Bash Split เป็น 2 Characters: c และ 3 เพื่อความง่าย ข้าม Invalid Entry Handling

 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

จากนั้น Calculate Exact Index Assign Index ของ Input Coordinates ให้ Field นั้น

ใช้ shuf Command มาก shuf Linux Utility ให้ Random Permutation ของ Information Option -i Denote Indexes หรือ Ranges ที่ Shuffle และ -n Denote Max Number หรือ Output ให้กลับ Double Parentheses ให้ Mathematical Evaluation ใน Bash ใช้หนักในที่นี้

สมมติ Example ก่อนหน้า Receive c3 Via Stdin แล้ว ro=3 และ o=3 จาก Switch Case ข้างบน Convert c เป็น Integer เทียบเท่า ใส่ Formula คำนวณ Final Index '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.

เดินทางคณิตศาสตร์เข้าใจ Final Index 'i' คำนวณยังไง:

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

Final Index Value คือ 33 บน Board ที่พิมพ์ข้างบน Final Index ชี้ Cell ที่ 33 และควรเป็น Row ที่ 3 (เริ่ม 0 ไป 4th) และ Column ที่ 3 (C)

สร้าง Logic กำหนด Available Minefield

Extract Mine หลัง Decode Coordinates หา Index แล้ว Program Check ว่า Field นั้น Available หากไม่ แสดง Warning Player เลือก Coordinate อื่น

ใน Code นี้ Cell Available หากมี Dot (.) Character สมมติ Available Reset Value ใน Cell และ Update Score หาก Cell Unavailable เพราะไม่มี Dot Variable not_allowed ถูก Set เพื่อความกระชับ ขอให้ดู Source Code ของเกมสำหรับ Warning Statement ใน Game Logic

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
}

เชี่ยวชาญ Bash Scripting: สร้างเกม Minesweeper ใน Terminal เอง

หาก Coordinate ที่ Enter Available Mine Discovered ตามภาพ เมื่อให้ Input h6 ค่าสุ่มบางตัว Populated บน Minefields ค่าเหล่านี้เพิ่มเข้า User Score หลัง Mines Extracted

เชี่ยวชาญ Bash Scripting: สร้างเกม Minesweeper ใน Terminal เอง

จำ Variables ที่ Declare ตอนต้น [a-g] ตอนนี้นำมาใช้ Extract Random Mines Assign ค่าให้ Variable m ใช้ Bash Indirection ขึ้นอยู่กับ Input Coordinates Program เลือก Set เพิ่มเติมของ Numbers (m) Random คำนวณ Additional Fields ที่ Populated (ภาพด้านบน) โดยบวกเข้ากับ Original Input Coordinates แทนด้วย i (คำนวณข้างบน)

โปรดสังเกต Character X ใน Code Snippet ด้านล่าง คือ GAME-OVER Trigger เพียงตัวเดียว เพิ่มเข้า Shuffle List เพื่อสุ่มด้วย ความงดงามของ shuf Command อาจปรากฏหลังโอกาสกี่ครั้ง หรือไม่ปรากฏเลยสำหรับ Lucky Winning User

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

ต้องการ Revealed Cells ทั้งหมด Contiguous กับ Cell ที่ Player เลือก

เชี่ยวชาญ Bash Scripting: สร้างเกม Minesweeper ใน Terminal เอง

นับ Available และ Extracted Mines

Program ต้อง Track Available Cells ใน Minefield มิฉะนั้นจะถาม Player Input ตลอดแม้ Cells ทั้งหมด Revealed แล้ว Implement โดยสร้าง Variable free_fields เริ่มต้น 0 ใน for Loop Defined โดย Remaining Available Cells/Fields ใน Minefields หาก Cell มี Dot (.) Increment free_fields

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

รอ ถ้า free_fields=0? หมายถึง User Extract Mines หมด ดู Source Code ตรงๆ เข้าใจดีกว่า

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

สร้าง Logic Gameover

สถานการณ์ Gameover Print ไปกลาง Terminal ด้วย Logic น่าสนใจ ขอให้ Reader Explore เองว่าใช้ยังไง

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

สุดท้าย พิมพ์ 2 บรรทัดที่คาดหวังมากที่สุด

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

เชี่ยวชาญ Bash Scripting: สร้างเกม Minesweeper ใน Terminal เอง

แค่นี้ครับ! หากอยากรู้เพิ่ม เข้า Source Code เกม Minesweeper นี้และเกมอื่นๆ ใน Bash จาก GitHub Repo ของผม หวังว่าให้ Inspiration เรียน Bash มากขึ้น และสนุกพร้อมกัน