IchigoLatte でひらがな計算ゲーム

JavaScript 風の言語でプログラミングができるマイコンボード [IchigoLatte](http://ichigolatte.shizentai.jp/) で、標準でひらがなのフォントが組み込まれていることを活かし、ひらがな計算ゲームを作ってみた。

:::note info
IchigoLatte には他にも Python 風、Ruby 風、FORTH 風のバージョンがあるが、今回は JavaScript 風のバージョンを用いる。
以降、この記事における「IchigoLatte」は JavaScript 風のバージョンのものを指す。
:::

# IchigoLatte

## ハードウェア

今回は、だいぶ前に購入して放置していたこの製品を用いる。
[IchigoLatte プリント基板ハーフキット ICHIGOLATTE-KIT jig.jp製｜電子部品・半導体通販のマルツ](https://www.marutsu.co.jp/pc/i/835511/)

:::note warn
執筆時点において、まだ在庫はあるらしいものの、在庫限りとなっている。
:::

![IchigoLatte (外装)](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99656/bfcc6c10-3a24-28c5-dd73-a0f370edfaa2.jpeg)

外袋を外すと、こんな感じ。

![IchigoLatte (箱)](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99656/f0fc1401-5928-7db9-77dc-3a5279a42665.jpeg)

セット内容は

* 注意点の紙
* 組み立て方の紙
* システムの使い方の紙
* 関数リファレンスの紙
* 基板 (一部の部品を実装済み)
* 部品セット
* シール

であった。

![IchigoLatte (セット内容)](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99656/948b8434-cb60-4a19-d180-e22f391510af.jpeg)

基板表面と部品。

![IchigoLatte (基板表面と部品)](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99656/8bd8d42b-302b-8802-e0c6-8c728f7242c3.jpeg)

基板裏面。

![IchigoLatte (基板裏面)](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99656/46b97541-e8e3-4184-d41c-e29d82029e7e.jpeg)

組み立てた状態。

![IchigoLatte (組み立てた状態)](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99656/5e513917-accc-1d39-63fc-92b278bf1318.jpeg)

## 主な周辺機器

IchigoLatte には、主に以下の周辺機器を接続して用いる。

### ディスプレイ

コンポジット信号を入力して表示できるディスプレイ・テレビ・プロジェクター・キャプチャ機器などに接続し、画面の出力を見る。

### キーボード

USB-A端子を持ち、**PS/2プロトコルに対応した**キーボードを接続し、システムへのコマンドや実行中のプログラムへの入力の操作を行う。
適切な変換コネクタを使用すれば、PS/2端子のキーボードも使用できると考えられる。

### 電源

USB Micro-B 端子経由で、5Vの電源を供給する。
もしくは、(USB-シリアル変換器などから) 5ピンのピンソケットの一番奥の「5V」に直接5Vの電源を供給してもよい。

### UART (シリアル通信)

IchigoLatte 単体でもテキストエディタが内蔵されており、プログラムを書くことができる。
しかし、この方法には

* ひらがなの入力が直感的でない
* ソースコードを一度に見られる範囲が限られる

などの短所がある。
よって、プログラムは通常のパソコン上で書き、USB-シリアル変換器などを経由して UART で IchigoLatte に流し込むのがよいだろう。
UART の設定は

* 115200bps (初期値)
* データ：8ビット
* ストップビット：1ビット
* パリティなし
* フロー制御なし

である。
電圧は3.3V (5Vの入力も可) である。

## 基本的な操作方法

IchigoLatte を起動すると、まず **lash** という簡易シェルが立ち上がる。

![lashが起動した状態](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99656/5d338d30-d797-8c1b-1416-83429ecd474d.png)

ここでは、以下のコマンドなどを使用できる。

|コマンド|効果|
|---|---|
|`ms`|コードをインタラクティブに実行する|
|`ms .`|保存しているプログラムを実行する|
|`vi`|保存してあるプログラムを編集する|
|`cat uart > .`|UART からプログラムをインポートする|
|`cat . > uart`|UART にプログラムをエクスポートする|

また、lash が起動している状態で本体のボタンを押すと、画面・キーボードのかわりに UART で入出力を行うモードのオン/オフを切り替えることができる。

### コードをインタラクティブに実行する

```text
ms
```

というコマンドを実行すると、コードをインタラクティブに実行できるモード (REPL) になる。
ただし、変数に格納した文字列をうまく出力できない、`if` 文を実行すると謎のメッセージ (`[VT: 34]`) が表示されたり強制終了・システム再起動したりすることがあるなど、信頼性は微妙である。

以下のスクリーンショットでは、

* 変数に格納した文字列を期待通り出力できていないこと
* 同じ `if(1)log(1);` というコードを実行しても、`[VT: 34]` が出力されたりされなかったりすること

がわかる。

![REPLにおける文字列とif文の実験](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99656/79a7f7ac-3f88-5944-b030-37f5ad39a76d.png)

また、以下のように文字列の実験を行わずに `if` 文を実行すると、2回目の実行時に強制終了・システム再起動してしまった。

![REPLで文字列の実験をせずにif文の実験をする](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99656/bd1a2de3-6d79-5441-8fe3-cf1ead5053d0.png)

### 保存してあるプログラムを実行する

```text
ms .
```

というコマンドを実行すると、IchigoLatte 本体に保存してあるプログラムを実行できる。
`ms` コマンドとは違い、ここでは変数に格納した文字列も普通に表示できた。

![ms . を用いて変数に格納した文字列を表示する実験](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99656/e918cce8-d3cd-ef58-e660-c799a095c7bb.png)

### 保存してあるプログラムを編集する

```text
vi
```

というコマンドを実行すると、IchigoLatte 本体に保存してあるプログラムを編集するモードになる。

:::note warn
コマンドは `vi` であるが、操作方法は一般的な Vi / Vim とは関係ないようである。
:::

以下の操作を行うことができる。

|キー|動作|
|---|---|
|文字|文字を入力する|
|矢印|カーソルを移動する|
|Enter|改行を入力する|
|BackSpace|カーソルの前の文字を削除する|
|Delete|カーソルの位置の文字を削除する|
|Home|行頭にカーソルを移動する|
|End|行末にカーソルを移動する|
|Page Up|今表示されている最初の行にカーソルを移動する<br>もともと最初の行にカーソルがある場合は上にスクロールする|
|Page Down|今表示されている最後の行にカーソルを移動する<br>もともと最後の行にカーソルがある場合は下にスクロールする|
|Esc|プログラムを保存し、編集モードを終了する|
|Ctrl+D|プログラムを**保存せず**、編集モードを終了する|

### プログラムをインポート・エクスポートする

```text
cat uart > .
```

というコマンドを実行すると、UART からプログラムを受け取り、IchigoLatte 本体に保存 (インポート) できる。
[Tera Term](https://teratermproject.github.io/) において、送信遅延は字・行ともに 0 に設定した状態で貼り付けを行うことで、プログラムが欠けずにインポートできるようである。
プログラムを送ったあと、0x04 (Tera Term では Ctrl+D) を送ることで、インポートを完了させる。

:::note info
Tera Term では、プログラムを送信 (貼り付け) する前に改行コードを **LF** に設定すること。
また、(特にカナを扱う場合) コーディングを **SJIS** に設定すること。
:::

```text
cat . > uart
```

というコマンドを実行すると、IchigoLatte 本体に保存されているプログラムを UART に書き出し (エクスポート) できる。

:::note warn
インポート・エクスポートの方向を間違えないように注意すること。
:::

## プログラムの書き方

IchigoLatte では、**2046+1 バイト**までのプログラムを扱うことができる。

[IchigoLatte に関する Q&A - イチゴジャム レシピ](https://15jamrecipe.jimdofree.com/ichigolatte/q-a/)
では

> どの言語でも 2k バイト（2048 バイト）でプログラムを制作できる仕様になっています。

と主張しているが、試してみると UART からのインポートでは 2046 文字目までで切れてしまった。
この状態で内蔵のエディタを用いると、さらに1文字追加することができた。
UART へのエクスポートは、追加した1文字を含めた 2047 文字を出力できた。

### 使用できる変数

変数には文字列・数値・関数を格納できる。
数値は -2147483648 以上 2147483647 以下の整数 (32ビット符号つき整数) を扱うことができ、浮動小数点数は扱えない。

変数名の長さは、7文字までである。

### 使用できる文法

JavaScript の文法のうち、以下は IchigoLatte で使用できる。

* `//` によるコメント
* `""` による文字列
* `var` による変数の宣言
* `function` による関数の宣言 (名前あり、無名ともに)
* `if` 文 (単文・ブロックともに)
* `while` 文
* `break` 文 (`while` 文のループから抜ける)
* `return` 文 (関数から戻る)

:::note info
以下は IchigoLatte で使用**できない**。

* `/* */` によるコメント
* `''` や ``` `` ``` による文字列
* `const` や `let` による定数・変数の宣言
* `() => {}` による関数の定義
* `for` 文
* `switch` 文
* `do` 文
* `continue` 文
* インクリメント `++`・デクリメント `--`
* 論理AND `&&`・論理OR `||`
* 論理右シフト `>>>`
* 条件演算子 `?:`
:::

### 使用できる関数

IchigoLatte 用のプログラムでは、以下の組み込み関数を使用できる。

:::note info
ここでは、今回のプログラムで用いた関数のみを紹介する。
使用できる全関数は、リファレンス ([公式サイト](http://ichigolatte.shizentai.jp/)に掲載されている) などを参照すること。
:::

|関数|動作|
|---|---|
|`cls()`|画面をクリアする|
|`lc(x,y)`|次の `log` の出力開始位置を (x, y) に設定する|
|`log(data [, ...])`|画面に数値や文字列を出力する (複数可)|
|`inkey()`|入力された文字の文字コードを返す|
|`rnd(n)`|0 以上 n 未満の整数の乱数を返す|
|`tick()`|ミリ秒単位の時刻を返す|
|`new Array(n)`|n 要素の配列を作成する|

# ひらがな計算ゲーム

## ゲームの内容

「いち たす に は？」などのひらがなで書かれた計算式を計算し、答えをひらがなで表現したものを4個の選択肢の中から素早く選んでいく。
正しい選択肢を選ぶと、1点を加算して次の問題に行く。
間違った選択肢を選ぶと、1点を減算する。(次の問題には行かない)
30秒間で、なるべく高い点数を取ることを目指す。

## 操作方法

|キー|意味|
|---|---|
|Enter|ゲーム開始 (タイトル画面)<br>タイトルに戻る (結果画面)|
|Q / W / A / S|対応する選択肢を選ぶ (ゲーム中)|

## プログラム

```javascript
// ﾋﾗｶﾞﾅ ｹｲｻﾝ
var arr=new Array(11);
function dNum(x,y,n){
 lc(x,y);
 if(n==0)log(" ｾﾞﾛ");
 if(n==1)log(" ｲﾁ ");
 if(n==2)log(" ﾆ  ");
 if(n==3)log(" ｻﾝ ");
 if(n==4)log(" ﾖﾝ ");
 if(n==5)log(" ｺﾞ ");
 if(n==6)log(" ﾛｸ ");
 if(n==7)log(" ﾅﾅ ");
 if(n==8)log(" ﾊﾁ ");
 if(n==9)log(" ｷｭｳ");
 if(n==10)log("ｼﾞｭｳ");
}
function newQ(){
 var a=rnd(11),b,type=rnd(2),ans;
 lc(13,11);
 if(type==1){
  b=rnd(a+1);
  ans=a-b;
  log("ﾋｸ");
 } else {
  b=rnd(11-a);
  ans=a+b;
  log("ﾀｽ");
 }
 dNum(8,11,a);
 dNum(16,11,b);
 var i=0,apos=-1;
 while(i<11){
  arr[i]=i;
  i=i+1;
 }
 i=0;
 while(i<4){
  var sel=rnd(11-i)+i;
  var v=arr[sel];
  arr[sel]=arr[i];
  arr[i]=v;
  if(v==ans)apos=i;
  i=i+1;
 }
 if(apos<0){
  apos=rnd(4);
  arr[apos]=ans;
 }
 i=0;
 while(i<4){
  dNum(10+i%2*10,15+(i&2),arr[i]);
  i=i+1;
 }
 return apos;
}
function dScore(x,y,s){
 var t=s;
 lc(x,y);
 if(t<0)t=-t*10;
 if(t==0)t=1;
 while(t<10000){
  log(" ");
  t=t*10;
 }
 log(s);
}
function getSel(){
 var k=inkey()&0xdf,r=-1;
 if(k==0x51)r=0;
 if(k==0x57)r=1;
 if(k==0x41)r=2;
 if(k==0x53)r=3;
  return r;
}

while(1){
 cls();
 lc(11,10);log("ﾋﾗｶﾞﾅ ｹｲｻﾝ");
 lc(9,14);log("Enter ﾃﾞ ｽﾀｰﾄ");
 while(inkey()!=10);

 var sTime=tick(),cTime=0,disp=4;
 cls();
 while(cTime<3000){
  var newDisp=3-((cTime)/1000>>0);
  if(newDisp!=disp){
   disp=newDisp;
   lc(16,12);log(disp);
  }
  cTime=tick()-sTime;
 }

 var tLimit=30,score=0;
 cls();
 lc(10,8);log("ﾋﾞｮｳ");lc(23,8);log("ﾃﾝ");
 lc(21,11);log("ﾊ?");
 lc(8,15);log("Q:");lc(18,15);log("W:");
 lc(8,17);log("A:");lc(18,17);log("S:");
 sTime=tick();cTime=0;disp=31;
 var ans=newQ();
 dScore(18,8,score);
 while(cTime<tLimit*1000){
  var newDisp=tLimit-(cTime/1000>>0);
  if(newDisp!=disp){
   disp=newDisp;
   lc(8,8);
   if (disp<10)log(" ");
   log(disp);
  }
  var sel=getSel();
  if(sel>=0){
   if(sel==ans){
    score=score+1;
    ans=newQ();
   } else {
    score=score-1;
   }
   dScore(18,8,score);
  }
  cTime=tick()-sTime;
 }

 cls();
 dScore(12,10,score);log(" ﾃﾝ");
 lc(8,14);log("Enter ﾃﾞ ﾓｳｲｯｶｲ");
 while(inkey()!=10);
}
```

以下の関数を定義して用いている。

|関数名|役割|
|---|---|
|`dNum`|指定の位置に、0～10の数値に対応する文字列を描画する (draw number)|
|`newQ`|問題・正解・選択肢を生成して描画し、正解の番号を返す (new question)|
|`dScore`|指定の位置に、数値を5桁で描画する (draw score)|
|`getSel`|キー入力を受け取り、選択された選択肢の番号を返す (get selected)|

最後の `while` 文内で、

* タイトル画面
* カウントダウン画面
* ゲーム画面
* 結果画面

を順に処理している。

## 実行結果例

タイトル画面。

![タイトル画面](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99656/9ff91dd9-b1db-f7eb-a8ea-612deb1ce900.png)

ゲーム開始時のカウントダウン。

![カウントダウン](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99656/0dd2b893-5d14-baa9-6390-26d8e8248d0f.png)

ゲーム開始。

![ゲーム開始](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99656/337721e6-9b6c-d5f7-5205-20a2f684be9b.png)

ゲーム中。

![ゲーム中](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99656/9dd499c1-c0c8-7d4b-db8a-cbfde9d120c9.png)

ゲーム終了、結果画面。

![結果画面](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99656/e49617de-9221-a6c3-15e2-26883ed2a944.png)

# まとめ

* IchigoLatte の使い方や言語の仕様を確認した
* IchigoLatte で「ひらがな計算ゲーム」を作ってみた