การสร้างเกม Digital Bingo สำหรับบอร์ด Micro:bit#

Keywords: BBC Micro:bit, Microsoft MakeCode, TypeScript, Wireless Data Communication, 2.4GHz Radio Interface, Event-Driven Programming, Digital Bingo Game


บอร์ดไมโครบิตและการสื่อสารข้อมูลแบบไร้สาย#

บอร์ดไมโครคอนโทรลเลอร์ "ไมโครบิต" (Micro:bit) ทั้งรุ่นแรกและรุ่นที่สอง (v1 & v2) สามารถรับส่งข้อมูลและสื่อสารกันได้แบบไร้สาย (Wireless Data Communication) เนื่องจากใช้ชิปของบริษัท Nordic Semiconductor รุ่น nRF51822 และ nRF52833 ตามลำดับ ภายในชิปมีวงจรและเฟิร์มแวร์สำหรับการสื่อสารข้อมูลไร้สายด้วยคลื่นวิทยุย่านความถี่ 2.4GHz และรองรับโพรโตคอล Bluetooth / BLE ด้วย

บทความนี้นำเสนอตัวอย่างการเขียนโค้ดด้วย MakeCode JavaScript (JS) หรือ Static TypeScript (STS) เพื่อรับส่งข้อมูลระหว่างบอร์ดไมโครบิต 2 บอร์ด และสาธิตการสร้างเกมที่มีชื่อว่า Digital Bingo

