7
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【エモい】名前わかんないけど"10秒ピッタリに止めるあのゲーム"がやりたい⏱

Last updated at Posted at 2024-07-23

"10秒ピッタリに止めるあのゲーム"とは?

言わずもがな、皆さんは小学生の頃ストップウォッチを使って "10秒ピッタリに止めるあのゲーム" をやったことありますよね??

念のため説明しておくと、ストップウォッチのスタートボタンを押してから、画面を見ずに10秒ピッタリにストップできた人が勝ちのゲームです。

誰が考えて誰が広めたのか分かりませんが、おそらく全国、全世代共通ですよね??

あのころのエモい感覚を再び思い出したい!
ということで今回はRaspberry Pi Pico Wを使ってゲームの再現、アレンジをしていきます!

Raspberry Pi Picoには無印タイプW付きタイプがありますが、無線(Wireless)機能が付くかどうかだけの違いです。
今回はどちらのタイプでも問題ありません。

ゲームの要件は?

簡単ではありますが、ゲームの要件は以下のようになるかと思います。
フロー図はClaude 3.5 SonnetのArtifacts機能を使用して作成しました。

今回、ボタンはどこかで拾った詳細不明のタクトスイッチフィードバックは砲弾型LEDを使用します。

フィードバックはWebアプリやエディタ上に表示すればよいではないかと思うかもしれませんが、愛用しているobnizにはないスタンドアロンで動作できる点を活かすためにあえてLEDを使用しています。

まだRaspberry Pi PicoもしくはRaspberry Pi Pico Wを使ったことがないという方は、以下の記事からまず 環境構築およびLチカ をやってみてください。
少々古いですが、分かりやすいハンズオン記事です。

デモ

計測タイムが

  • 10秒±0.1秒であればスペシャル点滅パターン
  • 10秒±0.3秒であれば青色点滅パターン
  • 10秒±1秒であれば黄色点滅パターン
  • それ以外であれば赤色点滅パターン
    となります。

スタンドアロン動作のためPC接続せずとも、モバイルバッテリーからの給電でゲームを楽しむことができます。

開発環境

  • Thonny (Python用の無料オープンソース統合開発環境)

使用したもの

  • Raspberry Pi Pico W(ラズベリーパイ財団)
  • 抵抗内蔵 砲弾型 青色LED (OptoSupply社製 OSB5SA5B64A)
  • 抵抗内蔵 砲弾型 黄色LED(OptoSupply社製 OSR6LU5B64A)
  • 抵抗内蔵 砲弾型 赤色LED(OptoSupply社製 OSY5LU5B64A)
  • タクトスイッチ(詳細不明)
  • 圧電スピーカー (株式会社村田製作所 PKM13EPYH4000-A0)
  • ミニブレッドボード×2 (Cixi Wanjie Electronic社製 BB-601(白))
  • ジャンパーワイヤ(Cixi Wanjie Electronic社製 BBJ-20)

制作物

ポート表(使用したポートのみ抜粋)

Port デバイス 機能
GPIO13 青色LED(アノード側) 出力
GPIO14 黄色LED(アノード側) 出力
GPIO15 赤色LED(アノード側) 出力
GPIO16 タクトスイッチ(VCC側) 入力(内蔵プルアップ)
GPIO18 圧電スピーカー(VCC側) 出力
GND 各LEDカソード側、タクトスイッチ(GND側)、圧電スピーカー(GND側) -

今回のソースコード

←の三角マークを押すと確認することができます。
main.py
from machine import Pin, PWM, Timer
import time

# GPIOピンの設定
led_red = Pin(15, Pin.OUT)
led_yellow = Pin(14, Pin.OUT)
led_blue = Pin(13, Pin.OUT)
button = Pin(16, Pin.IN, Pin.PULL_UP)
speaker = PWM(Pin(18))
led_onboard = Pin('LED', Pin.OUT)

# グローバル変数
button_pressed = False
last_press_time = 0
DEBOUNCE_TIME = 50  # デバウンス時間(ミリ秒)

def turn_off_leds():
    led_red.value(0)
    led_yellow.value(0)
    led_blue.value(0)

def turn_on_leds():
    led_red.value(1)
    led_yellow.value(1)
    led_blue.value(1)

def blink_led_with_sound(led, times, interval, freq):
    for _ in range(times):
        led.value(1)
        beep(freq, interval)
        led.value(0)
        time.sleep(interval)

