ORANGE pico で10秒連打ゲーム

[ORANGE pico](http://www.picosoft.co.jp/orange/) で10秒連打ゲームを作ってみた。

10秒間でのゲームコントローラの連打数を測定するのに加え、1秒ごとそれぞれの連打数も測定する。

# プログラム

```
10 x=button(-1):pb=0:yoffset=6
20 cls:clt:pt=0:count=0:curcnt=0:locate 0,yoffset-2:print "READY?";
30 t=tick():b=button(0)
40 if pt<60 && 60<=t then locate 7,yoffset-2:print 3;
50 if pt<60*2 && 60*2<=t then locate 9,yoffset-2:print 2;
60 if pt<60*3 && 60*3<=t then locate 11,yoffset-2:print 1;
70 if pt<60*4 && 60*4<=t then locate 13,yoffset-2:print "GO!";:locate 0,yoffset:print "0 0";
80 if 60*14<=t then locate 0,yoffset+11:print "TOTAL ";count;:goto 140
90 if 60*4<=pt && pt/60<>t/60 then locate 0,yoffset+t/60-4:print t/60-4;" 0";:curcnt=0
100 pt=t
110 if b<0 then goto 30
120 if 60*4<=t && !(pb & 16) && (b & 16) then count=count+1:curcnt=curcnt+1:locate curcnt+1,yoffset+t/60-4:print chr$(&H8F);curcnt;
130 pb=b:goto 30
140 locate 0,yoffset+13:print "Press START to retry."
150 b=button(0)
160 if b>=0 && (b & 1024) then pb=b:goto 20
170 goto 150
```

このプログラムは、[CC0 1.0](https://creativecommons.org/publicdomain/zero/1.0/deed.ja) でライセンスする。

# 操作方法

ゲームコントローラを接続して実行する。

* Aボタン：連打
* STARTボタン：終了時のリトライ

# 実行結果例

<iframe width="640" height="480" src="https://www.youtube.com/embed/Mf4xR54Zy4o?si=LFFSDEBT6-HZ_OgN" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>

# 解説

## 10行目：全体初期化

ボタンの初期化と描画位置の設定を行う。

## 20行目：ラウンド初期化

画面と状態(カウント・時刻)の初期化を行う。

## 30行目：状態取得

時刻とボタンの状態を取得する。

## 40～90行目：描画

時刻に応じて描画を行う。

* 1秒、2秒、3秒：カウントの更新
* 4秒：カウントの更新と、最初の1秒用のグラフの最初の描画
* 5～13秒の整数秒：グラフの最初の描画
* 14秒：合計連打数の描画、ゲーム終了

## 100～130行目：状態反映

* 100行目：現在時刻を更新する
* 110行目：ボタンの状態の取得に失敗している場合、処理をスキップする
* 120行目：測定開始後、かつAボタンが新たに押された場合、カウントを増やしてグラフを描画する
* 130行目：現在のボタンの状態を更新する

## 140～170行目：ラウンド終了

メッセージを表示し、STARTボタンが押されるまで待機する。
STARTボタンが押されたら、ラウンド初期化に戻り、次のラウンドを開始する。

# 今回新たに発見した罠

## ボタンの状態の取得に成功するとtickms()の時刻が遅れる

今回のプログラムを作成するにあたり、最初は `tick()` ではなく `tickms()` を使用して時刻を取得していた。
すると、1秒間隔のはずのカウントが約2秒間隔と、想定より時間がかかっていた。
`tick()` に切り替えたところ正常に動作したので、原因は `tickms()` が想定通りに動いていないことであると考えられる。

そこで、以下の仮説を立てた。

* I2C通信を行うと `tickms()` の時刻が遅れる
* ボタンの状態の取得を試みると `tickms()` の時刻が遅れる
* ボタンの状態の取得に成功すると `tickms()` の時刻が遅れる

まず、I2C通信と `tickms()` の関係を調べるため、以下のプログラムを実行した。
これは、標準搭載のEEPROMの適当な場所を読み込むプログラムである。
さらに、1秒ごとに `tick()` の値と `tickms()` の値を出力する。

```
10 clt:cltms:toprint=60
20 t=tick():m=tickms()
30 x=i2cr(2,&H50,256,1)
40 if toprint<=t then print t;" ";m:toprint=toprint+60
50 if t<300 then goto 20
```

以下の出力が得られた。

```
60 986
120 1981
180 2982
240 3976
300 4970
```

`tick()` の時刻に比べて `tickms()` の時刻は少し遅れているが、あまり差は大きくない。
とはいえ、少しずつ差が大きくなっているようであり、影響が全く無いとはいえない。

ここで、上のプログラムの30行目を以下に差し替え、I2C通信で読み込む量を増やしてみた。

```
30 x=i2cr(2,&H50,8000,1)
```

すると、以下の出力が得られた。

```
65 1071
129 2143
181 3000
245 4072
310 5143
```

やはり少しずつ差が大きくなっているものの、あまり差は大きくない。

次に、プログラムを以下に変更し、ゲームコントローラのボタンの状態を取得するようにした。

```
10 y=button(-1):clt:cltms:toprint=60
20 t=tick():m=tickms()
30 x=button(0)
40 if toprint<=t then print t;" ";m:toprint=toprint+60
50 if t<300 then goto 20
```

ゲームコントローラを接続せずに実行すると、以下の出力が得られた。

```
60 989
120 1986
180 2983
240 3980
300 4977
```

きれいに差が3(ms)ずつ広がっているが、やはり差は大きくない。

ゲームコントローラを接続して実行すると、以下の出力が得られた。

```
60 526
120 1077
180 1693
240 2143
300 2666
```

`tickms()` の返り値が想定の約半分と、**大幅に小さくなっている。**
すなわち、ゲームコントローラのボタンの状態の取得に失敗した場合の `tickms()` への影響は無いまたは少ないが、取得に成功すると `tickms()` に大きな影響が出ることが読み取れる。

対照実験のため、繰り返す処理をI2C通信を行わない処理に差し替えてみた。

```
10 clt:cltms:toprint=60
20 t=tick():m=tickms()
30 x=sin(1)
40 if toprint<=t then print t;" ";m:toprint=toprint+60
50 if t<300 then goto 20
```

以下の実行結果が得られた。

```
60 987
120 1984
180 2981
240 3977
300 4974
```

I2C通信を行わなくても、I2C通信を行ったときと同程度の `tickms()` の遅れが生じたことがわかる。
よって、一般のI2C通信では `tickms()` への影響はみられないと考えられる。

これまでの調査で、[`pause` コマンドを実行すると `tickms()` のカウントが止まる](https://qiita.com/mikecat_mixc/items/41aff51253bd1daced4b#pausebeepplay-%E3%81%AE%E5%AE%9F%E8%A1%8C%E4%B8%AD%E3%81%AF-tickms-%E3%81%AE%E3%82%AB%E3%82%A6%E3%83%B3%E3%83%88%E3%81%8C%E6%AD%A2%E3%81%BE%E3%82%8B)ことがわかっている。
さらに、[ゲームコントローラのボタン状態の取得](https://qiita.com/mikecat_mixc/items/af614b225734adb1173a#%E3%82%B2%E3%83%BC%E3%83%A0%E3%82%B3%E3%83%B3%E3%83%88%E3%83%AD%E3%83%BC%E3%83%A9%E3%81%AE%E7%8A%B6%E6%85%8B%E3%82%92%E8%AA%AD%E3%81%BF%E5%8F%96%E3%82%8B%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%A0-%E3%81%9D%E3%81%AE1)に成功した場合は、約10msかかり、そのうちほとんどの時間で通信を行っていない。
この通信と通信の間の時間において、内部で `pause` コマンドと同様の処理が行われ、`tickms()` のカウントが止まっている可能性が考えられる。
この場合、ボタンの状態の取得に成功しなくても、最初の `0x00` の送信にだけ成功すれば、待機処理が行われ、`tickms()` の遅れが生じる可能性が考えられる。