ซอฟต์แวร์ที่ใช้คือ เว็บเบราว์เซอร์ เช่น Google Chrome และ Web App ที่มีชื่อว่า MakeCode Editor for Micro:bit (https://makecode.microbit.org/) รองรับการเขียนโค้ดหลายรูปแบบ ทั้งแบบการต่อบล็อก (Block-based Coding) และการเขียนโค้ดด้วยภาษา JavaScript (JS) / Static TypeScript หรือ Python อีกทั้งยังสามารถจำลองการทำงานของโค้ดได้ (MakeCode Micro:bit Simulator) โดยยังไม่จำเป็นต้องใช้งานบอร์ดทดลองจริง

จุดเด่นของซอฟต์แวร์อีกประการหนึ่งคือ ความสามารถในการแปลงให้เป็นโปรแกรมในรูปแบบการต่อบล็อก หรือสลับไปมาระหว่างโหมดการเขียนโค้ดด้วยภาษา JS / Python ได้โดยอัตโนมัติ

รูป: คำสั่งแบ่งตามกลุ่มสำหรับการเขียนโค้ดด้วย MakeCode (https://makecode.microbit.org/reference/)

 


การใช้งาน Multi-Board Editor#

เนื่องจากจะต้องใช้บอร์ดไมโครบิต 2 จำนวนบอร์ด และทำงานพร้อม ๆ กัน ดังนั้นถ้าจะจำลองการทำงานของบอร์ดทั้งสอง จะต้องเปิดใช้งาน Multi-Board Editor (https://makecode.com/multi#)

ผู้ใช้สามารถสร้างโปรเจกต์ (กดปุ่ม "New Project") แยกกันระหว่าง 2 บอร์ดที่จะใช้งาน แล้วเปิดโปรเจกต์ใน Editor ในส่วนการทำงานทางด้านซ้ายมือ และขวามือตามลำดับ

รูป: MakeCode Multi-Board Editor

รูป: การสร้างโปรเจกต์ใหม่ เพื่อใช้กับบอร์ด Bingo Controller

รูป: การสร้างโปรเจกต์ใหม่ เพื่อใช้กับบอร์ด Bingo Player

รูป: การเลือกโหมดการเขียนโค้ดให้เป็น JavaScript (JS) แทนการต่อบล็อก

 


ตัวอย่างโค้ดสาธิต Digital Bingo#

การทำงานของโค้ดตัวอย่างในบทความนี้ มีลักษณะการทำงานดังนี้

  • ใช้บอร์ด Micro:bit จำนวน 2 บอร์ด แบ่งเป็นโค้ดสำหรับ Bingo Controller และ Bingo Player
  • การทำงานของ Bingo Player:
    • เมื่อเริ่มต้นทำงาน ให้สุ่มเลขสำหรับเซตที่มีตัวเลขจำนวนเต็ม จำนวน 25 ตัวเลข อยู่ระหว่าง 0 ถึง 24 และไม่ซ้ำกัน ค่าตัวเลขแต่ละตัว จะถูกนำไปแปลงเป็นพิกัด (x,y) บน LED Matrix ขนาด 5x5 ถ้า คือ ค่าตัวเลข ก็ให้แปลงเป็น (หารแบบเหลือเศษ หรือ Modulo) และ (การหารแบบปัดเศษทิ้ง)
    • รอให้มีการกด Button A หรือ เขย่าบอร์ดหนึ่งครั้ง (Shake) ถ้าเป็นเขย่าบอร์ด ก็จะเริ่มต้นดึงตัวเลขออกมาจากเซตหรือรายการ โดยต่อเนื่องและอัตโนมัติ แล้วส่งไปยังบอร์ด Bingo Player ทีละตัว เว้นระยะเวลา แล้วทำซ้ำไปจนครบตามจำนวนที่ตั้งไว้ เช่น 15 ตัว
    • แต่ถ้าเป็นการกดปุ่ม Button A จะนำค่าตัวเลขในตำแหน่งแรกออกมาใช้เพียงหนึ่งตัว แล้วส่งไปยังบอร์ด Bingo Player ถ้าจะใช้ตัวเลขถัดไป ต้องกดปุ่มอีกครั้ง ไม่ทำโดยต่อเนื่องอัตโนมัติ
    • ถ้าส่งตัวเลขไปจนครบตามจำนวนแล้ว ก็ให้โปรแกรมเริ่มต้นการทำงานใหม่ โดยทำคำสั่งของฟังก์ชัน resetGame() และส่งตัวเลขที่มีค่า -1 ไปยังอีกบอร์ดหนึ่ง เพื่อแจ้งให้ทราบว่า เป็นการเริ่มต้นใหม่
  • การทำงานของ Bingo Player:
    • เมื่อเริ่มต้นทำงาน ให้สุ่มเลขจำนวนสำหรับเซตที่มีตัวเลขจำนวนเต็ม จำนวน 25 ตัวเลข เหมือนกรณีของ Bingo Controller
    • เมื่อได้รับค่าตัวเลข มาแล้วก็ให้ตรวจสอบกับตัวเลขที่มีอยู่ว่า ตรงกับตำแหน่งใด แล้วเปลี่ยนสถานะของ LED ในตำแหน่ง (x,y) ที่เกี่ยวข้องกับตัวเลขดังกล่าว ให้อยู่ในสถานะ ON
    • นอกจากนั้นแล้ว เมื่อได้รับตัวเลขและอัปเดตสถานะของ LED Matrix แล้ว จะต้องมีการตรวจสอบเงื่อนไข สำหรับ BINGO ด้วย กล่าวคือ มี LED อยู่ในสถานะ ON เต็มหนึ่งแถวแนวนอนหรือแนวตั้ง หรือแนวทแยงมุมหรือไม่ ถ้าใช่ แสดงว่า BINGO
    • ในกรณี BINGO จะกระพริบ LED Matrix และส่งข้อความ BINGO ไปยังอีกบอร์ดหนึ่งด้วย ก่อนที่จะรอคำสั่งเริ่มต้นใหม่

ลองดูตัวอย่างของตัวเลขทั้งหมด 25 ตัว ได้จากการสุ่มตัวเลข และแสดงให้อยู่ในรูปของเมทริกซ์ ซ้ายมือสำหรับ Bingo Controller และขวามือสำหรับ Bingo Player ตามลำดับ

ในการเลือกค่าตัวเลขในเมทริกซ์ทางซ้ายมือ จะทำจากแถวบนจากซ้ายไปขวา (ถ้าครบแถวก็ให้ขึ้นแถวใหม่) เลือกมาทีละตัว ไปตามลำดับ แต่จะไม่เกินจำนวนที่กำหนดไว้ เช่น 15

รูป: ตัวอย่างตัวเลขที่ได้จากการสุ่มในสถานะเริ่มต้น (ทางซ้ายมือเป็นของ Bingo Controller ทางขวามือเป็นของ Bingo Player)

รูป: เมื่อเลือกค่าตัวเลขตัวแรกในเมทริกซ์ทางซ้ายมือ คือ 5 ก็จะไปดูว่า เลข 5 อยู่ในตำแหน่งใดของเมทริกซ์ทางขวามือ และตำแหน่งดังกล่าว ก็จะมีสถานะเป็น ON สำหรับ 5x5 LED Matrix บนบอร์ด Bingo Player

รูป: ตัวเลขถัดไปในเมทริกซ์ทางซ้ายมือ คือ 8 และนำไปอัปเดตสถานะของเมทริกซ์ทางขวามือ

รูป: ตัวเลขถัดไปในเมทริกซ์ทางซ้ายมือ คือ 1 และนำไปอัปเดตสถานะของเมทริกซ์ทางขวามือ

รูป: ตัวเลขถัดไปในเมทริกซ์ทางซ้ายมือ คือ 20 และนำไปอัปเดตสถานะของเมทริกซ์ทางขวามือ

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

รูป: ตัวอย่างการจำลองการทำงาน (Simulator Mode)

รูป: โหมดการจำลองการทำงานแบบ Fullscreen

รูป: ตัวอย่างการเกิดกรณี BINGO

Code: Bingo Controller

// Bingo Controller
let numbers: number[] = []
let poppedNumbers: number[] = []
let maxPops = 15 // Choose a number between 10..15
let keepRunning = false 

// Function to display the corresponding LED for the number
function showNumber(num: number) {
    let x = num % 5
    let y = Math.floor(num / 5)
    led.plot(x, y)
}

function showNumbers() {
    basic.clearScreen();
    numbers.forEach(function (num) {
        showNumber(num);
        basic.pause(100); 
    });
    basic.pause(1000); 
    basic.clearScreen();
}

// Function to pop a number from the list
function popNumber() {
    if (numbers.length > 0) {
        let n = numbers.pop()
        poppedNumbers.push(n)
        showNumber(n)
        radio.sendNumber(n)
    } else{
        radio.sendNumber(-1) // Send reset command
    }
}

function resetGame() {
    keepRunning = false
    basic.clearScreen()
    radio.sendNumber(-1)  // Send reset command
    // Generate a new set of unique random numbers from 0 to 24
    numbers = []
    for (let i = 0; i < 25; i++) {
        numbers.insertAt(0,i)
        if (i % 2 == 1) {
            numbers = numbers.sort(() => Math.random() - 0.5)
        }
    }
    poppedNumbers = []
    pause(1000)
    showNumbers();
}

// Button A or shake to pop a number
input.onButtonPressed(Button.A, function () {
    if (poppedNumbers.length < maxPops) {
        popNumber()
    } else {
        resetGame()
    }
})

input.onGesture(Gesture.Shake, function () {
    keepRunning = !keepRunning
})

// Button B to reset the Bingo controller
input.onButtonPressed(Button.B, function () {
    resetGame()
})

radio.onReceivedString( function (receivedString: String) {
   if (receivedString == "BINGO") {
       led.stopAnimation()
       basic.clearScreen()
       pause(500)
       basic.showString("BINGO!")
       pause(1000)
       resetGame()
   }
})

// Set the radio group
radio.setGroup(1)
// Show the let C for Bingo controller
basic.showString('C')
// Initialize the game
resetGame()

basic.forever( function() {
    if (keepRunning) {
        if (poppedNumbers.length < maxPops) {
           popNumber()
           pause(1000)
        } else {
           resetGame()
       }
    }
})

Code: Bingo Player


// Bingo Player
let numbers: number[] = []
let marks: boolean[][] = []
let bingo = false

// Function to display the corresponding LED for the number
function showNumber(num: number) {
    let x = num % 5
    let y = Math.floor(num / 5)
    led.plot(x, y)
}

function showNumbers() {
    basic.clearScreen();
    numbers.forEach(function (num) {
        showNumber(num);
        basic.pause(100);
    });
    basic.clearScreen();
}

// Function to reset or initialize the game
function resetGame() {
    basic.clearScreen()
    marks = []
    bingo = false
    // Initialize the 2D array with false (5x5 grid)
    for (let x = 0; x < 5; x++) {
        marks[x] = []
        for (let y = 0; y < 5; y++) {
            marks[x][y] = false
        }
    }
    numbers = []
    for (let i = 0; i < 25; i++) {
        numbers.insertAt(0, i)
        if (i % 2 == 1) {
            numbers = numbers.sort(() => Math.random() - 0.5)
        }
    }
    showNumbers();
}

// Function to check for Bingo
function checkBingo(): boolean {
    let complete: boolean
    // Check rows for Bingo
    for (let y = 0; y < 5; y++) {
        complete = true
        for (let x = 0; x < 5; x++) {
            if (!marks[x][y]) {
                complete = false
                break
            }
        }
        if (complete) {
            return true
        }
    }
    // Check columns for Bingo
    for (let x = 0; x < 5; x++) {
        complete = true
        for (let y = 0; y < 5; y++) {
            if (!marks[x][y]) {
                complete = false
                break
            }
        }
        if (complete) {
            return true
        }
    }
    // Check diagonals for Bingo
    let diag1Complete = true
    let diag2Complete = true
    for (let i = 0; i < 5; i++) {
        // Check top-left to bottom-right diagonal
        if (!marks[i][i]) {
            diag1Complete = false
        }
        // Check top-right to bottom-left diagonal
        if (!marks[4 - i][i]) {
            diag2Complete = false
        }
    }
    // Return true if any diagonal is complete
    if (diag1Complete || diag2Complete) {
        return true
    }
    // No Bingo found
    return false
}

// Listen for numbers from the Bingo Controller
radio.onReceivedNumber(function (n: number) {
    if (n == -1) { // reset command received
        resetGame()
    }
    else if (numbers.indexOf(n) !== -1) {
        let index = numbers.indexOf(n)
        let x = index % 5
        let y = Math.floor(index / 5)
        marks[x][y] = true  // Mark the number as found
        led.plot(x, y)  // Light the corresponding LED
        // Check for Bingo
        if (checkBingo() && !bingo) {
            bingo = true
            radio.sendString("BINGO")
            for ( let i=0; i < 5; i++) {
               led.setBrightness(0)
               pause(250)
               led.setBrightness(255)
               pause(250)
            }
            basic.clearScreen()
            basic.showString("BINGO!")
       }
    } 
})

radio.onReceivedString(function (receivedString: String) {
    if (!bingo && receivedString == "BINGO") {
        basic.clearScreen()
        resetGame()
    }
})

// Set the radio group
radio.setGroup(1)
// Show the letter 'P' for Bingo player
basic.showString('P')
// Reset or initialize the game
resetGame()

 

การทดสอบการทำงานกับบอร์ดไมโครบิต#

หากต้องการจะทดสอบการทำงานของโค้ดตัวอย่าง จะต้องมีบอร์ดไมโครบิตอย่างน้อย 2 บอร์ด และดาวน์โหลดโปรแกรมสำหรับ Bingo Controller และ Bingo Player แยกกัน แต่ถ้ามีมากกว่า 2 บอร์ด ก็เพิ่มจำนวน Bingo Players ได้

รูป: ตัวอย่างการจับคู่ (Device Pairing) เมื่อเชื่อมต่อคอมพิวเตอร์กับบอร์ดไมโครบิต ผ่านทางพอร์ต USB

รูป: การดาวน์โหลดไฟล์โปรแกรมไปยังบอร์ดไมโครบิต

รูป: บอร์ดไมโครบิต แสดงตัวอักษร P ซึ่งหมายถึง BINGO Player

รูป: บอร์ดไมโครบิตจำนวน 2 บอร์ด แสดงสถานะ ON บนแผง LED Matrix (5x5) ซึ่งตรงกับตัวเลขที่มีการสุ่มเลือกมาได้ 3 ตัว ในขณะนั้น

รูป: เมื่อได้การสุ่มเลือกตัวเลขมา 12 ตัว แต่ยังไม่ตรงกับเงื่อนไข BINGO

 


กล่าวสรุป#

บทความนี้ได้นำเสนอ ตัวอย่างการเขียนโค้ดเพื่อสร้างเกมที่มีชื่อว่า Digital Bingo เพื่อนำไปทดลองใช้งานกับบอร์ด Micro:bit ซึ่งจะต้องมีอย่างน้อย 2 บอร์ด และเขียนโค้ดด้วยภาษา MakeCode JavaScript (JS)

บทความที่เกี่ยวข้อง

 


This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.

Created: 2024-09-28 | Last Updated: 2024-10-01