[Learning Ruby 2] Minesweeper

กลับมาที่ Learning Ruby บ้างครับ โปรแกรมที่สองที่ผมหัดเขียนใน Ruby คือ Minesweeper ครับ ผมบังเอิญอ่านเจอใน reddit สักที่ถึงกฎของ minesweeper เลยมีความรู้สึกอยากเขียนเหมือนกัน จนวันต่อมาคุยกับพ่อแล้วพ่อก็แนะนำเกมนี้พอดีก็เลยเหมาะเหมง ได้เวลาเขียน

**Disclaimer:** Entry นี้เขียนขณะผมกำลังเรียนรู้ Ruby ข้อมูลในนี้คือสิ่งที่ผมเข้าใจในขณะที่กำลังเรียน ไม่จำเป็นว่าเป็นวิธีที่ถูกต้องที่สุดหรือเหมาะสม

อธิบายถึงกฎของ Minesweeper ก่อนครับ น่าจะทราบดีแล้วว่าเกมจะมีระเบิดซ่อนในกระดานอยู่ และมีตัวเลขบอกว่ารอบๆ ช่องนั้นมีระเบิดกี่ช่อง แต่ที่อาจจะไม่ทราบ

– Minesweeper ที่เขียนถูกต้อง กดครั้งแรกต้องไม่โดนระเบิดเสมอ (ในเครื่องเล่น MP4 อันแรกของผมกดแล้วโดนระเบิดได้นะ)
– เลขในช่องสูงสุดคือ 8 ฉะนั้นแสดงว่าต้องนับระเบิดในแนวเฉียงด้วย
– เมื่อคลิกแล้วพบว่าเลขในช่องเป็น 0 ให้คลิกทุกช่องรอบๆ ช่องนั้นด้วย นั่นคือทำไมกดแล้วกระดานเปิดออกมากว้างมากในบางครั้ง

เกมนี้ใช้ Curses เหมือนเดิมครับ คราวนี้เริ่มชินกับ Curses แล้ว

จากโค้ดจะเห็นว่าผมแบ่งเป็น 2 ส่วนนะครับ คือบรรทัด 14-33 เป็นหน้าตั้งค่า ใช้ puts, gets ตามปกติ โดยจะใช้ while loop เช็คว่าผู้ใช้ป้อนถูกต้องหรือไม่ ถ้าไม่ถูกต้องจะต้องป้อนใหม่จนกว่าจะถูก

และในส่วนที่สองคือบรรทัดที่ 35 เป็นต้นไปจะเป็นส่วนของเกมครับ มีฟังค์ชั่นคือ

– `gen_bomb` จะทำการสร้างระเบิด โดยจะเก็บเป็น (x,y) ของระเบิด และไม่วางระเบิดในช่องปัจจุบัน ฟังก์ชั่นนี้ใช้การสุ่มวางแล้วเช็คช่องเอา ไม่ใช้การไล่ช่องแล้วสุ่ม true/false เอา (วิธีหลังนี้จะเร็วกว่าหลายเท่า) ก็เพราะว่าวิธีหลังระเบิดจะกระจุกตัวอยู่ตามด้านบนครับ
– `open_tile` จะเปิดช่องที่ส่งเข้ามาครับ โดยถ้าเจอช่องที่เป็น 0 คะแนนปุ๊บจะเรียกตัวเอง (recursive function) เปิดช่องรอบๆ อัตโนมัติ ข้อสังเกตคือต้องมีการเก็บช่องที่เปิดแล้วนะครับ ไม่อย่างนั้น recursive function จะทำงานไม่รู้จบได้
– `winterface` เป็นหน้าแสดงเวลาแพ้หรือชนะ (Win + interface)

ระบบเกมนี้ไม่ใช่ Tick แล้วครับ เพราะไม่มีกระบวนการเบื้องหลัง แต่จำเป็นต้องใช้ non-blocking input อยู่ดีเพราะว่าต้องอ่านค่าจนหมด line buffer ถึงจะรู้ว่าเป็นตัวอะไร

สิ่งที่ได้จากโปรแกรมนี้:

– การเขียน block ใช้เอง (`do .. done`) ใน `get_until_cond` คือฟังก์ชั่นสามารถรับ block ได้ โดย block จะเป็นชนิด `Proc` เมื่อต้องการจะเรียกให้ฟังค์ชั่นที่ส่งมาเป็น block ทำงาน ให้ใช้คำสั่ง `yield` (สามารถมี argument ได้ ซึ่งจะส่งต่อไปให้ใน block)
– `Proc` ไม่ใช่ฟังก์ชั่น ไม่สามารถ `return` ได้ไม่อย่างนั้นจะ error เพราะไม่รู้ return ให้ใคร (อ่านใน docs ประมาณว่า `Proc` มัน bind scope ไว้ ทำให้ return ไม่ได้)
– Function ทุกตัว รวมถึง Proc จะ return ผลลัพท์ของคำสั่งสุดท้ายใน Function นั้นเสมอ (และนี่คือวิธีเดียวในการ return จาก Proc)
– การเรียก Function ที่ใส่ \* (เรียกว่า splat) หน้า argument ตัวแรกและตัวเดียว คือการเอาค่าใน argument แต่ละตัวใส่เป็นแต่ละ argument ของฟังก์ชั่น (argument นั้นต้องเป็น array) ตัวอย่างเช่น ถ้า `x=[1,2]` การเรียก `setpos *x` มีค่าเท่ากับการเรียก `setpos 1,2` — พฤติกรรมนี้คล้ายๆ กับ \*args ของ Python
– Function บางตัว โดยเฉพาะใน `String` จะมีทั้งรูปปกติ และมี ! ต่อท้าย แบบหลังคือ in-place หรืออธิบายได้ว่า `x.rstrip!` ได้ผลเหมือนกับ `x=x.rstrip`
– ใน for จะข้ามไปลูปต่อไป ใช้ว่า `next` ไม่ใช่ `continue` เหมือนภาษาอื่น
– สุ่มเลขใช้ว่า `rand` ตอนหลังผมมาทราบว่า `rand` รับ argument 1 ตัวคือค่าสูงสุด (exclusive) เช่น `rand 5` จะได้คำตอบในช่วง {x|x∈I⁺∧x∈[0,5)} (0-4 นั่นแหละ)

ที่เกมยังทำไม่ได้คือกระดานขนาดใหญ่มากๆ ระเบิดน้อยมากๆ อาจจะติด maximum recursion ได้ และน่าจะทำคือพอ `winterface` แล้วให้แสดงระเบิดด้วย ซึ่งผมขี้เกียจเขียนเพราะตอนนั้นเขียนแบบ ssh ไปหาเครื่องอื่น ซึ่งมันขยายจอ nano ไม่ได้ ก๊อปวางก็ไม่สะดวก

ตอนต่อไปใน Learning Ruby … ผมก็ยังไม่รู้เหมือนกันครับ เขียนไว้สองโปรแกรมเอง

One thought on “[Learning Ruby 2] Minesweeper”

Comments are closed.