カテゴリー別アーカイブ: C/C++

Qt5 再入門>Hello, World (デザイナ・Label使用)

まずは画面に「Hello, World」を表示してみよう。
それにはいくつかの方法があるが、まずは一番簡単なLabelを使ったものを紹介する。
下図の左側のプロジェクト部分の Form を展開し、mainwindow.ui をダブルクリックする。

そうすると、下図のようなデザイン画面に切り替わるはずだ。
左にウィジェット一覧があるので、そこからテキストを表示するためのウィジェットである Labelを探す。ウィジェットはあまりに多すぎて探すのが大変なので、上部のフィルター部分に「la」と入力すると、下図のように一番下に出てくるぞ。

左側のウィジェット一覧から「Label」をドラッグし中央のフォームにドロップすると、下図の様に Labelウィジェットが画面に配置される。

次に「TextLabel」の部分をダブルクリックし、「Hello, World」とキーボードから入力し、ラベルテキストを変更しよう。

これで、プログラムは完成なので、Ctrl + R を押して、ビルド&実行すると、下図のように表示されるはずだ。

デザイナ右下のプロパティ部分で、フォントやアライメントを指定できるぞ。
下図はフォントを大きくし、左右中央揃えに変更してみたものだ。

Ctrl + R で実行すると下図のようになるぞ。

演習問題

  1.  Label ではなく PushButton を配置し、テキストを「Push Me」してみなさい。

まとめ

  • form>ui ファイルをダブルクリックすることでフォームをデザインできるぞ
  • ウィジェットを画面に貼り付けることができるぞ
  • 目的とするウィジェットを探すときはフィルターを使うと便利だぞ
  • プロパティでウィジェットの属性を変更することができるぞ

Qt5 再入門>空のプロジェクト作成

久々にQtでアプリを作ろうと思ったら、いろいろすっかり忘れていたので、自分の備忘録としてもQt5 を使ったアプリの作成方法を記述しておく。

なお、Qt のバージョンは 5.9, Qt Creator のバージョンは 4.3.1を使用する

空のプロジェクト作成

まずは「ようこそ」の上部中央の【+ 新しいプロジェクト】をクリックして、新しいプロジェクトの作成を開始する。

作成するプロジェクトの種別を指定するダイアログ(下図)が表示される。
通常アプリを作成したいのであれば、下図の様に「アプリケーション」、「Qtウィジェットアプリケーション」を選び、【選択】を押す。

次にプロジェクト名、プロジェクトファイルのパスを入力する。

次にクラス名、ファイル名等を設定できるダイアログが表示される。
通常は特に変更する必要は無い。

最後に、プロジェクト管理、概要が表示されるので、問題が無ければ【完了】を押す。

以上で、プロジェクトが生成される。

プロジェクトが作成できたら Ctrl + R を押してみよう。
下図のような空のウィンドウが表示されるはずだ。

クロススクエアドナンバーパズル(Cross Squared Number Puzzle)

下図のような盤面の □ 部分に0~9の数字を入れ、2桁以上の数値は平方数(n==r*r)となるようにするパズルを「クロススクエアドナンバーパズル(Cross Squared Number Puzzle)」と言う。
※ Cマガジン2000/01 電脳クラブより
本稿では、このパズルを解くためのC/C++プログラムについて解説する。

□□□□■□
□□■□□□
□□□□□■
■□□□□□
□□□■□□
□■□□□□

ちなみに、下図は上記パズルの回答例だ。

1225#6
49#324
49729#
#25921
196#49
6#9216

盤面が大きくなければ単純な探索でも(今時のCPUであれば)すぐに全部の解を求めることができる。
番人付きの1次元配列を用意しておき、最初のマスから最後のマスまで、0~9 の数字を順に入れていき、パズルの条件が成立していれば、それを解として表示すればよい。
全部の場合を生成し、それを逐一チェックするのはパズルプログラミングの基本だ。今時のCPUは驚くほど高速なので、この基本アルゴリズムを使うことで実用的な時間内で解ける問題は数多くある。

void solve(char *board, int ix) {  // ix: 数字を入れる位置
  if( 盤面全部に数字を入れた ) {
    解発見;
    return;
  }
  for(int n = 0; n <= 9; ++9) {
    if( n == 0 && ix が複数桁の先頭 ) continue;
    board[ix] = n;
    if( 複数桁 && 右または下が番人 ) {
      if( 平方数になっていない )
        continue;
    }
    solve(board, ix+1);
  }
}

で、このパズルの場合、考えなくてはいけないのは、入れた数字が平方数かどうかをどうやってチェックするかだ。
単純な方法としては以下のものがすぐに思いつく。

・ int r = (int)sqrt(n) を計算し、n == r*r で判定
・ (6x6までという制限をつければ、)6桁分のテーブルを用意し、数値が平方数かどうかを予め計算しておく