def beep(frequency, duration):
    speaker.freq(frequency)
    speaker.duty_u16(32768)
    time.sleep(duration)
    speaker.duty_u16(0)

def special_blink():
    leds = [led_blue, led_yellow, led_red]
    freqs = [523, 587, 659]
    for _ in range(2):
        for led, freq in zip(leds, freqs):
            blink_led_with_sound(led, 2, 0.1, freq)
    for _ in range(3):
        turn_on_leds()
        beep(784, 0.1)
        turn_off_leds()
        time.sleep(0.1)

def button_handler(pin):
    global button_pressed, last_press_time
    current_time = time.ticks_ms()
    if time.ticks_diff(current_time, last_press_time) > DEBOUNCE_TIME:
        button_pressed = True
        last_press_time = current_time

def wait_for_button_press():
    global button_pressed
    led_onboard.value(1)
    button_pressed = False
    while not button_pressed:
        led_onboard.toggle()
        time.sleep(1)
    led_onboard.value(0)
    time.sleep(0.1)  # ボタンを離すのを待つ

def game():
    global button_pressed
    
    while True:
        turn_off_leds()
        
        print("タクトスイッチを押してゲームを開始してください。")
        wait_for_button_press()
        
        print("ゲーム開始!")
        for _ in range(3):
            turn_on_leds()
            beep(440, 0.1)
            turn_off_leds()
            time.sleep(0.1)
        
        print("10秒経ったと思ったらもう一度タクトスイッチを押してください。")
        start_time = time.ticks_ms()
        
        button_pressed = False
        while not button_pressed:
            time.sleep(0.01)
        
        end_time = time.ticks_ms()
        elapsed_time = (end_time - start_time) / 1000
        
        print(f"経過時間: {elapsed_time:.3f}")
        
        if abs(elapsed_time - 10) <= 0.1:
            print("素晴らしい!ぴったり10秒です!")
            special_blink()
        elif 9.7 <= elapsed_time <= 10.3:
            print("惜しい!もう少しで10秒でした。")
            blink_led_with_sound(led_blue, 5, 0.2, 659)
        elif 9 <= elapsed_time <= 11:
            print("まあまあです。もう少し頑張りましょう。")
            blink_led_with_sound(led_yellow, 5, 0.2, 587)
        else:
            print("残念!もっと練習しましょう。")
            blink_led_with_sound(led_red, 5, 0.2, 523)
        
        time.sleep(1)

# 割り込み設定
button.irq(trigger=Pin.IRQ_FALLING, handler=button_handler)

# メインループ
while True:
    game()

ファイル名はmain.pyに!

ソースコードができて、Thonny上からの動作は確認することができました。
ただ、ソースファイルをラズパイPicoに書き込んでスタンドアロン動作させようと思うと動かない。。。

原因はファイル名をmain.pyにしていないことでした。
ラズパイPicoに電源を投入するとmain.pyのファイルを読みに行くため、該当のファイルがないと何も動作しないっぽいです。

以下の記事を参考にこの現象を解明することができました。

あとがき

携帯を持っていなかった幼いころはこんな単純なゲームにも興奮していたことを思い出しました。

肝心の具体的な秒数のフィードバックがないことが不足点かと思いますが、この不完全さも悪くないと感じました。
計測タイムを確認するためには、LCD(液晶ディスプレイ)7セグLEDなどを実装する必要がありそうです。

ゲームの進化は著しいですが、たまにはアナログなこんなミニゲームも意外と面白いものです。

WISH

今回、統合開発環境(IDE)として "Thonny" を使用しましたが、当初はPython以外でも汎用性の高い "VS Code" を使用したいと思っていました。

ただ、Runが走らないなど環境構築の段階でつまづいてしまったため、比較的二次情報の多い"Thonny"を使用しました。

比較的に簡単と言われるRaspberry Pi Picoですらこれだけ苦労したので、Raspberry Pi 5などになるともっと大変であると想像します。

一長一短ありますが、環境構築の簡単さからも電子工作初心者にはobnizの方がおすすめかもしません。

とはいえ、放置しておくには気持ちが悪いのでStack OverflowなどのQAサイトを利用して解決を図っていきたいです。

おまけ

obnizとの違いをGensparkでまとめてみました。
よかったらご覧ください。

これまで作製したプロダクトのご紹介

7
2
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
7
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?