スプーキーズのちょっとTech。

SPOOKIES社内のより技工的な、専門的なブログページです。

Are you Spookies?? 機械学習で顔の画像認識をしよう

最近、アニメを見まくっているIoT開発部の西村です。
こんにちは。
感動シーンでナミダしている僕の横で、犬がうるせーなって顔してるのがたまらないです。
どうぞよろしくお願い致します。

課題・モチベーション

ということで 人の顔を機械学習で覚えさせ、メンバーが来たかもよ君にバージョンアップだ!

Spookiesメンバーの顔画像認識パターンモデルを作ってみる

顔画像の収集

毎日顔を合わせて一緒に仕事してますが、いざメンバーの顔写真が必要となっても、あまり持っていないもんです。 携帯で何枚か撮ったかもしれませんが、そんなに数は無いですよね。

合宿等の写真を拾い集める

会社の共有フォルダーに、合宿の写真が入ってました。

  • 沖縄合宿
  • 和歌山合宿
  • 淡路島合宿

こちらを使いましょう。

DLしてフォルダを開くと、こんな写真がいっぱいあります。

f:id:spookies-nishimura:20171014200226p:plain

顔画像のみ抽出

さて、ここから顔画像を取得しましょう。
一つのフォルダの中に全ての画像をぶち込んで、OpenCVを使って画像を切り出しましょう。

OpenCVには、画像から顔を抽出するCascade Classifier=分類器と呼ばれるものが公開されてるので、それを使って顔だけを切り出します。

さらにデータが少ないので、反転・コントラスト調整を行い、画像の水増しを行います。

寺師君の結果
f:id:spookies-nishimura:20171014200300p:plain

ではこれらのデータを使って、モデルを作ります。

モデルの作成

機械学習ライブラリといえば、TensorFlow
但し、素人にはなかなか扱いづらい為、日本語ドキュメントが完備されているラッパーライブラリの Keras を使いました。

from keras.models import Sequential
from keras.layers import Dense, Activation

# 画像データ読込(txtファイルは画像パスとラベルを設定した一覧)
(X_train, y_train)= train_input.read_data('./face_detect/train.txt') # X_train: 学習データ, y_train: 学習ラベル
(X_test, y_test)= train_input.read_data('./face_detect/test.txt') # X_test: テストデータ, y_test: テストラベル

### モデル定義 ###
# Sequentialはただ層を積み上げるだけの単純なモデル
model = Sequential()

model.add(Conv2D(32, (3, 3), padding='same', input_shape=X_train.shape[1:])) # 2次元畳み込み層
model.add(Activation('relu'))   # 活性化関数(レイヤをつなぐもの)
model.add(Conv2D(32, (3, 3)))
model.add(BatchNormalization()) # 誤差・分類性能向上の層
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))   # レイヤ縮小を行い、扱いやすくする層
model.add(Dropout(0.25))    # 過学習の防止

model.add(Flatten())    # 入力を平滑化
model.add(Dense(512)) # 全結合(クラス分類)
model.add(Activation('relu'))
model.add(BatchNormalization())
model.add(Dropout(0.5))
model.add(Dense(nb_classes)) 
model.add(Activation('softmax')) # 活性化関数

# compile
model.compile(loss='categorical_crossentropy',  # 損失関数
              optimizer='adam', # 最適化関数
              metrics=['accuracy']) # 評価指標のリスト

### 学習させる ###
model.fit(X_train, Y_train,
          batch_size=batch_size,
          nb_epoch=nb_epoch,
          validation_data=(X_test, Y_test),
          shuffle=True,
          callbacks=[csv_logger, cp_cb, stopping])

...

これでモデル作成です。(MacBookPro で 2時間くらいかかりました)

実験

では、早速できたモデルを使って画像認識してみました。

f:id:spookies-nishimura:20171014203702p:plain:w300 (パジャマですみません)

oops!!!! takanoになってる!

京都オフィスでも何名か試してもらいましたが、だいたい takano になってしまいました orz

原因

今回、精度が出なかった原因としては

  • 元画像の質が悪すぎる
  • 学習データ量が圧倒的に少ない

ですかね。

まとめ

