このエントリーは Siv3D Advent Calendar 2017 – Qiita の 12 日目のエントリーです。前のエントリーは、para7 さんの OpenSiv3Dのトランプ描画機能(PlayingCard) です。
LattePanda とは
唐突ですが、LattePanda という組み込みボードがあります。組み込みボードというとRaspberry Pi が有名だったり、もっと小さくやろうと思うと Arduino が良かったりというのもあるのですが、Raspberry Pi は OS が Rasbian という Linux であったり、Arduino の場合はそもそも OS が無かったりという特徴があります。じゃあ、LattePanda はなんじゃ?というと、
- Arduino のような GPIO ピンを持っていて
- Windows 10 が動く
という特徴を持つボードです。
まあ、さっそく種明かしをしてしまえば、Windows 10 が動くボードと Arduino が I2C でくっついているだけのボードなので、普通のノート PC と Arduino を USB ケーブルで接続してしまえば同じことができます。中古なノートPCを買って、格安な Arduino を買えばモニタ付きで(当然 LattePanda にはモニタがありません)組み込みな環境が整えられるので、じゃあ LattePanda を使う意味があるのか?という問題は多々あるのですが、
- 大きさがラズパイ並みに小さい(ラズパイよりも一回り大きい程度)
- 電源が USB micro で 5V2A 程度で動く
- モニタが HDMI で繋げる
- フルな Windows 10 が動く
大きさがラズパイ並みということはモニタを必要としない場合には、展示品の脇において制御するときなど場所が小さくて済みます。電源もノートPCのような特殊なアダプタを必要とせずスマートフォンの充電器で十分です。ちなみに、スマホのモバイルバッテリで動かすこともできます(もちろん、バッテリー切れになると OS ごと落ちるので、かなり難点ですが)。
また、HDMI コネクタがついているので、大抵のモニタならば行けます。秋ごろに学研ワールドアイという球型モニタで試してみましたが十分いけました。
そして、なんといってもフルで Windows 10 が動くということでしょう。ラズパイ+Windows IoT Core という組み合わせもあるのですが(私としては、それはそれで使いようがあるのだけれど)、この小ささでフルの Windows 10 が動くのであれば動作するアプリを UWP アプリにしなくても、Windows アプリのままいけるということです。
価格としては秋月 http://akizukidenshi.com/catalog/g/gM-12549/ で 14k 位、直で DFROBOT https://www.dfrobot.com/product-1405.html から買うと $120 位なのであまり変わりません。私が買ったときは、直で買うしかなかったので郵送料が掛かりましたがあまり値段は変わっていないハズです。
ということは Siv3D が動くということだな
フルで Windows 10 が動くということは DirectX とかなんか適当な GPU 絡みのなんとかも動くはずです。ためしに、OpenSiv3D のサンプルコードをダウンロードを動かしてみたのが、これです。
球型モニタを使っているので走馬燈のような不思議なものができますね。まあ、走馬燈のほうが相当安いんですが。それは言わない約束で。
という訳で、こういう小型のボードと OpenSiv3D あるいは Siv3D の組み合わせると、ユーザーインターフェース記述が手軽な Siv3D とボードの小ささ(裏に隠れるという意味で)の相乗効果で何か新しい道が開けるのではないか?(新しくなくてもいいけど)と思った次第なのです。
じゃあ、具体的に何か?ってのをプログラマならばコードを書かねば、と思ってたところなのですがね、ちょっと先週の土曜日に子供からインフルエンザを移されたらしく沈没しておりまして、現在も沈没中。でもって、寝ながら考えていたネタが、hota1024 さんの Siv3Dで15パズルを極限までハイクオリティーにしてみる にネタ被りしてしまったという。要はアズレンの16パズルを16パズルにするというネタだったのですが…どうしたものか。
そんな訳で、形だけでもバトン的に AkiraKoizumi さんの「Siv3Dでグラデーション文字を作る」まで繋げておきます。完治したら、ここの記事を増やしておきますので、しばしお待ちを、では。
追記 2017/12/30
帰省中に、えいっと25駒のスライドパズルを作りました。LattePanda を持ってくるのを忘れてしまったので、LattePanda で動かす場合には、ってのは後から書きますが。ひとまず動作するコードだけをざっと。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 | # include <Siv3D.hpp> // OpenSiv3D v0.1.7 // 画像を指定位置でクリップする s3d::Image &clip( const s3d::Image &src, s3d::Image &dest, int l, int t, int w, int h) { for ( int y = 0; y < h; y++ ) { for ( int x = 0; x < w; x++) { dest[y][x] = src[t + y][l + x]; } } return dest; } /// スライドパズルのボードクラス class Board { int xmax; int ymax; int cellWidth, cellHeight; std::vector<Texture*> *_cells; std::vector<Texture*> *_goal; Image *_image; Texture *_textrueOriginal; Texture *_blank; public : /// ボードの初期化 Board( int width, int height) { xmax = width; ymax = height; _cells = new std::vector<Texture*>(xmax * ymax); } public : /// 画像を設定する void setImage( const wchar_t *path) { _image = new Image(FilePath(String(path))); _textrueOriginal = new Texture(*_image); cellWidth = _image->width() / xmax; cellHeight = _image->height() / ymax; Image imageTemp(cellWidth, cellHeight); for ( int y = 0; y < ymax; y++) { for ( int x = 0; x < xmax; x++) { clip(*_image, imageTemp, x * cellWidth, y * cellHeight, cellWidth, cellHeight); _cells->at(x + y * xmax) = new Texture(imageTemp); } } // 25枚目だけ黒で塗りつぶし const Image imageBlank(cellWidth, cellHeight, Color(100, 100, 100)); _blank = _cells->at(xmax*ymax -1) = new Texture(imageBlank); // 最初の配置を覚えておく _goal = new std::vector<Texture*>(); for (auto it : *_cells) { _goal->push_back(it); } } /// シャッフル void shuffle() { std::random_device seed_gen; std::mt19937 engine(seed_gen()); std::shuffle(_cells->begin(), _cells->end(), engine); } /// 完成かどうかをチェックする bool IsGoal() { bool same = true ; for ( int i = 0; _cells->size(); i++) { if (_cells->at(i) != _goal->at(i)) { same = false ; break ; } } return same; } // ブランクセルの位置を取得 s3d::Point GetBlank() { for ( int y = 0; y < ymax; y++) { for ( int x = 0; x < xmax; x++) { if (_cells->at(x + y * xmax) == _blank) { return s3d::Point(x, y); } } } // 本来はここには来ない return s3d::Point(0, 0); } // ブランクのセルを動かす void MoveTo( int mx, int my) { s3d::Point pt = GetBlank(); s3d::Point pt2 = s3d::Point(pt.x + mx, pt.y + my); // 移動先がはみ出ていないこと if (pt2.x < 0 || pt2.x >= xmax) return ; if (pt2.y < 0 || pt2.y >= ymax) return ; // セルを移動させる _cells->at(pt.x + pt.y * xmax) = _cells->at(pt2.x + pt2.y * xmax); _cells->at(pt2.x + pt2.y * xmax ) = _blank; } /// キー操作を処理する void InputKey() { if (s3d::KeyUp.down()) this ->MoveTo( 0,-1); if (s3d::KeyDown.down()) this ->MoveTo( 0,+1); if (s3d::KeyLeft.down()) this ->MoveTo( -1, 0); if (s3d::KeyRight.down()) this ->MoveTo(+1, 0); } /// マウス操作を処理する void InputMouse() { if (s3d::MouseL.down()) { auto m = Cursor::Pos(); auto b = s3d::Point( GetBlank().x * cellWidth, GetBlank().y * cellHeight ); if (b.y > m.y && ( b.x <= m.x && m.x <= b.x + cellWidth )) this ->MoveTo(0, -1); if (b.y + cellHeight < m.y && (b.x <= m.x && m.x <= b.x + cellWidth)) this ->MoveTo(0, +1); if (b.x > m.x && (b.y <= m.y && m.y <= b.y + cellHeight)) this ->MoveTo(-1, 0); if (b.x + cellWidth < m.x && (b.y <= m.y && m.y <= b.y + cellHeight)) this ->MoveTo(1, 0); } } /// 現在のボードを描画する void Draw() { for ( int y = 0; y < ymax; y++) { for ( int x = 0; x < xmax; x++) { _cells->at(x + y * xmax)->draw(x * cellWidth, y * cellHeight); } } } /// 現在のボードを描画する void GoalDraw() { _textrueOriginal->draw(); } }; void Main() { Graphics::SetBackground(ColorF(0.8, 0.9, 1.0)); const Font font(50); // ボードの作成 Board board(5, 5); // ボードに画像を読み込む #define IMAGE L"C:\\Users\\masuda\\Pictures\\IMG_0139.jpg" #define IMAGE L"C:\\Users\\masuda\\Pictures\\azurlane001.jpg" board.setImage(IMAGE); // 配列をシャッフル board.shuffle(); while (System::Update()) { // すべて揃っているか? if ( board.IsGoal() ) { // 揃っていれば、ゲーム終了 board.GoalDraw(); font(L "GOAL, Siv3D!" ).drawAt(Window::Center(), Palette::Black); continue ; } // キー入力 board.InputKey(); // マウス入力 board.InputMouse(); // 配列に従って、画像を表示 board.Draw(); font(Cursor::Pos()).draw(20, 400, ColorF(0.6)); Circle(Cursor::Pos(), 30).draw(ColorF(1, 0, 0, 0.5)); } } |
コード自体は、200行程度です。あまりにも雑に書きすぎてしまったので、クラスはボードクラス(Board)しかありませんが、まあ、ほどよく動作します。中で、std::shuffle してしまっているので本当にスライドゲームが完成するのかは不明…というひどいゲームですが。
実は、OpenSiv3D をきちんと書くのは(たった200行ですが)、これが初めてで、12/30の午前中に初めて、途中で買い物に行って夕方にクラス化するというパターンで、調べ調べしながら4時間ほどで作っています。思うに、この手のゲームのようなものは自分の思ったように作ることが重要で、あれこれなプログラミングなテクニックは後から追加していったほうが良いのでは?と思っています。仕事自体はシステム屋さんなところが多いので、設計してからコーディング(とはいえ、紙に設計するのではなく疑似コードレベルですが)というパターンが多く、数時間で思ったようなものを作るという機会は仕事ではなかなかありません。OpenSiv3D はそういうところで、手軽に使える「道具立て」ではないかな、と思っています。
プログラムは実行するとこんな感じです。
完成すれば、こんな感じになるはず。
C++ なのにメモリを解放していないという悪さ(苦笑)はありますが、そのあたりきちんとデストラクタを作って処理するか、const 使いつつスコープ内での自動解放な機能を使えばもう少し安全なコードになるはずです。できるだけ表にポインタが出ないのが Siv3D の良いところではないか、というのにポインタを使いまくっているのがアレなのですが、main 関数だけ見れば、なんかすっきりしているという具合でしょうか。そのあたりは、C++ なオブジェクト指向のよいところ。