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

Qt5 のIME(InputMethod)周り

実は「さくさくエディタ」は最初、Qt5 の勉強もかねて Qt5.0 で開発し始めたのだが、IME(InputMethod)周りが期待したようには動作せず(いったいどうい問題があったのかは覚えてない)、情報が少なく対処も難しそうだったので Qt4.x に変更した経緯がある。

が、もうバージョンも 5.5 になってて、さすがにいろいろ治ってるだろうと、Qt 5.5 でIME周りの動作確認をしてみたところ、以下のコードでちゃんと動作した。
めでたし、めでたし。

void MainWindow::inputMethodEvent(QInputMethodEvent * event)
{
	QWidget::inputMethodEvent(event);
	auto pre = event->preeditString();
	auto txt = event->commitString();
	qDebug() << "inputMethodEvent(): pre = " << pre;
	qDebug() << "inputMethodEvent(): txt = " << txt;
}
QVariant MainWindow::inputMethodQuery( Qt::InputMethodQuery query ) const
{
	if( query == Qt::ImCursorRectangle ) {
		qDebug() << "query == Qt::ImCursorRectangle";
		return QVariant(QRect(m_px, m_py, 1, 1));
	}
	return QMainWindow::inputMethodQuery(query);
}

まとめ:

  • 1文字入力は、keyPressEvent ( QKeyEvent * event ); を再実装する。
  • IME イベントを処理したい場合は setAttribute(Qt::WA_InputMethodEnabled); を実行しておく
  • IME イベント処理は inputMethodEvent(QInputMethodEvent * event); を再実装する。
    • preeditString() で現候補文字を取得
    • commitString() で確定文字を取得
  • IME 候補ウィンド位置は inputMethodQuery( Qt::InputMethodQuery query ) を再実装する
    • query が Qt::ImCursorRectangle の時に、ウィンドウ位置を返す
    • 矩形サイズは無視されるようだが、0, 0 を指定すると左上に表示されてしまう
    • 実際に表示されるのは文字高さ?分だけ下にずれるようだ

VSC2015 > 桁区切り

数字リテラルの桁数が大きくなると、数値の認識が難しくなる。
1000000000 だとよくわからないが、1,000,000,000 と表記してあれば、10^9 だとすぐわかる。
が、C/C++ でカンマは式の列挙用なので使うことができなかった。
で、C++14 でアポストロフィを数値リテラルの区切り記号として自由に使えるようになったわけだ。
1’000’000’000 なら、分かりやすい。最初は違和感があるかもしれないが、慣れれば大丈夫だろう。

10進数にかぎらず、16進数、2進数でも使える。特に2進数では桁が多くなる傾向があるので
便利だと予想される。
0b1010’0101’0000’1111 と 0b10100101000111 を見比べれば、一目瞭然だろう。

以下は、確認コードとその実行結果だ。
temp

こういう細かい改良っていいよね。

VSC2015 > 2進数リテラルを試してみる

VSC2015 C++ はC++14 及び、それ以前で未サポートだった C++11 の機能が数多くサポートされている。
なので、それらをすこしずつ試していくことにする。
まずは、2進数リテラルだ。

これまでは 0x を前置すると16進数、0 を前置すると8進数だったが、それらに加え 0b を前置で2進数リテラルがサポートされた。

int a = 0b1010;  // 0b1010 = 0xa = 10

上記の用に、2進数を直接表記できるので、ビット演算を行なう時などは、可読性が向上する。
2進数を書きたいという人は少ないと思うが、個人的はずっと欲しかった機能だ。

んで、以下が、書いてみたテストコードと実行結果。

temp

当然だけど、ちゃんと動いてますな。
素晴らしい!

cocos 2d-x タッチイベントリスナーの登録

bool GameLayer::init()
{
.....
    // タッチイベントリスナー
    auto touchListener = EventListenerTouchOneByOne::create();
    touchListener->onTouchBegan = [this](Touch* touch, Event *){
    	auto loc = touch->getLocation();
    	return true;
    };
    touchListener->onTouchMoved = [this](Touch* touch, Event *){
    };
    touchListener->onTouchEnded = [this](Touch* touch, Event *){
    };
    this->getEventDispatcher()->addEventListenerWithSceneGraphPriority(touchListener, this);
.....
}

【自分用メモ】とうぶつしょうぎ > 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だ。

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 で、前掲論文の結果とだいたいあっているのだが、ピッタリ一致していない。
まだ、何かバグがあるのかもしれない。

cocos2d-x v3 > 画像を動的に生成

cocos2d-x で高速に動作する Conway’s Game Of Life (ライフゲーム)を作ろうと思い、画像を動的に生成する方法を調べてみた。

動的に画像を生成するには DrawNode::drawDot() を使うと簡単だ。
が、drawDot() は大きさを持ったドットを三角形の組み合わせに分解し、その頂点座標と塗りつぶし色を OpenGL に送るので、Game Of Life 用としては無駄が多く、パフォーマンス的に問題だ。

よって、矩形スプライトを作成し、そのテクスチャパターンを動的に生成する方がよい。

宣言ファイル:

 std::vector<Color3B> m_buffer;  //  テクスチャデータを格納する動的配列
 cocos2d::Texture2D *m_texture;
 cocos2d::Sprite *m_sprite;

実装ファイル:

const int WIDTH = 800;
const int HEIGHT = 1000;
m_buffer.resize(WIDTH*HEIGHT);
for(int i = 0; i != m_buffer.size(); ++i) {
    m_buffer[i].r = 赤色値;
    m_buffer[i].g = 緑色値;
    m_buffer[i].b = 青色値;
}
Size contentSize;
contentSize.width  = WIDTH;
contentSize.height = HEIGHT;
m_texture = new Texture2D();
m_texture->initWithData((const void *)&m_buffer[0], WIDTH*HEIGHT*sizeof(Color3B),
			Texture2D::PixelFormat::RGB888, WIDTH, HEIGHT, contentSize);
m_sprite = Sprite::createWithTexture(m_Texture);
m_sprite->setPosition(位置);
addChild(m_sprite);

Nexus 7 (2013) 800×1000 グリッドサイズの場合、DrawNode::drawDot() を使った場合は数FPSだったのが、上記の方法により約30FPSを実現するのに成功した。