ข้ามไปยังเนื้อหาหลัก
บทความทั้งหมด
หลักการทำงาน

วงล้อรู้คำตอบอยู่แล้ว

เหตุใดผู้ชนะจึงถูกสุ่มเลือกก่อนที่แอนิเมชันจะเริ่มหมุน และสิ่งนี้ให้อะไรกับคุณ

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

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

ทำไมต้องเรียงลำดับแบบนี้

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

คุณสามารถปัดแรงขึ้นเพื่อเปลี่ยนผลลัพธ์ได้หรือไม่? การคลิกหยุดกลางคันทำให้ผลลัพธ์เอียงเอียงหรือไม่? แล้วจะเกิดอะไรขึ้นกับผู้ใช้ที่เปิดการตั้งค่า prefers-reduced-motion ซึ่งแทบไม่เห็นแอนิเมชันเลย?

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

อะไรทำหน้าที่สุ่มผลลัพธ์

ค่าเอนโทรปีได้มาจาก crypto.getRandomValues ซึ่งเป็นตัวสร้างค่าสุ่มเชิงวิทยาการเข้ารหัสของเบราว์เซอร์เอง และเราไม่เคยใช้ Math.random

การแปลงค่าดังกล่าวให้ได้ตัวเลขที่มีการกระจายตัวสม่ำเสมอจำเป็นต้องมีขั้นตอนเพิ่มอีกหนึ่งขั้น วิธีการง่ายๆ อย่าง draw % count จะมีความเอนเอียงทันทีเมื่อ count หาร 2³² ไม่ลงตัว ส่งผลให้รายการแรกๆ มีโอกาสชนะเพิ่มขึ้นหนึ่งในสี่พันล้าน แม้จะเป็นส่วนต่างเพียงเล็กน้อย แต่มันคือความไม่เป็นกลาง และวงล้อที่อ้างว่ายุติธรรมไม่ควรมีสิ่งนี้ ดังนั้นการสุ่มใดๆ ที่ตกอยู่ในช่วงเศษเหลือจะถูกละทิ้งไป แล้วทำการสุ่มใหม่อีกครั้ง

const limit = Math.floor(2 ** 32 / max) * max;

let draw = crypto.getRandomValues(new Uint32Array(1))[0];
while (draw >= limit) {
  draw = crypto.getRandomValues(new Uint32Array(1))[0];
}

return draw % max;

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

ค่าน้ำหนักคือโอกาส ไม่ใช่การเลือกที่รักมักที่ชัง

หากกำหนดค่าน้ำหนักของรายการหนึ่งเป็น 3 รายการนั้นก็จะได้สิทธิ์ในการสุ่มสามส่วนแทนที่จะเป็นหนึ่งส่วน วงล้อจะวาดช่องดังกล่าวให้กว้างขึ้น และรายการจะแสดงเปอร์เซ็นต์โอกาสชนะที่แท้จริงที่คำนวณจากค่าน้ำหนักนั้น คุณจึงไม่ต้องเสียเวลาคำนวณเอง

หากมีสองรายการที่มีข้อความเหมือนกัน ระบบจะยังคงนับเป็นสองรายการแยกกัน แต่ละรายการยังคงรักษาโอกาสของตัวเองไว้ และรายการจะระบุจำนวนที่ซ้ำกันให้ทราบ แทนที่จะแอบรวมเข้าด้วยกัน เพราะการลดโอกาสของใครบางคนลงครึ่งหนึ่งโดยไม่แจ้งให้ทราบล่วงหน้า ไม่ใช่ความหวังดีที่ใครต้องการ

แล้วแอนิเมชันยังมีไว้เพื่ออะไร

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

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