Computer >> คอมพิวเตอร์ >  >> การเขียนโปรแกรม >> การเขียนโปรแกรม BASH

ฝึกฝนทักษะ Bash ขั้นสูงด้วยการสร้าง Minesweeper

ฉันไม่มีความเชี่ยวชาญในการสอนการเขียนโปรแกรม แต่เมื่อฉันต้องการทำให้ดีขึ้นในบางสิ่ง ฉันพยายามหาวิธีที่จะสนุกกับมัน ตัวอย่างเช่น เมื่อฉันต้องการพัฒนาเชลล์สคริปต์ให้ดีขึ้น ฉันตัดสินใจฝึกฝนโดยการเขียนโปรแกรมเวอร์ชันของเกม Minesweeper ใน Bash

หากคุณเป็นโปรแกรมเมอร์ Bash ที่มีประสบการณ์และต้องการฝึกฝนทักษะในขณะที่สนุกสนาน ให้ทำตามเพื่อเขียน Minesweeper เวอร์ชันของคุณเองในเทอร์มินัล พบซอร์สโค้ดที่สมบูรณ์ในที่เก็บ GitHub นี้

เตรียมตัวให้พร้อม

ก่อนที่ฉันจะเริ่มเขียนโค้ดใดๆ ฉันได้สรุปส่วนประกอบที่จำเป็นในการสร้างเกมของฉัน:

  1. พิมพ์เขตที่วางทุ่นระเบิด
  2. สร้างตรรกะการเล่นเกม
  3. สร้างตรรกะเพื่อกำหนดเขตที่วางทุ่นระเบิด
  4. นับจำนวนทุ่นระเบิดที่มีอยู่และค้นพบ (สกัด)
  5. สร้างตรรกะหลังจบเกม

ใน Minesweeper โลกของเกมคืออาร์เรย์ 2 มิติ (คอลัมน์และแถว) ของเซลล์ที่ซ่อนอยู่ แต่ละเซลล์อาจมีหรือไม่มีระเบิด วัตถุประสงค์ของผู้เล่นคือเพื่อเปิดเผยเซลล์ที่ไม่มีทุ่นระเบิดและไม่เคยเปิดเผยทุ่นระเบิด เกมเวอร์ชัน Bash ใช้เมทริกซ์ขนาด 10x10 ใช้งานโดยใช้อาร์เรย์ทุบตีแบบง่าย

อันดับแรก ฉันกำหนดตัวแปรสุ่มบางตัว เหล่านี้เป็นตำแหน่งที่สามารถวางทุ่นระเบิดบนกระดานได้ ด้วยการจำกัดจำนวนสถานที่ มันจะง่ายต่อการสร้างบนนี้ ตรรกะน่าจะดีกว่านี้ แต่ฉันต้องการให้เกมดูเรียบง่ายและยังไม่บรรลุนิติภาวะ (ฉันเขียนนี้เพื่อความสนุก แต่ฉันยินดียินดีที่คุณมีส่วนร่วมทำให้ดูดีขึ้น)

ตัวแปรด้านล่างนี้คือตัวแปรเริ่มต้นบางตัว ซึ่งประกาศให้เรียกแบบสุ่มสำหรับการจัดวางภาคสนาม เช่น ตัวแปร a-g เราจะใช้ตัวแปรเหล่านี้ในการคำนวณทุ่นระเบิดที่แยกออกมาได้:

# ตัวแปร
score=0 # จะถูกใช้เพื่อเก็บคะแนนของเกม
# ตัวแปรด้านล่างจะถูกใช้เพื่อสุ่มรับเซลล์/ฟิลด์ที่แยกได้จากเหมืองของเรา
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"
#
# ประกาศ
declare -a room  # ประกาศห้องอาร์เรย์ ซึ่งจะแสดงแต่ละเซลล์/ฟิลด์ของเรา

ต่อไป ฉันพิมพ์กระดานด้วยคอลัมน์ (0-9) และแถว (a-j) เพื่อสร้างเมทริกซ์ขนาด 10x10 เพื่อใช้เป็นเขตที่วางทุ่นระเบิดสำหรับเกม (M[10][10] คืออาร์เรย์ 100 ค่าที่มีดัชนี 0-99) หากคุณต้องการทราบข้อมูลเพิ่มเติมเกี่ยวกับอาร์เรย์ Bash โปรดอ่าน คุณไม่รู้จัก Bash:บทนำสู่อาร์เรย์ Bash .