残念ながら現在は takano が来たかもよ君になってしまいましたが、新しい事をするのは凄く楽しいですね。
引き続き精度を向上を図り、ラズパイにも組み込みたいと考えております。 f:id:spookies-nishimura:20171014200226p:plain

イエェェェイ!

f:id:spookies-nishimura:20171014193126j:plain:w100

labs.spookies.co.jp

プログラミングをやってみよう

プログラミングに興味はあるけど何やったらいいの?

こんにちは、ももです。

スプーキーズで働きはじめて1ヶ月。

ちょっとプログラミングの話もしてみたいなと思っておりました。

ただ、スーパープログラマではないひよっこですので技術的なことはまだまだです。

そこで今回は、プログラミング初心者である私がどんなサービスを利用してきたのかご紹介したいと思います。

私は、基本的にはWEBで提供されているものを利用していました。

プログラミングをやってみたいけど...

何したらいいかわからない

どんなことするの?

状態の方はまずは無料のWEBプログラミング学習サービスを利用してみましょう。

有名なものは

・ドットインストール

https://dotinstall.com/

・Progate

prog-8.com

・CODEPREP

codeprep.jp

でしょうか

私はこの3つをまんべんなく利用しています。

こちらは基本無料で、興味が湧いてきたら、月額の有料会員に切り替えが可能になります。

私は、ドットインストールとCODEPREPは無料会員ですが、Progateは有料会員です。

3つもあるけどどれがいいの?と思われるかもしれませんが

オススメはProgateとCODEPREPです。

ProgateかCODEPREPから始めると、プログラミングに必要な環境が整った状態ですので

簡単にプログラミングを体験することができます。初心者にはわかりやすいと思います。

私はとてもわかりやすかったです。

無料の部分でまずはプログラミングってどんな感じか試してみましょう。

触ってみると自分がプログラミングが好きか嫌いかもわかるのではないかと思います。

次に、ドットインストールですが、こちらは一回3分の動画をみる形の学習スタイルになります。

私がドットインストールに大変お世話になったのは、開発環境の構築です。

ドットインストールのいいところって隣でプログラミングを教えてくれてる感じがあるんですよね。

実際に画面上のどの部分を選択するのだとか、動作がとてもわかりやすいです。

私は、いろんなものを試したかったのでまんべんなくスタイルですが

一つに絞ってやるのも、もちろん人それぞれだと思います。

プログラミングに慣れてくるときっとWEBサービスだけでは、物足りなくなってくると思います。

そんな時がくれば、興味のある言語を選んで参考書を買ってみましょう!

もっと楽しい世界が広がると思いますよ!!私もそうでした

ちなみにスプーキーズでは、会社の参考書の貸し出しも行っていて自由に借りて、勉強できる制度も整っています。

プログラミングに興味があって勉強する意欲のある方!ぜひ、一緒にスプーキーズで働いてみませんか?

スプーキーズでは、一緒に働く仲間を募集しています!

f:id:spookies_momo:20170928191623j:plain

JavaScriptを高速化する5つの手法

こんにちは、モリタです。

JSを高速化 する

f:id:moritanian:20170926151527p:plain:w160

nativeに比べて遅いと言われるjsですがパフォーマンスチューニングによりずいぶん改善しているようです。しかしながらWebGLなどまだまだ高速化が必要な分野も数多くあります。そこで、今回はjavascriptを高速化する手法を簡単に見ていきたいと思います。

Typed Array

動的な配列であるjsの通常の配列に対し、固定長の配列であるTypedArrayが用意されています。低機能ですが、各要素に高速でアクセスする事ができます。以下のようにまずサイズを決めてバッファを確保してからTypedArrayを生成します。 canvasの getImageData () で取得できる画像データもTypedArrayのようですね。

var buffer = new ArrayBuffer(8); // 8byteのサイズのバッファ確保
var arr   = new Int32Array(buffer); // 32bit(4byte)のint配列
console.log(arr); // [0, 0]

WebAssembly

動的型付け言語であるJsは実行時に型チェックや型変換が行われ、それがネイティブに比べて遅くなる原因のひとつになっていました。そこでMozillaからasm.jsという手法が提案されました。asm.jsでは全ての変数が静的な型付けを行います。TypedArrayの考え方をさらに推し進めたものと言えるでしょう。

