ラリコン試作1号でラリーした話

奈良でラリってきました(言い方)

「ミステリーラリー」という誰でも参加できる超楽しいラリーです。 teamkomaren.amebaownd.com

今回はArduinoで自作したGPSラリコンを使ってみます。

怪しい雰囲気を醸し出しておる。

何があっても良いように四輪にしました。
ま、防水機能も無いしな…

ダム湖畔に佇む我が愛車

クイズを解きながら、奈良をぐるっと180kmぐらい走行。

柿の葉寿司うまいうまい

ソフトクリームうまいうまい

で、ラリーどうでした?

結果ですが、無事にゴールは出来ました。
成績的にはクイズの結果が・・・ぐぬぬ
あとむっちゃ暑かったのに、クルマだからと油断して帽子もタオルも持っていかなかったのは失敗^^; (←キモい顔文字)

次どうする

しかし、なーんか精度がイマイチで、iPhone12のGPSと比較すると4kmで100mぐらい、遅れる方向の誤差がでます。 これが計算誤差なのか割り込み禁止区間がどこかに残ってるのか、そもそもタイマー割り込みの精度が悪いのか・・・

カジュアルラリーで使う分にはコマ毎に補正してやれば良いのでそんなに問題にならないのですが・・・わたし、気になります

TVアニメ『氷菓』より、千反田える

えるたそに言われては仕方ないので、とりあえず要調査

あと、GPSだから当然トンネル入った時に速度がわからなくなるのですが、トンネル出た時に何か処理して誤差補正やったほうが良いのか?コードを追加してまで補正する意味があるのか? みたいなところも課題です。