เรียกมันว่าฟังก์ชั่น คันไถ เราพิมพ์ส่วนหัวก่อน:สองบรรทัดว่าง ส่วนหัวของคอลัมน์ และบรรทัดเพื่อร่างส่วนบนของสนามเด็กเล่น:

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

ต่อไป ฉันสร้างตัวแปรตัวนับที่เรียกว่า r เพื่อติดตามจำนวนแถวแนวนอนที่มีการเติมข้อมูล โปรดทราบว่าเราจะใช้ตัวแปรตัวนับเดียวกัน 'r ' เป็นดัชนีอาร์เรย์ของเราในภายหลังในรหัสเกม ในทุบตี สำหรับ วนซ้ำโดยใช้ seq คำสั่งเพิ่มจาก 0 ถึง 9 ฉันพิมพ์ตัวเลข (d% ) เพื่อแสดงหมายเลขแถว ($row ซึ่งกำหนดโดย seq ):

r=0 # ตัวนับของเรา
สำหรับแถวใน $(seq 0 9); do
  printf '%d  ' "$row" # พิมพ์หมายเลขแถวตั้งแต่ 0-9

ก่อนจะไปต่อจากนี้ มาเช็คกันว่าเราทำอะไรลงไปบ้าง เราพิมพ์ลำดับ [a-j]  ในแนวนอนก่อนแล้วจึงพิมพ์หมายเลขแถวในช่วง [0-9] เราจะใช้ช่วงทั้งสองนี้เพื่อทำหน้าที่เป็นผู้ใช้ป้อนพิกัดเพื่อค้นหาเหมืองที่จะสกัด 

ต่อไป  ภายในแต่ละแถวจะมีจุดตัดของคอลัมน์ ดังนั้นจึงถึงเวลาเปิด สำหรับ . ใหม่ ห่วง อันนี้จัดการแต่ละคอลัมน์ ดังนั้นมันจึงสร้างแต่ละเซลล์ในสนามเด็กเล่นเป็นหลัก ฉันได้เพิ่มฟังก์ชันตัวช่วยบางอย่างที่คุณสามารถดูคำจำกัดความทั้งหมดในซอร์สโค้ดได้ สำหรับแต่ละเซลล์  เราต้องการบางสิ่งเพื่อทำให้ฟิลด์ดูเหมือนกับระเบิด ดังนั้นเราจึงเริ่มต้นเซลล์ว่างด้วยจุด (.) โดยใช้ฟังก์ชันที่กำหนดเองชื่อ is_null_field . นอกจากนี้ เราจำเป็นต้องมีตัวแปรอาร์เรย์เพื่อเก็บค่าสำหรับแต่ละเซลล์ เราจะใช้ตัวแปร global array ที่กำหนดไว้ล่วงหน้า ห้อง พร้อมกับตัวแปรดัชนี r . เป็น  เพิ่มขึ้น เราทำซ้ำผ่านเซลล์ ทิ้งทุ่นระเบิดระหว่างทาง

  สำหรับ col ใน $(seq 0 9); do
    ((r+=1))  # เพิ่มตัวนับในขณะที่เราเคลื่อนที่ไปข้างหน้าในลำดับคอลัมน์
    is_null_field $r  # สมมติฟังก์ชันที่จะตรวจสอบว่าฟิลด์ว่างหรือไม่ ถ้าใช่ ให้เริ่มต้นด้วย a dot(.)
    printf '%s \e[33m%s\e[0m ' "|" "${room[$r]}" # ในที่สุดพิมพ์ตัวคั่น โปรดทราบว่าค่าแรกของ ${room[$r]} จะเป็น '.' เนื่องจากเพิ่งเริ่มต้น
  #close col วนซ้ำ
  เสร็จแล้ว

สุดท้าย ฉันรักษากระดานที่มีการกำหนดไว้อย่างชัดเจนโดยใส่เส้นล้อมรอบด้านล่างของแต่ละแถว แล้วปิดการวนซ้ำของแถว:

printf '%s\n' "|" # พิมพ์ตัวคั่นบรรทัดสุดท้าย
printf '   %s\n' "-------------------------------- ---------"
# ปิดแถวสำหรับวนซ้ำ
เสร็จสิ้น
printf '\n\n'

คันไถเต็มคัน ฟังก์ชันมีลักษณะดังนี้: 

plough()
{
  r=0
  printf '\n\n'
  printf '%s' "     a   b   c   d   e   f   g   h   i   j"
  printf '\n   %s\n' "----------------------------- -----"
  สำหรับแถวใน $(seq 0 9); do
    printf '%d  ' "$row"
    for col in $(seq 0 9); ทำ
       ((r+=1))
       is_null_field $r
       printf '%s \e[33m%s\e[0m ' "|" "${room[$r]}"
    done
    printf '%s\n' "|"
    printf '   %s\n' "-------------------------------------- ---"
  เสร็จแล้ว
  printf '\n\n'
}