このasm.jsに対して各ブラウザベンダーが協力して策定しているバイナリフォーマットの規格がwebAssemblyです。 webAssemblyを利用するにはc, c++ コードをEmscriptenツールによって.wasmバイナリに変換するのが一般的なようです。  各ブラウザの実装状況は以下のようにデスクトップではほぼすべてのブラウザ最新で実装されています。 f:id:moritanian:20170926114309p:plain

また、unityのwebGLビルドでもオプション設定でwebAssemblyを使用できるようです。

WebWorker

jsは本来シングルスレッドで動作しますが、webWorkerを利用することでマルチスレッド動作を実現できます。 以下のように new Worker("ファイル名") で実行することができます。 また、postMessage() , onmessage() でスレッド間でデータをやり取りできます。

main.js

var myWorker = new Worker("worker.js"); // worker.js  を別スレッドで実行
 myWorker.postMessage([first.value,second.value]); // Sending message as an array to the worker
myWorker.onmessage = function(e) {
        result.textContent = e.data;
        console.log('Message received from worker');
    };

worker.js

onmessage = function(e) {
  console.log('Message received from main script');
  var workerResult = 'Result: ' + (e.data[0] * e.data[1]);
  console.log('Posting message back to main script');
  postMessage(workerResult);
}

参考

github.com

gpgpu

gpgpuとは本来画像処理に使われるgpuの計算資源を画像処理以外の演算に使うことです。gpuは行列計算など計算密度が大き(条件分岐などが少ない)く、並列度が大きい演算に関してCPUに比べて性能を発揮します。最近は機械学習が利用例としてホットですね。 gpgpuを利用するために以下のライブラリがあります。 gpu.rocks

github.com

gpu.jsでは以下のように記述できます。

const gpu = new GPU();
const multiplyMatrix = gpu.createKernel(function(a, b) {
  var sum = 0;
  for (var i = 0; i < 512; i++) {
    sum += a[this.thread.y][i] * b[i][this.thread.x];
  }
  return sum;
}).setOutput([512, 512]);

const c = multiplyMatrix(a, b);

Simd

こちらも並列化により高速化を実現する試みの一種で命令セットレベルの並列化を実現します。 以下ではベクトル演算を並列化しています。

var a = SIMD.Float32x4(1, 2, 3, 4);
var b = SIMD.Float32x4(5, 6, 7, 8);
var c = SIMD.Float32x4.add(a,b); // Float32x4[6,8,10,12]

残念ながらEcmascriptでまだドラフト段階のためリリース版ブラウザの実装はまだのようです。

番外編

javasciptで演算子オーバーロードしたい

高速化とは全く関係ありませんがみなさんも一度はjsでも演算子オーバーロードしたいと思ったことがあるはずです。いや、そんなことはないですね。 少し気になったので調べてみたのですが結論としては、 【Javascript】演算子をオーバーロードしたい話 - Qiita にある通り普通のやり方では実現できずコードを構文解析して演算子オーバーロードしている箇所を書き換える必要があるようです。 そこで実際に実装してライブラリ化してみました!

GitHub - moritanian/minamike.js: このライブラリに過度な期待はしないでください

f:id:moritanian:20170926124329p:plain

このライブラリでCのstd::cout 風に記述するとこのようになります

const cout = "std::cout";
String.prototype.__operators__ = {
    "<<" : function(s1, s2){
        console.log(s2);
    }
  };
 
 
 cout << "Hello World!";

まあ、全く需要なさそうですが。

それではよきjsライフを!

Web Audio APIを用いた自動作曲

こんにちは、ロックです!

今回は先々週ぐらいに社内で行った勉強会の内容となります。

ブログを書く手番が回ってきたので、書いていきたいと思います。

概要

  • 日頃からの思いとして、音楽に携わるプログラミングも興味があった
  • 最近AIがブーム

ということで、プログラミングを通じて作曲できないかな?

さらにAIブームに乗って、作曲の自動化とか行けそうではないだろうか。

何を使って実現する?

普段の趣味ではC言語なので、C言語ベースでやろうと思いましたが、

Webで簡単に音を出せるらしいので、そちらで実現していきます。

