0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

micro:bitのセンシングデータを用いたテトリス風ゲーム

Posted at

概要

micro:bitとPCをBluetoothで接続し、取得したセンシングデータを用いて、JavaScript上のテトリス風ゲームをコントロールします.

使用するセンシングデータと役割

  1. 加速度センサー
    • x軸
      左右に傾けると、ブロックが左右に移動.
    • y軸
      奥に傾けると、ブロックが90°回転.
  2. 温度センサー
    温度に応じて、ブロックの落下速度を調節.
  3. 照度センサー
    周囲の明るさに応じて、ゲーム画面の明るさを調節.
  4. (Bボタン)
    Bボタンを押すと、ブロックを落下.

micro:bit(MakeCode)のプログラム

micro:bitのプログラムは、センシングデータをBletoothで送信するだけですので、非常にシンプルです.

microbit-bluetooth-sensor.hex
micro:bitプログラムのJavaScript版はこちら
bit-makecode.js
bluetooth.onBluetoothConnected(function () {
    basic.showIcon(IconNames.Yes)
})
bluetooth.onBluetoothDisconnected(function () {
    basic.showIcon(IconNames.No)
})
// Bボタンの状態
let bButtonState = 0
let temperature = 0
let lightLevel = 0
let accZ = 0
let accY = 0
let accX = 0
bluetooth.startUartService()
bluetooth.startAccelerometerService()
bluetooth.startTemperatureService()
bluetooth.startLEDService()
basic.forever(function () {
    // 加速度センサーのデータ
    accX = input.acceleration(Dimension.X)
    accY = input.acceleration(Dimension.Y)
    accZ = input.acceleration(Dimension.Z)
    // 光センサーのデータ
    lightLevel = input.lightLevel()
    // 温度センサーのデータ
    temperature = input.temperature()
    // Bボタンの状態
    if (input.buttonIsPressed(Button.B)) {
        // Bボタンが押されたと認識
        bButtonState = 1
    } else {
        // Bボタンが離された状態
        bButtonState = 0
    }
    // センサーのデータを送信
    bluetooth.uartWriteString(`a:${accX},${accY},${accZ}`)
    basic.pause(100)
    bluetooth.uartWriteString(`t:${temperature};l:${lightLevel};b:${bButtonState}`)
    basic.pause(100)
})

micro:bitとPCをBluetoothで接続

通常のWeb Bluetooth APIではセンシングデータが取得できなかったため、ラッパーライブラリ "BlueJelly" を使用しました.
3年前にBlueJellyをmicro:bit用に改良していましたので、今回はそちらを使用しました.

BlueJelly.jsのコード
bluejelly.js
/*
============================================================
BlueJelly.js
============================================================
Web Bluetooth API Wrapper Library

Copyright 2017-2020 JellyWare Inc.
https://monomonotech.jp/

GitHub
https://github.com/electricbaka/bluejelly
This software is released under the MIT License.

Web Bluetooth API
https://webbluetoothcg.github.io/web-bluetooth/
*/

//--------------------------------------------------
//BlueJelly constructor
//--------------------------------------------------
var BlueJelly = function(){
  this.bluetoothDevice = null;
  this.dataCharacteristic = null;
  this.hashUUID ={};
  this.hashUUID_lastConnected;

  //callBack
  this.onScan = function(deviceName){console.log("onScan");};
  this.onConnectGATT = function(uuid){console.log("onConnectGATT");};
  this.onRead = function(data, uuid){console.log("onRead");};
  this.onWrite = function(uuid){console.log("onWrite");};
  this.onStartNotify = function(uuid){console.log("onStartNotify");};
  this.onStopNotify = function(uuid){console.log("onStopNotify");};
  this.onDisconnect = function(){console.log("onDisconnect");};
  this.onClear = function(){console.log("onClear");};
  this.onReset = function(){console.log("onReset");};
  this.onError = function(error){console.log("onError");};
}


