2014年9月11日木曜日

2048のロジックを実装する

私はブームを2周くらい遅れて追いかける性質があるようで、ここ最近、2048というゲームにはまっています。ググればたくさん出てきますし、オープンソースってだけあって亜種も結構あるようなのでまあリンクは張らずにおいておきます。

さて、このようにシンプルなゲーム、ある程度やり始めると「これ、自動でやらせればよくね?」って思うようになってきますよね。えっ、ならないだって?:;(∩´﹏`∩);:なってください~。昔、マインスイーパーを自動で解くプログラムを書いたことがありましたが、今回は2048を解いていきたいと思います。

といってもスマホアプリは完全に守備範囲外ですし、別にゲームをやりたいんじゃなければスマホアプリにする必要もありません。
というわけで、お得意のC#で作ろうと思いましたが、オープンソースの2048はJavascriptで作られているようです。というわけで、2048をC#で再実装してしまいました。


まずは、オートソルバーを想定してインターフェースを実装してみましょう。
要するに、2048で必要な操作や、ゲームに際して提供する必要がある操作をインターフェースで提供してやります。そうすることで、別の人が書いた2048のアプリケーションでもそのインターフェースさえ実装していればオートソルバーが使えるようになります。

public interface I2048
{
    /// <summary>
    /// 行の本数
    /// </summary>
    int RowCount { get; }

    /// <summary>
    /// 列の本数
    /// </summary>
    int ColumnCount { get; }

    /// <summary>
    /// スコア
    /// </summary>
    uint Score { get; }

    /// <summary>
    /// ゲームオーバーかどうか
    /// </summary>
    bool IsGameOver { get; }

    /// <summary>
    /// row, columnの値
    /// </summary>
    /// <param name="row">行</param>
    /// <param name="column">列</param>
    /// <returns>その値</returns>
    uint this[uint row, uint column] { get; }

    /// <summary>
    /// 指定した行を取得するメソッド
    /// </summary>
    /// <param name="row">行番号</param>
    /// <returns>行の中身</returns>
    IEnumerable<uint> GetRow(int row);

    /// <summary>
    /// 指定した列を取得するメソッド
    /// </summary>
    /// <param name="column">列番号</param>
    /// <returns>列の中身</returns>
    IEnumerable<uint> GetColumn(int column);


    /// <summary>
    /// 左に動かす
    /// </summary>
    /// <returns>動かせたかどうか</returns>
    bool MoveLeft();

    /// <summary>
    /// 左に動かせるかどうか
    /// </summary>
    bool CanMoveLeft { get; }

    /// <summary>
    /// 右に動かす
    /// </summary>
    /// <returns>動かせたかどうか</returns>
    bool MoveRight();

    /// <summary>
    /// 右に動かせるかどうか
    /// </summary>
    bool CanMoveRight { get; }

    /// <summary>
    /// 上に動かす
    /// </summary>
    /// <returns>動かせたかどうか</returns>
    bool MoveUp();

    /// <summary>
    /// 上に動かせるかどうか
    /// </summary>
    bool CanMoveUp { get; }

    /// <summary>
    /// 下に動かす
    /// </summary>
    /// <returns>動かせたかどうか</returns>
    bool MoveDown();

    /// <summary>
    /// 下に動かせるかどうか
    /// </summary>
    bool CanMoveDown { get; }
}

これくらいの機能さえあれば2048のゲームの状態を取得することも操作することもできますかね。
2048の行数、列数はともに4が一般的ですが、別に4じゃないとこのゲームができないわけではないので(ゲームバランスの問題はあるでしょうけども)任意の列・行に対応できるような仕様にしています。
インデクサーで値が取得できればGetRowとかGetColumnは必須ではないですが、このゲームは行単位や列単位でものを見ることが多いので実装を義務付けても罰は当たらないでしょう。

さて、このインターフェースを実装していきます。
C言語系の言語は変数名等の最初が数字にできないので「2048」とかいうクラスを作るわけにはいきません。なので、Boardっていうクラスにしてみました。

コンストラクタはゲームの初期化を行っています。

public Board(int row, int column)
{
    if(row < 2 || column < 2)
        throw new ArgumentOutOfRangeException("The row and the column count must be bigger than or equals 2");

    RowCount = row;
    ColumnCount = column;

    table = new uint[row, column];

    PossibilityOfTwo = 0.75;

    MakeNumberAppear();
    MakeNumberAppear();
}


1マスだけではゲームができないので、2x2以上を義務付けています。正方形である必要はなく、例えば5x3などの長方形でもOKです。
PossibilityOfTwoは新しい数字が湧き出してくるときに2が出る確率です。残りは4が出てきます。とりあえず確率75%にしてみました。
MakeNumberAppearは新しい数字を湧き出させるメソッドです。ゲームスタート時点では2ヶ所に数字を表示しますから、2回呼び出しています。

ここでMakeNumberAppearの中身を見てみましょう。

/// <summary>
/// 新しい数字を沸き上がらせるメソッド
/// </summary>
private void MakeNumberAppear()
{
    var zero = Enumerable.Range(0, RowCount).
        SelectMany(row => GetRow(row).Select((val, col) => new { Row = row, Col = col, Val = val })).
        Where(p => p.Val == 0).
        ToArray();

    if(zero.Length == 0)
        throw new InvalidOperationException("There are no free space.");

    uint cellNumber = (uint)(random.Next() % zero.Length);

    table[zero[cellNumber].Row, zero[cellNumber].Col] = (random.NextDouble() < PossibilityOfTwo) ? 2u : 4u;
}

まずは、ゼロのセルを列挙しています。
とりあえず全てのセルを列挙し、匿名クラスでその行番号、列番号とそのセルの値を表すクラスを作り、最後にWhereでセルの値がゼロのもののみを拾ってきています。
そして、そして乱数でそのゼロのセルのなかから1つを選び、さらにそこから指定した確率で2か4を入れています。


指定した行をまるごと引っ張ってくるメソッドGetRowですが、実はとても簡単に実装できます。

/// <summary>
/// 指定した行を取得するメソッド
/// </summary>
/// <param name="row">行番号</param>
/// <returns>行の中身</returns>
public IEnumerable<uint> GetRow(int row)
{
    for(int i = 0; i < ColumnCount; i++)
        yield return table[row, i];
}

IEnumerableの概念を最初に勉強した時は「使う分には便利だけど実装はクッソめんどくさいだろうなー」って思いましたが、yield文によって上記のように超簡単に実装できるんですよね。なんていうか、for文から抜けてないかのような表記なのに抜けたような挙動の構文ってことでフローがわかりにくいようにも思えますが、この構文のシンプルさは私は大好きですね。

最後に、上下左右に動かすプログラムを実装しましょう。これは案外複雑です。
  1. 動かした方向に数が入ってるすべてのセルを詰める
  2. 動かした先のほうから反対方向へ順に見て行って、その反対方向側の隣のセルに同じ値があった場合、合体する。合体した場合は、合体した次のセルから隣のセルを見ていき、合体しなかった場合は次のセルを見ていく。
  3. ゲームスコアは、合体した後の値が加算される。
1はそこまで難しくなく実装できそうです。しかし、2が面倒くさいですね。普通に「隣のセルと同じだったら合体する」みたいなロジックで組んでしまうと、例えば4 2 2って並んでいた時に右に動かしたら、1回目で2 2が合体して4 4になり、さらにここで並んでしまうのでこれが合体して8になってしまいます。そのような現象を回避するために、動かした先のほうから反対方向へ順番に見ていく必要があります。結構複雑な感じになりそうです。

あ、ここで2の動作を「縮退」と名付けました。なんか2つのセルが合体して1つのセルに縮まる感じです。量子力学とかサーバー運用等に関して言う縮退の意味を考えるとどうなのかなーって気もしなくもないですが、なんていうか、ほかに良い日本語が見つからなかったので。

さて、ここで左に動かすメソッドを見ていきます。

/// <summary>
/// 左に動かす
/// </summary>
/// <returns>動かせたかどうか</returns>
public bool MoveLeft()
{
    if(CanMoveLeft) {
        for(int i = 0; i < RowCount; i++) {
            uint score;

            SetRow(i, GetRow(i).Where(p => p > 0).Degenerate(out score).AddZero(ColumnCount));

            Score += score;
        }
        MakeNumberAppear();
    }
    return CanMoveLeft;
}

あらかじめ動かせるかを調べてから、動かせた場合に各行について処理をしています。行を取得し、ゼロのセル、すなわち何も数字が無いセルをWhereで省きます。その後、縮退のDegenerateメソッドを呼び、最後に列数になるまでゼロのセルを後ろに付けたします。これだけです。DegenerateとAddZeroは私が実装した拡張メソッドです。中身は後述します。

このDegenerateメソッドのアルゴリズム、動かせなかったら動かさないまま値を返すので一見あらかじめ動かせるかどうかを調べてから動かす必要はなさそうです。ではなぜこれが必要かというと、2か4を湧き上がらせる条件として「動かした」ということが必要だからです。動かせる時のみ動かし、新しい数字を湧き上がらせるという手順を踏んでいます。

さて、上記の例は左へ動かす場合ですが、同様に上へ動かす場合はGetRow(i)の代わりにGetColumn(i)を呼び出してあげれば問題ないことは容易く想像がつくでしょう。もちろん、AddZeroの引数とかには注意してあげる必要がありますが。
じゃあ、右や下へ動かす場合はどうするか。
簡単です。Whereの後とAddZeroの後にReverse()を入れてあげるだけです。

GetRow(i).Where(p => p > 0).Reverse().Degenerate(out score).AddZero(ColumnCount).Reverse();

行の中身の順番を入れ替えて縮退させれば現象は左右逆になりますよね。下へ動かす場合も同様です。これでだいぶシンプルに各方向へ動かすメソッドが実装できました。

ここで、Degenerate()とAddZero()の中身を見てみましょう。まずはDegenerateから。

/// <summary>
/// 行または列をくっつけて成長させるメソッド
/// </summary>
/// <param name="array">成長前の配列</param>
/// <param name="score">点数</param>
/// <returns>成長後の配列</returns>
public static IEnumerable<uint> Degenerate(this IEnumerable<uint> array, out uint score)
{
    score = 0;
    if(array.Count() <= 1)    //要素が1個以下ならそのまま
        return array;
    else if(array.First() == array.Skip(1).First()) {    //次と同じだったら足して、残りをDegenerateする
        uint add = array.First();
        IEnumerable<uint> ret = array.Take(1).Select(p => p * 2).Concat(array.Skip(2).Degenerate(out score));
        score += add;
        return ret;
    } else    //次と同じじゃなかったら、その次のからDegenerateする
        return array.Take(1).Concat(array.Skip(1).Degenerate(out score));
}

3パターンにわかれます。
まず、要素が1個以下ならば縮退は起こりえないのでそのまま返します。
次に、最初の要素と2つ目の要素が同じだったら1つ目の要素をarray.Take(1).Select(p => p * 2)で2倍にしています。さらに、この配列を2つスキップした先の要素をDegenerateで再帰呼び出しします。そして、2倍にした側にConcatで連結しています。このとき、点数計算も同時にしています。
最後のパターンは1つ目と2つ目が同じじゃなかった時、2つ目の要素以降を再帰呼び出しで縮退させています。
これで全ての動作が網羅できますよね。我ながらなかなかシンプルにまとめあげたと思っています

つづいてAddZeroです。

/// <summary>
/// 指定した個数の要素数になるまで末尾にゼロをつなげるメソッド
/// </summary>
/// <typeparam name="T">型</typeparam>
/// <param name="array">元の配列</param>
/// <param name="TotalLength">最終的な長さ</param>
/// <returns>ゼロをつなげた配列</returns>
public static IEnumerable<T> AddZero<T>(this IEnumerable<T> array, int TotalLength)
{
    int count = array.Count();

    if(count == TotalLength)
        return array;
    else if(TotalLength < count)
        throw new InvalidOperationException("TotalLength must be bigger than or equals to array length");
    else
        return array.Concat(Enumerable.Range(1, TotalLength - count).Select(p => default(T)));
}

指定した個数より元の配列のほうが長かったら例外を吐くようにしています。
Enumerable.Rangeで追加分の長さの連続した数列を作りますが、これは必要ないのでSelectで使わないで捨てています。ジェネリックメソッドにしているので、default(T)になりますね。


最後に、動かせるかどうかを示すプロパティについて見てみます。
とはいっても、動かした後のデータと動かす前のデータをSequenceEqualで比べてるだけですがね。

/// <summary>
/// 左に動かせるかどうか
/// </summary>
public bool CanMoveLeft
{
    get
    {
        for(int i = 0; i < RowCount; i++) {
            if(!GetRow(i).Where(p => p > 0).Degenerate().AddZero(ColumnCount).SequenceEqual(GetRow(i)))
                return true;
        }
        return false;
    }
}

先述した通り、このプロパティで動かせるかを判断した後、改めて実際に動かす動作をしています。2度手間であまり賢い実装じゃないかもしれませんが、まあ、まとまりはいいのでこれでいいでしょう。

これに付随して、ゲームオーバーを表すプロパティ「IsGameOver」も実装しました。ゲームオーバーしたかどうかはすなわち全方向に動かせないということですので、そうなるように実装しました。

/// <summary>
/// ゲームオーバーかどうか
/// </summary>
public bool IsGameOver
{
    get
    {
        return !CanMoveLeft && !CanMoveRight && !CanMoveUp && !CanMoveDown;
    }
}


さて、ここまで見てもらってわかると思いますが、C#でデータの集まりを操作しようとするとガチガチのLINQ実装になりました。LINQは遅延評価なので、何度も同じIEnumerable<T>を評価するような式は実効効率の意味ではあまり賢くないかもしれませんが(1度配列などに落としておくと早くなると見込める)、まあ、今の段階では特に考えなくていいでしょう。全部LINQで書いてしまったほうが綺麗ですし。


さて、これでだいたいのロジックが出そろいました。
今回のテーマが「ロジックの実装」で、最終目標が「2048のオートソルバー」なのでUIは今のところ全然本気出して実装していませんが、とりあえず動作を確認するために簡単に実装しました。
こんな感じになっています。


次回はUIの整理かな~?

2014年9月8日月曜日

LINQ to Twitter v3で遊ぶ

今回はLINQ to Twitterのネタです。

C#でTwitterやるんだったらLINQ to Twitterでしょう。TwitterのAPI変更にもしつこくついていってくれて、ユーザーとしてはとても助かります。

久しぶりにLINQ to Twitterをいじったらバージョンが2から3に上がってました。ほうほう。と思って、安易にアップデートしてみたらいろいろと破壊的仕様変更がされていて焦りました。 いろいろといじってみるとバージョン3になってから.NET4.5の非同期処理に対応したようでした。Asyncメソッドの嵐です。まあ、Windowsストアアプリとかに使われることも想定しているライブラリですから、当然といえば当然の流れですよね。

ということで、使い方を一通りまとめてみます。

認証


Twitterのアプリケーションを作成するにはConsumerKeyとConsumerSecretが必要なことは多くの人がご存知かと思います。https://apps.twitter.com/からアプリケーションを登録してこれらのキーを手に入れます。

さて、認証に関して、2通りの方法を紹介します。

すでにAccessToken、AccessTokenSecretを知っているとき

アプリケーションに対応するキーの他、そのアプリケーションでユーザーを認証するために使うキーが2つあります。これらのキーを取得する必要がありますが、これを別経路で知っている場合はこれを直接入力して使うことができます(最近の上記のTwitter開発者向けサイトではアプリケーションを登録した本人のそのアプリケーション用のAccessToken、AccessTokenSecretを教えてくれるようです)。もちろん、その場合は他人にそのアプリケーションを渡せなくなる(渡したら自分のアカウントで好き放題されてしまう)ので注意してください。

SingleUserAuthorizer auth = new SingleUserAuthorizer() {
    CredentialStore = new SingleUserInMemoryCredentialStore() {
        ConsumerKey = "****",
        ConsumerSecret = "****",
        AccessToken = "****",
        AccessTokenSecret = "****"
    }
};

このように書けばおkです。超簡単です。

PINコードで認証をするとき

さて、では実際に自分のアプリケーションを他人に配布できるようなものにするとき、上記のような方法では不十分ですね。PIN認証する必要があります。

PinAuthorizer auth = new PinAuthorizer() {
    CredentialStore = new InMemoryCredentialStore() {
        ConsumerKey = "****",
        ConsumerSecret = "****",
    }
};

auth.SupportsCompression = false;
auth.GoToTwitterAuthorization = (link) => {
    Process.Start(link);
    Console.WriteLine(link);
};
auth.GetPin = () => {
    Console.Write("Input PIN :");
    return Console.ReadLine();
};
auth.AuthorizeAsync().Wait();

ConsumerKeyとConsumerSecretを設定し、認証用リンクを受け取り適切に転送するデリゲート、入力されたPINコードを返すデリゲートを設定してやります。そして、AuthorizeAsync関数を呼び出せば非同期で認証を行ってくれます。非同期ですので、これを同期的に処理するためにWaitメソッドを呼び出して終了を待っています。こうすると、linkのところでブラウザを起動して、ReadLineでPINの入力を要求して入力が終わったら認証が完了します。もしも誤ったPINが入力されたらWaitメソッドが例外を吐くようなので、適切に処理してやれば良いでしょう。

各種データの取得

タイムラインを取得する

つづいてタイムラインを取得します。この辺りからLINQ to Twitterの威力が発揮されます。

var q = from p in context.Status 
        where p.Type == StatusType.Home && p.Count == 100
        orderby p.CreatedAt
        select p;

foreach(var s in q) {
    ShowStatus(s);
    Console.WriteLine();
}

LINQがわかってる人なら超簡単に取得できていることがわかると思います。個人的に、取得する個数の指定で「p.Count == 100」とかいうのはちょっと不満なんですが…。だって、個々の要素についてのことじゃないじゃんってね。

特定のツイートを取得する

今度は特定のIDのツイートを取得します。InReplyToからリプライ元を取得する時などに役に立ちます。

var q = from p in context.Status
        where p.Type == StatusType.Show && p.ID == 508907673895436288
        select p;

ShowStatus(q.Single());
Console.WriteLine();

とってもシンプルですね。ただ、単一のツイートってわかりきってるのに列挙可能型になるのは冗長な気はしますが。ちなみに、バージョン2まではp.IDはstring型でしたが、バージョン3ではulong型になってました。個人的には良改変だと思ってます。

ユーザー情報を取得する

今度は任意のユーザーのアカウント情報を取得してみましょう。

var q = from p in context.User
        where p.Type == UserType.Show && p.ScreenName == "EH500_Kintarou"
        select p;
User u = q.Single();

Console.WriteLine("UserID = " + u.UserIDResponse);
Console.WriteLine("UserScreenName = " + u.ScreenNameResponse);
Console.WriteLine();

はい、簡単です。
UserクラスのScreenNameはどうもこの検索時にユーザー名を設定するためのプロパティのようで、サーバーからの応答はScreenNameResponseに入るみたいですね。この辺はかなり直感的じゃなくて使いにくいところですが、改良はなかなかされてないですね…。

自分のアカウント情報を取得する

次は自分のアカウント情報を見てみます。特に、PINコードで認証した時なんかは自分のアカウント情報は自明ではありません。調べる必要があります。

var q = from p in context.Account
        where p.Type == AccountType.VerifyCredentials
        select p;
Account a = q.Single();

Console.WriteLine("UserID = " + a.User.UserIDResponse);
Console.WriteLine("UserScreenName = " + a.User.ScreenNameResponse);
Console.WriteLine();

context.Accountを使ってる点はユーザー情報の取得とは違いますが、あとは概ね同じです。
ていうか、この辺全部返ってくる情報は1つってわかってるのに列挙可能型(ry はい、Singleで決め打ちしちゃってます。

UserStreamを受信する

Twitterにはリアルタイムでタイムラインなどを取得する機能があります。さらに、API1.1の仕様変更のタイミングでタイムラインの取得を頻繁にやりすぎるとすぐにAPIが切れてしまう使用になりました。UserStreamは必須と言えるでしょう。

(from p in context.Streaming where p.Type == StreamingType.User select p).StartAsync(async s => {
    await Task.Run(() => {
        Status status = new Status(LitJson.JsonMapper.ToObject(s.Content));
        if(!string.IsNullOrEmpty(status.Text)) {
            ShowStatus(status);
            Console.WriteLine();
        }
    });
});

なんていうか、もはやUserStreamまでにLINQを使う必要なんてあるのかって気すらしてきてしまいますが、その辺はLINQのプライド?なんでしょうね。
若干の設定をしてStartAsyncを呼び出すと、その中のデリゲートにUserStreamを受信するたびにデータが飛んできます。そのデリゲートがasyncなので、中でawaitメソッドの呼び出しをするためにあえてTask.Runを挟んでいます。
ちなみに、受信するデータはStatusだけではないので、あえてシリアライズされたままのJsonの情報で飛んできますので、そのままではツイートは見られません。なので、 new Status(LitJson.JsonMapper.ToObject(s.Content))でStatusクラスを作ってあげています。ただ、これでは例えばフォローされたとかいったような情報が拾えなくなります。ツイートだけを選り分けるためにTextプロパティが空じゃないかを見ていますが、もしもフォローされたとかの情報をちゃんと取得したければ、Jsonの時点でパースをしてあげる必要があるでしょう。
LINQ to TwitterとしてはUserStreamsParser for LinqToTwitterというサードパーティ製のライブラリの使用を推奨しているっぽいれすが、これを昔導入してみたところ、LINQ to Twitterの仕様変更かなんかで上手く動いてくれなかったと記憶しています。このライブラリもアプデされてないっぽいので、まあ、動かないのでしょう。

ツイートする

普通にツイートをする

ツイートも非同期メソッドになっています。

context.TweetAsync("なんていうか、LINQ to Twitter v3で.Wait()の嵐になってる(((").Wait();

非同期メソッドを同期的に使うならば、.Wait()を付ける必要がありますね。

リプライをする

さて、問題はリプライです。というのも、バージョン2ではツイートするためのメソッドであるUpdateStatusのオーバーロードとしてリプライが実装されていました。しかし、同様のオーバーライドがTweetAsyncには見当たりません。
と思って見てみたら、TwitterContextにReplyAsyncという別のメソッドが用意されていました。

context.ReplyAsync(508969959364362242, "非同期メソッドばっかりだからな。").Wait();

これは自分自身へのリプライなので@を本文につける必要が無いパターンですね。

位置情報をツイートをする

位置情報はオーバーロードとして実装されています。

context.TweetAsync("皇居なう(´へωへ`*)", 35.6855323M, 139.7527346M).Wait();