Web Audio API

HTML5 で音関連も強化されているようです。

ノード同士を繋げていく仕組み

仕組み解説(音量操作の例を通して)

1. 音を出す

音の発生源(node)出力(node) に繋げた(connect)状態で再生(start)を行うと音が聞こえてくる。

f:id:ishiyamacocoa:20170919164857p:plain

2. 音量を調整してみる

音量(node)を間に挟むことで音量を調節できる。

f:id:ishiyamacocoa:20170919164901p:plain

3. 複数音を鳴らしたい!

新しい音の発生源2(node)音量(node)に繋げればok

f:id:ishiyamacocoa:20170919164905p:plain

4. 音の発生源1だけ少し音量を上げたいなあ

音の発生源ごとに音量(node)を作り、全体音量(node)と繋げるようにするとok

マスターボリュームと個別のボリューム!!

f:id:ishiyamacocoa:20170919164909p:plain

以上のようにノードを繋げていく。

音の発生源から出力の間にいろいろ挟むことで楽しくなる。

音程を求める

440Hzが世界標準でラ(A)の音

※例えばドの音は262Hz

周波数を求める式として MIDI tuning standard というもの存在する

2note_number - 69/12・440Hz

※note_number = MIDI のノート番号(0 ~ 127)

ノート番号で言うと、69が440Hzのラ(A)の音である

参考URL :MIDIノート番号と音名、周波数の対応表

無限に鳴らす

音も出せるようになり、音程も操れるようになったので、無限に鳴らせれば任務完了です。

今回は javascript を使っているので、 setInterval() でok

ちょっと音楽っぽくする

和音というものがあります。

いわゆる ドミソ

ピアノで言う左手(コード、ベース)

音楽といえばメロディもありますよね

ランランララランランラン♪

ピアノで言う右手(メロディ、コード)

和音

  • メジャーコード
  • マイナーコード

を選べるようにしておきました。

メジャーコードを選んだら全てメジャーコードに

マイナーコードを選んだら全てマイナーコードになります。

メロディ

和音は3つの音で構成されています(4つとかもあるが)

メロディはこの和音に出てくる音の3つのうちからランダムで流れるようにしてます。

これだけで案外メロディっぽくなるものなのです。

設定項目

  • スケール(今回は全て長調)
  • 和音(メジャーかマイナーを選べる)
  • オクターブ
  • BPM(3桁まで入力可能)

見た目

autoMusic_Image.png (4.3 kB)

実際のものがこちら

autoMusic.html (4.0 kB)

Webに公開する力がなかったので生ファイル。

DLしてブラウザで開くと見れる(はず)

展望

  • コード進行を考えるともっと音楽っぽくなる
  • 対応スケールを増やす
  • パーカッションとかを入れる(楽器を増やせるようにする)

反省

  • AI関係なくなってしまった(一晩でどうにかなるものでもなかった)

先日見かけた自動作曲記事

『Scalaで自動作曲の練習』を社内勉強会で話した - Sexually Knowing

参考サイト(Web Audio API)

Web Audio API - Web API インターフェイス | MDN

HTML5 の Web Audio API で音楽してみる | CYOKODOG

https://www.html5rocks.com/ja/tutorials/webaudio/intro/

ゲームAI同士を対戦させてみる

初めまして。新人?アルバイトのzeosuttです。


秋です。

皆でゲームAIを持ち寄って対戦したい季節ですね。

何のゲームにしましょうか。

そうですね、簡単に実装できるので、〇×ゲームにしましょう。

f:id:spookies_yano:20170919125328p:plain
実は、両者が最善を尽くすと必ず引き分けになる

さて、どんなに強いAIを作っても、それを対戦させる仕組みがなくては意味がありませんね。

この記事では、AI同士を対戦させるための仕組みを作ってみます。

AIの作成自体は主題ではありません。

人対人

まずは、人間同士で対戦ができるようにしましょう。

#include <stdbool.h>
#include <stdio.h>
#include <string.h>

const char *sign = "ox";   // 〇/× は o/x で表現

// 手を表現する構造体
typedef struct {
    int i, j;
} Choice;

