2015年10月24日土曜日

デレステのスタミナ管理ツールを作ってみた

最近、TVでCMもしているアイドルマスター シンデレラガールズ スターライトステージというゲームをプレイしている。
ゲーム自体はそれなりに楽しんでいるのだが今回の趣旨はそこではなく。

このゲームはスマホゲーらしくスタミナ制なのだが、
  • 曲を演奏するにはスタミナが必要
  • 曲を演奏すると経験値が手に入る
  • スタミナは時間経過とともにじわじわと回復する
  • レベルアップするとスタミナは全回復する
とまぁ、真面目にプレイしようと考えると凄く手間が掛かるのである。

そこで、勉強がてらにスタミナ管理ツールを作ってみた。
その名も、「デレステタイマー」。

javascriptを使っているので、本ブログではなく ここ に置いている。

宜しければどうぞ。

2015年7月8日水曜日

Windows7でリモートデスクトップ part3

前回紹介した方法を更に発展させてみよう。

リモートデスクトップのクライアントには自動で再接続を行うオプションがある。
つまりユーザが意図しないタイミングで切断された場合に文字通り再接続する機能なのだが、この機能と前回紹介した方法はちょっと相性が悪いという弱点があった。

クライアントが再接続したタイミングでサーバ側が切断を検知してセッションを切り替えてしまうのだ。折角繋ぎ直したのに今度はサーバ側からブチンとやられてしまうのである。

そこで、サーバ側が切断を検知してからセッションの切り替えを行うまでに時間的な猶予を儲ける方法を考えた。
つまり、こんな感じの動作である。

  1. サーバがセッションの切断を検知する。 
  2. ウィンドウを表示して、カウントダウンを開始する。
  3. カウントダウン終了までにキャンセルされなければセッションを切り替える。
 カウントダウンには、System32フォルダの中のtimeout.exeを利用する。

それでは前回設定したタスクの内容をタイムアウト対応するよう変更してみよう。 ここに書かなかった項目は前回と同じ設定とする。

  1. コントロールパネルから「管理ツール」を開く。
  2. 管理ツールの中の「タスク スケジューラ」を起動する。
  3. 左側のツリーから「イベントビューアー タスク」を選択する。
  4. 前回追加した「リモートデスクトップ切断後の処理」を右クリックしプロパティ画面を開く。
  5. 「全般」→「ユーザがログオンしている時のみ実行する」にチェックを入れる。
  6. 「操作」→「プログラムの開始」を選択し、編集ボタンを押下する。
  7. 「プログラム/スクリプト」の欄を「cmd」に変更する。
  8. 「引数の追加」の欄を「/q /c timeout 30 && tscon 1 /DEST:console /PASSWORD:*****」に変更して「OK」を押下する(*****は実際のパスワードを入れる)。
これで切断時は最初にカウントダウンが表示され、カウントダウン中に再接続が間に合えば、カウントダウンウィンドウ内でCtrl+Cを押すか、×ボタンでウィンドウ自体を閉じればセッションの切り替えをキャンセルすることが出来るようになった。

2015年6月28日日曜日

androidで辞書のアップデートが終わらない

今月辺りから始まった、androidスマホの通知エリアに「辞書のアップデート情報」が居座って消えてくれない現象があります。
全国のandroidユーザーの端末で一斉に同様の現象が起こり、ググると数万人単位で困っている模様。

これまでに分かっていることを自分なりにまとめるとこんな感じらしいです。
  1. Wifiに繋ぐと通知は消える
  2. ダウンロードマネージャのデータを消去すると消える
参考リンク:Yahoo知恵袋

ひとまず1番の方法で対処したのですが、数日単位で再発して毎回Wifiに繋ぐのも面倒臭い…。

そんな中新しい情報が飛び込んできました。
犯人は「Google日本語入力」らしい!

早速スマホのストアを確認すると「Google日本語入力」アプリに見事に「インストール」の文字が。
あれ、入ってない…。

と言う訳で他に疑わしいのがないか調べてみました。ちなみにandroid4.2.2での操作です。

  1. スマホの設定画面を開く
  2. 「言語と文字入力」を選ぶ
  3. 「キーボードと入力方法」の項目を見る
  4. 「Googleキーボード 英語(米国)」の右側の「>」を選択
  5. 「テキストの修正」を選択
  6. 「アドオン辞書」を選択
 ここまで操作すると辞書の一覧がずらっと出てきます。