ฉันต้องใช้เวลาพอสมควรในการตัดสินใจว่าจะต้องใช้ is_null_field ลองมาดูกันดีกว่าว่ามันทำอะไรได้บ้าง เราต้องการสถานะที่เชื่อถือได้ตั้งแต่ต้นเกม ทางเลือกนั้นขึ้นอยู่กับการตัดสินใจ อาจเป็นตัวเลขหรืออักขระใดก็ได้ ฉันตัดสินใจที่จะถือว่าทุกอย่างถูกประกาศเป็นจุด (.) เพราะฉันเชื่อว่ามันทำให้เกมกระดานดูสวยงาม นี่คือลักษณะ:

is_null_field()
{
  local e=$1 # เราใช้ index 'r' สำหรับห้องอาร์เรย์แล้ว ให้เรียกว่า 'e'
    if [[ -z "${room [$e]}" ]];แล้ว
      room[$r]="" # นี่คือที่ที่เราใส่จุด (.) เพื่อเริ่มต้นเซลล์/เขตที่วางทุ่นระเบิด
    fi
}

ตอนนี้ฉันมีเซลล์ทั้งหมดในเหมืองที่เริ่มต้นแล้ว ฉันได้รับจำนวนทุ่นระเบิดที่มีอยู่ทั้งหมดโดยการประกาศและเรียกใช้ฟังก์ชันง่าย ๆ ที่แสดงด้านล่างในภายหลัง:

