「 AI竜星戦2019 だとばかり思っていて 無いな……、と思ったら
↓第11回UEC杯コンピューター囲碁大会 に戻ってて わらう☆」
「 WPF で どうやって囲碁盤 描くのか わたしが教えてほしいぐらいだぜ☆」
「 キャンバスか何かを全画面に出したらどうか☆?
スクロールバーや テキストボックスといった ウィジェットは 何も置くなだぜ☆
テキストは ログに吐き出して Visual studio code で開けば リアルタイムで更新されるんだぜ☆」
XAML でクラウディアさんを描いてみました
Shapes and Basic Drawing in WPF Overview
「 さっぱり分からん……☆ こいつらは XML で絵でも描いてるのか……☆?」
「 そうなんじゃないの? サンプル・プログラムをコピー貼り付けしなさいよ」
「 貼り付けろと言ったって、 どこに 貼り付けたらいいのか分からん……☆ <Grid>
タグの中でいいのか……☆?」
「 ↑サンプル・プログラムって できたら何か 嬉しさ が沸いてくるようなもののことだろ☆
なにも嬉しさが沸いてこない このサンプルは いったい……☆」
濃い緑色(#008833)の色見本とブランドを作る配色情報のホームページ
「 こういうとき 自分の想像や勘で闇雲にやるのではなく ツールや他人の資料、ひとのおススメに頼るのが上達のコツだぜ☆」
「 ↑置いたが、ウィンドウ・サイズに応じてリサイズする方法が分からんぜ☆」
「 イベントハンドラで再計算して 更新したらいいんじゃないのか☆?」
「 CSS (カスケード・スタイル・シート)にしてくれたら API(操作方法) 分かるのに……☆
.Width
(ワイス)、.Height
(ハイト)はあるのに .X
、.Y
相当のものが無いぜ☆」
How to draw a rectangle in WPF at a specific x,y screen location?
How to get a WPF window's ClientSize?
private void Window_SizeChanged(object sender, SizeChangedEventArgs e)
{
var window = (Window)sender;
Trace.WriteLine($"サイズチェンジ 横幅={window.Width} 縦幅={window.Height} グリッド {grid.RenderSize.Width}, {grid.RenderSize.Height}");
// 昔でいう呼び方で Client area は WPF では grid.RenderSize らしい(^q^)
// 短い方の一辺を求めようぜ☆(^~^)ぴったり枠にくっつくと窮屈なんで 0.95 掛けで☆(^~^)
var shortenEdge = System.Math.Min(grid.RenderSize.Width, grid.RenderSize.Height) * 0.95;
// センターを求めようぜ☆(^~^)
var centerX = grid.RenderSize.Width / 2;
var centerY = grid.RenderSize.Height / 2;
Canvas.SetLeft(board, centerX - shortenEdge/2);
Canvas.SetTop(board, centerY - shortenEdge/2);
board.Width = shortenEdge;
board.Height = shortenEdge;
}
「 ググって 質問しているやつらの話しをまとめて 図形を計算すると こうだが……☆」
WPFサンプル:Line, Polyline, Polygonで直線、図形を描く
4.2 線を引く
「 z-order
か z-index
か、何かがあるんじゃないの?」
「 Panel.ZIndex
というプロパティがあるな☆ これを使っていいのか知らないが、思ったような働きをするんで使おう……☆」
「 コピー貼り付けして x:Name
の数字を打ち直すの めんどうなんだけど……☆」
「 ↑ Line が なんか見えない枠の中にいて、ここから出ると 見えなくなるんだが☆」
「 その枠は何なの? ウィンドウをリサイズするたびに 伸ばせばいいの?」
// タテ線20本☆(^~^)
var verticalLines = new List<Line>() {
verticalLine0,
verticalLine1,
verticalLine2,
verticalLine3,
verticalLine4,
verticalLine5,
verticalLine6,
verticalLine7,
verticalLine8,
verticalLine9,
verticalLine10,
verticalLine11,
verticalLine12,
verticalLine13,
verticalLine14,
verticalLine15,
verticalLine16,
verticalLine17,
verticalLine18,
verticalLine19,
};
// ヨコ線20本☆(^~^)
var horizontalLines = new List<Line>() {
horizontalLine0,
horizontalLine1,
horizontalLine2,
horizontalLine3,
horizontalLine4,
horizontalLine5,
horizontalLine6,
horizontalLine7,
horizontalLine8,
horizontalLine9,
horizontalLine10,
horizontalLine11,
horizontalLine12,
horizontalLine13,
horizontalLine14,
horizontalLine15,
horizontalLine16,
horizontalLine17,
horizontalLine18,
horizontalLine19,
};
// タテ線をヨコに並べるぜ☆(^~^)
for (var column = 0; column < 20; column++)
{
var verticalLine = verticalLines[column];
Canvas.SetLeft(verticalLine, 0);
Canvas.SetTop(verticalLine, 0);
verticalLine.Width = grid.RenderSize.Width;
verticalLine.Height = grid.RenderSize.Height;
verticalLine.Stroke = Brushes.Black;
verticalLine.StrokeThickness = 1.5;
Panel.SetZIndex(verticalLine, 110);
// 盤の幅を21で割ろうぜ☆(^~^)
verticalLine.X1 = boardLeft + board.Width * 0.05 + board.Width / 21 * column;
verticalLine.Y1 = boardTop + board.Height * 0.05;
verticalLine.X2 = verticalLine.X1;
verticalLine.Y2 = verticalLine.Y1 + board.Height * 0.905; // 線の太さのせいでうまく合わん
}
// ヨコ線をタテに並べるぜ☆(^~^)
for (var row = 0; row < 20; row++)
{
var horizontalLine = horizontalLines[row];
Canvas.SetLeft(horizontalLine, 0);
Canvas.SetTop(horizontalLine, 0);
horizontalLine.Width = grid.RenderSize.Width;
horizontalLine.Height = grid.RenderSize.Height;
horizontalLine.Stroke = Brushes.Black;
horizontalLine.StrokeThickness = 1.5;
Panel.SetZIndex(horizontalLine, 110);
// 盤の幅を21で割ろうぜ☆(^~^)
horizontalLine.X1 = boardLeft + board.Width * 0.05;
horizontalLine.Y1 = boardTop + board.Height * 0.05 + board.Height / 21 * row;
horizontalLine.X2 = horizontalLine.X1 + board.Width * 0.905; // 線の太さのせいでうまく合わん
horizontalLine.Y2 = horizontalLine.Y1;
}
すやぁ すやー ずぴー
ゴンッ!
「 起こすのか寝かすのか どちらかにしてほしい☆
WPFに 配列は無いのかだぜ☆? 361個も 石のタグ 埋め込むの嫌なんだが…☆」
「 WPF ではなく XAML(ザムル)で調べればいいのか……☆」
Xamlで繰り返し要素を記述する
WPFでボタンの配列を作成する方法
「 動的に解決できないのかだぜ☆!? いつの時代のプログラマーなんだぜ☆!?」
How to: Draw an Ellipse or a Circle
「 ↑なんで こういうサンプルに 画像が付いてないんだぜ☆?
昔の雑誌なら 白黒のページにも 喜んで 画像が付いてただろ☆」
「 ↑とりあえず 丸は描けそうだな☆
線の交点の上に うまく置けるかどうか計算だな☆」
(うつらうつら)
ゴンッ!
// 石を描こうぜ☆(^~^)?
{
stone0.Width = board.Width / 21 * 0.9;
stone0.Height = board.Height / 21 * 0.9;
stone0.Stroke = Brushes.Black;
stone0.StrokeThickness = 1.5;
Panel.SetZIndex(stone0, 120);
stone0.Fill = Brushes.White;
// 盤の幅を21で割ろうぜ☆(^~^)
Canvas.SetLeft(stone0, boardLeft + board.Width * 0.05 - stone0.Width / 2);
Canvas.SetTop(stone0, boardTop + board.Height * 0.05 - stone0.Height / 2);
}
「 売れなかったゲームの数だけ書いてきたぜ☆
ゲームで儲けるより サラリーマンになった方が確実に稼げるぜ☆」
「 Microsoft社は何も疑問に思わないのだろうか……、
なんか オブジェクトを new できたりしないのかだぜ☆?」
<Canvas x:Name="canvas">
<Ellipse x:Name="stone0" />
</Canvas>
「 Ellipse を new して、 Canvas に Add できたら いいんだがなぁ☆」
// 石を描こうぜ☆(^~^)?
{
var stone = new Ellipse();
stone.Width = board.Width / 21 * 0.9;
stone.Height = board.Height / 21 * 0.9;
stone.Stroke = Brushes.White;
stone.StrokeThickness = 1.5;
Panel.SetZIndex(stone, 120);
stone.Fill = Brushes.Black;
// 盤の幅を21で割ろうぜ☆(^~^)
Canvas.SetLeft(stone, boardLeft + board.Width * 0.05 - stone0.Width / 2 + board.Width / 21 * 1);
Canvas.SetTop(stone, boardTop + board.Height * 0.05 - stone0.Height / 2);
canvas.Children.Add(stone);
}
「 ↑置けはした……☆ が、ウィンドウをリサイズするたびに増えてしまう……☆」
「 .Initialize
とか、 .OnLoad
とか、それっぽい名前のイベント・ハンドラーは無いの?」
「 じゃあ Loaded
に書いてみるかだぜ☆
それより コーヒー 飲もうぜ☆?」
「 ↑うっ、目が錯覚を☆! そして20路盤なんじゃないか これ☆?」
「 オブジェクトの生成は、 .Initialize
の方に書くか……☆
こういう 何はどこに書いたらいいのか さっぱり 分からんよな☆」
「 盤はできても、石をどこに置くのか 指示を飛ばすのが めんどうなのよね。
テキストボックスを置くのか、マウスでクリックするのか」
input.txt
&clear
.
「 30秒ごとに input.txt ファイルを読み込めだぜ☆
最後の空行に ドット .
を打ち込んだら 実行だぜ☆」
「 .Width
、 .Height
と、 .FontSize
をいじり回せだぜ☆」
「 トライ&エラーで 想像に近づけていってるんだが、タイポグラフィーの調整は むずかしいぜ☆」
「 フォントが正方形じゃないから 合わせるのは無理☆ てきとうで☆」
「 GNUGO では A1
だったかな……☆? x, y の並びだな☆」
「 foreach ループで回すなら A19, B19, C19… みたいな順になるの?」
「 そこは 0, 1, 2 … でいいのでは……☆
配列に入れて高速化しようぜ☆?」
input.txt:
K10
.
「 黒、白 の順に交互に打つので 色を指定する必要は無いだろう……☆」
「 めんどくさいんで 半透明で 端っこに出しとけばいいだろ……☆」
「 A19
を 0、 B19
を 1……、と変換するパーサーを作っておきたいな☆」
「 多分昔のは Rust言語なんだぜ☆ C#言語で ぱぱっと書いてしまおう……☆」
namespace kifuwarabe_uec11_gui
{
using System;
using System.Globalization;
/// <summary>
/// 盤の符号のパーサー。
/// </summary>
public static class CellSignParser
{
/// <summary>
/// `A19` を 0、 `B19` を 1、…。
/// </summary>
/// <param name="text"></param>
/// <returns></returns>
public static int ToIndex(string text)
{
if (text == null)
{
throw new ArgumentNullException(nameof(text));
}
// 最初の1文字はアルファベット。
var column = ((char)text[0]) - 65; // 65はAsciiCodeのA。97はa。コンピューター囲碁では I は抜く慣習。
if (32 <= column)
{
// 小文字から大文字へ変換。
column -= 32;
}
if (8 <= column)
{
column--;
}
var row = 0;
if (int.TryParse(text[1].ToString(CultureInfo.CurrentCulture), out int outRow1))
{
row = outRow1;
}
if (2 < text.Length)
{
row *= 10;
if (int.TryParse(text[2].ToString(CultureInfo.CurrentCulture), out int outRow2))
{
row += outRow2;
}
}
return (19-row)*19+column;
}
}
}
「 これぐらい いい加減でいいだろ……☆ きょうは終わり☆」
input.txt:
&set k10 b
&set k10 w
&set k10 .
「 石を置く命令文は &set
でいいかだぜ☆?
列の符号は 小文字でも OK とするぜ☆」
「 &
を打鍵するの、 Shiftキーを押さなくちゃいけなくて めんどくさくない?」
「 日本語キーボードだと 一番打ちやすい記号は ;
なんだが、 .ini
ファイルのコメントみたいで 嫌だしな……☆」
put b to k10.
put w to k10.
put s to k10.
「 構文に凝ると パーサーのメンテナンスするの大変だからな☆」
black j10 m10 n10
white k10 o10 p10
space L10 q10 r10
go.
「 繰り返し入力のしやすさを加味すると 色でまとめた方が いいかだぜ☆?
go.
と書くと ファイルの内容は空っぽになって input.log
に移るとしようぜ☆」
「 C#のロガーは 何を使うの?
エラーと、 input.log
は分けるの?」
「 ファイルを 追加書き込みモードで開けっぱなしにするかだぜ☆」
「 インプットのログだけ取るのかだぜ?
アウトプットもログを取らないと 前後関係が分からないのでは?」
「 じゃあ communication.log
に名称変更☆」
namespace kifuwarabe_uec11_gui
{
using System;
using System.IO;
using System.Text;
/// <summary>
/// 特に通信ログを書き込むことを想定したロガー。
/// </summary>
public sealed class CommunicationLogWriter : IDisposable
{
private StreamWriter StreamWriter { get; set; }
public CommunicationLogWriter(string file)
{
// 追加書き込みモードでファイルを開きます。
this.StreamWriter = new StreamWriter(file, true, Encoding.UTF8);
}
public void WriteLine(string text)
{
this.StreamWriter.WriteLine(text);
}
public void Flush()
{
this.StreamWriter.Flush();
}
/// <summary>
/// 破棄。
/// </summary>
public void Dispose()
{
this.StreamWriter?.Close();
this.StreamWriter = null;
}
}
}
「 ラッピングしている意味は何だぜ☆? 直接 StreamWriter
でいいのでは☆?」
「 In/Out の区別を付けるメソッドとか あとでほしくなるかも知らん☆」
InputTextReader:
namespace kifuwarabe_uec11_gui
{
using System;
using System.IO;
using System.Text;
/// <summary>
/// `input.txt` の読取☆(^~^)
/// </summary>
public sealed class InputTextReader : IDisposable
{
private StreamReader StreamReader { get; set; }
public InputTextReader(string file)
{
this.StreamReader = new StreamReader(file, Encoding.UTF8);
}
/// <summary>
/// 行読込。
/// </summary>
/// <returns>読み込んだ行、またはヌル。</returns>
public string ReadLine()
{
return this.StreamReader.ReadLine();
}
/// <summary>
/// 破棄。
/// </summary>
public void Dispose()
{
this.StreamReader?.Close();
this.StreamReader = null;
}
}
}
タイマにより一定時間間隔で処理を行うには?(WPFタイマ編)
「 ↑UIスレッドで動くタイマーがあるらしいぜ☆ 精度はそんなもんでいいだろ☆」
InputTextReader.cs:
namespace kifuwarabe_uec11_gui
{
using System;
using System.IO;
using System.Text;
/// <summary>
/// `input.txt` の読取☆(^~^)
/// TODO `input.txt` ファイルが必要☆(^~^)
/// ファイルにロックを掛けずに開くことが重要☆(^~^)
/// </summary>
public sealed class InputTextReader : IDisposable
{
/// <summary>
/// 読込用ファイル・ストリーム☆(^~^)
/// </summary>
private FileStream FileStreamR { get; set; }
/// <summary>
/// 読込用ストリーム・リーダー☆(^~^)
/// </summary>
private StreamReader StreamReader { get; set; }
/// <summary>
/// ファイル名☆(^~^)
/// </summary>
private string File { get; set; }
public InputTextReader(string file)
{
this.File = file;
this.FileStreamR = new System.IO.FileStream(
file,
FileMode.Open,
FileAccess.Read,
FileShare.ReadWrite);
this.StreamReader = new System.IO.StreamReader(this.FileStreamR, Encoding.UTF8);
}
/// <summary>
/// ファイル全部読み込む。
/// </summary>
/// <returns>読み込んだ行、またはヌル。</returns>
public string ReadToEnd()
{
var text = this.StreamReader.ReadToEnd();
// ファイルの先頭に読込位置を戻す。
this.FileStreamR.Position = 0;
// 書込み用ストリーム☆(^~^)
using (var writer = new StreamWriter(this.File, false, Encoding.UTF8))
{
// ファイルを空にするぜ☆(^~^)
writer.Write("");
writer.Flush();
}
return text;
}
/// <summary>
/// 破棄。
/// </summary>
public void Dispose()
{
this.FileStreamR?.Close();
this.FileStreamR = null;
this.StreamReader?.Close();
this.StreamReader = null;
}
}
}
MainWindows.xaml.cs:
private void Window_Initialized(object sender, System.EventArgs e)
{
// 中略
// UIスレッドで動くタイマー☆(^~^)
{
this.DispatchTimer = new DispatcherTimer();
this.DispatchTimer.Start();
this.DispatchTimer.Interval = TimeSpan.FromSeconds(5);
this.DispatchTimer.Tick += (s, e) =>
{
var line = this.InputTextReader.ReadToEnd();
Trace.WriteLine($"Read | {line}");
};
}
// 中略
}
「 .ReadToEnd()
は 1文字ずつ取っている気がする……、仕組みが分からんが☆
行ごとに取れないのかだぜ☆?」
「 いや、文字列を foreach文で回すと1文字ずつ取ってしまうのか……☆」
// UIスレッドで動くタイマー☆(^~^)
{
this.DispatchTimer = new DispatcherTimer();
this.DispatchTimer.Start();
this.DispatchTimer.Interval = TimeSpan.FromSeconds(5);
this.DispatchTimer.Tick += (s, e) =>
{
var text = this.InputTextReader.ReadToEnd();
Trace.WriteLine($"Text | {text}");
this.CommunicationLogWriter.WriteLine(text);
this.CommunicationLogWriter.Flush();
foreach (var line in text.Split(Environment.NewLine))
{
Trace.WriteLine($"Read | {line}");
}
};
}
「 多分 BOM かと思うが、バイナリ・エディターで開いてみてくれだぜ☆」
Binary Editor BZ
UTF-8のBOM付き・BOM無しの違いと確認方法
「 BOM だったら EF BB BF
というデータがあるはずだぜ☆
ちなみに 0d 0a
は Windows の改行☆」
「 先頭に EF BB BF
があるわよ。 それ以外のところにも EF BB BF
があるわよ」
「 じゃあ このファイルは BOM付きのUTF だぜ☆
先頭のBOMだけ無視されるが、途中に出てくるBOM は文字化けするだろう☆」
「 じゃあ命令を プログラミングしてくれだぜ☆
これは 別個のプロジェクトを立てた方がいいだろう☆
プロジェクト名は kifuwarabe-uec11-gui-api
とか どうだぜ☆?」
「 プロジェクトを別に立てると めんどいんで しばらく ネームスペースを kifuwarabe_uec11_gui.API
にして使うぜ☆」
MainWindow.xaml.cs:
private void Window_Loaded(object sender, RoutedEventArgs e)
{
// テスト
Trace.WriteLine($"A19 | {CellAddress.Parse("A19", 0).Item1?.ToDisplay()}");
Trace.WriteLine($"B19 | {CellAddress.Parse("B19", 0).Item1?.ToDisplay()}");
Trace.WriteLine($"S1 | {CellAddress.Parse("S1", 0).Item1?.ToDisplay()}");
Trace.WriteLine($"T1 | {CellAddress.Parse("T1", 0).Item1?.ToDisplay()}");
Trace.WriteLine($"a19 | {CellAddress.Parse("a19", 0).Item1?.ToDisplay()}");
Trace.WriteLine($"b19 | {CellAddress.Parse("b19", 0).Item1?.ToDisplay()}");
Trace.WriteLine($"s1 | {CellAddress.Parse("s1", 0).Item1?.ToDisplay()}");
Trace.WriteLine($"t1 | {CellAddress.Parse("t1", 0).Item1?.ToDisplay()}");
}
「 ↑テストは MSTest などを使って Assert で書くべきなんだが、めんどくさかったんで ひとまず Trace で出力ウィンドウに出すことにする☆」
CellAddress.cs
namespace kifuwarabe_uec11_gui.API
{
/// <summary>
/// セル番地☆(^~^) A1 とか T19 みたいなやつだぜ☆(^~^)
/// </summary>
public class CellAddress
{
public ColumnAddress ColumnAddress { get; private set; }
public RowAddress RowAddress { get; private set; }
public CellAddress(ColumnAddress columnAddress, RowAddress rowAddress)
{
this.ColumnAddress = columnAddress;
this.RowAddress = rowAddress;
}
public static (CellAddress, int) Parse(string text, int start)
{
ColumnAddress columnAddress;
var next = 0;
{
(columnAddress, next) = ColumnAddress.Parse(text, start);
if (columnAddress == null)
{
// 片方でもマッチしなければ、非マッチ☆(^~^)
return (null, start);
}
}
// 列はマッチ☆(^~^)
RowAddress rowAddress;
{
(rowAddress, next) = RowAddress.Parse(text, next);
if (rowAddress == null)
{
// 片方でもマッチしなければ、非マッチ☆(^~^)
return (null, start);
}
}
// 列と行の両方マッチ☆(^~^)
return (new CellAddress(columnAddress, rowAddress), next);
}
public int ToIndex()
{
return (19 - this.RowAddress.Number) * 19 + this.ColumnAddress.Number;
}
/// <summary>
/// デバッグ表示用☆(^~^)
/// </summary>
/// <returns></returns>
public string ToDisplay()
{
return $"{this.ColumnAddress.ToDisplay()}{this.RowAddress.ToDisplay()}";
}
}
}
「 ↑セルは、列アドレスと、行アドレスの2つで できている☆」
ColumnAddress.cs
using System.Globalization;
namespace kifuwarabe_uec11_gui.API
{
/// <summary>
/// 列アドレスだぜ☆(^~^) A ~ T で、 I が欠番だな☆(^~^)
/// </summary>
public class ColumnAddress
{
public int Number { get; private set; }
public ColumnAddress(int number)
{
this.Number = number;
}
public static (ColumnAddress, int) Parse(string text, int start)
{
if (text == null || text.Length < start + 1)
{
return (null, start);
}
// 最初の1文字はアルファベット。
var column = ((char)text[start]) - 65; // 65はAsciiCodeのA。97はa。
if (32 <= column)
{
// 小文字から大文字へ変換。
column -= 32;
}
// コンピューター囲碁では I は抜く慣習。
if (8 <= column)
{
column--;
}
return (new ColumnAddress(column), start + 1);
}
/// <summary>
/// デバッグ表示用☆(^~^)
/// </summary>
/// <returns></returns>
public string ToDisplay()
{
// 65はAsciiCodeのA。コンピューター囲碁では I は抜く慣習。
return ((char)(65 + (this.Number < 8 ? this.Number : this.Number + 1))).ToString(CultureInfo.CurrentCulture);
}
}
}
RowAddress.cs:
namespace kifuwarabe_uec11_gui.API
{
using System;
using System.Globalization;
/// <summary>
/// 行番号だぜ☆(^~^) コンピューターチェスの国際式では 一番下が1行目だぜ☆(^~^)
/// </summary>
public class RowAddress
{
public int Number { get; private set; }
public RowAddress(int number)
{
this.Number = number;
}
public static (RowAddress, int) Parse(string text, int start)
{
if (text == null)
{
throw new ArgumentNullException(nameof(text));
}
var row = 0;
{
if (int.TryParse(text[start].ToString(CultureInfo.CurrentCulture), out int outRow))
{
row = outRow;
// 先頭桁目は確定☆(^~^)
start++;
}
else
{
// 1文字もヒットしなかった場合☆(^~^)
return (null, start);
}
}
if (start < text.Length)
{
if (int.TryParse(text[start].ToString(CultureInfo.CurrentCulture), out int outRow))
{
row *= 10;
row += outRow;
// 先頭から2桁目は確定☆(^~^)
start++;
}
}
// 1文字以上のヒットがある場合☆(^~^)
return (new RowAddress(row), start);
}
/// <summary>
/// デバッグ表示用☆(^~^)
/// </summary>
/// <returns></returns>
public string ToDisplay()
{
return this.Number.ToString(CultureInfo.CurrentCulture);
}
}
}
「 ↑こんな風に 部分に分解してパースする☆
パーサーのライブラリを使えば 便利かもしれないが、ルールを覚えるのがめんどくさいので 自作する☆」
ExactlyKeyword.cs:
namespace kifuwarabe_uec11_gui.API
{
using System;
/// <summary>
/// キーワードの完全一致☆(^~^)
/// </summary>
public class ExactlyKeyword
{
/// <summary>
/// キーワード☆(^~^)
/// </summary>
private string Word { get; set; }
public ExactlyKeyword(string word)
{
this.Word = word;
}
public static (ExactlyKeyword, int) Parse(string word, string text, int start)
{
if (word == null || text == null || text.Length < start + word.Length)
{
return (null, start);
}
if (text.Substring(start).StartsWith(word, StringComparison.Ordinal))
{
// 一致。
return (new ExactlyKeyword(word), start + word.Length);
}
return (null, start);
}
/// <summary>
/// デバッグ表示用☆(^~^)
/// </summary>
/// <returns></returns>
public string ToDisplay()
{
return this.Word;
}
}
}
「 ↑ black
や space
のマッチングも使用感を 同じにしておくぜ☆」
WhiteSpaces.cs:
namespace kifuwarabe_uec11_gui.API
{
using System.Text.RegularExpressions;
/// <summary>
/// 1個以上の空白☆(^~^)
/// </summary>
public class WhiteSpace
{
private static Regex regex = new Regex("(\\s+)", RegexOptions.Compiled);
/// <summary>
/// マッチングした文字☆(^~^)
/// </summary>
public string Text { get; private set; }
public WhiteSpace(string text)
{
this.Text = text;
}
public static (WhiteSpace, int) Parse(string text, int start)
{
if (text == null || text.Length <= start)
{
return (null, start);
}
var m = regex.Match(text.Substring(start));
if (m.Success)
{
// 一致。
var whiteSpaces = m.Groups[1].Value;
return (new WhiteSpace(whiteSpaces), start + whiteSpaces.Length);
}
return (null, start);
}
/// <summary>
/// デバッグ表示用☆(^~^)
/// </summary>
/// <returns></returns>
public string ToDisplay()
{
return this.Text;
}
}
}
BlackInstruction.cs:
using System.Collections.Generic;
using System.Text;
namespace kifuwarabe_uec11_gui.API
{
/// <summary>
/// black命令☆(^~^)
/// `black k10 k11 k12` みたいにして黒石を置いていこうぜ☆(^~^)
/// </summary>
public class BlackInstruction
{
/// <summary>
/// セル番地のリスト☆(^~^)
/// </summary>
public List<CellAddress> CellAddressList { get; private set; }
public BlackInstruction(List<CellAddress> cellAddressList)
{
this.CellAddressList = cellAddressList;
}
public static (BlackInstruction, int) Parse(string text, int start)
{
var cellAddressList = new List<CellAddress>();
var next = start;
ExactlyKeyword black;
{
(black, next) = ExactlyKeyword.Parse("black", text, next);
if (black == null)
{
// 非マッチ☆(^~^)
return (null, start);
}
}
// あとはリスト☆(^~^)
for (; ; )
{
WhiteSpace whiteSpace;
{
(whiteSpace, next) = WhiteSpace.Parse(text, next);
if (whiteSpace == null)
{
// おわり☆(^~^)
break;
}
}
CellAddress cellAddress;
{
(cellAddress, next) = CellAddress.Parse(text, next);
if (cellAddress == null)
{
// おわり☆(^~^)
break;
}
}
// マッチ☆(^~^)
cellAddressList.Add(cellAddress);
}
// 列と行の両方マッチ☆(^~^)
return (new BlackInstruction(cellAddressList), next);
}
/// <summary>
/// デバッグ表示用☆(^~^)
/// </summary>
/// <returns></returns>
public string ToDisplay()
{
// Python言語の mapコンビネーター とかあれば1行で書けるんだが、無いからforeachループで回そうぜ☆(^~^)
var tokens = new List<string>();
foreach (var cellAddress in this.CellAddressList)
{
tokens.Add(cellAddress.ToDisplay());
}
return $"{string.Join(' ', tokens)}";
}
}
}
(ず ず ずい ずいっ びゃー) # 熱いコーヒーをすする音
「 ぶぇぇ☆」
「 命令文を読み取れるようになったのなら、
input.txt
ファイルを読み込んだ結果は 命令文オブジェクトをリストに入れて返せばいいんじゃないの?」
「 タイム・スタンプなんか タイトルにしても コーディングはそんなに進まん☆」
「 その行が 何命令か 判定しないと、パーサーも当てれなくない?」
「 行の先頭のトークンを取得してから パーサーを選ぶのが実用的かだぜ☆?」
Word.cs:
namespace kifuwarabe_uec11_gui.API
{
using System;
using System.Text.RegularExpressions;
/// <summary>
/// 次の空白までの文字列☆(^~^)
/// </summary>
public class Word
{
/// <summary>
/// 英語がいうところの、単語☆(^~^)
/// </summary>
private static Regex regex = new Regex("(\\w+)", RegexOptions.Compiled);
/// <summary>
/// マッチングした文字☆(^~^)
/// </summary>
public string Text { get; private set; }
public Word(string text)
{
this.Text = text;
}
public static (Word, int) Parse(string text, int start)
{
if (text == null || text.Length <= start)
{
return (null, start);
}
var m = regex.Match(text.Substring(start));
if (m.Success)
{
// 一致。
var word = m.Groups[1].Value;
return (new Word(word), start + word.Length);
}
return (null, start);
}
/// <summary>
/// デバッグ表示用☆(^~^)
/// </summary>
/// <returns></returns>
public string ToDisplay()
{
return this.Text;
}
}
}
this.DispatchTimer.Tick += (s, e) =>
{
var text = this.InputTextReader.ReadToEnd();
// 空行は無視☆(^~^)
if (!string.IsNullOrWhiteSpace(text))
{
Trace.WriteLine($"Text | {text}");
this.CommunicationLogWriter.WriteLine(text);
this.CommunicationLogWriter.Flush();
foreach (var line in text.Split(Environment.NewLine))
{
Trace.WriteLine($"Read | {line}");
var (word, next) = Word.Parse(line, 0);
if (word != null)
{
switch (word.Text)
{
case "black": // thru
case "white": // thru
case "space":
ColorInstructionParameter cip;
(cip, next) = ColorInstructionParameter.Parse(line, next);
if (cip == null)
{
Trace.WriteLine($"Error | {line}");
}
else
{
Trace.WriteLine($"Test | {word.Text} {cip.ToDisplay()}");
}
break;
}
}
}
}
};
「 ↑コマンド名をまず取って、次に引数をパースする感じに変更だぜ☆
コマンド名と 引数の間のスペースも読み飛ばすように パーサーを変更☆ 詳しくは Git hub を見ろだぜ☆」
「 ネーム・スペースを kifuwarabe_uec11_gui.API
から kifuwarabe_uec11_gui.Script
にリネームしよう……☆
感覚的に☆」
「 スクリーン・ショットを乗せなさいよ!
ソースコードばっかで つまんないのよ!」
Instruction.cs:
namespace kifuwarabe_uec11_gui.Script
{
/// <summary>
/// 命令。
/// </summary>
public class Instruction
{
public string Command { get; private set; }
public object Argument { get; private set; }
public Instruction(string command, object argument)
{
this.Command = command;
this.Argument = argument;
}
}
}
「 ↑パースし終わったら、こういうオブジェクトに変換していこうぜ☆」
Window_Initialized:
this.DispatchTimer.Tick += (s, e) =>
{
var text = this.InputTextReader.ReadToEnd();
// 空行は無視☆(^~^)
if (!string.IsNullOrWhiteSpace(text))
{
Trace.WriteLine($"Text | {text}");
this.CommunicationLogWriter.WriteLine(text);
this.CommunicationLogWriter.Flush();
}
var scriptDocument = ScriptDocument.Parse(text);
if (scriptDocument != null)
{
// TODO:
}
};
ScriptDocument.cs:
namespace kifuwarabe_uec11_gui.Script
{
using System;
using System.Collections.Generic;
using System.Diagnostics;
public class ScriptDocument
{
public List<Instruction> Instructions { get; private set; }
public ScriptDocument(List<Instruction> instructions)
{
this.Instructions = instructions;
}
public static ScriptDocument Parse(string text)
{
// 空行は無視☆(^~^)
if (string.IsNullOrWhiteSpace(text))
{
return null;
}
var instructions = new List<Instruction>();
foreach (var line in text.Split(Environment.NewLine))
{
Trace.WriteLine($"Read | {line}");
var (word, next) = Word.Parse(line, 0);
if (word != null)
{
switch (word.Text)
{
case "black": // thru
case "white": // thru
case "space":
ColorInstructionArgument argument;
(argument, next) = ColorInstructionArgument.Parse(line, next);
if (argument == null)
{
Trace.WriteLine($"Error | {line}");
}
else
{
// Trace.WriteLine($"Test | {word.Text} {argument.ToDisplay()}");
instructions.Add(new Instruction(word.Text, argument));
}
break;
}
}
}
return new ScriptDocument(instructions);
}
}
}
「 お父んがソースコード ばんばん貼るから ブログが重くなってきた……☆」
「 命令を受け取ったら、それを画面に反映するコードがいるわよ?」
input.text:
black a19 k10 t1
white a19 k10 t1
space a19 k10 t1
「 こんな感じかだぜ☆ 明日はもっと調整、今日はここまで☆(^~^)!」
<次回につづく>
Crieitは個人で開発中です。
興味がある方は是非記事の投稿をお願いします! どんな軽い内容でも嬉しいです。
なぜCrieitを作ろうと思ったか
また、「こんな記事が読みたいけど見つからない!」という方は是非記事投稿リクエストボードへ!
こじんまりと作業ログやメモ、進捗を書き残しておきたい方はボード機能をご利用ください!