[Learning Ruby 3] unfairRandom

*หมายเหตุ: โพสต์นี้เขียนวันที่ 5 มกราคม 2556 แต่ delay การ publish ไว้*

@winwanwon [โยนคำถาม](http://twitter.yfrog.com/z/obem2rdp)มาครับ เป็นคำถามมาจากค่าย [IT CAMP 9](http://itcamp.in.th/camp9/) ค่ายย่อย Modern Developer ถามว่า

>มีฟังก์ชัน `unfairRandom()` มาให้ ซึ่งมีโอกาส 60% ที่จะให้ผลลัพธ์เป็นตัวเลข 0 และ 40% ให้ผลลัพธ์เป็นตัวเลข 1 จงหาวิธีสร้างฟังก์ชัน `fairRandom()` ซึ่งจะต้องสุ่มเลข 0 และ 1 อย่างยุติธรรมที่สุด ( โอกาส 50-50 ) โดยห้ามใช้ฟังก์ชันสุ่มอื่นๆ ที่นอกเหนือจาก `unfairRandom()`?

ตอนแรกก็จะใช้ Python ล่ะครับ แต่เรียนรูบี้อยู่ ลองดูสักตั้ง

~~~~~~
def unfair_rand
rand < 0.6 ? 0 : 1 end ~~~~~~ ฟังก์ชั่นนี้ก็ง่ายๆ ครับ คือ `rand` จะคืนค่าทศนิยมในช่วง [0-1) ออกมา สมมุติว่าฟังก์ชั่นนี้เป็นฟังก์ชั่นสุ่มที่มีความยุติธรรมแล้วกันนะครับ ก็ถ้ามันสุ่มมาได้น้อยกว่า 0.6 ก็ตอบ 0 ถ้ามากกว่าก็ตอบ 1 เสร็จแล้วก็ต้องมาตรวจคำตอบครับ ~~~~~~~ def random_check(x,times=1000.0) arr=[] times.to_i.times do arr << x.call; end (arr.count 0)/times end ~~~~~~~ ฟังก์ชั่น `random_check` จะตรวจสอบความยุติธรรมของฟังก์ชั่นที่มีคำตอบเป็น 0 และค่าอื่นอีก 1 ค่า (ในที่นี้คือ 1) เท่านั้นนะครับ โดยใช้วิธีทำการเรียกฟังก์ชั่นสุ่มตามจำนวนครั้งที่กำหนดไว้คือ 1,000 ครั้ง แล้วหาจำนวนเหตุการณ์ที่ออก 0 ทีนี้มันมีปัญหาละครับ ใน Ruby เนี่ย การเรียก function เหมือนตัวแปร มันไม่ได้เป็นการส่งตัวฟังก์ชั่นเข้าไปเป็น argument แต่มันเป็นการส่งผลของฟังก์ชั่นนั้นเข้าไป แล้วเราจะส่ง `unfair_rand` เข้าไปยังไงดี? ผมไป Google หาคำตอบมาครับ ก็เจอกับ[คำตอบ](http://alan.dipert.org/post/339368222/passing-methods-like-blocks-in-ruby) ~~~~~~ puts random_check(method(:unfair_rand)) ~~~~~~ ทีนี้ `random_check` ก็จะได้คลาส `Method` เข้าไปครับ เพียงแค่ใช้คำสั่ง `call` เข้าไปก็สามารถเรียกใช้ `Method` นั้นๆ ได้แล้ว สำหรับผลก็เป็นไปตามคาดครับ รันปุ๊บ คำตอบออกมาทันทีว่า 0.605 ค่อนข้างใกล้เคียงกับผลที่คาดไว้คือ 0.6 ทีนี้จะแก้ความแฟร์ยังไงดี ผมก็นั่งนึกอยู่หลายวิธีครับ เช่นว่าให้สุ่มหลายๆ ครั้งแล้วนับจำนวนครั้งที่ได้ 0 หรือ 1 ถ้าน้อยกว่าค่าหนึ่งก็ให้ตอบ 0 หรือตอบ 1 แต่มันก็ยังไม่ถูกเท่าไร จนกระทั่งวิธีนึงที่อยู่ดีๆ ปิ๊งออกมา ~~~~~ def fair_rand unfair_rand == unfair_rand ? 0 : 1 end ~~~~~ ลองเช็คดูด้วย `random_check` ผลออกมาได้ 0.52096 ใกล้เคียงกับคำตอบที่ต้องการคือ 0.5 อีกแล้ว ทีนี้มานั่งนึกๆ ดูครับว่าทำไมคำตอบถึงเป็นแบบนี้... เวลาสั่ง `unfair_rand` คำตอบที่ได้คือ 0 และ 1 ใช่ไหมล่ะครับ โดยโอกาสออก 0 มี 0.6 โอกาสออก 1 มี 0.4 ทีนี้ถ้าเราสุ่มสองครั้ง เหตุการณ์ที่เลขสองตัวออกเหมือนกันจะมีสองแบบคือ ออก 0 ทั้งคู่ และออก 1 ทั้งคู่ โดยโอกาสออก 0 ทั้งคู่ก็คือ `0.6 * 0.6 = 0.36` และโอกาสที่ออก 1 ทั้งคู่คือ `0.4 * 0.4 = 0.16` โอกาสที่เลขทั้งสองตัวออกมาเหมือนกัน อาจเป็นได้ทั้ง 0 และ 1 เหมือนกัน ก็จะมีค่าเท่ากับ `0.16 + 0.36 = 0.52` นั่นเอง ตรงกับคำตอบของโปรแกรมเลย *(อันที่จริงคำตอบของโปรแกรมเมื่อสักครู่นี้ผมปรับจำนวนรอบของ `random_check` เพิ่มขึ้นครับ ถ้ารอบน้อยๆ คำตอบออกมา 0.499 ยังมีเลย)* ผมไม่ค่อยชอบปัญหาพวกนี้นะ มันเหมือนปัญหาโปรแกรม แต่มันคือปัญหาเลขที่ใช้โปรแกรมแก้ ผมไม่ใช่นักคณิตศาสตร์ แต่บางทีเจอแล้วมันก็ติดกับนั่งทำอยู่เหมือนกัน เขียนพวกแสดง text เป็นกังหัน เป็นต้นไม้ เป็นดาว อะไรพวกนี้สนุกกว่า **สิ่งที่ได้จากโปรแกรมนี้:** - การส่งฟังก์ชั่นเข้าไปในฟังก์ชั่นด้วย `method`

[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 … ผมก็ยังไม่รู้เหมือนกันครับ เขียนไว้สองโปรแกรมเอง