//--------------------------------------------------
//setUUID
//--------------------------------------------------
BlueJelly.prototype.setUUID = function(name, serviceUUID, characteristicUUID){
  console.log('Execute : setUUID');
  console.log(this.hashUUID);

  this.hashUUID[name] = {'serviceUUID':serviceUUID, 'characteristicUUID':characteristicUUID};
}


//--------------------------------------------------
//scan
//--------------------------------------------------
BlueJelly.prototype.scan = function(uuid){
  return (this.bluetoothDevice ? Promise.resolve() : this.requestDevice(uuid))
  .catch(error => {
    console.log('Error : ' + error);
    this.onError(error);
  });
}


//--------------------------------------------------
//requestDevice
//--------------------------------------------------
BlueJelly.prototype.requestDevice = function(uuid) {
  console.log('Execute : requestDevice');
  return navigator.bluetooth.requestDevice({
      acceptAllDevices: true,
      optionalServices: [this.hashUUID[uuid].serviceUUID]})
  .then(device => {
    this.bluetoothDevice = device;
    this.bluetoothDevice.addEventListener('gattserverdisconnected', this.onDisconnect);
    this.onScan(this.bluetoothDevice.name);
  });
}


//--------------------------------------------------
//connectGATT
//--------------------------------------------------
BlueJelly.prototype.connectGATT = function(uuid) {
  if(!this.bluetoothDevice)
  {
    var error = "No Bluetooth Device";
    console.log('Error : ' + error);
    this.onError(error);
    return;
  }
  if (this.bluetoothDevice.gatt.connected && this.dataCharacteristic) {
    if(this.hashUUID_lastConnected == uuid)
      return Promise.resolve();
  }
  this.hashUUID_lastConnected = uuid;

  console.log('Execute : connect');
  return this.bluetoothDevice.gatt.connect()
  .then(server => {
    console.log('Execute : getPrimaryService');
    return server.getPrimaryService(this.hashUUID[uuid].serviceUUID);
  })
  .then(service => {
    console.log('Execute : getCharacteristic');
    return service.getCharacteristic(this.hashUUID[uuid].characteristicUUID);
  })
  .then(characteristic => {
    this.dataCharacteristic = characteristic;
    this.dataCharacteristic.addEventListener('characteristicvaluechanged',this.dataChanged(this, uuid));
    this.onConnectGATT(uuid);
  })
  .catch(error => {
      console.log('Error : ' + error);
      this.onError(error);
    });
}


//--------------------------------------------------
//dataChanged
//--------------------------------------------------
BlueJelly.prototype.dataChanged = function(self, uuid) {
  return function(event) {
    self.onRead(event.target.value, uuid);
  }
}


//--------------------------------------------------
//read
//--------------------------------------------------
BlueJelly.prototype.read= function(uuid) {
  return (this.scan(uuid))
  .then( () => {
    return this.connectGATT(uuid);
  })
  .then( () => {
    console.log('Execute : readValue');
    return this.dataCharacteristic.readValue();
  })
  .catch(error => {
    console.log('Error : ' + error);
    this.onError(error);
  });
}


//--------------------------------------------------
//write
//--------------------------------------------------
BlueJelly.prototype.write = function(uuid, array_value) {
  return (this.scan(uuid))
  .then( () => {
    return this.connectGATT(uuid);
  })
  .then( () => {
    console.log('Execute : writeValue');
    data = Uint8Array.from(array_value);
    return this.dataCharacteristic.writeValue(data);
  })
  .then( () => {
    this.onWrite(uuid);
  })
  .catch(error => {
    console.log('Error : ' + error);
    this.onError(error);
  });
}


//--------------------------------------------------
//startNotify
//--------------------------------------------------
BlueJelly.prototype.startNotify = function(uuid) {
  return (this.scan(uuid))
  .then( () => {
    return this.connectGATT(uuid);
  })
  .then( () => {
    console.log('Execute : startNotifications');
    this.dataCharacteristic.startNotifications()
  })
  .then( () => {
    this.onStartNotify(uuid);
  })
  .catch(error => {
    console.log('Error : ' + error);
    this.onError(error);
  });
}


