カエルコール

カエルコールを Google の Play ストアでダウンロードできるようにしました。
SMSでカエルコールを送っている方は、ニーズにマッチするかも。

https://play.google.com/store/apps/details?id=jp.poyonshot.app.KaeruCall

わたくし、2013年3月のリリースからずっと使い続けていますが、現在も問題なく使えてます。
まだまだ、現役のアプリです。

ビリ練習

入れの精度を測ってみようと、トラブルが無いように6個のボールを適当に配置してフリーボールから番号の小さい順に取り切る練習をしました。
ミスすればその回は終了で集中できなくなるまでトライ。始める前は、5、6回は取り切れるんじゃないかなと考えていましたが...

以下、連続してポケットしたボールの数です。
1 2 1 0 6 4 5 2 1 6 6 2 4

6個を取りきれたのは3回で、成功率 23% (3/13)は思ったよりも低い!
もっと取り切れると思ったのになぁ。

ただ、シュート率をみると 80% (40/50)で、これはまずまず良い値です。
これが6回連続で成功する確率は、0.8 ^ 6 ≒ 26%
なんということでしょう確率通りの結果です(^^;


ミスしたボールをメモって練習するのが上達への近道かなと感じました。

ビリヤード楽しいね

久しぶりにビリヤード屋さんへ行って、B級の方(3人)と9ボールの4先でセットマッチ。
対Aさん 2勝2敗
対Bさん 2勝1敗
対Cさん 2勝1敗
マスワリ2回でましたし、勝ち越せたので十分良い結果です。

ゲームをやっていて、簡単な球を入れ続けるメンタル?技術?を鍛える練習をする段階にきたのかなっと思うようになりました。どんな練習をしようか考えるのも楽しいです。

CSemaphore セマフォの使い方

CSemaphore を使ったとき動作がわかるサンプルを並列パターンライブラリ(PPL)を使えば、簡潔にかけると思ったのでやってみました。並列パターンライブラリのtask_groupを使うと複数のスレッドでタスクを同時に実行してくれます。この並列に実行するタスクの数をセマフォで管理します。

MSDNによると「セマフォのカウントは、1 つのスレッドがセマフォに入るたびにデクリメントされ、1 つのスレッドがセマフォを解放するたびにインクリメントされます。カウントが 0 になると、それ以降の要求は他のスレッドがセマフォを解放するまでブロックされます。すべてのスレッドがセマフォを解放すると、カウントはセマフォの作成時に設定された最大値になります。」とあります。

書いてある意味がわかりますか?私はわかりませんでした(^^; それでは、用意したサンプルを実行してセマフォの動作を確認しましょう。このサンプルは2秒間スリープするタスクを20個実行します。また、サンプルではログの時刻を1ミリ秒の精度で出すためにtimeGetTimeを使っています。

#include "stdafx.h"
#include <afxmt.h>
#include <windows.h>
#include <iostream>
#include <string>
#include <vector>

#include <mmsystem.h>
#pragma comment(lib, "winmm.lib")

#include <ppl.h>

using namespace Concurrency;
using namespace std;

const std::string ToStr(int num, DWORD msec, const std::string &msg)
{
    vector<char> v(1024);
    sprintf_s(&v[0], v.size(), "#%02d %5dmsec %s\n", num, msec, msg.c_str());
    return &v[0];
}

int _tmain(int argc, _TCHAR* argv[])
{
    int n = 4;   //セマフォの初期値
    CSemaphore smp(n, n);

    task_group tasks;
    
    const auto t0 = ::timeGetTime();
    
    cout << ToStr(0, ::timeGetTime() - t0, "開始");

    for(int i = 0; i < 20; ++i)
    {
        tasks.run([&, i]
        {
            cout << ToStr(i+1, ::timeGetTime() - t0, "Run");

            CSingleLock lock(&smp, TRUE);
            
            cout << ToStr(i+1, ::timeGetTime() - t0, "Start");

            Sleep(2000);
            
            cout << ToStr(i+1, ::timeGetTime() - t0, "End");
        });
    }   
    
    cout << ToStr(0, ::timeGetTime() - t0, "登録完了");
    
    //すべてのタスクが終了するのを待ちます。
    tasks.wait();
    
    cout << ToStr(0, ::timeGetTime() - t0, "すべて終了");

    return 0;
}
const std::string ToStr(int num, DWORD msec, const std::string &msg)

ToStrはログに出力する文字列を作成する関数です。特に説明はいりませんね。

cout << ToStr(i+1, ::timeGetTime() - t0, "Run");
CSingleLock lock(&smp, TRUE);
cout << ToStr(i+1, ::timeGetTime() - t0, "Start");
Sleep(2000);            
cout << ToStr(i+1, ::timeGetTime() - t0, "End");

並列処理するタスクの中身です。2秒間Sleepするだけですが、セマフォにアクセスする前後とスリープ後にログを出力します。
 
 
セマフォの初期使用カウントを1で実行した結果

#00     0msec 開始
#01     0msec Run
#01     0msec Start
#02     0msec Run
#03     1msec Run
#04     1msec Run
#05     1msec Run
#06     1msec Run
#07     2msec Run
#00     2msec 登録完了
#20     2msec Run
#08     2msec Run
#01  2000msec End
#09  2000msec Run
#02  2000msec Start
#02  4000msec End
#10  4000msec Run
#03  4000msec Start
#03  6000msec End
#11  6000msec Run
#04  6000msec Start
#04  8000msec End
#12  8000msec Run
#05  8000msec Start
#05 10000msec End
#13 10000msec Run
#06 10000msec Start
#06 12000msec End
#07 12000msec Start
#14 12000msec Run
#07 14000msec End
#15 14000msec Run
#20 14000msec Start
#20 16000msec End
#19 16000msec Run
#08 16000msec Start
#08 18001msec End
#09 18001msec Start
#16 18001msec Run
#09 20001msec End
#17 20001msec Run
#10 20001msec Start
#10 22001msec End
#18 22001msec Run
#11 22001msec Start
#11 24001msec End
#12 24001msec Start
#12 26001msec End
#13 26001msec Start
#13 28001msec End
#14 28001msec Start
#14 30001msec End
#15 30001msec Start
#15 32001msec End
#19 32001msec Start
#19 34001msec End
#16 34001msec Start
#16 36002msec End
#17 36002msec Start
#17 38002msec End
#18 38002msec Start
#18 40002msec End
#00 40002msec すべて終了

同時に実行できるタスクが1つなので、処理時間は2×20で40秒になると予想できますね。
結果を見てみます。
20個のタスクを登録が完了したのが2msecです。
並列処理により同時に9個(実行環境により数は変わります)のタスクが起動しました。
起動したタスクの中でセマフォへのアクセスが成功したのは#01の1つだけです。
2秒経過し、#01のタスクが終了するまで、変化はありません。
#01のタスクが終了すると新しいタスクが起動し、セマフォへのアクセスを待機していたタスクの中から1つのタスクがシグナル状態になりました。
あとは同じ処理の繰り返しです。すべて終了したのが、40.002secで予想した時間で完了しました。
 
 
セマフォの初期使用カウントを2で実行した結果

#00     0msec 開始
#01     0msec Run
#01     1msec Start
#02     1msec Run
#02     1msec Start
#03     1msec Run
#04     2msec Run
#05     2msec Run
#06     2msec Run
#07     2msec Run
#00     2msec 登録完了
#20     2msec Run
#08     3msec Run
#02  2001msec End
#01  2001msec End
#03  2001msec Start
#09  2001msec Run
#10  2001msec Run
#04  2001msec Start
#04  4001msec End
#03  4001msec End
#12  4001msec Run
#11  4001msec Run
#05  4001msec Start
#06  4001msec Start
#06  6001msec End
#05  6001msec End
#14  6001msec Run
#13  6001msec Run
#07  6001msec Start
#20  6001msec Start
#20  8001msec End
#07  8001msec End
#19  8001msec Run
#15  8001msec Run
#08  8001msec Start
#09  8001msec Start
#09 10001msec End
#08 10001msec End
#16 10001msec Run
#17 10001msec Run
#10 10001msec Start
#12 10001msec Start
#10 12001msec End
#12 12001msec End
#18 12001msec Run
#11 12001msec Start
#14 12001msec Start
#14 14001msec End
#11 14001msec End
#13 14001msec Start
#19 14001msec Start
#19 16001msec End
#13 16001msec End
#15 16001msec Start
#16 16001msec Start
#16 18002msec End
#15 18002msec End
#17 18002msec Start
#18 18002msec Start
#17 20002msec End
#18 20002msec End
#00 20002msec すべて終了

同時に実行できるタスクが2個なので、20秒で終わっていますね。


セマフォの初期使用カウントを4で実行した結果

#00     0msec 開始
#01     0msec Run
#01     0msec Start
#02     0msec Run
#02     1msec Start
#03     1msec Run
#03     1msec Start
#04     1msec Run
#04     1msec Start
#05     1msec Run
#06     2msec Run
#07     2msec Run
#00     2msec 登録完了
#20     2msec Run
#08     2msec Run
#01  2000msec End
#09  2000msec Run
#05  2000msec Start
#02  2001msec End
#03  2001msec End
#04  2001msec End
#10  2001msec Run
#07  2001msec Start
#11  2001msec Run
#12  2001msec Run
#06  2001msec Start
#20  2001msec Start
#05  4000msec End
#13  4000msec Run
#08  4000msec Start
#20  4001msec End
#06  4001msec End
#07  4001msec End
#10  4001msec Start
#19  4001msec Run
#14  4001msec Run
#11  4001msec Start
#09  4001msec Start
#15  4001msec Run
#08  6000msec End
#16  6000msec Run
#12  6000msec Start
#10  6001msec End
#11  6001msec End
#09  6001msec End
#13  6001msec Start
#14  6001msec Start
#17  6001msec Run
#19  6001msec Start
#18  6001msec Run
#12  8000msec End
#15  8000msec Start
#14  8001msec End
#16  8001msec Start
#13  8001msec End
#17  8001msec Start
#19  8001msec End
#18  8002msec Start
#15 10000msec End
#16 10001msec End
#17 10001msec End
#18 10002msec End
#00 10002msec すべて終了

同時に実行できるタスクが4個なので、10秒で終わっていますね。
 
 
セマフォの初期使用カウントを10で実行した結果

#00     0msec 開始
#01     1msec Run
#01     1msec Start
#02     1msec Run
#02     1msec Start
#03     1msec Run
#03     1msec Start
#04     2msec Run
#04     2msec Start
#05     2msec Run
#05     2msec Start
#06     2msec Run
#06     2msec Start
#07     2msec Run
#07     2msec Start
#00     2msec 登録完了
#20     2msec Run
#20     3msec Start
#08     3msec Run
#08     3msec Start
#02  2001msec End
#01  2001msec End
#03  2001msec End
#09  2001msec Run
#10  2001msec Run
#11  2001msec Run
#09  2001msec Start
#10  2001msec Start
#11  2001msec Start
#07  2002msec End
#06  2002msec End
#05  2002msec End
#04  2002msec End
#12  2002msec Run
#14  2002msec Run
#13  2002msec Run
#15  2002msec Run
#12  2002msec Start
#14  2002msec Start
#13  2002msec Start
#15  2002msec Start
#08  2003msec End
#20  2003msec End
#16  2003msec Run
#16  2003msec Start
#19  2003msec Run
#19  2003msec Start
#11  4001msec End
#10  4001msec End
#18  4001msec Run
#17  4001msec Run
#18  4001msec Start
#17  4001msec Start
#09  4001msec End
#15  4002msec End
#14  4002msec End
#12  4002msec End
#16  4003msec End
#13  4003msec End
#19  4003msec End
#18  6001msec End
#17  6001msec End

私の実行環境では、同時に実行できるタスクが9個なので、セマフォに指定したカウンタが0になることはありませんでした。


初めよくわからなかったMSDNの説明もこのサンプルの動作ログをみながら読み返せば、書いてある意味がわかったんじゃないでしょうか?私はわかりました(^^;

テキストレイアウトでヒットテストを実行する方法

今回は、How to Perform Hit Testing on a Text Layout - Windows applications | Microsoft Docsです。そして、参考にしたサンプルの内容に文字列全体の領域の取得とヒットテストでヒットした文字の領域も描画するコードを追加しました。


まず、Direct2D に挑戦を参考にサンプルを作成する前の準備を行ってください。準備ができたら、CChildViewにサンプルコードを実装していきます。


1.デバイス非依存のオブジェクトを作成

void CChildView::DoCreateDeviceIndependentResources()
{
    //Direct2Dのファクトリオブジェクト
    m_d2dFactory = dx::Direct2D::CreateFactory();
    
    //DirectWriteのファクトリオブジェクト
    m_dwFactory = dx::DirectWrite::CreateFactory();
    
    //テキストレイアウトに使用するテキスト形式オブジェクト
    m_TextFormat = m_dwFactory.CreateTextFormat(L"consolas", 32.0F);
    m_TextFormat.SetTextAlignment(dx::DirectWrite::TextAlignment::Leading);
    m_TextFormat.SetParagraphAlignment(dx::DirectWrite::ParagraphAlignment::Near);

    //テキストレイアウトオブジェクト
    CString msg = L"Click on this text.\nKlicken Sie auf diesen Text.\nこのテキストをクリックしてください。";
    m_TextLayout = m_dwFactory.CreateTextLayout(msg, msg.GetLength(), m_TextFormat, FLT_MAX, FLT_MAX);
    
    //テキストを描画する位置
    m_orgText.X = 10.0F;
    m_orgText.Y = 10.0F;

    //テキストの描画サイズ
    dx::DirectWrite::TextMetrics text_metrics;
    m_TextLayout->GetMetrics(text_metrics.Get());

    //テキストを描画する領域を計算します。
    m_rcText.Left = text_metrics.Left + m_orgText.X;
    m_rcText.Right = m_rcText.Left + text_metrics.Width;
    m_rcText.Top = text_metrics.Top + m_orgText.Y;
    m_rcText.Bottom = m_rcText.Top + text_metrics.Height;
}

文字列全体の描画サイズは、IDWriteTextLayout::GetMetricsを使って、実際に描画する前に取得できます。GetMetricsは、文字列中の改行も認識して期待するサイズを返してくれます。これらを確認するためにデバイス非依存のオブジェクトの作成時に1度だけ取得し、このサイズの四角を描画しています。
このIDWriteTextLayout::GetMetrics関数は、dxライブラリで定義されていなかったので直接元のオブジェクトにアクセスして呼び出しています。こういった使い方ができるのもdxライブラリのいいところです。


2.デバイス依存のオブジェクトを作成

void CChildView::DoCreateDeviceResources()
{
    // レンダーターゲットの作成
    m_rt = m_d2dFactory.CreateHwndRenderTarget(*this);

    //ブラシの作成
    m_Brush = m_rt.CreateSolidColorBrush(dx::Color());
}


3.デバイス依存のオブジェクトを後始末

void CChildView::DoDiscardDeviceResources()
{
    m_Brush.Reset();
}


4. 描画する処理

    //背景クリア 
    m_rt.Clear(dx::Color(1.0F, 1.0F, 1.0F));

    //文字列を描画します。
    m_Brush.SetColor(dx::Color(0.0F, 0.0F, 0.0F));
    m_rt.DrawTextLayout(m_orgText, m_TextLayout, m_Brush);

    //文字列の領域を描画します。
    m_Brush.SetColor(dx::Color(0.0F, 0.4F, 0.5F));
    m_rt.DrawRectangle(m_rcText, m_Brush);

    //ヒットテストでヒットした文字の領域を描画します。
    m_Brush.SetColor(dx::Color(1.0F, 0.0F, 0.0F));
    m_rt.DrawRectangle(m_rcHitText, m_Brush);

SolidColorBrush の色の変更は十分に無視できるほど短い処理時間なので描画毎に色を設定しても問題ありません。ヒットテストでヒットした文字の領域は赤で囲っています。この領域はマウスの左クリックしたときに取得しています。


5.クリックしたときの処理

void CChildView::OnLButtonDown(UINT nFlags, CPoint point)
{
    DWRITE_HIT_TEST_METRICS hitTestMetrics;
    BOOL isTrailingHit;
    BOOL isInside;

    m_TextLayout->HitTestPoint(
                    (FLOAT)point.x - m_orgText.X, 
                    (FLOAT)point.y - m_orgText.Y,
                    &isTrailingHit,
                    &isInside,
                    &hitTestMetrics
                    );

    m_rcHitText.Left = hitTestMetrics.left + m_orgText.X;
    m_rcHitText.Right = m_rcHitText.Left + hitTestMetrics.width;
    m_rcHitText.Top = hitTestMetrics.top + m_orgText.Y;
    m_rcHitText.Bottom = m_rcHitText.Top + hitTestMetrics.height;

    if(isInside)
    {
        BOOL underline = m_TextLayout.GetUnderline(hitTestMetrics.textPosition);

        DWRITE_TEXT_RANGE textRange = {hitTestMetrics.textPosition, 1};

        m_TextLayout.SetUnderline(!underline, textRange);
    }

    //再描画
    Invalidate(FALSE);

    D2dWnd::OnLButtonDown(nFlags, point);
}


ここまでの実装が済むとCChildViewクラスは下記のようになっています。

class CChildView : public dx::D2dWnd
{
protected:
    virtual BOOL PreCreateWindow(CREATESTRUCT& cs);

    virtual void DoCreateDeviceIndependentResources();
    virtual void DoCreateDeviceResources();
    virtual void DoDiscardDeviceResources();
    virtual void DoDraw();

private:
    dx::Direct2D::Factory1 m_d2dFactory;
    dx::DirectWrite::Factory1 m_dwFactory;
    dx::DirectWrite::TextFormat m_TextFormat;
    dx::DirectWrite::TextLayout m_TextLayout;
    
    dx::Direct2D::SolidColorBrush m_Brush;

    dx::Point2F m_orgText;
    dx::RectF m_rcText;
    dx::RectF m_rcHitText;

    DECLARE_MESSAGE_MAP()

public:
    afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
};


それでは、ビルドして実行して下さい。サンプルと同じ画面がでれば成功です。サンプルコードはここに置いてあります。