// 盤面を初期化する
void init_field(char field[3][4]) {
    for (int i = 0; i < 3; i++) {
        strcpy(field[i], "...");
    }
}

// 指定されたストリームに盤面を送る
void send_field(const char field[3][4], FILE *fp) {
    for (int i = 0; i < 3; i++) {
        fprintf(fp, "%s\n", field[i]);
    }
}

// 指定されたストリームから手を受け取る
void receive_from_ai(Choice *pchoice, FILE *fp) {
    fscanf(fp, "%d %d", &pchoice->i, &pchoice->j);
}

// 手が有効か?
bool is_valid(const Choice *pchoice, const char field[3][4]) {
    const int i = pchoice->i, j = pchoice->j;
    return i >= 0 && i < 3 && j >= 0 && j < 3 && field[i][j] == '.';
}

// 手をシミュレートする
void simulate(int player, const Choice *pchoice, char field[3][4]) {
    field[pchoice->i][pchoice->j] = sign[player];
}

// 指定されたプレイヤーが勝利しているか?
bool is_win(int player, const char field[3][4]) {
    // 手抜きだけど怒らないで
    return field[0][0] == field[0][1] && field[0][1] == field[0][2] && field[0][2] == sign[player]
        || field[1][0] == field[1][1] && field[1][1] == field[1][2] && field[1][2] == sign[player]
        || field[2][0] == field[2][1] && field[2][1] == field[2][2] && field[2][2] == sign[player]
        || field[0][0] == field[1][0] && field[1][0] == field[2][0] && field[2][0] == sign[player]
        || field[0][1] == field[1][1] && field[1][1] == field[2][1] && field[2][1] == sign[player]
        || field[0][2] == field[1][2] && field[1][2] == field[2][2] && field[2][2] == sign[player]
        || field[0][0] == field[1][1] && field[1][1] == field[2][2] && field[2][2] == sign[player]
        || field[0][2] == field[1][1] && field[1][1] == field[2][0] && field[2][0] == sign[player];
}

// 盤面を表示する
void print_field(const char field[3][4]) {
    send_field(field, stdout);
}

// 手を表示する
void print_choice(const Choice *pchoice) {
    printf("choice: %d %d\n", pchoice->i, pchoice->j);
}

int main(void) {
    char field[3][4];
    int turn;

    init_field(field);

    print_field(field);
    for (turn = 0; turn < 9; turn++) {
        const int player = turn & 1;
        Choice choice;

        printf("AI%d(%c)'s turn\n", player + 1, sign[player]);

        // 一時的に標準入力を指定
        receive_from_ai(&choice, stdin);

        print_choice(&choice);

        if (!is_valid(&choice, field)) {
            const int oppo = player ^ 1;
            puts("invalid input");
            printf("AI%d(%c) win!\n", oppo + 1, sign[oppo]);
            break;
        }

        simulate(player, &choice, field);

        print_field(field);

        if (is_win(player, field)) {
            printf("AI%d(%c) win!\n", player + 1, sign[player]);
            break;
        }
    }
    if (turn == 9) {
        puts("draw...");
    }

    return 0;
}

はい。できました。

座標はi jの形式で入力してください。

例えば、左上なら0 0、その下なら1 0となります。

AI対AI

〇×ゲーム自体のコードは書けました。

後は、AIと通信をするように修正すれば完成です。

ここで、AIには、次の例のようなフォーマットで入力を与えることとします。

o
...
.o.
..x

最初の行は、自分が〇と×のどちらであるかを表します。

2行目以降は盤面の状態を表します。

また、AI作成者の負担を軽減するためにも、AIからは標準入出力でやり取りできるようにしましょう。

AI実装例

先に、このプログラムの入出力仕様に沿って、簡単なAIを実装しますね。

このプログラムの理解の一助となるほか、AI作成未経験者にとっては、入力を受け取り、手を考え、出力するという、AIの基本的な流れの理解にも役立つかもしれません。

#include <stdbool.h>
#include <stdio.h>

typedef struct {
    int i, j;
} Choice;

// 入力を受け取る
bool input(char *pmark, char field[3][4]) {
    if (scanf(" %c", pmark) == EOF) {
        return false;
    }

    for (int i = 0; i < 3; i++) {
        scanf("%s", field[i]);
    }

    return true;
}