//--------------------------------------------------
//stopNotify
//--------------------------------------------------
BlueJelly.prototype.stopNotify = function(uuid){
  return (this.scan(uuid))
  .then( () => {
    return this.connectGATT(uuid);
  })
  .then( () => {
  console.log('Execute : stopNotifications');
  this.dataCharacteristic.stopNotifications()
})
  .then( () => {
    this.onStopNotify(uuid);
  })
  .catch(error => {
    console.log('Error : ' + error);
    this.onError(error);
  });
}


//--------------------------------------------------
//disconnect
//--------------------------------------------------
BlueJelly.prototype.disconnect= function() {
  if (!this.bluetoothDevice) {
    var error = "No Bluetooth Device";
    console.log('Error : ' + error);
    this.onError(error);
    return;
  }

  if (this.bluetoothDevice.gatt.connected) {
    console.log('Execute : disconnect');
    this.bluetoothDevice.gatt.disconnect();
  } else {
   var error = "Bluetooth Device is already disconnected";
   console.log('Error : ' + error);
   this.onError(error);
   return;
  }
}


//--------------------------------------------------
//clear
//--------------------------------------------------
BlueJelly.prototype.clear= function() {
   console.log('Excute : Clear Device and Characteristic');
   this.bluetoothDevice = null;
   this.dataCharacteristic = null;
   this.onClear();
}


//--------------------------------------------------
//reset(disconnect & clear)
//--------------------------------------------------
BlueJelly.prototype.reset= function() {
  console.log('Excute : reset');
  this.disconnect(); //disconnect() is not Promise Object
  this.clear();
  this.onReset();
}