とりあえず、ATtiny44で作る予定の試作2号機で検討しようっと(←未来のオレにぶん投げ

Arduboyで麻雀ゲーム作る話(3)~ 放浪編

ただでさえ月末月初で忙しいところに大連の発注先がコロナでロックダウン一歩手前になってしまったり、大学生の息子が帰省してきたり、ラリーに参加する準備をしてたり、うっかり韓国語の単語を暗記を忘れていたり、うっかり青いドゥカッティモンスター696+を見つけてしまったり、もう何が何やらみたいな状況なのですが、時間を見つけてプログラミングを進めています。

ラリーの準備は楽しいなあ!

このブルーは多分全塗装だと思うのですが、、、

役判定を作らねば

さて、ここまででメンツが完成しているかどうかを判定するところまで作ったので、これからそれが役になっているかを判定するロジックを作ります。
最初は正規表現でズバッと解決しようと思っておったのですが、麻雀の役って改めて調べてみるとなかなかアナログで「いやだこれ、1パターンずつハードコーディングしたほうが早くない・・・?」みたいな感じです。 それだと個人的に面白くないので、なんかこうパターンマッチングできないかと試行錯誤して、

2段構えで対処することにしました。

例えばみんな大好きタンヤオなんかは、一九字牌以外のメンツで構成されているので、中張牌メンツの定義

define CHU ([TK][MPZ][2-8])|([S].[2-6])

としてやって、役そのものは

yaku TANYAO 1 'all CHU

なんて書いてやれればOKです。
わかりやすいように日本語に訳すと

  • 中張牌CHUってのは「対子か刻子」で「萬子筒子索子」の「2-8」か、「順子」で「2-6」
  • タンヤオTANYAOは、1翻で全部のメンツが中張牌

みたいな感じですね。

別にyaku直接メンツ構成を書いても構いません

yaku PINHU 1 'all (T[MPZ].)|(TFy)|(S..) #ただし両面待ち
ピンフは1翻で全部のメンツ「萬筒索」の対子オタ風の対子もしくは順子

・・・「両面待ちでアガったか」はハードコーディングするしかないかな~

それをどうするかというと「これら定義を元にCのソースを吐くpythonスクリプトを組む」のでした。

yacc&lexっぽいですね!
麻雀役だけに、麻雀ヤック・・・

「映画大好きポンポさん2」より抜粋
そして一部組んだものgithubにあります。
github.com

ファイル 説明
yaku.ms 役の定義をするスクリプトです
yaku.py yaku.msを食ってCソースを吐くpythonプログラムです
yakutest.h テスト用にgccに食わせる時に使うヘッダファイルです
役判定仕様.md yaku.msに記述するための定義仕様書

現状は本当に一部の役(しかも面前)しか想定してないです(←さすがに全部作るの大変かなあ・・・と諦めてきた^^;)

コマンドラインにて

#!/bin/bash
cat yaku.ms | py.exe yaku.py > ptest.c
gcc -fsyntax-only -w -DYKDEBUG ptest.c

などとしてやると、yaku.msを食ってptest.cを作って、gccでチェックしてくれます。

治具的なプログラムなので、エラーチェックがかなりいい加減なのもご愛嬌です。

さて次は

とりあえず、全部スクリプトを組んで、PCのコンソールアプリで役判定・点数計算できるとこまで開発しても良いかもしれません(デバッグが簡単なので・・・)。
Arduboy関係ないじゃん!という向きもあろうかと思いますが、最終的には乗せます故・・・

ATtiny44用の書き込みシールドを作った話

個人で会社を運営している何から何まで自分でやらないといけないので、請求やら経理処理が大量発生する月末月初はとっても忙しいです^^;

ああ~ 忙しい忙しい・・・へあっ!?

お前は・・・ ATtiny44用書き込みシールド・・・

すばらしい出来
はっ! いつのまにかハンダゴテ握っていた、だとー!? (棒

ジャンパーの処理もバッチリ(自画自賛)
信じられねーと思うが俺も何をされたかわからなかった・・・!

書き込みシールドとは

ま、冗談はさておき、書き込みシールドちゅのはATtiny44をいじるのに都度ブレッドボードを持って歩くのが大変邪魔くさいので、UNOのシールドにしたものです。

要はこの回路ですな↓

書き込み回路

簡単なようで、配線間違えるとチェックがめんどくさい
でもやっぱり簡単なシールドです。

できた

側面から見た図

わあ、ぴったり・・・!(←専用ボードだから当たり前)

※「waves Arduino 用 UNO プロトタイプ シールド ブレッドボード 付属」480円(現在欠品中)
https://www.amazon.co.jp/gp/product/B079FQD44P/

試してみる

できたら試してみます。 まずはLチカから

シールドでLチカ

改めて調べてみたら、案外ATtiny44でのLチカ情報が少ないので、自分の備忘も兼ねてこちらに回路とプログラムをアップしときますね。
ATtiny44の右上のピン(PA5)でLチカします。
抵抗は1kΩ(Fritzingで設定忘れてます)

PA5でLチカする回路

void setup() {
  pinMode(5, OUTPUT); // PA5
}

void loop() {
  digitalWrite(5, HIGH);
  delay(1000);
  digitalWrite(5, LOW);
  delay(1000);
}

この前試したLCDも繋いでみましょう。

動く動く

↓ちなみに「この前」の記事 kikyujin.hatenablog.com

さて次は

ラリコンのソフトシリアルを使った作業を進めたい・・・
Arduboy麻雀のプログラムも進めたいし・・・

しかしまずは、事務仕事をやっつけねば・・・^^;

Arduinoでラリコン作る話(2)→ATtiny44でラリコン作る話(2)

Arduino UNOGPS使った簡易ラリコンを作った話を「Arduinoでラリコン作る話」としてまとめようと思っていたんですが、ATtiny44が来ちゃったのでこっちに載せ替えることにしました。

ほら、現在進行系でハマってるほうが楽しいでしょ?

構成要素と方針

基本同じです(ATtiny44+GPSモジュール+LCD1602+スイッチボード)。
ただ、Arduino UNOよりGPIOが少ないので、LCDはI2Cで制御してみます(今回)。
しかし、ここにGPSのソフトウェアシリアルとかタイマー割り込みかけると結構CPU負荷高そうだけど大丈夫かな・・・ もしダメそうなら、またその時に対応考えます(泥縄式)

ちなみに、それぞれCPUの違いなどはこのページが詳しいです↓
Arduinoで作った回路の小型化(Arduino互換機の製作)(10) - しなぷすのハード製作記

まずはArduino UNOでLCD1602を動かしてみる

これまでは4bitパラレルで接続して表示させていたので、I2Cで動かすのは初めてです。 とりあえず、Arduino UNOを使って、ちゃんと動作するか確認します。
ライブラリは"liquidcrystal-i2c"を使います。
普通にIDEの スケッチ|ライブラリをインクルード|ライブラリを管理 からインストールしてやればOK。 www.arduino.cc

ソースは適当に拾ってコピペします(苦笑)

#include <LiquidCrystal_I2C.h>

// I2C address 0x27 16x2
LiquidCrystal_I2C lcd(0x27, 16, 2);

void setup() {
  lcd.init();
  lcd.backlight();
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Hello world");
  lcd.setCursor(0, 1);
  // キョウハイイテンキ
  lcd.print("\xb7\xae\xb3\xca\xb2\xb2\xc3\xdd\xb7");
}

void loop() {
}

Arduino UNOとの接続ですが、

  • SDA→A4
  • SCL→A5

としてあげれば良いようです(D4/D5ではありません!チューイせよ)

接続したらスケッチをコンパイルしてアップロード

動いた!

簡単ですね!
ライブラリがいろいろ揃っていると本当に楽です。

はい!次行ってみよー!(cv.いかりや長介)

ATtiny44でもLCD1602を動かしてみる

さて本日のメインイベント
ネット見てみたんですが、あんまりATtiny44でLCD1602動かしたみたいなページないんですよね・・・

ということはアレだ、むっちゃ簡単むっちゃ難しい2択だ!
多分むっちゃ簡単なんだろう(←楽観的)

まず配線ですが、ATtiny44の場合、

  • SDA→PA6
  • SCL→PA4

となっているようです(ピン配置などはここにデータシートがありますぶん投げ)。

スケッチの書き込みにはさっきのArduino UNOを使うので、ごちゃごちゃ配線する前にArduinoISPを書き込んでおかないと・・・ほらさっき試しにUNOで1602動かしたでしょ?
忘れるとハマりますよ!ハマるからね!ほらハマった!

配線は次のようにしました↓

UNOにArduinoISPを書いてから配線ヨシッ!

で、ソースはどうなんだろう・・・
なんか、このままコンパイルしたら動く気がする・・・ ていうか、きっと動く!
ライブラリの中の人が設定とかいい感じにしてくれるに違いない!

じゃ、そのままコンパイル・・・の前に、ボードの設定をUNOからATtiny44にしないと・・・
忘れるとハマ(以下略)

こんな設定ですな。

じゃあコンパイルしてアップロード(「書き込み装置を使って書き込む」)。

動いた!
SUGEEEEEEEEEE!!!
ほんとに動いた!

これ何気なく動いてますが、割りとスゴいことなんでは?
上位CPUのスケッチをコンパイルし直すだけで動かせるなんて・・・

本当にプロトタイピングに向いているんだな!Arduinoは!(富野話法)

次どうする?

今回表示が動いたので、最低限の情報が確認できるようになりました。
となればGPSモジュールを動かしてみるのになんの躊躇(ちゅうちょ)がありましょうか、いやない(反語)

ていうか、ソフトウェアシリアルがI2Cとバッティングしないのか?ちょっと心配^^;(←キモい絵文字)

ATtiny44/85にブートローダーを書き込んだ話

GPSラリコンを小型化しようと思って、秋月電子にATtiny44を注文しておいたのが届きました。
何かに使うかもと思ってついでにATtiny85も注文しておきました(何かって・・・

ちっちぇぇ~~

左の14ピンのヤツがATtiny44、右の8ピンのヤツがATtiny85です。
で、ここが大事なのですが、コヤツらもArduino IDEでプログラムが作れます

必要なものは、

  • なんかArduino IDEが動作するPC
  • なんかArduinoUNOが良いと思われます)
  • なんかATtinyマイコン(ここではATtiny44と85)

あとブレッドボードとかジャンパーとか10μFの電解コンデンサとかLチカ用にLEDとか1kΩの抵抗とか・・・一般家庭に普通にあるもので大丈夫ですね。

準備するArduinoくんですが、実はプログラム書き込み機として使われます。
あとは電源とか・・・

まさに体目当てです。

準備しよう

いろんなサイトが紹介してくれています。
このあたりが絵が多くて親切でしょう。

ArduinoからATtiny85へ書き込んでLチカする

また、このサイトはArduinoの種類と接続を詳しく説明してくれています。

Arduino ブートローダーを書き込む (AVR)

ハマりポイント

一連のオペレーションの中に「ボードマネージャでボードを追加する」工程があるのですが、どのサイトを見ても

ArduinoIDEの環境設定で「追加のボードマネージャのURL」に"http://drazzy.com/package_drazzy.com_index.json"と入れて、ツール|ボード|ボードマネージャで"ATTinyCore"をインストールせよ

澄ました顔でサラっと説明されています。 ところが、私がこれをやったらエラーが出てインストールできませんでした・・・↓

https://azduino.com/bin/micronucleus/micronucleus-cli-2.5-azd1b-i686-mingw32.zipのダウンロード時にエラーが発生しました
java.lang.RuntimeException: java.lang.Exception: https://azduino.com/bin/micronucleus/micronucleus-cli-2.5-azd1b-i686-mingw32.zipのダウンロード時にエラーが発生しました
    at cc.arduino.contributions.packages.ui.ContributionManagerUI.lambda$onInstallPressed$2(ContributionManagerUI.java:179)

四月は君の嘘」8巻より抜粋

ぐぬぬ・・・と思いつつ、本家のGitHub ATTinyCore/Installation.md at OldMaster---DO-NOT-SUBMIT-PRs-here-Use-2.0.0-dev · SpenceKonde/ATTinyCore · GitHub を見てみますと、マニュアルでもインストールできると書かれています。
やっぱオートはダメだ!時代はマニュアルだ!
簡単にやり方だけ説明すると、

  1. ここんちからReleasesのLatestなzipをダウンロードするgithub.com
  2. IDEの環境設定にある「スケッチブックの保存場所」をみて、そこに「hardware」というフォルダを作る
  3. そこにさっきのzipを展開したフォルダを突っ込む(私の場合は"ATTinyCore-1.5.2"というフォルダでした)

そうすると、IDEのツール|ボードメニューにずらずらと対応するATtinyチップが出てきます

こんないっぱいあるのか!こまる~(嬉)

後は、諸先輩方の言うようにしてやればだいたいできると思います(ぶん投げ

さてLチカ

ATtiny44

www.youtube.com

ATtiny85

www.youtube.com

こいつなのですが、最初こんな動作をしてまして「DQN車のハイフラッシャーか!?」と思って電源入れ直したら治ったという・・・まあ、そういうこともあるよねというお話

www.youtube.com

次はどうする?

ATtiny44ですが、これは冒頭でも書いたようにGPSラリコンに使おうと思っています(ワンボード化できるといいなあ・・・)

ATtiny85については、これを使ったArduboyっぽいプロダクトがありまして、なかなか趣深いので、この互換機でも作ろうかなあ・・・と思っています。 詳細はこちら↓
www.tinyjoypad.com - TINYJOYPAD_ATTINY85

日本語でも「ATtiny85 ゲーム」で検索すると、こちらの互換機を作られているサイトがいくつかあります。 ご参考までにm(_ _)m(←キモい顔文字)

Arduboyで麻雀ゲーム作る話(2)

今回は和了判定の一歩手前、メンツが完成しているかどうかの判定ロジックを作成しました。

なお、現在進行中でプログラムを書いているため、バグっているかもしれませんのでご注意ください。

データ定義

ラリコン作っている時に知ったのですが、実はAVRマイコンは8bitマイコンだったのでした。
つい「今どきふつーならintは32bitの16bitマイコンだろ?JK」と思い込んでいてハマってました^^;
ということはですよ、アセンブラちゃんと見てないので良くわかりませんが、レジスタの都合など考えると8bitで処理したほうが何かとお得なような気もします。
そこでプログラム中では

u8(unsigned char) s8(signed char)

を多用しています。
オーバーフローに注意しましょうm(_ _)m

あと、牌については素直に

  • 萬子(1~9)
  • 筒子(10~18)
  • 索子(19~27)
  • 風牌(28~31)
  • 三元牌(32~34)
    ※0は未定義ということで、セオリー通りやってます。

とコードするということで。
0~34なので、6bitで十分表せるということですな。
また、黙ってソートしてやれば理牌できるっちゅうことでもあります。

メンツ完成している?

さて、麻雀1
麻雀は14枚2の牌で役を作るゲームです。
役は七対子国士無双などの例外を除き、

にて構成されています。
最低限、この組み合わせになってないと役として成立しません
ということは、まずは手牌がこの組み合わせになっているか調べる必要があります。
みんな大好き正規表現を使うと、

[TKS][TKS][TKS][TKS][TKS]

みたいな感じか?

ただし、対子は1つの役に1つしか現れません。
しかも都合が悪いことに、対子は牌2枚、刻子順子は牌3枚で構成されています。
素直に頭から読んでってもダメっちゅうことですな。 まあ、

111 222 333 44 555

みたいなヤツだと、

123 123 123 44 555

だったり、

123 11 234 234 555
11 123 234 234 555

だったりしますから、総当りするしか無さそうですね。
みんな大好きツリー構造図で書くと

となりますわな。
これが辿れれば成立、辿れなければ不成立ということで、簡単に再帰で書けそうです。
再帰で書くとメモリ食って遅いというイメージですが(そうでもない?)今回はたかだか最大5段程度のネストなので良いことにしましょう。

ちなみに昔私が買っていじってたPC-9801DX12MHz駆動の80286CPUを搭載し、2.6MIPS程度の性能だったそうです(ちなみに318k円)。
こんなちっこいArduboy16MHzのATmega32U4CPUを搭載して、16MIPSで動作するという、CISC/RISCの違いはあれど隔世の感がありますね・・・(←ジジィの昔話)

Anyway, コーディングを始めましょう。
長いので構造のみこちらで。

// 解析中手牌
u8 g_analyze[TEHAI_NUM];

// 1メンツ判定
#define PI_ANALYZE_MAX  64      // 最大バッファ(最大126=0x7e)
#define PI_LEAF   0x80          // 末端マーク
#define PI_ROOT   0xff          // ルートマーク

u8 g_pindex[PI_ANALYZE_MAX];    // 解析結果
u8 g_piRoot[PI_ANALYZE_MAX];    // 親ノード保持
u8 g_bToitChk;   // 対子チェック済みマーク
u8 g_piLast;    // g_pindexの末尾

bool recIndex (u8 root, u8 index) { ... }
void initAnalyzeMent(const u8* pai = NULL) { ... }

// 最大5段程度の再帰にしかならないので深度優先検索を採用します
// root g_pindexに対するインデックス(初期値PI_ROOT)
// now  g_analyzeに対する牌解析位置(初期値0)
bool analyzeOneMent (u8 root, u8 now)
{
  if (now >= TEHAI_NUM) {
    // 1ルート探索終了
    // 末端マーク
    g_piRoot[root] |= PI_LEAF;
    return true;
  }

  // 残り1枚か?
  if (now == TEHAI_NUM - 1) {
    // もはやメンツは作れない
    return false;
  }

  // 対子か?(対子は1解析セットに雀頭1つしかない)
  bool res = false;
  if (!g_bToitChk && isToit(now)) {
    // インデックス記録
    if (!recIndex(root, PI_TOIT | PAI_KIND(g_analyze[now]))) {
      return false;
    }

    // 深度優先検索
    g_bToitChk = true;
    if (!analyzeOneMent(g_piLast - 1, now + 2)) {
      // このルートは無い
      g_piLast--;   // 記録を消す
    } else {
    // このルートで最後まで解析できた
      res = true;
    }
    // 探索を続ける
    g_bToitChk = false;
  }

  // 刻子か?
  if (isKot(now)) {
    // インデックス記録
    if (!recIndex(root, PI_KOT | PAI_KIND(g_analyze[now]))) {
      return false;
    }

    // 深度優先検索
    if (!analyzeOneMent(g_piLast - 1, now + 3)) {
      // このルートは無い
      g_piLast--;   // 記録を消す
    } else {
    // このルートで最後まで解析できた
      res = true;
    }
    // 探索を続ける
  }

  // 順子か?
  if (now >= TEHAI_NUM - 2) {
    // 2枚組なので違う
    return res;
  }


  // そもそもMAN/PIN/SOUの1~7でないと駄目
  if (!isShuntTop(now)) {
    return res;
  }

  // スタック節約のために分かりにくいループにしてあります
  // 2枚目を探す
  // 同じ数字は省く
  u8 p2, p3;
  for (p2 = now + 1;;) {
    u8 a = PAI_KIND(g_analyze[now]);
    u8 p = PAI_KIND(g_analyze[p2]);
    if (a == p) {
      if (++p2 >= TEHAI_NUM - 1) {
        // 残り牌が無い
        return res;
      }
      // else continue;
    } else {
      // a != p
      if (a + 1 == p) {
        break;    // 三枚目を探す
      } else {
        // 順子ではない
        return res;
      }
    }
  }

  // 3枚目を探す
  // 同じ数字は省く
  for (p3 = p2 + 1;;) {
    u8 a = PAI_KIND(g_analyze[p2]);
    u8 p = PAI_KIND(g_analyze[p3]);
    if (a == p) {
      if (++p3 >= TEHAI_NUM) {
        // 残り牌が無い
        return res;
      }
      // else continue;
    } else {
      // a != p
      if (a + 1 == p) {
        break;    // 順子だ
      } else {
        // 順子ではない
        return res;
      }
    }
  }

  // 順子だ
  // インデックス記録
  if (!recIndex(root, PI_SHUNT | PAI_KIND(g_analyze[now]))) {
    return false;
  }

  // 面倒だけど牌を入れ替える
  swapPaiForShunt(now, p2, p3);

  // 深度優先検索
  if (!analyzeOneMent(g_piLast - 1, now + 3)) {
    // このルートは無い
    g_piLast--;   // 記録を消す
  } else {
  // このルートで最後まで解析できた
    res = true;
  }

  // 牌を戻す
  restorePaiForShunt(now, p2, p3);

  return res;
}

ソースはgithubのmajan.inoにあります。

やっているのは、ツリーに沿って片っ端(左端)からこの牌が対子になるか、刻子になるか、順子になるかをチェックしているだけです。
そして、メンツになったならばg_pindex[]というバッファに記録して、再帰で下層を探索に行きます。

ちょっとトリッキーなのは順子の探索でしょうか。 手牌はソートされている前提なので、

333444555

みたいな形がありえます。
そこで、同じ数牌はスキップしながら345という順子を見つけたならば、

345 334455

強引に並べ替えて下層の探索に行き、帰ってきたら元の形(333444555)に戻しています!

まるで、80286のプロテクトモードからリアルモードに復帰してくるのにCPUリセットするようなノリで、インテルの人に

「なんと野蛮な・・・」

と呆れられてしまうことでしょう3

動かしてみる

とりあえずできたんで、動かしてみます。
確認はデバッグシリアルを使いましょう。
じゃあ、サンプルにさっきの、

111 222 333 44 555

を使いましょう。
動かしてみると

と表示がされます。
おお、動いてる。 トシちゃんかんげきー(棒)

下の4行+1行が解析結果です。

S1S1S1T4K5
S1T1S2S2K5
K1K2K3T4K5
T1S1S2S2K5
4pattern

彼が言ってることを翻訳すると、

123-123-123-44-555
123-11-234-234-555
111-222-333-44-555
11-123-234-234-555
の4パターン

ということですね。確かに合ってます。
const unsigned char test_tehai[] をいじって他のパターンも試してみましょう。

111 222 333 44 556

K1K2S3T3S4  
K1K2T3S3S4  
2pattern

111 222 333 44 557 だと

no ten

と、にべもなく回答してくれます。

まだバグっているかもしれません。
他にもいろいろ試してみましょう。

次どうする

ここまでできたら、ほとんどできたようなものです!(本当か?)
次は、役になっているかどうかの判定を作りたいと思っています。
ハードコーディングしてやれば簡単に実装できそうに思うのですが、それではあまり面白くないので別の(正規表現を使うとかそういった類の)アプローチが無いかな?と考えています。

しばしお時間をいただけますと幸いです。


  1. 麻雀の基本的なルールや単語は適当に知ってください!

  2. 槓子は後で考えます!(2度め)

  3. 嘘か本当か知らないが、80286用にプロテクトメモリを使うEMS規格か何かを策定する時に、インテルから「どうやってリアルモードに戻るのか?」と聞かれたマイクロソフトプログラマが迷わず「CPUをリセットする」と答えたら「ソフト屋は野蛮ですな」と言われたとか何とか

Arduboyで麻雀ゲーム作る話(1)

Arduinoに小さいディスプレイと十字+ABボタンつけた製品?があります。 その名もArduboy

どっかで会うてなかったかワレ?
名刺より小さいですね!
UE/Unity用モデル制作や各種プログラム制作など雑多にやってる弊社もよろしくお願いします
昔、キックスターターで購入して、ちょっと遊んで打ち捨ててあったのですが、これが持ち歩くのにちょうど良くて、仕事部屋でもダイニングでも出張先でもArduinoプログラムができてしまう優れものだと気が付きました。
どこでも開発できちゃうぜ!ヒャッハー

んじゃ、なんか作るか (←いつもどおり手段と目的を履き違えている)
それにしても小さいディスプレイ(1.3インチOLED 128x64)やの、ワレ
せや!この狭いとこに小さい牌を並べたら絶対ワシらの老眼では見えへんやろ!
ジジィ発見器に使えるし、麻雀作ったろ!

という軽いノリから作ることにして、ちょっとどこまで作りきれるかわかりませんが、できるところまでやってみようかと思っています。

イメージの準備

麻雀といえば和了に14枚の牌が必要1なので、横128ドットの液晶に並べるとすると最大幅で横9ドットになります。 牌と牌の間には仕切り線が欲しいので、中身は横8ドットで描くことになります。 高さはちょっと余裕があるので11ドットで描きます。
ドット絵作成は便利なサイトがあったので、こちらを使いました。
ミニドット絵メーカー3

またイメージはプログラム中に持つことになるので、Cの配列形式になっていると都合が良いです。 これまたArduboyに特化した便利なページがあったので、使わせてもらいました。
Image Converter

通常、ゲームに必要な麻雀牌は

  • 萬子1~9
  • 筒子1~9
  • 索子1~9
  • 風牌(東南西北)
  • 三元牌(白發中)

の34種類2。 それに

  • 牌の外枠(10x15dot)

を作成しました。 githubにgifファイルと.hファイルをまとめた.zipを放り込んでおきました。

プログラム

せっかくデータを作ったので、早速表示してみます。
Arduboyの開発には専用のライブラリが必要なのでインストールします。
このあたりが詳しいです↓
Arduboy

とりあえず表示できれば良いので、setupにいろいろ書きます。

void setup() {
  // initiate arduboy instance
  arduboy.begin();

  // here we set the framerate to 10, we do not need to run at
  // default 60 and it saves us battery life
  arduboy.setFrameRate(10);

  //   arduboy.clear();
  arduboy.fillRect(0, 0, 128, 64);
  for (int i = 0; i < 9; i++) {
    arduboy.drawBitmap(i * 9, 0, img_back, 10, 15, BLACK);
    arduboy.drawBitmap(i * 9 + 1, 2, &img_manzi[i * 16], 8, 11, BLACK);
  }
  for (int i = 0; i < 9; i++) {
    arduboy.drawBitmap(i * 9, 14, img_back, 10, 15, BLACK);
    arduboy.drawBitmap(i * 9 + 1, 16, &img_manzi[(i+9) * 16], 8, 11, BLACK);
  }
  for (int i = 0; i < 9; i++) {
    arduboy.drawBitmap(i * 9, 28, img_back, 10, 15, BLACK);
    arduboy.drawBitmap(i * 9 + 1, 30, &img_manzi[(i+18) * 16], 8, 11, BLACK);
  }

  for (int i = 0; i < 4; i++) {
    arduboy.drawBitmap(128 - 36 + i * 9, 0, img_back, 10, 15, BLACK);
    arduboy.drawBitmap(128 - 35 + i * 9, 2, &img_manzi[(i+27) * 16], 8, 11, BLACK);
  }
  for (int i = 0; i < 3; i++) {
    arduboy.drawBitmap(128 - 36 + i * 9, 14, img_back, 10, 15, BLACK);
    arduboy.drawBitmap(128 - 35 + i * 9, 16, &img_manzi[(i+31) * 16], 8, 11, BLACK);
  }
  arduboy.display();
}

なお、

  • img_backが牌の外形のイメージ
  • img_manziが牌の中身イメージの配列(manziといいながら筒子から索子から何から全部突っ込んであります)

になります。
ちなみに8×11のイメージで16バイト使っているようです。11バイトでいいように思うんですが、なぜでしょう?
とりあえず動けば良いので、あんまり突っ込んで調べていません^^;(←キモい顔文字)

出力イメージはこちら↓

なお、Arduboyは本来白黒反転液晶なので、何もしないと黒バックに白文字になります。 個人的に見づらくてかなわんので、わざと白黒を逆転させています。

俺は黒白が好きだぜ!という人はソースコード

arduboy.fillRect(0, 0, 128, 64);

arduboy.clear();
に(いまコメントアウトされてますね)、
また
arduboy.drawBitmap(ほにゃらら, BLACK);
となっているところを
arduboy.drawBitmap(ほにゃらら);
にしてあげてください(,BLACKを削除する)。

次回、テンパイかどうかの判定ロジック(の前段階)を組みます。 というか、ここまではもう組んでいるのでアップするだけです!
その後はまだこれからなので、ぼちぼちやろうと思っています。
※解説なんかいらねーぜ!いま見せろって方は、上の表示部分含めてgithubにmajan.inoがあります。


  1. とりあえず槓子は後で考えます!

  2. 赤5・花牌は考えません!