ポルトガル語とか、明らかに自分で入れた憶えも使う予定もない辞書がインストール済みになっていたりする辺り、システムが自動的にダウンロードしている気配がぷんぷんします。
私の環境では大体1/4位がダウンロード済みになっていました。

つまりこいつが犯人だとすると、全ての辞書がインストール済みになるまでは何度通知を消してもゾンビのように復活してくる…?

なら手動で全部インストール済みにしてやれば良いじゃない!
と言うことでWifiに繋いで片っ端から辞書をインストールしてみました。

これで暫く様子を見てみます。何週間か経って、二度と通知が出てこなければこいつが犯人だったと言うことですね。

(2015/7/2追記)
また再発しました。
確認してみると、辞書の一覧でアップデートが必要な状態になっている辞書が幾つか。
と言う訳で犯人はこいつで確定ですね。そして手動インストール程度では解決出来ない、と。
はた迷惑すぎる…。

2015年6月3日水曜日

FireFoxでPixivを見るとフリーズする対策

FireFoxでWebを巡回していると、たまに非常に重いサイトがある。
中でもPixivは重い。開くだけでかなりの確率でフリーズする。なんだこれ。

アドオンを見直してみたり、セキュリティソフトを疑ったり、キャッシュや履歴をクリアしてみたり、ハードウェアアクセラレータを切ったり、何をやっても解決しなかったのだが、先日解決方法が分かったので忘れないうちにメモしておこう。

その方法とは、以下の通り。
  1. FireFoxのURL入力欄に「about:config」といれて設定画面を開く。
  2. 検索欄に「network.prefetch-next」と入れる。
  3. 値をfalseにする。
本来はページ内のリンク先のコンテンツを自動的に取得しておいて、そのリンクをユーザが開きたくなった時には既にキャッシュにあるから高速に開けるよ!と言う機能なのだが、これが悪さをしていた模様。

falseに変えたら笑いたくなるほどPixivが軽くなりました。
うん、疑ってゴメンねPixivさん。犯人は君じゃなくてFireFoxだったよ…。

もっとも、設定をfalseに変えることで先読みしなくなるため、場合によってはWebブラウジングの体感速度が遅くなることも予想されるが、フリーズしたりクラッシュするよりはマシなのでこのまま行こうと思う。

2015年4月15日水曜日

拡張メソッドでイベントを定型化する

.NETでは自作イベントを記述する処理は大体決まっていて、コピペの量産祭になることが多い。
そもそもイベントはメモリリークの原因になることが多いのでRxを使え、との議論もあるがここでは置いておいて。

大体こんな感じのコードが随所に溢れることになる。

//----イベントを宣言
public event HogeEventHandler Hoge;
protected virtual void OnHoge(HogeEventArgs e) {
    if(Hoge != null) {
        Hoge(this,  e);
    }
}

//-----イベントを発生
var e = new HogeEventArgs();
OnHoge(e);

何が嫌かというと、イベントの数だけメソッドが増えてしまうことだ。似たようなコードがずらずらと並んでいるのはとても見栄えが悪い。

そこで半分くらいはネタなのだが、以下のような方法を提案してみる。

public static class EventExtension {
    public static void Fire(this EventArgs e, object sender, dynamic handler) {
        if(handler != null) {
            handler(sender, e);
        }
    }
}

dynamic型を利用しているのが格好悪いが、ハンドラを上手い感じに引数に渡す方法がこれしか見付からなかった。もっと上手い指定の方法があれば良いのだが。

これを定義しておくと、以下のように書くことが出来るようになる。

//----イベントを宣言
public event HogeEventHandler Hoge
//----イベントを発生
var e = new HogeEventArgs();
e.Fire(this, Hoge);

同様にしてCancelEventArgsを拡張してみる。

public static class EventExtension {
    public static void Fire(this CancelEventArgs e, object sender, dynamic before, dynamic after, Action action) {
        if(before != null) {
            before(sender, e);
        }
        if(!e.Cancel) {
            action();
            if(after != null) {
                after(sender, e);
            }
        }
    }
}

これを定義しておくと、 以下のように書くことが出来るようになる。