// 手を考える
bool think(Choice *pchoice, int mark, const char field[3][4]) {
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++) {
            if (field[i][j] == '.') {
                // 普通は、各手を何らかの基準で評価し、最も評価値が高いものを選ぶ
                // しかし、今回の主題はAI作成ではないため、ここは手を抜く
                *pchoice = (Choice){i, j};
                return true;
            }
        }
    }

    return false;
}

// 手を出力する
void print_choice(const Choice *pchoice) {
    printf("%d %d\n", pchoice->i, pchoice->j);
}

int main(void) {
    char mark, field[3][4];

    while (input(&mark, field)) {
        Choice choice;

        if (think(&choice, mark, field)) {
            print_choice(&choice);
        } else {
            // 有効な手がない場合の処理(今回は起こり得ない)
            puts("-1 -1");
        }

        fflush(stdout);
    }

    return 0;
}

左上から順に走査し、〇も×も書かれていない座標があれば、それを出力するAIです。

雑魚です。

対戦プログラムの修正

では、〇×ゲームのコードをAIと通信するように修正しましょう。

まず、以下を追加します。

#include <stdlib.h>
#include <unistd.h>

// コマンドライン引数のバリデーション
void validate(int argc, const char * const *argv) {
    if (argc != 3) {
        fprintf(stderr, "usage: %s AI1 AI2\n", argv[0]);
        exit(EXIT_FAILURE);
    }
}

// AIと通信をする準備をする
void prepare_ai(FILE *fp_to_ai[2], FILE *fp_from_ai[2], const char * const path_to_ai[2]) {
    for (int i = 0; i < 2; i++) {
        int pipefd_to_ai[2], pipefd_from_ai[2];

        pipe(pipefd_to_ai);
        pipe(pipefd_from_ai);

        if (fork() == 0) {
            // 標準入出力を繋ぎ変えた上で、プロセスをAIで置き換える

            close(pipefd_to_ai[1]);
            dup2(pipefd_to_ai[0], STDIN_FILENO);
            close(pipefd_to_ai[0]);

            close(pipefd_from_ai[0]);
            dup2(pipefd_from_ai[1], STDOUT_FILENO);
            close(pipefd_from_ai[1]);

            execlp(path_to_ai[i], path_to_ai[i], (char *)NULL);

            // AIの実行に失敗した場合はエラー
            fprintf(stderr, "%s: no such file\n", path_to_ai[i]);
            exit(EXIT_FAILURE);
        }

        close(pipefd_to_ai[0]);
        close(pipefd_from_ai[1]);

        // AIと高水準入出力関数でやりとりできるようにしておく
        fp_to_ai[i] = fdopen(pipefd_to_ai[1], "w");
        fp_from_ai[i] = fdopen(pipefd_from_ai[0], "r");
    }
}

// 指定されたストリームに、AIへの入力にあたるデータを送る
void send_to_ai(int player, const char field[3][4], FILE *fp) {
    fprintf(fp, "%c\n", sign[player]);
    send_field(field, fp);
    fflush(fp);
}

prepare_ai()がこの記事の肝です。

やっていることは単純で、

  1. pipe()でAIとやり取りするためのパイプを作成し、

  2. 後ほど子プロセスをAIに置き換える、という意図を持ってfork()し、

  3. 子プロセスの標準入出力をdup2()で先ほどのパイプに書き換え、

  4. 最後に子プロセスをexec()でAIに置き換えて

いるだけです。

各システムコール等の詳細はmanを読んでください。

なお、言うまでもなく、Windowsでは動作しません。

最後に、main()を次のように修正します。