前者は桁数制限がゆるいが、sqrt() の計算時間が大きくなりそうだ。整数演算で平方根を求めるコードを書けば、より高速化できるかもしれない。
後者は処理速度は速いが、桁数が大きくなるとテーブルサイズが肥大化する。平方数である確率は極めて低いので、なんらかの方法でテーブルを圧縮することを考える必要がありそうだ。

んで、実際にコードを書いて処理時間を計測してみたところ、sqrt() を使う版は約1秒、6桁分の平方数かどうかを判定するテーブルを用いたものは約0.66秒であった。
概ね予想どおりだが、sqrt() の処理速度は想像していたよりは遥かに高速だった。
整数演算により平方根を求める関数も書いてみたが、処理時間は約3秒で返って遅くなってしまった。
最近の x86 は sqrt() を処理する命令を持っているので、ソフトで処理するより遥かに高速、ということみたいだ。

【cocos2d-x v3】 RadioButton の使い方

RadioButton クラスは v3.8 で追加された比較的新しいクラスだ。

  • RadioButtonGroup オブジェクトを生成し、子ノードとしてシーンに追加
  • 選択肢の分だけ、RadioButton を生成し、RadioButtonGroup オブジェクトに 追加し、シーンへも子ノードとして追加する
  • RadioButton オブジェクトにイベントリスナーを指定する
// Create a radio button group
auto radioButtonGroup = RadioButtonGroup::create();
this->addChild(radioButtonGroup);

// Create the radio buttons
for(・・・) {
        RadioButton* radioButton = RadioButton::create("cocosui/radio_button_off.png", "cocosui/radio_button_on.png");
        float posX = startPosX + BUTTON_WIDTH * i;
        radioButton->setPosition(Vec2(posX, winSize.height / 2.0f + 70));
        radioButton->setScale(1.2f);
        radioButton->addEventListener(CC_CALLBACK_2(LabelToggleTypeTest::onChangedRadioButtonSelect, this));
        radioButton->setTag(i);
        radioButtonGroup->addRadioButton(radioButton);
        this->addChild(radioButton);
}

【自分用メモ】とうぶつしょうぎ > KKM テーブル

次にKKM(※ M は持ち駒を表す)
KKP 同様に、KK 分の場合の数は 6*7 = 42。

持ち駒は6種類で、値は 0~2 なので、short KKM[6][7][6*3]; で充分。
が、よく考えると、持ち駒の歩が無いのと持ち駒の角が無いのを重複して評価する必要は無いので、
持ち駒数が 1, 2 の場合のみテーブル化し、持ち駒数0 は KK だけで共通化するとよい。

short KK[6][7];
short KKM[6][7][6*2];

要素数は 42*(12+1) = 546 となる。

【自分用メモ】とうぶつしょうぎ > KKP テーブル

先手玉位置を k1, 後手玉位置を k2, 玉以外の盤上の駒タイプ・位置の識別番号を p とするとき、
short KKP[12][12][12*8]; を作っておけば、KKP[k1][k2][p] で評価値を計算できる。
要素数は 12*12*12*8 = 13,824 だ。
先手玉位置が盤面の右側にある場合は左右反転すれば、先手玉位置の場合の数は 8 に減る。
さらに、評価関数は先手番で呼ばれるため、先手玉が相手陣地に入っている場合は既に終局となっていて、
評価関数が呼ばれることは無い。したがって、先手玉位置の場合の数は 6 にな。
同様に、後手玉位置も9に減る。
先手・後手玉が隣接している場合、相手玉を取って終局となるので、これも評価関数で評価する必要は無い。
ただし、テーブルとしては最大数を用意しておく必要があるので、後手玉位置の場合の数は7になる。
玉以外の盤上の駒については、双方の玉位置には配置できないので、その分場合の数が減り、10*8 = 80 となる。

よって、KKP 配列は short KKP[6][7][80]; と宣言でき、要素数は 3,360 となる。
最初の要素数の約3分の1だ。

【自分用メモ】どうぶつしょうぎ KKP KPP 評価関数

盤面が12箇所しかないので、本将棋に比べるとテーブルはかなり小さくなる

KKP

対称形を排除するため先手玉位置を左または中央列のみとする。右列にあった場合は左右反転を行なう。
これで、場合の数が8に減少する。
後手玉位置は先手局位置以外の11箇所なので、場合の数は 8*11 = 88 となる。
盤上の玉以外の駒をPとすると、位置は10箇所、駒の種類はと金を含めて8なので、
KKPの場合の数はトータルで 88 * 10*8 = 7,040 となる。
KK が両方共中央にある場合は、P を左に寄せることが出来る。もう少し場合の数が減る。

KKM

持ち駒はMで表すことにする。
持ち駒の種類は6。それぞれ [0, 2] の3つの状態があるので、場合の数は 6*3 = 18。
よって、KKM の場合の数はトータルで 88 * 18 = 1,584 となる。