get_free_fields()
{
  free_fields=0    # เริ่มต้นตัวแปร
  สำหรับ n ใน $(seq 1 ${#room[@]}); ทำ
    if [[ "${room[$n]}" ="." ]]; จากนั้น  # ตรวจสอบว่าเซลล์มีค่าเริ่มต้น dot(.) หรือไม่ จากนั้นนับเป็นช่องว่าง
      ((free_fields+=1))
    fi
  done
}

นี่คือเขตที่วางทุ่นระเบิดที่พิมพ์โดย [a-j] เป็นคอลัมน์ และ [0-9 ] เป็นแถว

ฝึกฝนทักษะ Bash ขั้นสูงด้วยการสร้าง Minesweeper

สร้างตรรกะเพื่อขับเคลื่อนผู้เล่น

ตรรกะของผู้เล่นอ่านตัวเลือกจาก stdin เป็นพิกัดไปยังทุ่นระเบิดและแยกฟิลด์ที่แน่นอนบนเขตที่วางทุ่นระเบิด มันใช้การขยายพารามิเตอร์ของ Bash เพื่อแยกอินพุตคอลัมน์และแถว จากนั้นป้อนคอลัมน์ไปยังสวิตช์ที่ชี้ไปที่สัญกรณ์จำนวนเต็มเทียบเท่าบนกระดาน เพื่อให้เข้าใจสิ่งนี้ ดูค่าที่กำหนดให้กับตัวแปร 'o' ในคำสั่ง switch case ด้านล่าง ตัวอย่างเช่น ผู้เล่นอาจป้อน c3 ซึ่ง Bash แบ่งออกเป็นสองอักขระ:c และ 3 . เพื่อความง่าย ฉันกำลังข้ามวิธีจัดการกับรายการที่ไม่ถูกต้อง

  colm=${opt:0:1}  # รับอักขระตัวแรก, ตัวอักษร
  ro=${opt:1:1}    # รับอักขระตัวที่สอง, ตัวเลข
  case $colm ใน
    a ) o=1;; # สุดท้าย แปลงตัวอักษรเป็นสัญกรณ์จำนวนเต็มเทียบเท่า
    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

จากนั้นจะคำนวณดัชนีที่แน่นอนและกำหนดดัชนีของพิกัดอินพุตให้กับฟิลด์นั้น

นอกจากนี้ยังมีการใช้ shuf . เป็นจำนวนมาก สั่งที่นี่ สับ เป็นยูทิลิตี้ Linux ที่ออกแบบมาเพื่อให้มีการสับเปลี่ยนข้อมูลแบบสุ่มโดยที่ -i ตัวเลือกแสดงถึงดัชนีหรือช่วงที่เป็นไปได้ในการสับเปลี่ยนและ -n หมายถึงจำนวนสูงสุดหรือผลลัพธ์ที่ได้รับกลับมา วงเล็บคู่ช่วยให้สามารถประเมินผลทางคณิตศาสตร์ใน Bash ได้ และเราจะใช้มันอย่างหนักที่นี่

สมมติว่าตัวอย่างก่อนหน้านี้ของเราได้รับ c3 ผ่าน stdin จากนั้น ro=3 และ o=3 จากด้านบนคำสั่ง switch case แปลง c เป็นจำนวนเต็มเท่ากัน ให้ใส่ลงในสูตรเพื่อคำนวณดัชนีสุดท้าย 'i'

  i=$(((ro*10)+o))   # ปฏิบัติตามกฎ BODMAS เพื่อคำนวณดัชนีสุดท้าย 
  is_free_field $i $(shuf -i 0-5 -n 1)   # เรียกใช้ฟังก์ชันแบบกำหนดเองที่ตรวจสอบว่าค่าดัชนีสุดท้ายชี้ไปที่เซลล์/ฟิลด์ว่าง/ว่าง

เดินผ่านคณิตศาสตร์นี้เพื่อทำความเข้าใจว่าดัชนีสุดท้าย 'i ' ถูกคำนวณ:

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

ค่าดัชนีสุดท้ายคือ 33 พิมพ์บนกระดานของเรา ดัชนีสุดท้ายชี้ไปที่เซลล์ที่ 33 และควรเป็นแถวที่ 3 (เริ่มจาก 0 มิฉะนั้น แถวที่ 4) และคอลัมน์ที่ 3 (C)

สร้างตรรกะเพื่อกำหนดเขตที่วางทุ่นระเบิด

ในการดึงทุ่นระเบิด หลังจากถอดรหัสพิกัดและพบดัชนีแล้ว โปรแกรมจะตรวจสอบว่าฟิลด์นั้นว่างหรือไม่ หากไม่เป็นเช่นนั้น โปรแกรมจะแสดงคำเตือน และผู้เล่นจะเลือกพิกัดอื่น

ในโค้ดนี้ เซลล์จะพร้อมใช้งานหากมีจุด (. ) อักขระ. สมมติว่าพร้อมใช้งาน ค่าในเซลล์จะถูกรีเซ็ตและคะแนนจะได้รับการอัปเดต หากเซลล์ไม่พร้อมใช้งานเนื่องจากไม่มีจุด แสดงว่าตัวแปร not_allowed ถูกตั้งค่า เพื่อความกระชับ ฉันปล่อยให้คุณดูซอร์สโค้ดของเกมสำหรับเนื้อหาของข้อความเตือนในตรรกะของเกม

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

ฝึกฝนทักษะ Bash ขั้นสูงด้วยการสร้าง Minesweeper

หากมีพิกัดที่ป้อน เหมืองจะถูกค้นพบดังที่แสดงด้านล่าง เมื่อ h6 ถูกจัดเตรียมเป็นข้อมูลเข้า ค่าบางค่าที่สุ่มสร้างขึ้นบนเขตที่วางทุ่นระเบิดของเรา ค่าเหล่านี้จะถูกเพิ่มไปยังคะแนนของผู้ใช้หลังจากแยกนาที

ฝึกฝนทักษะ Bash ขั้นสูงด้วยการสร้าง Minesweeper

ตอนนี้จำตัวแปรที่เราประกาศไว้ตอนเริ่มต้น [a-g] ตอนนี้ฉันจะใช้ตัวแปรเหล่านี้ที่นี่เพื่อแยกเหมืองสุ่มที่กำหนดค่าให้กับตัวแปร m โดยใช้ Bash ทางอ้อม ดังนั้น ขึ้นอยู่กับพิกัดอินพุต โปรแกรมจะสุ่มชุดตัวเลขเพิ่มเติม (m ) เพื่อคำนวณฟิลด์เพิ่มเติมที่จะเติม (ดังที่แสดงด้านบน) โดยการเพิ่มลงในพิกัดอินพุตเดิม ซึ่งแสดงที่นี่โดย i ( คำนวณข้างต้น) .

โปรดสังเกตอักขระ X ในข้อมูลโค้ดด้านล่าง เป็นทริกเกอร์ GAME-OVER เพียงรายการเดียวของเรา เราเพิ่มลงในรายการสุ่มเพื่อให้ปรากฏแบบสุ่ม พร้อมความสวยงามของ สุ่ม คำสั่ง มันสามารถปรากฏขึ้นหลังจากจำนวนครั้งหรืออาจไม่ปรากฏสำหรับผู้ใช้ที่โชคดีของเรา

m=$(shuf -e a b c d e f g X -n 1)   # เพิ่มอักขระพิเศษ X ในการสับเปลี่ยน เมื่อ m=X เกมโอเวอร์ของมัน
  if [[ "$m" !="X" ]]; จากนั้น        # X จะเป็นทริกเกอร์ระเบิดของเรา (GAME-OVER)
    สำหรับขีดจำกัดใน ${!m}; do          # !m แทนค่าของค่า m
      field=$(shuf -i 0-5 -n 1)     # สุ่มตัวเลขอีกครั้งและ
      index=$((i+limit)) # เพิ่มค่า m ลงในดัชนีของเราแล้วคำนวณดัชนีใหม่จนกว่า m จะถึงองค์ประกอบสุดท้าย
      is_free_field $index $field
    done

ฉันต้องการให้เซลล์ที่เปิดเผยทั้งหมดอยู่ติดกับเซลล์ที่ผู้เล่นเลือก

ฝึกฝนทักษะ Bash ขั้นสูงด้วยการสร้าง Minesweeper

นับจำนวนทุ่นระเบิดที่มีอยู่และสกัดได้

โปรแกรมจำเป็นต้องติดตามเซลล์ที่มีอยู่ในเขตที่วางทุ่นระเบิด มิฉะนั้น จะขอให้ผู้เล่นป้อนข้อมูลต่อไปแม้ว่าเซลล์ทั้งหมดจะถูกเปิดเผยแล้วก็ตาม ในการดำเนินการนี้ ฉันสร้างตัวแปรชื่อ free_fields เริ่มแรกตั้งค่าเป็น 0 ใน สำหรับ วนซ้ำที่กำหนดโดยจำนวนเซลล์/ฟิลด์ที่เหลืออยู่ในเขตที่วางทุ่นระเบิดของเรา ถ้าเซลล์มีจุด (. ) จากนั้นนับ free_fields เพิ่มขึ้น

get_free_fields()
{
  free_fields=0
  สำหรับ n ใน $(seq 1 ${#room[@]}); ทำ
    if [[ "${room[$n]}" ="." ]]; แล้ว
      ((free_fields+=1))
    fi
  done
}

เดี๋ยวก่อน แล้วถ้า free_fields=0 ? นั่นหมายความว่า ผู้ใช้ของเราได้ขุดเหมืองทั้งหมดแล้ว โปรดอ่านโค้ดที่ถูกต้องเพื่อให้เข้าใจมากขึ้น

ถ้า [[ $free_fields -eq 0 ]]; ถ้าอย่างนั้น   # ก็หมายความว่าคุณสกัดทุ่นระเบิดทั้งหมด
      printf '\n\n\t%s:%s %d\n\n' "You Win" "you score" "$score"
      ทางออก 0
fi

สร้างตรรกะสำหรับ Gameover

สำหรับสถานการณ์ Gameover เราพิมพ์ไปที่ตรงกลางของเทอร์มินัลโดยใช้ตรรกะดีๆ ที่ฉันปล่อยให้ผู้อ่านได้สำรวจว่ามันทำงานอย่างไร

if [[ "$m" ="X" ]]; จากนั้น
    g=0                      # เพื่อใช้ในการขยายพารามิเตอร์
    room[$i]=X               # แทนที่ดัชนีและพิมพ์ X
    สำหรับ j ใน {42..49}; ทำ    # กลางทุ่นระเบิด
      out="gameover"
      k=${out:$g:1}          # พิมพ์ตัวอักษรหนึ่งตัวในแต่ละเซลล์
      room[$j]=${k^^}
      ((g+=1))
    done
fi

สุดท้าย เราสามารถพิมพ์สองบรรทัดที่รอมากที่สุดได้

if [[ "$m" ="X" ]]; แล้ว
      printf '\n\n\t%s:%s %d\n' "GAMEOVER" "คุณได้คะแนน" "$score"
      printf '\n\n\t%s\n \n' "คุณอยู่ห่างออกไปเพียง $free_fields กับระเบิด"
      exit 0
fi

ฝึกฝนทักษะ Bash ขั้นสูงด้วยการสร้าง Minesweeper

แค่นั้นแหละ! หากต้องการทราบข้อมูลเพิ่มเติม ให้เข้าถึงซอร์สโค้ดสำหรับเกม Minesweeper นี้และเกมอื่นๆ ใน Bash จาก repo GitHub ของฉัน ฉันหวังว่ามันจะเป็นแรงบันดาลใจให้คุณเรียนรู้ Bash เพิ่มเติมและสนุกไปกับมัน