65c117,120
< int main(void) {
---
> int main(int argc, char **argv) {
>    validate(argc, (const char **)argv);
> 
>    FILE *fp_to_ai[2], *fp_from_ai[2];
68a124,125
>    prepare_ai(fp_to_ai, fp_from_ai, (const char **)argv + 1);
> 
78,79c135,136
<       // 一時的に標準入力を指定
<       receive_from_ai(&choice, stdin);
---
>        send_to_ai(player, field, fp_to_ai[player]);
>        receive_from_ai(&choice, fp_from_ai[player]);

完成です。

$ ./tic_tac_toe ./ai1 ./ai2 のようにして実行してください。

公平性の問題やセキュリティの問題がありますが、とりあえずこれでおkとしましょう。

お疲れ様でした。

戦略SLGの移動範囲計算を実装してみた

こんにちは、ロックです!

今回は皆さんもお馴染みのゲームジャンル「戦略SLG」についてです!

戦略SLGとは


ターン性でキャラクターを動かし合うシミュレーションゲーム。

代表としては、ファミコンウォーズシリーズですね。

今回の主題を考えると、ファイアーエムブレムシリーズにも密接に関わってくるでしょう。

どちらにも共通してキャラクターを動かす時にどれくらい移動できるかが表示されます。

本日はその移動範囲を求めてみたいと思います。

環境


  • Ubuntu16.04(VirtualBoxでの仮想環境)
  • gcc version 5.4.0

c言語とCUIで表現してみようと思います。

実行結果画面


f:id:ishiyamacocoa:20170727113049p:plain

前準備


  1. map_move
    • 20*20の移動範囲計算結果格納
  2. cal_can_move_range(y, x, n)
    • 移動範囲を計算して map_move を更新していく関数

以上のものを用意します。

それでは考えて行きましょう。

方針


  • 位置:(5,5)
  • 移動可能量:3
  • 検索順:↑→←↓
  • 検索条件①:一度行った場所は検索しない(足跡)
  • 検索条件②:????

1.思考用に9*9用いてみましょう。

1 2 3 4 5 6 7 8 9
1
2
3
4
5 P
6
7
8
9

2.上が歩けそうなので上に1歩行ってみましょう。

1 2 3 4 5 6 7 8 9
1
2
3
4 1
5 P
6
7
8
9

3.まだ上に行けそうです、上に2歩目。

1 2 3 4 5 6 7 8 9
1
2
3 2
4 1
5 P
6
7
8
9

4.まだまだ上に行けそうです、上に3歩目。

1 2 3 4 5 6 7 8 9
1
2 3
3 2
4 1
5 P
6
7
8
9

5.3歩動いてしまいました。これ以上は上に行けないので、一つ前のマスから右へ3歩目を踏み出してみましょう。

1 2 3 4 5 6 7 8 9
1
2 3
3 2 3
4 1
5 P
6
7
8
9

6.3歩目なのでこれ以上動けません、一つ前のマスに戻って左へ行ってみましょう。

1 2 3 4 5 6 7 8 9
1
2 3
3 3 2 3
4 1
5 P
6
7
8
9

7.このように続けていくと、以下のようになりますね(まだ終わりではない)

1 2 3 4 5 6 7 8 9
1
2 3
3 3 2 3
4 3 2 1 2 3
5 3 P 3
6 3 2 1 2 3
7 3 2 3
8 3
9

おやおや、左右に伸びきれてませんね。

検索の都合上、(4,5),(6,5)は3歩目の足跡がついてしまうのですね。

これを修正するのはとても簡単です。

以下の検索条件を加えてあげれば良さそうです。

検索条件②:すでに足跡がついていても、現在の歩数よりも大きければ、上書きする

8.すると、こうなります。

1 2 3 4 5 6 7 8 9
1
2 3
3 3 2 3
4 3 2 1 2 3
5 3 2 1 P 1 2 3
6 3 2 1 2 3
7 3 2 3
8 3
9

無事に求めていたことが実現できました。

では実際のコードはどのように実現できるでしょうか。

設計


  • map_move
    • 20*20の移動範囲計算結果格納

これに歩数を入れていきます。

さて、肝心のコードですが、マスを順々に追っていくような処理に適したものがあります。

それは、再帰です。

再帰でなくとも良いですが、再帰だと短く書けるのでおすすめです。(某パズルゲームの連結しているかも再帰で判定できますね)

実際のコードがこちら

void cal_can_move_range(int y, int x, int n){
  // 足跡をつける(現在歩数を代入)
  map_move[y][x] = n;

  // 残り歩数が0ならば終了
  if(n == 0) return;
 


  // 上へ行けるなら
  if(map_move[y-1][x] < n) cal_can_move_range(y-1,x,n-1);

  // 右へ行けるなら
  if(map_move[y][x+1] < n) cal_can_move_range(y,x+1,n-1);

  // 左へ行けるなら
  if(map_move[y][x-1] < n) cal_can_move_range(y,x-1,n-1);

  // 下へ行けるなら
  if(map_move[y+1][x] < n) cal_can_move_range(y+1,x,n-1);
}

※ map_moveは全て-1で初期化しておく

1 2 3 4 5 6 7 8 9
1
2 0
3 0 1 0
4 0 1 2 1 0
5 0 1 2 P 2 1 0
6 0 1 2 1 0
7 0 1 0
8 0
9

※ 残り歩数を入れているので、方針時とは数字が異なる

これで完成です。

発展編


マスによっては移動コストが異なる場合もあるでしょう。

平地であれば移動に1かかるが、森であれば移動には2必要である。

これは実は簡単です。

先ほど n-1 していた部分を変えてあげれば良いでしょう。

平地なら n-1 森なら n-2 という風に。

そこまで実装した画面が一番初めに見せた実行結果画面ということになります。

f:id:ishiyamacocoa:20170727113049p:plain

^ が森として、移動コストは2必要としています。どうなるか見てみましょう。

f:id:ishiyamacocoa:20170727122326p:plain

森を通ると残り歩数の減少量が2であることがわかるかと思います。

# は壁を意味しており、通行不可マスです。

移動コストは100必要としています(これで実質通行不可)

f:id:ishiyamacocoa:20170727122331p:plain

あとがき


ということで本日の主題は、戦略SLGの移動範囲計算を実装してみた、でした!

今年の冬は自作でこれを用いたゲームでも作ってみたいなーと夢想しているロックがお送りしました!

ありがとうございました!

3Dモデルを作りたい

みなさまこんにちは、新人のnytkと申します。

趣味は、ゲームと、TCG(カードゲーム、とくにマジック・ザ・ギャザリング)です。

以前、社内でカードゲーム勉強会もしました。

ゲームの中で3Dのモデルが動くっていいですよね。

特に、自分が作ったやつが動いたら楽しそうです。

突然ですが、3Dのモデルを作ってみたくなったので作ってみます。

今回使うのは昔少し触ったMetasequoia LEです。(新しいやつ買おう)

起動するとこんな画面が出ます。

f:id:spookies_nytk:20170714094350p:plain

今回作ろうと思うのは画鋲です。 3Dでも簡単なやつならすぐに作れるということを見せようと思います。

今回は回転体の機能を使って作ります。(だから回転で作れそうな画鋲です。)

回転体とは断面をぐるっとまわした軌跡の部分をモデルにするというものです。

というわけで断面を作りましょう。

f:id:spookies_nytk:20170714094610p:plain

座標をぽちぽち入力、数値を直接しないでクリックでもいいです(ずれるのが嫌なので数値で打ってますが)

f:id:spookies_nytk:20170714094948p:plain

こんな感じでしょうか。

断面を作ったらオブジェクトを選択して、

f:id:spookies_nytk:20170714095032p:plain

回転体を適用しましょう

f:id:spookies_nytk:20170714095257p:plain

f:id:spookies_nytk:20170714095405p:plain

(ちょっと分厚いような)

大丈夫です、後からでも修正可能です。

範囲選択を選んで

f:id:spookies_nytk:20170714095454p:plain

この辺りを選択して

f:id:spookies_nytk:20170714095609p:plain

移動を選んで

f:id:spookies_nytk:20170714095636p:plain

選択部分を下にずらします。

f:id:spookies_nytk:20170714095726p:plain

ほっそりしました。

あとは色でもつけましょう。

材質を作成して色を黄色にします。

f:id:spookies_nytk:20170714095848p:plain

材質を未着色部分に貼り付けます

f:id:spookies_nytk:20170714095950p:plain

完成!

f:id:spookies_nytk:20170714100033p:plain

ちょっとしょぼい気もしますが、こんな感じに3Dを作成することができます。

今回使ったMetasequoiaは、新しいものも出ています。

metaseq.net | 3Dモデリングソフトウェア「Metasequoia(メタセコイア)」公式サイト

EX版はUnityで使える.fbxにも対応しているみたいですね。