//----イベントを宣言
public event HogeCancelEventHandler BeforeHoge, AfterHoge;
//----イベントを発生
var e = new HogeCancelEventArgs();
e.Fire(this, BeforeHoge, AfterHoge, ()=> Console.WriteLine("Hogeがキャンセルされなかったので実行したよ!"));

如何だろうか。これで個人的にはかなり楽になったのだが、他にやっている人を見掛けないのでバッドノウハウに分類される技なのかも知れない。

PropertyGridのプロパティ名をリソースを使って日本語化する

.NETで設定画面のGUIを作るとき、PropertyGridを使う人は多いだろう。
その際、例えばこんなモデルを設定したとする。

public class AppProperty {
    [Category("基本情報"), Description("名前です。")]
    public string Name { get; set; }
}

すると、画面には「Name」と表示される。
「Name」ならまだ良いが、これがもっと複雑なプロパティ名だった場合は、使いづらくて仕方がない。

解決策としては @IT さんの PropertyGridコントロールに表示されるプロパティ名を変更するには? に自作のAttributeを使う方法が提示されている。非常に有用な方法なのでそのままコピーして再利用されている方も多いだろう。

その方法では、こんな感じにモデルを作ることになる。

[TypeConverter(typeof(PropertyDisplayConverter))]
public class AppProperty {
    [Category("基本情報"), PropertyDisplayName("名前"), Description("名前です。")]
    public string Name { get; set; }
}

大抵の場合は、上記の方法で必要十分だろう。

だが、例えば以下のような場合では上記以外の方法が必要になる。
  • 多言語対応したアプリを作りたい
  •  同じ名前のプロパティが一杯あって、同じ属性の記述を量産するのは何か違う気がする。
こういう用途には、リソースファイルが非常に相性が良い。
つまり、属性からではなくリソースファイルからプロパティ名を取得するようなTypeConverterを自作してやれば良い。

と言う訳で LocalizableConverter なるものを作ってみた。

public class LocalizableConverter<RESOURCES>: TypeConverter {
    public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes) {
        return new PropertyDescriptorCollection(
            TypeDescriptor.GetProperties(value, attributes, true)
            .Cast<PropertyDescriptor>()
            .Select(original => new LocalazablePropertyDescriptor(original)).ToArray());
    }
    public override bool GetPropertiesSupported(ITypeDescriptorContext context) {
        return true;
    }
    public class LocalazablePropertyDescriptor: PropertyDescriptor {
        readonly PropertyDescriptor original;
        public LocalazablePropertyDescriptor(PropertyDescriptor original)
            : base(original) {
            this.original = original;
        }
        public override bool CanResetValue(object component) {
            return original.CanResetValue(component);
        }
        public override Type ComponentType {
            get { return original.ComponentType; }
        }
        public override object GetValue(object component) {
            return original.GetValue(component);
        }
        public override string Description {
            get { return original.Description; }
        }
        public override string Category {
            get { return original.Category; }
        }
        public override bool IsReadOnly {
            get { return original.IsReadOnly; }
        }
        public override void ResetValue(object component) {
            original.ResetValue(component);
        }
        public override bool ShouldSerializeValue(object component) {
            return original.ShouldSerializeValue(component);
        }
        public override void SetValue(object component, object value) {
            original.SetValue(component, value);
        }
        public override Type PropertyType {
            get { return original.PropertyType; }
        }
        public override string DisplayName {
            get {
                var name = base.DisplayName;
                var prop = typeof(RESOURCES).GetProperty(name, BindingFlags.Static | BindingFlags.NonPublic);
                if(prop != null) {
                    return (string)prop.GetValue(null);
                }
                return name;
            }
        }
    }
}


ジェネリクスを利用し、型パラメータにResourcesクラスを指定して使う。
リソースにプロパティ名と同名の文字列を定義していればその値が表示され、定義していなければプロパティ名がそのまま表示されるという寸法だ。

使い方としては、こんな感じになる。

[TypeConverter(typeof(LocalizableConverter<Resources>))]
public class AppProperty {
    [Category("基本情報"), Description("名前です。")]
    public string Name { get; set; }
}

あとは、Properties\Resources.resxに「Name」=「名前」になるように文字列を追加してやれば良い。