作成者別アーカイブ: vivisuke

vivisuke について

ぼちぼちソフト作家 テニス・オセロ・ゲーム・パズル類が趣味の年齢不詳のおじさん 自宅研究員(主席) vi と C++が好き 某国立大学 大学院 理工学研究科 修士課程修了(工学修士) 管理工学研究所、 Lotus Development(現IBM)、 ジャストシステム にて研究員・ソフトウェアエンジニアとして勤務した後、 現在福岡市にて テキストエディタViVi などを研究開発 (1998/01~2001/03 は某国立大学理工学部客員助教授)

【cocos2d-x v3.x】矩形描画における座標系

cocos2d-x で画面に矩形を描画する方法はいくつかあるが、お手軽なのは Sprite または LayerColor を使用する方法だ。通常、Sprite は用意しておいた画像ファイルを表示するのだが、単色の矩形であれば setTextureRect(const Rect& rect), setColor(iconst Color3B&) をコールすれば画像を用意する必要がなくお手軽だ。

以下が、矩形スプライトの表示コードサンプルと実行結果である。

// 矩形スプライト表示
auto rectSprite = Sprite::create();  //  スプライト生成
rectSprite->setTextureRect(Rect(0, 0, rwd, rht));  // 矩形位置・サイズ
rectSprite->setColor(Color3B(0x80, 0xff, 0x80));  // 明るい緑
rectSprite->setPosition(Vec2(screen_cx, screen_cy));  // 画面中央
addChild(rectSprite, 2);  //  スプライトオブジェクトをスクリーンに付加

rwd, rht は表示する矩形サイズ、screen_cx, cy は画面中央座標だ。
なお、わかりやすいように画面中央に十字の白色グリッドを表示している。

次に LayerColor を利用するコード、実行結果を示す。

// 矩形レイヤー表示
auto rectLayer = LayerColor::create(Color4B(0x80, 0x80, 0xff, 0xff), rwd, rht);  //  明るい青
rectLayer->setPosition(Vec2(screen_cx, screen_cy));  // 画面中央
addChild(rectLayer, 3);

Sprite の場合とほぼ同じようなコードで矩形を表示できる。が、ここで注意すべきは setPosition() で指定した位置だ。Sprite の方は矩形中央が指定した位置に表示されるが、LayerColor は左下が指定した位置に表示されるという点だ。

あと、スプライト・レイヤーに子オブジェクトを付加する場合の座標原点を確認しておく。

下記はスプライト矩形の上に赤い矩形を表示するコード。表示位置は (0. 0) としている。

// 矩形の上にスプライト表示
auto sprite = Sprite::create();
sprite->setTextureRect(Rect(0, 0, rwd/8, rht/8));
sprite->setColor(Color3B(0xff, 0x80, 0x80));
sprite->setPosition(Vec2(0, 0));
rectSprite->addChild(sprite, 1);

結果を見ると、スプライト矩形の左下点が座標原点となっているのがわかる。

同様に、レイヤーの上に赤い矩形を (0, 0) 位置に表示すると以下のようになる。

まあ、レイヤーなので当然と言えば当然であるが、こちらも矩形の左下点が座標原点となっているのがわかる。

以上、スプライトとレイヤーで矩形を描画した場合の座標系の違いついて見てきた。スプライト・レイヤーの意味を鑑みて、このような仕様にしたのであろうが、理解があやふやだと、意図した位置とは違う位置にオブジェクトが描画され混乱することもあるので、ちゃんと理解しておくことを推奨する。

まとめ:

  • オブジェクト生成時、スプライトは画像中央の位置を、レイヤーは左下点位置を指定する
  • 小オブジェクトを付加する場合の座標原点は、スプライト・レイヤーともに左下位置が座標原点となる。

cocos2d-x 3.17 アプリに SDKBOX admob リワード広告を組み込む

  • コマンドプロンプトで sdkbox import admob を実行し、admob プラグインを組み込む
  • プロジェクト/Resource/sdkbox_config.json を開き、”AdMob” > “ads” > “rewarded” > “id” を表示したいリワード広告IDに置き換える
  • sdkbox::AdMobListener 派生クラスを定義し、virtual void reward(const std::string &name, const std::string &currency, double amount) を再実装する
    • このメンバ関数はリワード広告が正しく表示された場合にコールされるので、その処理を記述する
  • sdkbox::AdMobListener 派生クラスオブジェクトを生成し、sdkbox::PluginAdMob::setListener(AdMobListener*) に渡す
  • sdkbox::PluginAdMob::cache(リワード広告ID); をコールし、広告をキャッシュしておく
  • ユーザがリワード広告表示を承諾したら sdkbox::PluginAdMob::show(リワード広告ID); をコールする