//--------------------------------------------------
//micro:bit UUID(class constant)
//--------------------------------------------------
Object.defineProperty(BlueJelly, 'MICROBIT_BASE_UUID', {value: "e95d0000-251d-470a-a062-fa1922dfa9a8", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_SERVICESGENERIC_ACCESS', {value: "00001800-0000-1000-8000-00805f9b34fb", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_DEVICE_NAME', {value: "00002a00-0000-1000-8000-00805f9b34fb", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_APPEARANCE', {value: "00002a01-0000-1000-8000-00805f9b34fb", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_PERIPHERAL_PREFERRED_CONNECTION_PARAMETERS', {value: "00002a04-0000-1000-8000-00805f9b34fb", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_GENERIC_ATTRIBUTE', {value: "00001801-0000-1000-8000-00805f9b34fb", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_SERVICE_CHANGED', {value: "2a05", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_DEVICE_INFORMATION', {value: "0000180a-0000-1000-8000-00805f9b34fb", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_MODEL_NUMBER_STRING', {value: "00002a24-0000-1000-8000-00805f9b34fb", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_SERIAL_NUMBER_STRING', {value: "00002a25-0000-1000-8000-00805f9b34fb", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_HARDWARE_REVISION_STRING', {value: "00002a27-0000-1000-8000-00805f9b34fb", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_FIRMWARE_REVISION_STRING', {value: "00002a26-0000-1000-8000-00805f9b34fb", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_MANUFACTURER_NAME_STRING', {value: "00002a29-0000-1000-8000-00805f9b34fb", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_ACCELEROMETER_SERVICE', {value: "e95d0753-251d-470a-a062-fa1922dfa9a8", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_ACCELEROMETER_DATA', {value: "e95dca4b-251d-470a-a062-fa1922dfa9a8", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_ACCELEROMETER_PERIOD', {value: "e95dfb24-251d-470a-a062-fa1922dfa9a8", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_MAGNETOMETER_SERVICE', {value: "e95df2d8-251d-470a-a062-fa1922dfa9a8", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_MAGNETOMETER_DATA', {value: "e95dfb11-251d-470a-a062-fa1922dfa9a8", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_MAGNETOMETER_PERIOD', {value: "e95d386c-251d-470a-a062-fa1922dfa9a8", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_MAGNETOMETER_BEARING', {value: "e95d9715-251d-470a-a062-fa1922dfa9a8", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_MAGNETOMETER_CALIBRATION', {value: "e95db358-251d-470a-a062-fa1922dfa9a8", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_BUTTON_SERVICE', {value: "e95d9882-251d-470a-a062-fa1922dfa9a8", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_BUTTON_A_STATE', {value: "e95dda90-251d-470a-a062-fa1922dfa9a8", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_BUTTON_B_STATE', {value: "e95dda91-251d-470a-a062-fa1922dfa9a8", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_IO_PIN_SERVICE', {value: "e95d127b-251d-470a-a062-fa1922dfa9a8", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_PIN_DATA', {value: "e95d8d00-251d-470a-a062-fa1922dfa9a8", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_PIN_AD_CONFIGURATION', {value: "e95d5899-251d-470a-a062-fa1922dfa9a8", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_PIN_IO_CONFIGURATION', {value: "e95db9fe-251d-470a-a062-fa1922dfa9a8", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_PWM_CONTROL', {value: "e95dd822-251d-470a-a062-fa1922dfa9a8", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_LED_SERVICE', {value: "e95dd91d-251d-470a-a062-fa1922dfa9a8", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_LED_MATRIX_STATE', {value: "e95d7b77-251d-470a-a062-fa1922dfa9a8", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_LED_TEXT', {value: "e95d93ee-251d-470a-a062-fa1922dfa9a8", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_SCROLLING_DELAY', {value: "e95d0d2d-251d-470a-a062-fa1922dfa9a8", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_EVENT_SERVICE', {value: "e95d93af-251d-470a-a062-fa1922dfa9a8", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_MICROBIT_REQUIREMENTS', {value: "e95db84c-251d-470a-a062-fa1922dfa9a8", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_MICROBIT_EVENT', {value: "e95d9775-251d-470a-a062-fa1922dfa9a8", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_CLIENT_REQUIREMENTS', {value: "e95d23c4-251d-470a-a062-fa1922dfa9a8", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_CLIENT_EVENT', {value: "e95d5404-251d-470a-a062-fa1922dfa9a8", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_DFU_CONTROL_SERVICE', {value: "e95d93b0-251d-470a-a062-fa1922dfa9a8", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_DFU_CONTROL', {value: "e95d93b1-251d-470a-a062-fa1922dfa9a8", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_TEMPERATURE_SERVICE', {value: "e95d6100-251d-470a-a062-fa1922dfa9a8", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_TEMPERATURE', {value: "e95d9250-251d-470a-a062-fa1922dfa9a8", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_TEMPERATURE_PERIOD', {value: "e95d1b25-251d-470a-a062-fa1922dfa9a8", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_UART_SERVICE', {value: "6e400001-b5a3-f393-e0a9-e50e24dcca9e", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_TX_CHARACTERISTIC', {value: "6e400002-b5a3-f393-e0a9-e50e24dcca9e", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_RX_CHARACTERISTIC', {value: "6e400003-b5a3-f393-e0a9-e50e24dcca9e", writable: false});

センシングデータ 受信確認プログラム (HTML/JavaScript)

加速度・照度・温度センサーの値が正しく取得できているかを確認します.

BlueJelly.jsを同じディレクトリに置く必要があります.

受信確認用のHTML/JavaScriptのコード
data-recive-test.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>micro:bit Sensor Data</title>
    <script src="bluejelly.js"></script> <!-- BlueJelly ライブラリの読み込み -->
    
    <style>
        #sensorData {
            font-size: 20px;
            margin-top: 20px;
        }
        #dataList {
            font-size: 16px;
            margin-top: 20px;
            max-height: 200px;
            overflow-y: scroll;
        }
    </style>
</head>
<body>
    <h1>micro:bit 取得データ表示</h1>

    <button id="startButton">micro:bit 接続</button>

    <div id="sensorData">
        <p><strong>加速度 (X, Y, Z): </strong><span id="accData">0, 0, 0</span></p>
        <p><strong>温度: </strong><span id="tempData">0</span></p>
        <p><strong>明るさ: </strong><span id="lightData">0</span></p>
    </div>

    <div id="dataList">
        <ul id="sensorDataList"></ul>
    </div>

    <script>
        const blueJelly = new BlueJelly();

        // UUIDの設定(UARTサービスを利用)
        const serviceUUID = '6e400001-b5a3-f393-e0a9-e50e24dcca9e'; // UART Service
        const characteristicUUID = '6e400002-b5a3-f393-e0a9-e50e24dcca9e'; // TX Characteristic

        let characteristic;

        // ボタンが押されたときにBluetoothデバイスを接続
        document.getElementById('startButton').addEventListener('click', async () => {
            try {
                // デバイスをスキャンして接続
                const device = await navigator.bluetooth.requestDevice({
                    filters: [{ namePrefix: 'BBC micro:bit' }], // 名前が "BBC micro:bit" を含むデバイスに限定
                    optionalServices: [serviceUUID]
                });
                console.log('Device selected:', device);

                const server = await device.gatt.connect();
                console.log('Connected to GATT server');

                const service = await server.getPrimaryService(serviceUUID);
                console.log('Service found');

                characteristic = await service.getCharacteristic(characteristicUUID);
                console.log('Characteristic found');

                // 通知の開始
                await characteristic.startNotifications();
                console.log('Notifications started');

                // データ受信時のイベントリスナーを設定
                characteristic.addEventListener('characteristicvaluechanged', event => {
                    const value = event.target.value; // DataView
                    const decoder = new TextDecoder('utf-8');
                    const decodedString = decoder.decode(value); // UTF-8デコード
                    console.log(decodedString);

                    // リストに追加
                    //const dataList = document.getElementById('sensorDataList');
                    //const newListItem = document.createElement('li');
                    //newListItem.textContent = `${decodedString}`;
                    //dataList.appendChild(newListItem);

                    // データ解析
                    const accMatch = decodedString.match(/a:([\d\-,]+)/);
                    const tempMatch = decodedString.match(/t:(\d+)/);
                    const lightMatch = decodedString.match(/l:(\d+)/);

                    if (accMatch) {
                        const accData = accMatch[1].split(',');
                        document.getElementById('accData').textContent = `X: ${accData[0]}, Y: ${accData[1]}, Z: ${accData[2]}`;
                    }

                    if (tempMatch) {
                        document.getElementById('tempData').textContent = tempMatch[1];
                    }

                    if (lightMatch) {
                        document.getElementById('lightData').textContent = lightMatch[1];
                    }
                });
            } catch (error) {
                console.error('Connection Error:', error);
            }
        });
    </script>
</body>
</html>

センシングデータを用いたテトリス風ゲーム

センシングデータを用いたテトリス風ゲームのHTML/JavaScriptのコード
sensor-data-tetris.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>micro:bit Tetris</title>
    <script src="bluejelly.js"></script>
    <style>
        body {
            margin: 0;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            background-color: rgba(0, 0, 0, 1); /* 初期の背景色 */
            transition: background-color 0.5s;
        }
        canvas {
            display: block;
            margin: 20px auto;
            border: 5px solid white; /* 枠線を追加 */
            border-radius: 10px;
        }
        button {
            font-size: 18px;
            padding: 10px 20px;
            margin-top: 20px;
        }
    </style>
</head>
<body>
    <button id="connectButton">micro:bit 接続</button>
    <canvas id="gameCanvas" width="300" height="600"></canvas>

    <script>
        const blueJelly = new BlueJelly();
        const serviceUUID = '6e400001-b5a3-f393-e0a9-e50e24dcca9e'; // UART Service
        const characteristicUUID = '6e400002-b5a3-f393-e0a9-e50e24dcca9e'; // TX Characteristic

        let characteristic;
        let gameStarted = false;
        let currentBlock;
        let board = Array.from({ length: 20 }, () => Array(10).fill(0)); // 横10列
        let fallSpeed = 1000; // ms
        let lastFallTime = Date.now();
        let lastRotationTime = 0; // 回転のクールダウン管理
        const rotationCooldown = 1000; // 回転のクールダウン時間 (ms)
        let brightness = 1; // 背景の明るさ
        const canvas = document.getElementById("gameCanvas");
        const ctx = canvas.getContext("2d");

        // ブロックの形状パターン(7種類)
        const blockShapes = [
            [[1, 1], [1, 1]], // 正方形
            [[0, 1, 0], [1, 1, 1]], // T字
            [[1, 1, 0], [0, 1, 1]], // Z字
            [[0, 1, 1], [1, 1, 0]], // 逆Z字
            [[1, 0], [1, 0], [1, 1]], // L字
            [[0, 0, 1], [1, 1, 1]], // 逆L字
            [[1, 1, 1, 1]], // 長方形
        ];

        let bButtonPressed = false; // Bボタンの状態
        let lastBPressTime = 0; // 最後にBボタンが押された時間

        document.getElementById('connectButton').addEventListener('click', async () => {
            try {
                const device = await navigator.bluetooth.requestDevice({
                    filters: [{ namePrefix: 'BBC micro:bit' }],
                    optionalServices: [serviceUUID]
                });
                const server = await device.gatt.connect();
                const service = await server.getPrimaryService(serviceUUID);
                characteristic = await service.getCharacteristic(characteristicUUID);

                await characteristic.startNotifications();
                characteristic.addEventListener('characteristicvaluechanged', handleSensorData);

                document.getElementById('connectButton').style.display = 'none'; // 接続ボタンを非表示
                gameStarted = true; // ゲームを開始
                startGame();
            } catch (error) {
                console.error('Connection Error:', error);
            }
        });

        function handleSensorData(event) {
            const value = event.target.value;
            const decoder = new TextDecoder('utf-8');
            const decodedString = decoder.decode(value);

            const accMatch = decodedString.match(/a:([\d\-,]+)/);
            const tempMatch = decodedString.match(/t:(\d+)/);
            const lightMatch = decodedString.match(/l:(\d+)/);
            const buttonBMatch = decodedString.match(/b:(\d+)/); // Bボタンの入力

            // 加速度センサーのデータ
            if (accMatch) {
                const accData = accMatch[1].split(',');
                const tiltX = parseInt(accData[0]) / 2; // 感度調整
                const tiltY = parseInt(accData[1]) / 2; // 感度調整

                if (tiltX > 100 && canMove(currentBlock, 1, 0)) currentBlock.x++; // 右移動
                if (tiltX < -100 && canMove(currentBlock, -1, 0)) currentBlock.x--; // 左移動

                // 手前に傾けた際の回転
                const now = Date.now();
                if (tiltY > 200 && now - lastRotationTime > rotationCooldown) {
                    rotateBlock(currentBlock);
                    lastRotationTime = now; // 回転時間を更新
                }
            }

            // 温度センサーで落下速度調整
            if (tempMatch) {
                const temperature = parseInt(tempMatch[1]);
                fallSpeed = Math.max(200, 1000 - (temperature - 20) * 250); // 温度で落下速度調整
            }

            // 明るさ調整
            if (lightMatch) {
                const lightLevel = parseInt(lightMatch[1]);
                brightness = Math.min(1, lightLevel / 255); // 明るさを計算
                document.body.style.backgroundColor = `rgba(0, 0, 0, ${1 - brightness})`; // 背景色に適用
            }

            // Bボタンが押された時の処理
            if (buttonBMatch && buttonBMatch[1] === "1") {
                const now = Date.now();
                if (!bButtonPressed || now - lastBPressTime > 1000) { // 1秒間のクールダウン
                    bButtonPressed = true;
                    lastBPressTime = now;
                    dropBlockImmediately();
                }
            } else {
                bButtonPressed = false; // ボタンが離された場合はリセット
            }
        }

        function rotateBlock(block) {
            const newShape = block.shape[0].map((_, index) =>
                block.shape.map(row => row[index]).reverse()
            );
            if (canMove({ ...block, shape: newShape }, 0, 0)) block.shape = newShape; // 衝突がない場合のみ回転
        }

        function dropBlockImmediately() {
            while (canMove(currentBlock, 0, 1)) {
                currentBlock.y++;
            }
            fixBlockToBoard(currentBlock);
            clearLines();
            currentBlock = generateBlock();
        }

        function canMove(block, dx, dy) {
            return block.shape.every((row, y) =>
                row.every((value, x) => {
                    if (!value) return true; // 空白部分は無視
                    const newX = block.x + x + dx;
                    const newY = block.y + y + dy;
                    return newX >= 0 && newX < 10 && newY < 20 && board[newY]?.[newX] === 0;
                })
            );
        }

        function fixBlockToBoard(block) {
            block.shape.forEach((row, y) => {
                row.forEach((value, x) => {
                    if (value) board[block.y + y][block.x + x] = value;
                });
            });
        }

        function clearLines() {
            board = board.filter(row => row.some(cell => cell === 0)); // 空白の行のみ残す
            while (board.length < 20) board.unshift(Array(10).fill(0)); // 上に空白行を追加
        }

        function generateBlock() {
            const shape = blockShapes[Math.floor(Math.random() * blockShapes.length)];
            return { x: Math.floor((10 - shape[0].length) / 2), y: 0, shape }; // 初期位置調整
        }

        function drawBoard() {
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            for (let y = 0; y < 20; y++) {
                for (let x = 0; x < 10; x++) {
                    if (board[y][x]) {
                        ctx.fillStyle = "blue";
                        ctx.fillRect(x * 30, y * 30, 30, 30);
                    }
                }
            }
        }

        function drawBlock(block) {
            ctx.fillStyle = "red";
            block.shape.forEach((row, dy) => {
                row.forEach((value, dx) => {
                    if (value) {
                        ctx.fillRect((block.x + dx) * 30, (block.y + dy) * 30, 30, 30);
                    }
                });
            });
        }

        function updateGame() {
            if (!gameStarted) return;

            // ゲームオーバーチェック
            if (board[0].some(cell => cell !== 0)) {
                alert("Game Over!");
                gameStarted = false;
                return;
            }

            const now = Date.now();
            if (now - lastFallTime > fallSpeed) {
                if (canMove(currentBlock, 0, 1)) {
                    currentBlock.y++;
                } else {
                    fixBlockToBoard(currentBlock);
                    clearLines();
                    currentBlock = generateBlock();
                }
                lastFallTime = now;
            }

            drawBoard();
            drawBlock(currentBlock);
            requestAnimationFrame(updateGame);
        }

        function startGame() {
            currentBlock = generateBlock();
            updateGame();
        }
    </script>
</body>
</html>

こちらからプレイできます.↓

工夫した点

  • ブロックの回転
    micro:bitを手前に傾けるとブロックが90°回転しますが、1度傾けると2回連続で回転してしまったため、1度傾けられたら1秒間は反応しないようにしました.
  • 文字列でのセンシングデータ送信
    micro:bitがBluetoothで送信できる文字列には上限があるようで、全てのセンシングデータを一度に送信することはできなかったため、2度に分けて送信するようにしました.

改善したかった点

温度によってブロックの落下スピードが変化するようにしておりますが、ゲームスタート時の温度を基準にして、micro:bitで操作しきれる範囲での調整にすれば良かったです.
(micro:bitは大学に返却してしまい、実機でのテストができないため、どうにもできず...)

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?