緯度経度はdecimal型なのでMまたはmのサフィックスを付ける必要があります。

画像をツイートする

画像はTweetWithMediaAsyncというメソッドになります。

context.TweetWithMediaAsync("コンソールTL", false, System.IO.File.ReadAllBytes(@"ss001.png")).Wait();

そのまま画像ファイルのバイト列を突っ込めばいいっぽいです。

ツイートを表示する

最後にツイートの表示です。
先ほどのユーザー情報の表示でUserクラスのScreenNameプロパティはデータを読みだした時には使えないなどといった若干のクセがありますが、Statusクラスにも同様にクセがあります。というよりか、C#ユーザーに取って自然な形を目指すよりTwitter APIに忠実にクラスを設計したといった感じなんですかね。

static void ShowStatus(Status s)
{
    if(s.RetweetedStatus != null && !string.IsNullOrEmpty(s.RetweetedStatus.Text)) {
        ShowStatus(s.RetweetedStatus);
        Console.WriteLine("Retweeted by @{0}", s.User.ScreenNameResponse);
    } else {
        Console.WriteLine("@{0}/{1}", s.User.ScreenNameResponse, s.User.Name);
        Console.WriteLine(s.Text.Replace("&lt;", "<").Replace("&gt;", ">").Replace("&amp;", "&"));
        Console.WriteLine("via {0} at {1}", new Regex(@"\>(?<source>.*)\<").Match(s.Source).Groups["source"].Value, s.CreatedAt);
    }
}

まず、リツイートですが、bool型のRetweetedっていういかにもリツイートされたツイートかどうかを示すプロパティっぽいのがありますが、これでリツイートされたツイートかどうかは見分けられないようです結局、RetweetedStatusに中身があるかどうかを見て判断しています。
ツイートの本文は、Jsonのために<>&などといった記号がエスケープされた状態でそのまま格納されています。なので、これらはReplaceを用いて展開をしてやる必要があります。もしかしたらこれ以外にもエスケープされてる文字があるかもしれません。
最後に、viaを表すSourceプロパティですが、これはそのリンクを含めたHTMLのAタグがまるごと入っています。なので、今回は手抜きで>と<に囲まれた領域を正規表現で抜き出してあげています。ちゃんとやるならタグをパースしてあげたりすると良いでしょう。

結構面倒ですね。



とまあ、概ねこんな感じでしょうか…。 まだ検索とか隅々まで試したわけじゃないですが、いろいろ使えそうです。

今回は非同期メソッドを活かしたコードは書いていませんが、また機会があったら書いてみたいですねえ。
Windowsストアアプリ、XAML/C#で作れるけど、それ以外の敷居が高すぎるからなあ…。