cocos2d-x 3.17 Android に Admob SDK を組み込む

 

・ AdMobのライブラリをバンドル
  - app/build.gradle に以下を追加
dependencies {
    ...
    compile 'com.google.android.gms:play-services-ads:15.0.1'
}
・ パーミッション設定
  - app/マニュフェストに以下を追加
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
・ メタデータ、広告アクティビティの設定
  - AndroidManifest.xmlのapplicationタグに以下を追加
<meta-data android:name="com.google.android.gms.version"
           android:value="@integer/google_play_services_version" />
 
<activity
    android:name="com.google.android.gms.ads.AdActivity"
    android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|uiMode|screenSize|smallestScreenSize"
    android:theme="@android:style/Theme.Translucent" />
    
・ バナー広告を作成する処理
  - 以下を AppActivity.java に追加
import android.widget.LinearLayout;
import android.view.Gravity;
import com.google.android.gms.ads.AdRequest;
import com.google.android.gms.ads.AdSize;
import com.google.android.gms.ads.AdView;
.....
public class AppActivity extends Cocos2dxActivity {
    private AdView mAdView;  //  バナー広告
    .....
        // DO OTHER INITIALIZATION BELOW
        // 広告ビューのレイアウトパラメータを作成
        LinearLayout.LayoutParams adParams = new LinearLayout.LayoutParams(
                LinearLayout.LayoutParams.WRAP_CONTENT,
                LinearLayout.LayoutParams.WRAP_CONTENT);
 
        // 表示位置を下部中央に設定
        adParams.gravity = (Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL);
 
        // 広告ビューを作成
        mAdView = new AdView(this);
        mAdView.setAdSize(AdSize.BANNER);
        mAdView.setAdUnitId("ca-app-pub-XXXXXXXXXX/XXXXX");
        //mAdView.setAdUnitId(getString(R.string.banner_ad_unit_id));
 
        // テストデバイスのIDを指定してAdRequestを作成
        AdRequest adRequest = new AdRequest.Builder()
                .addTestDevice(AdRequest.DEVICE_ID_EMULATOR)
                .addTestDevice("XXXXXXXXXX")
                //.addTestDevice(getString(R.string.test_device_id))
                .build();
 
        // 広告を読み込む
        mAdView.loadAd(adRequest);
 
        // 広告ビューをアクティビティに追加する
        addContentView(mAdView, adParams);        
    }

以上で、バナー広告が表示できる。

のだが、現状ではなぜか画面下部に表示されず、画面左上に表示されてしまう。

【数独プログラミング】問題チェック

  • 公開されている数独問題は、解がひとつしかないのが普通だ
    • 解が複数存在する問題は綺麗ではないし、解答を載せる場合にも話がややこしくなる。
  • 先に示したソルバーアルゴリズムでは解を発見したときに、探索をやめてしまうが、問題が唯一解を持つかどうかを判定したい場合は、解をひとつ発見しても探索を継続する。
  • 探索を継続し、2つめの解を発見した場合は、別解を持つことが確定するので、そこで探索をやめる。
  • 探索を終了した時点で、解を発見していた場合は、正しい問題となる。
  • チェック処理には、先のソルバーの約2倍の時間が必要
  • コードは以下のようになる
int g_count;  //  発見した解の個数
bool check() {
  g_count = 0;
  check(0);
  return g_count == 1;
}
bool check(int ix) {
  if( ix が盤面外 ) {  //  解を発見
    if( ++g_count > 1 ) {  // 解を複数発見
      return false;
    }
    //  最初の解を発見した場合は、探索を継続
  }
  for(int n = 1; n <= 9; ++n) {
    盤面[ix] に n を設定
    if( !check(ix+1) ) return false;  //  解を複数発見した場合
  }
  盤面[ix] = 0;
  return true;
}

数独プログラミング:ソルバー

■ 数独ソルバー

  • 数独」(Sudoku, ナンプレ、ナンバープレースとも呼ばれる)は非常に人気のあるパズルで、制約充足問題の一種である。
  • 「ソルバー」とは問題を解くプログラムのことである

■ データ構造

  • 各マスの値(1~9, 空欄は0)を uint8 の数値で表現し、盤面全体を1次元配列で表す。
  • 数値ではなくビットマップを用いる方法もあり、その方が高速になるが、大差ないし、ビットマップに慣れていない人には理解しずらくなるので、本稿では数値とする
  • 盤面は2次元なので2次元配列でもよいのだが、一般的に2次元配列は低速なので、1次元配列とした。

■ アルゴリズム

  • 人間が数独の難問を解くのは大変だが、意外なことに最近のCPUであれば、単純な探索アルゴリズムにより、どんな難問でも短時間で解を求めることができる
  • 探索木の大きさが、人間には膨大でも、CPUにとっては手頃なサイズということだ。
  • 擬似コードは以下の通り
