「憂鬱な勇者」を C# で
- 「憂鬱な勇者」を作った件 - hrkt0115311の日記
- 「憂鬱な勇者」がとても面白かったので、 Scheme に移植してみた - 黎明日記
- 「憂鬱な勇者」を Squeak Smalltalk で - sumim’s smalltalking-tos
C# でも書いてみました.
using System; using System.Linq; using System.Threading; using System.Collections.Generic; static class Program { public static IEnumerable<T> Repeat<T>(T element) { while (true) yield return element; } public static IEnumerable<int> UpToInfinity(this int initial) { for (var i = initial; ; i = checked(i + 1)) { yield return i; } } public static IEnumerable<TResult> Scan<TSource, TAccumulate, TResult>( this IEnumerable<TSource> source, TAccumulate seed, Func<TAccumulate, TSource, TAccumulate> func, Func<TAccumulate, TResult> resultSelector) { var result = seed; yield return resultSelector(result); foreach (var item in source) { result = func(result, item); yield return resultSelector(result); } } public static IEnumerable<TResult> ZipWith<T1, T2, TResult>( this IEnumerable<T1> source1, IEnumerable<T2> source2, Func<T1, T2, TResult> func) { using (var enumerator1 = source1.GetEnumerator()) using (var enumerator2 = source2.GetEnumerator()) { while (enumerator1.MoveNext() && enumerator2.MoveNext()) { yield return func(enumerator1.Current, enumerator2.Current); } } } public static IEnumerable<TResult> ZipWith<T1, T2, TResult>( this IEnumerable<T1> source1, IEnumerable<T2> source2, Func<T1, T2, int, TResult> func) { using (var enumerator1 = source1.GetEnumerator()) using (var enumerator2 = source2.GetEnumerator()) { int index = 0; while (enumerator1.MoveNext() && enumerator2.MoveNext()) { yield return func(enumerator1.Current, enumerator2.Current, index); index = checked(index + 1); } } } public static void ForEach<T>(this IEnumerable<T> source, Action<T> action) { foreach (var item in source){action(item);} } static string MultAsDecimal(this string a, string b) { var revA = a.Select(c => int.Parse(c.ToString())).Reverse().ToArray(); var revB = b.Select(c => int.Parse(c.ToString())).Reverse().ToArray(); return string.Concat( revA.SelectMany((n1, dig1) => revB.Select((n2, dig2) => new { N = n1 * n2, Digit = dig1 + dig2 })) .Concat(new[] { new { N = 0, Digit = revA.Length + revB.Length - 1 } }) .GroupBy(r => r.Digit, r => r.N) .OrderBy(g => g.Key) .Select(g => g.Sum()) .Scan(0, (rest, digitsum) => rest / 10 + digitsum, rest => rest % 10) .Skip(1) .Reverse() .SkipWhile(n => n == 0) .Select(n => n.ToString()) .ToArray()); } static void Main(string[] args) { //モンスター用配列 var monsters = new[] { "焼きたてパン", "強いシャチホコ", "もんじゃ焼き一年生", "怪人ホタテ男", "ニセ勇者", "逃げ足の早いアレ", "睡魔", "煩悩", "愛らしい子犬の中の人", "恋するスズメバチ", "勇敢なクマンバチ", "信じられない物", "勇者の師匠", "浮遊する鎧", "怪盗ドボン", "闇の招き猫", "誘惑のカスタードクリーム", "しょっぱすぎる籠手", "カレー味の兜", "光沢だけは一流の盾", "若葉マークのモンスター", "新緑の季節", "梅雨時の車両のニオイ", "暑すぎる夏", "新宿らしき何か", "やたら発達したドーナツ", "育ちすぎたクマー", "なごやかな雰囲気", "凍り付いた気配", "忍び寄る恐怖", }; //Lv up時に習得するもの var skills = new[] { "お豆腐の買い方","鉛筆の買い方", "消しゴムの使い方", "メモの取り方", "攻撃に使えないこともない呪文", "裏町の歩き方", "森林浴", "珈琲の味", "しじみのみそ汁の作り方", "回覧板の回し方", "郵便物の投函方法", "立ち話のコツ", "猫の呼び方", "犬の呼び方", "カラスの呼び方", "鳩専用豆鉄砲", "秘密の趣味", "速く走るコツ", "剣の使い方", "斧の使い方", "まきわりで、まっきわりわり", "聖なる祈り", "孤独", "涼しく過ごすコツ", "お洒落のコツ", "卵をふわっと焼く方法", "ごはんの研ぎ方", "油汚れの対応方法", "大人の振るまい", "Suicaの使い方", }; const string template1 = @"*----- {0}を倒した! {1}の経験値を得た。 勇者は{2}にレベルが上がった! 勇者は、{3}を覚えた。 "; const string template2 = @"*----- {0}を倒した! {1}の経験値を得た。 勇者は、また、レベルが上がった! 勇者は、ふと空しさを覚えた。 "; const string intermission = @" そして、 かくかくしかじかで、山あり谷ありの冒険が続いたが割愛。 "; // factorials var exps = 1.UpToInfinity() .Scan("1", (fact, n) => fact.MultAsDecimal(n.ToString()), _=>_) .Skip(1); var seq = Repeat(new Random()) .ZipWith(exps, (rand, exp, i) => new { Number = i, Exp = exp, Monster = monsters[rand.Next(monsters.Length - 1)], Skill = skills[rand.Next(skills.Length - 1)] }); const int NumButtles = 30; Enumerable.Repeat(template1, NumButtles - 1) .Concat(Enumerable.Repeat(template2, 1)) .ZipWith(seq, (template, param) => string.Format(template, param.Monster, param.Exp, param.Number + 1, param.Skill)) .SelectMany(msg => new[] { msg, intermission }) .Take(NumButtles * 2 - 1) .ForEach(msg => { Console.WriteLine(msg); Thread.Sleep(2000); }); } }
今回は変数名も関数名もインデントもやたら適当です.ごめんなさい.
しかし可変長整数が使えないのは困りました.結局文字列型を使って 10 進乗算を実装しました.
とまあご覧の通り C# でループを使わないコーディングスタイルの練習なわけですが,今まで何気なく書いてきたループ処理が新鮮に見えてくるから面白いですね.
異なる処理を交互に行いたい (今回はレベルアップの表示と,かくかくしかじかの表示) とか,最後の一回だけ別の処理をしたいとか.確かに結構あるかもしれません.とりあえず今回は SelectMany や Take,ZipWith で書いてみましたが,このあたりは追々別解も考えていくことにしましょう.
あと,Scan の 3 引数バージョンの挙動は後でちょっと弄るかもしれません.何か微妙に使いにくい.
外伝: 憂鬱な勇者の 3 分クッキング (2008年6月3日追記)
Mono 1.9.1 インストール済み openSUSE 10.3 Live CD を使って 3 分ぐらいで憂鬱な勇者を実行してみます.
OS 起動後,デスクトップ右下のネットワークアイコンからネットワークを有効化し,ターミナルを開いて,以下のように実行します.3 分クッキングらしく,ソースは作り置きのものを利用しましょう.
wget http://www.dwahan.net/nyaruru/hatena/melancholic.cs gmcs melancholic.cs mono melancholic.exe