これはかなり少ない数なので、KKMM を採用するのもありかもしれない。
場合の数は 88 * 18 * 5*3 = 23,760

KPP

KKP と同じく、玉の位置は左によせる。ただし、先手玉と後手玉があるので、場合の数は 8*2 = 16 だ。
P は8種類で位置は11または10なので、場合の数は 8*11 = 88, 8*10 = 80 で、
トータルの場合の数は 16*88*80 = 112,640 となる。

KPM

持ち駒の場合の数は 6*3 = 18 なので、
KPM の場合の数は 16*88*18 = 25,344 だ。

KMM

KMM の場合の数は 16*18*15 = 4,320 だ。

場合の数をすべて合計すると、

7,040 + 1,584 + 23,760 + 112,640 + 4,320 = 149,344

となる。評価値を2バイトで保持すれば充分なので、約300Kバイトで事足りることになる。

これらの線形和を計算すれば、かなり正確な評価関数になるように思われるのだが、どうだろうか?

んで、問題は約15万個の値を高速かつ正確に計算するにはどうしたらいいのか?ってことである。

cocos2d-x v3 > httpRequest GET

#include "network/HttpClient.h"
USING_NS_CC;
  cocos2d::network::HttpRequest* request = new cocos2d::network::HttpRequest();
  request->setUrl("http://vivi.dyndns.org/test/index.html?from=cocos2d-x");
  //	GET
  request->setRequestType(cocos2d::network::HttpRequest::Type::GET);
  request->setResponseCallback( [this](network::HttpClient* sender, network::HttpResponse* response) {
  	auto data = response->getResponseData();
  	auto sz = data->size();
  });
  request->setTag("GET test1");
  cocos2d::network::HttpClient::getInstance()->send(request);
  request->release();

どうぶつしょうぎ>後退解析

全局面の生成ができたので、次は「後退解析」で、全局面の解析を行なう。

「後退解析」とは、評価が確定した局面から順に順に局面の評価を確定していく処理だ。
評価とは 勝ち・負け・引き分けのいずれかだ。
最初は全ての局面の評価を不明(UNKNOWN)にしておき、ある局面について、その子局面を生成し、
・子局面のどれかひとつが勝ちであれば、勝ち
・子局面の全てが負けであれば、負け
という処理を行い、局面の評価が変化しなくなるまで繰り返せばよい。
このとき、問題になるのはどの局面を調べるか?ということだ。
全局面数がそう多くなければ、全局面について処理を行なっても速度的に問題はないが、局面数が膨大な場合はちょっと遠慮したくなる。
どうぶつしょうぎの場合は末端局面を覗いても約100M局面もあるので、かなり膨大だ。
どうしよう?

どうぶつしょうぎ>全局面生成

「どうぶつしょうぎ」の完全解析 を参考にして、まずは(末端局面を除く)全局面の生成を行ってみた。
アルゴリズムは単純で、以下のようなものだ。

初期局面をハッシュに入れる
初期局面をオープンリストに入れる
while( オープンリストが空でない間 ) {
  オープンリストから局面をひとつ取り出す
  全ての可能着手をリストアップ
  for(全ての着手について) {
    着手を実行
    if( 末端局面でなければ ) {
      初期局面をハッシュに入れる
      初期局面をオープンリストに入れる
    }
  }
}

前掲論文によれば、末端局面を除く全局面数は 99,485,568 で、末端局面を含めると 246,803,167 ということなので、全局面を保持するハッシュテーブルはかなり巨大になってしまう。
最初は std::unordered_map を使用していたのだが、130メガ局面を生成したあたりでおいらのマシンの物理メモリ容量の 12GB を消費してしまい、そのせいか win7 x64 OS がハングアップしてしまった。orz
std::unordered_map はおそらくオープンハッシュでリンクのためにメモリを消費するので、メモリ効率があまりよくない。
1局面は64bit = 8バイトで表現できるので、ハッシュのキー部分だけを考えれば、256M * 8 = 約2GBあれば足りるはずだ。
全局面数があらかじめわかっていれば、クローズドハッシュを自分で作った方がよいので、自作することにした。
衝突確率を下げるために倍の容量を確保したとしても 4GB で事足りる。末端局面を含めないとすれば、その半分の 2GB で充分なはずだ。
のだが、なんと VC++ は x64 モードでも固定配列では 31bit を超えることができない。
なので、new を使ってメモリを動的に確保するようにしたのだが、それも何故かうまくいかず露頭に迷ってしまった。
が、これはおいらの設定ミスで、ソリューションに新しいプロジェクトを追加し、そこでテストプログラムを書いていたのだが、追加したプロジェクトが x64 モードではなく x86 モードでビルドされていたというオチであった。

で、なんとか末端局面を含まない全局面をリストアップできたのだが、局面数が 96,814,709 で、前掲論文の結果とだいたいあっているのだが、ピッタリ一致していない。
まだ、何かバグがあるのかもしれない。