bool solve(int ix) {
  if( ix は盤外? ) return true;  // 解発見
  for(int n=1; n<=9; ++n) {
    if( 盤面[ix] に n を配置可能か? ) {
      盤面[ix] = n;  // 盤面[ix] に n を配置
      if( solve(ix+1) )
        return true;   // 解発見の場合
    }
  }
  盤面[ix] = 0;  //  空欄に戻しておく
  return false;
}
void solve() {
  solve(0);
}
  • 場所に関するループで処理してもよいのだが、再帰関数にした方が(筆者には)直感的でわかりやすいのでそうしている。
  • 上記コードで不明なのは、盤面[ix] に n を配置可能かどうかをどう判定するか?という部分である。
  • ix 位置の縦・横方向、ブロック内に既にその数字が配置されていないかどうかを単純にチェックすればよい
int x = ix % 9;  //  横方向位置
int y = ix % 9;  //  縦方向位置
int x0 = x - x % 3;  // 属するブロックの左端位置
int y0 = y - y % 3;  // 属するブロックの上端位置
int bix0 = y0 * 9 + x0;  // 属するブロックの上左端位置
uint8 used[10] = {0,0,0,0,0,0,0,0,0,0};  //  各数字が既に使われているかどうか
for(int i=0; i<9; ++i) {
  used[m_cell[y*N_HORZ+i]] += 1;
  used[m_cell[i*N_HORZ+x]] += 1;
}
for (int i = 0; i < 3; ++i) {
  for (int k = 0; k < 3; ++k) {
    used[m_cell[ix0 + i*N_HORZ + k]] += 1;
  }
}

以上で準備ができたので、盤面[ix] に n を配置する直前に、used[n] が0かどうかをチェックするとよい

■ パフォーマンス計測

  • 上記アルゴリズムを C++ で実装し、(人間的には)難問100問を解かせ、それに要する時間を計測した。
  • 環境:VS2017, Windows10, Core i7-6700@3.4GHz, 16GBMem
  • 結果:約500ミリ秒/100問 (1問あたり約5ミリ秒)

 

【自分用メモ】#数独 解がひとつだけかチェック

(webで公開されてた)数独の問題100問に対して、解がひとつだけかどうかをチェックするプログラムを書いてみた。
アルゴリズムは単純な探索を用いるものだが、セルの値をビットマップで表現しているために、重複チェックが数値を用いるものに比べるとはるかに高速だ。
また、数字を入れるときに、重複チェックを行うのではなく、数字を使用するたびに各縦横ブロックの使用済みフラグをオンにしている。
結果、100問をチェックするのに要した時間は 0.3036ミリ秒だった。ということは、約30マイクロ秒/問でチェック可能ということになる。

ちなみに、単に解を見つけるだけ(最初の解がみつかった時点で終了)の場合は、220マイクロ秒/100問だった。ひとつだけかチェックする場合の約15倍高速ということになる。

プロジェクトにファイルを追加 #cocos2d-x #AndroidStudio

・ External Buld Files/Android.mk の LOCAL_SRC_FILES 部分を以下のように修正

LOCAL_SRC_FILES := $(LOCAL_PATH)/hellocpp/main.cpp \
$(wildcard $(LOCAL_PATH)/../../../Classes/*.cpp)

・Classes 以下にソース・ファイルを追加
・プロジェクトをいったん閉じて開く
→ プロジェクト名/cpp/MyGame/プロジェクト名/Classes 以下に追加されたソースが表示される

cocos2d-x を Android Studio でビルド

※ 環境:Android Studio 3.0.1, cocos2d-x v3.16

・コマンドラインにて cocos New <プロジェクト名> -l cpp を実行するとプロジェクトが作成される。
・プロジェクト名/proj.android-studio を Android Studio で開く
・→ すこし時間がかかるが、Gradle(=ビルド設定)が自動的にビルドされ、プロジェクト自体のビルドが可能な状態になる。
・Build > Make Project を実行しビルド
・→ それなりに時間かかるが、プロジェクト/proj.android-studio/app/build/outputs/apk/debug に プロジェクト名-debug.apk が作成される
・リリース版をビルドする場合は、Build > Generate Signed APK を実行する
・デフォルトでは Classes 以下が一覧に表示されないので、Gradle.Scripts/settings.gradle をダブルクリックで開き、以下を追加する

include ‘:Classes’
project(‘:Classes’).projectDir = new File(settingsDir, ‘../Classes’)
include ‘:Resources’
project(‘:Resources’).projectDir = new File(settingsDir, ‘../Resources’)