「 なーーー んーーーー でーーーー おーーーー そーーーー いーーーー のーーーーー」
「 ↑ ボール型なら 簡単だな☆
その前に、餅の形状を 調べる必要があるんじゃないか☆?」
「 顔の出現位置と 関係のある形状だけ リスト しなさい!」
「 ↑ もう少し 正確に描くと こうか……☆
どこに出現するか、ではなく、よっぽど変わったところでなければ……、
正方形の形をしている石の上なら 出したいところに 出せる、という感じだぜ☆」
「 独立しているものと、従属しているものに分けて 法則を解き明かしていきましょう!」
「 独立しているのなら、周りの石に関わらず、自分の好きな所に 顔を出せばいいのよ!」
「 誘目性の強さや 視点誘導の先を 位置ごとに 数値化できるならいいんだが……☆
調べてみるかだぜ☆」
「 独立しているものは、ワイルド・カードとして使えるのだから、後回しでいいんじゃないか☆?
回りに影響されて 決まってしまうやつ を先に決めたらどうだぜ☆?」
「 ↑ コンピューター囲碁では 餅 とは呼ばず、連(Ren, String, Group) と呼ぶ☆
上図では 27個の連が 確認できるし、検出するための再帰アルゴリズムも 探せば出てくるだろう☆
liberty みたいな感じの名前の関数になっていると思うぜ☆」
◆◆◆◆ ↓挿入
「 liberty のアルゴリズム知りたいやつがいるかもしれないな……☆
ちょっと 書いておくかだぜ☆」
「 ↑ 将棋盤のように描くが、13路盤のときは、枠を付けて 15路盤 のメモリを用意しろだぜ☆」
「 ↑ 枠の内側の 盤の左上から 英文読みで スキャンをして、最初に 白石に ぶつかったとしようぜ☆」
「 ↑ そして 現在位置から 左、上、右、下 の順に 隣に進もうとするとしよう☆
今いる所の石の色と同じ色が 隣にあれば 進めるぜ☆」
「 ↑ 一度通ったところは、二度と通らないようにチェックを入れておけだぜ☆ この探索は ツリー構造になる☆
次は下に行けそうだな☆」
「 ↑ この調子で 進めていくだけだぜ☆ 進める所がなくなったら、ツリーを一段戻れだぜ☆
まだ進んでいない枝があれば、その先を再開しろだぜ☆」
def search(current):
for direction in [1, -15, -1, 15]: # 左、上、右、下
next = current + direction
if !board_check[next] and board[next] == my_color:
board_check[next] = True # Check
# TODO 石の形を覚えるなら、ここでリストに入れるなり何なり 書けだぜ☆(^~^)
search(next): # Recursive.
# 帰り道に書いてもいいな☆(^~^)
「 ↑ 再帰関数 の書き方を覚えると それは簡単にできる☆
ただ、上のサンプルは ビール飲んだあとに書いたので 合ってるのか知らん☆ 調べろだぜ☆」
◆◆◆◆ ↑挿入
「 この半歩ずれてるやつは 計算で 算出できるものなのかだぜ☆?」
「 ↑ ブロックの右端とか、下端とかを チョンプ しようぜ☆?」
「 ↑ で、あとは 半歩ずらした気分 になれだぜ☆ ほんとに ずらすと手間になる☆」
「 これで 顔の指し手一覧 をリストする手段も確立したわね」
「 このマスに 評価値を入れていって、一番大きなところに 顔を出せばいいんだぜ☆」
「 ↑ 忘れていたぜ☆
右端をチョンプ、下端をチョンプ、これだけでは消し残っているのがあるから、 右下から見て ナナメ1つ上もチョンプしろだぜ☆」
「 ↑ しかし 石に顔が付いているとか どっちを向いているとか 考えたことがなかったので、
思考が ツルツル 滑り落ちていってしまうぜ☆
考え方の フック が欲しいよな☆」
「 死に石は魅力的で、顔の向きは アームに影響を受けるわよ」
「 顔の位置と、顔の向き は どちらが先に決まるべきだぜ☆?」
「 ↑ 石の顔は 向き合うのだから、どの石を見合うか 1対N の組みが先に決まり、
そのときの N は 基本的に 同じ方向を 向いているのよ。例外もあるけど」
「 そうなんだぜ、例外があるんだぜ☆ ヒューリスティックだよな☆
とろろ が どういう考えを持って 石の顔を この方向に向けたのか説明できるのなら
わたしは とろろ評論家になるぜ☆」
「 遠くにある死に石と、近くにある相手の石なら、どちらを優先して向くんだぜ☆?」
「 ↑ 石が近くにあることを優先するか、ペアを作ることを優先するか、どっちだぜ☆?」
「 まず、近くの石の方を向いて、その次に、余剰の顔があれば、ペアを作ることに ちからを回すのよ」
「 スキャンした順で うしろのやつが 余剰でいいんじゃない?」
「 ↑ N対1 のルールから、周りの顔から見て 利便性のあるところに顔があるといいよな☆」
「 埼玉県民と 千葉県民と 神奈川県民 が集まる場所は 東京ね」
「 ↑ 位置が定まれば、距離のルールで 向きも定まるか……☆?」
「 ↑ 点 なら すぐ距離を測れるが、変な餅の形してると 中心がまず 分からんしな……☆
堂々巡りだぜ☆」
「 ↑ 双頭、三叉頭 だったら どうするんだぜ☆? エアロゾルだったら☆?」
「 個体という考えでは ダメ なのでは☆?
人口密度を求めてはどうだろうか☆?」
「 13路盤の 13 は 素数 だからな~☆
面積を 平等に分割するのは どうやるかだぜ☆?」
「 天元 を原点にしましょう!
ウィンドウ・サイズは 1辺を奇数 3、5 にしましょう!」
「 ↑ なるほど……、どこがが割を食うのを避けられないのであれば 端に割を食わせ、欠損も まあまあ 押さえるサイズかだぜ☆」
「 各マス 重なった人口密度を 足し合わせて、周辺に人の多い地点の目安としましょう」
「 ↑ 7、5、5、7……、やっと終わったぜ、へぇーつら☆」
「 なんか 説得力があるふりをしている数に なってきたんじゃない?」
「 顔の出現位置アルゴリズムは これでいいんじゃない?
やってみりゃ だいたい 動くでしょ」
「 ↑ なるべく広い範囲で 一様 にしようと思うと、端っこは 切り捨てちゃうぐらい 割り食うんだが……☆」
「 囲碁の 盤端で戦うことを欲する人は とろろ碁 なんか遊ばないのよ!
ゲーム開発は 割り食う石を見切って捨てて 助かるところを 助けることなのよ! 100点を目指したら 終わんないの!」
「 お父んの切り方、得票数の公平性は 保たれているのかだぜ☆?」
「 ↑ すべてのマスを調べる必要はなくて、上下反転と45°回転を使えば 同型になるところは省けるぜ☆」
「 ↑ 人口密度とは 面積分の人口 なのだから、各マスごとに割り当てられた 面積を測ればいいんだぜ☆」
「 右とか 下とかに ずらすと もっと 均等にできるわよ?」
「 点対象の囲碁盤に、右とか 下とか 差を付けたくないぜ☆」
「 この切り分け方なら、コンボリューション すれば 内側の方は 均等 になるわよ?」
「 ↑ コンボリューションの方が マシそうな見た目をしているな……☆」
「 ウィンドウを大きくしたコンボリューションは 端は捨てるという前提なんだな☆」
「 ↑ レクタングルの方は カウントが大きくならないか☆?」
「 1マス 0.25 にしたらどうだぜ☆ それでも 差あるけどマシだろ☆」
「 仮に 顔の出現位置アルゴリズムは 24近傍の和 をコンボリューションして求めることにしましょう。
実装して ダメだったら 変えましょう。
次は 向きよ」
「 ↑ めんどくさいから こういう人口密度を 8方向 求めて 多い方向こうぜ☆?」
「 ↑ 枠の太さは 2マス にしようぜ☆ 24近傍のサーチを 回すんだぜ☆」
「 ↑ と思ったが レクタングル を考慮すると 左と下は 枠の太さを 3マス にした方がいいか……☆」
「 範囲内の石を 数えているだけで 顔の出現位置と、顔の向き の評価値になるという算段かだぜ☆」
「 盤のマスに付いた評価値を 目視確認でテストすることを考えると、小数点第2位までの4桁の数(25.00とか)を表示する方法が 欲しいわねぇ」
https://github.com/muzudho/tororo-go-js
「 じゃあ 全てのマスに石が置いてあると思って、探索を回しなさい」
「 ↑ だいたい こんな感じか……☆
関数名を考えるかだぜ☆」
「 ↑ 東西南北を関数名に付けるのは かっこわるいな……☆」
「 じゃあ directed_stone_density みたいな感じで☆」
「 こういう図形って どうやって プログラミングに落とし込むの?」
「 ふつうに 黄色を原点 (0, 0)
として、 (1,1)
とか座標指定しろだぜ☆
他の方法でも いいけど……☆」
「 ↑ 枠も含めて 17×17 の盤だと分かっていれば、プラス・マイナスだけで行けるぜ☆」
「 ↑ x と y を入れ替えて、 xの符号を反転すれば 反時計回りに90°回転したのと同じだぜ☆
これを 4回 繰り返せば 4方向もOKだぜ☆
これが 四則演算で 計算でできるぜ☆」
「 ↑ タテ、ヨコ どっちだっけ……☆ 原理は覚えられるが タテ、ヨコ は覚えられないぜ☆
各マス毎に 行列演算 すれば出るぜ☆
興味を持ったら アフィン変換 で調べろだぜ☆ 全パターン解説されてる☆」
「 xとyをひっくり返して、xの符号を反転するのでいいのなら、こんな行列演算しなくていいのでは☆?」
「 簡単だと思う方を使えだぜ☆ やりたいことがないうちは 道具の使い方は見えないものだぜ☆」
「 できるが やりたいのは ただの回転ではなく 荷物がスライドするような回転なんで、
複雑なんで、45°スタートのパターンを作っておいて、90°回転しろだぜ☆」
「 x、y は 90°で交差しているからな☆
真実の回転とは 2つの軸の 90°の間で起こることを言う☆ 円というのは 4分の1の回転を4回しているに過ぎない☆」
「 じゃあ 左右反転か上下反転か いちいち調べるぐらいなら 90°回転を 2回やった方がマシか……☆」
「 あっ、 y は 上が小さくて、下が大きいように 逆転しておいてくれだぜ☆」
「 13×13 の盤に 太さ2の枠を付けて、 17×17 にリサイズするのは どうやったらいいの?」
「 ↑ 分解すると こうなってるから、地道に編集してくれだぜ☆」
「 太さ2の枠ではなくて、太さ2と、太さ3のところがある枠なんだぜ☆」
「 ループで回すから、頭の1行だけ切り取って 12回繰り返すのではなく、 まるっと 13回繰り返すようにしてほしいんだけど」
「 ↑ 頭から切り取っても、尻から切り取っても どちらも同じだが、こうだぜ☆」
def resize13to18(a: list, default=0):
"""13×13サイズのベクトルを、18×18サイズのベクトルに変換します。"""
b = [default] * 38
for row in range(13):
b.append(a[row*13:(row+1)*13])
b += [default] * 13
return b
[ '.', '.', '.', '.']
「 例えば [.] * 4
と書くと上記のようになってしまい、 '....'
にはならないぜ☆?」
「 ''.join(x)
のパラメーター部にリストを挟んでおいてちょうだい」
「 ×13 ではなくて ×5 だろ☆ 間違いだらけだな☆ 直すか……☆」
def resize13to18str(a: list, default='.'):
"""13×13サイズのベクトルを、18×18サイズのベクトルに変換します。"""
s = ''.join([default] * 38)
for row in range(13):
s += f"{''.join(a[row*13:(row+1)*13])}{''.join([default] * 5)}"
s += ''.join([default] * 52)
return s
「 Python のベスト・プラクティスは知らないが、上記で動くぜ☆」
board18 = resize13to18str(stone_board1, '.')
rows = [board18[i: i+18] for i in range(0, len(board18), 18)]
board18_text = '\n'.join(rows)
print(board18_text)
「 ↑ こう書くみたいだが、Pythonの内包表記、読みにくいぜ☆
英語が母国語なら 読みやすいんだろうけど☆」
..................
..................
...x...o..........
..ooxxxxoo........
...oox..x..o......
..o.oox..x........
..oooox....o......
..xxxx..x.o.......
..................
.........x.o.o....
...x........ox....
..ox.x...x.oxx....
...ox....xooxxo...
..oox....xxxoo....
...o..............
..................
..................
..................
「 リストを 表形式で表示するの よく使うから 関数化できないの?」
def line_to_table_str(s: str, width: int):
"""1行の文字列を、複数行にします。"""
rows = [s[i: i+width] for i in range(0, len(s), width)]
return '\n'.join(rows)
def line_to_table(li: list, width: int):
"""1行の配列を、二次元配列にします。"""
return [li[i: i+width] for i in range(0, len(li), width)]
「 ↑ 文字列型と、それ以外の型で 関数2つ作るの イケてないな……☆」
[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 19, 19, 19, 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 24, 0, 0,
24, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 24, 0, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 24, 24, 24, 24, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24, 0, 0, 0, 0], [0, 0, 0, 14, 0, 5, 0, 0, 0, 19, 0, 0, 24, 24, 0, 0, 0, 0], [0, 0, 0, 0, 5, 0, 0, 0, 0, 19, 0, 0, 19, 19, 0, 0, 0, 0], [0, 0, 0, 0, 3, 0, 0, 0, 0, 14, 14, 14, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0]]
「 pretty
がやりたいんだろ☆ 調べるか……☆
Google で python pretty print list as table
とか検索すれば出てくるだろ……☆」
row_format = "{:>3}" * 18
for row in num_board18x18:
print(row_format.format(*row))
Output:
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 14 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 19 19 19 19 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 24 0 0 24 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 24 0 0 14 0 0 0 0 0 0 0 0
0 0 0 0 0 0 24 0 0 0 0 0 0 0 0 0 0 0
0 0 24 24 24 24 0 0 10 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 10 0 0 0 0 0 0 0 0
0 0 0 19 0 0 0 0 0 0 0 0 0 24 0 0 0 0
0 0 0 14 0 5 0 0 0 19 0 0 24 24 0 0 0 0
0 0 0 0 5 0 0 0 0 19 0 0 19 19 0 0 0 0
0 0 0 0 3 0 0 0 0 14 14 14 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
「 プログラミングの語彙は、プログラマーのブログか、ソースコードのコメントを読めだぜ☆
海外のメーリング・リストもあり☆
生まれた場所とか近所の中だけで調べてもガラパゴスなんで 学習のソースは その時代でイケてるコミュニティを探しとけだぜ☆」
「 Python のラムダ計算って 書きにくいよな……☆
高階関数を用いて手軽に コンビネーターがどのような形をしているか見る、というラムダ計算の構文の簡潔性を失っている……☆」
「 ↑ 内側のテーブルをループするのって めんどくさいよな☆
めんどくさくないと思ってるやつは めんどくさがれだぜ☆」
def scan_pure_board(f):
"""枠を除いた、盤上を1マスずつイテレートして座標を返します。Y座標は行番号です。"""
for x in range(2, 15):
for y in range(2, 15):
f((x, y))
「 ↑ 計算機科学は ここから始まったと言ってもいい ラムダ計算 の考え方を Pythonコードに落とし込むと こうだぜ☆
チューリング・マシンと ラムダ計算 を自在に操ることは 計算機科学の古典に親しむことだぜ☆」
「 パラメーターに 変数ではなくて 関数が入ってるだけよね」
「 数学でよく目にするのは 四則演算 だろ☆ 1+2+3
とか☆ こいつらは 横に並んでいるだけだぜ☆
ラムダ計算 は f(g(h))
のように入れ子できる☆ すると ツリー構造 を作れる☆
その違いを お見せしよう☆」
「 なんか 惑星が回っていて、回っている惑星の周りを衛星が回っている みたいなこと、あるだろ☆
↑ 上図は 4重ループになってるよな☆ 黒線で示した盤のx、yループ、赤線で示したウィンドウのx、yループ☆」
def scan_window_5x5(f, p):
"""中心を p とする 5x5 のウィンドウの各マスをイテレートして座標を返します。"""
for x in range(-2, 3):
for y in range(-2, 3):
f((p[0]+x, p[1]+y))
「 ↑ Python でこんな書き方でいいのか知らないが、ループも 自分に分かりやすいところで 小分けにしろだぜ☆」
pure_board_map = []
for y in range(2, 15):
for x in range(2, 15):
pure_board_map.append((x, y))
window_5x5 = []
for y in range(-2, 3):
for x in range(-2, 3):
window_5x5.append((x, y))
「 ↑ 二重ループが いつも同じ値を返すのに 二重ループするのが 無駄くさいぜ☆
テーブルにしといたろ☆」
「 ラムダ計算の話しの途中だったのに……☆ 良いデータ構造を与えると プログラムは不要になることがあるのに……☆」
def scan_pure_board(f):
"""枠を除いた、盤上を1マスずつイテレートして座標を返します。Y座標は行番号です。"""
for p in pure_board_map:
f(p)
def scan_window_5x5(f, p):
"""中心を p とする 5x5 のウィンドウの各マスをイテレートして座標を返します。"""
for pp in window_5x5:
f((p[0]+pp[0], p[1]+pp[1]))
scan_pure_board(lambda p: scan_window_5x5(
lambda pp: xxxxxxxx(pp), p))
「 ↑ 例えば ラムダ計算では 関数を合成できるんだぜ☆ 何この 可読性の低い Python のラムダ計算の構文☆?」
scan_pure_board(|p| scan_window_5x5(|pp| xxxxxxxx(pp), p))
λp.(λq.(q 1) p) λx.(x+1)
「 ↑ 例えば結果に 2 を出したけりゃ こうだぜ☆
ラムダ計算は プログラムを書くのには向いてないぜ☆ どのような動きになるかシミュレーションして、考え方だけ プログラムに使うんで☆」
「 ラムダ計算が プログラムだったら意味ないんだぜ☆
枝葉末節を 全部削って、入り組んだプログラムは フローとして どうなるか、を手短に見せてくれるものなんだぜ☆
ライフ・ゲームとか見たことあるかだぜ☆? あれを数式のノーテーションでやったみたいなの☆」
「 点p と 点pp が どういう動きをしているか 頭の中で アニメーションできるだろうか☆?
アニメーションを作ってやれば すぐ分かると思うんだが めんどいんで作らないぜ☆」
「 ところで変数名についてなんだが、 点pp でも 点q でも 点p2 でも なんでもいい☆
特に決まりはない☆ pの次だ、ということが分かれば みな同じ☆」
「 お父んは p と q のどっちを向いているのが ぴー で、もう片方が きゅー なのか瞬間に判別付かないから p と pp なんだろ☆」
「 一般化してしまうよな☆ 左右反転してるだけで同じものなのだ、みたいな☆」
「 色弱はユニバーサル・デザインで知られるようになったが、アフィン変換頭は知られていないぜ☆」
「 さんぽ と 裏返っている ちんぽ は瞬時に判別できるのよね」
「 で、石の密度なんだが、 どうせ最後に総和するのなら色を区別する意味ない のでは……☆」
「 確かに 総和 してるな……☆ これでは 石の多いところに 顔が出てくるだけだぜ☆
なぜ それで もっともらしさがあるのか……☆?」
「 相手の顔を見るために お互い集まっているのなら、 石の密度が高いところに 顔があるのも
もっともらしいんじゃない?」
window_5x5 = []
for y in range(-2, 3):
for x in range(-2, 3):
if y != 0 and x != 0: # 原点を数えない
window_5x5.append((x, y))
def stone_density_node(stone_board):
"""盤サイズは 太さ2と3の枠が付いた 計18x18 にしてください。"""
num_board = [0] * (18*18)
def count_up(stone_board, p, pp):
nonlocal num_board
if stone_board[to_addr18x18(pp)] != '.':
num_board[to_addr18x18(p)] += 1
scan_pure_board(lambda p: scan_window_5x5(
lambda pp: count_up(stone_board, p, pp), p))
return num_board
Trace | Start.
..................
..................
...x...o..........
..ooxxxxoo........
...oox..x..o......
..o.oox..x........
..oooox....o......
..xxxx..x.o.......
..................
.........x.o.o....
...x........ox....
..ox.x...x.oxx....
...ox....xooxxo...
..oox....xxxoo....
...o..............
..................
..................
..................
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 6 8 9 10 9 8 6 6 4 2 1 1 0 0 0 0
0 0 8 11 13 13 12 11 8 7 5 3 1 1 0 0 0 0
0 0 11 15 18 17 15 13 9 8 6 4 2 2 0 0 0 0
0 0 13 18 21 18 17 14 10 9 8 5 3 2 0 0 0 0
0 0 10 14 16 13 12 9 6 6 6 4 3 2 0 0 0 0
0 0 8 11 13 10 9 8 6 6 6 6 4 3 1 0 0 0
0 0 7 9 10 8 6 5 4 5 6 7 6 5 3 0 0 0
0 0 6 8 8 6 4 5 4 6 8 10 8 7 5 0 0 0
0 0 5 6 6 5 2 4 4 7 10 14 12 11 8 0 0 0
0 0 8 9 9 7 3 5 6 10 14 19 16 14 10 0 0 0
0 0 9 10 10 8 3 4 5 8 12 16 14 12 9 0 0 0
0 0 8 9 9 7 3 4 5 8 11 14 12 10 7 0 0 0
0 0 6 6 6 5 2 2 4 6 8 10 9 7 5 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
Trace | Finished.
「 ↑ 多分 石密度 出てるだろ☆ それっぽい数が入ってるぜ☆」
def stone_density_sq(stone_board):
"""盤サイズは 太さ2と3の枠が付いた 計18x18 にしてください。"""
num_board = [0] * (18*18)
def count_up(stone_board, p, pp):
nonlocal num_board
if stone_board[to_addr18x18(pp)] != '.':
num_board[to_addr18x18(p)] += 0.25
num_board[to_addr18x18((p[0]+1, p[1]))] += 0.25
num_board[to_addr18x18((p[0], p[1]+1))] += 0.25
num_board[to_addr18x18((p[0]+1, p[1]+1))] += 0.25
scan_pure_board(lambda p: scan_window_6x6(
lambda pp: count_up(stone_board, p, pp), p))
return num_board
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 6 8 9 10 9 8 6 6 4 2 1 1 0 0 0 0
0 0 8 11 13 13 12 11 8 7 5 3 1 1 0 0 0 0
0 0 11 15 18 17 15 13 9 8 6 4 2 2 0 0 0 0
0 0 13 18 21 18 17 14 10 9 8 5 3 2 0 0 0 0
0 0 10 14 16 13 12 9 6 6 6 4 3 2 0 0 0 0
0 0 8 11 13 10 9 8 6 6 6 6 4 3 1 0 0 0
0 0 7 9 10 8 6 5 4 5 6 7 6 5 3 0 0 0
0 0 6 8 8 6 4 5 4 6 8 10 8 7 5 0 0 0
0 0 5 6 6 5 2 4 4 7 10 14 12 11 8 0 0 0
0 0 8 9 9 7 3 5 6 10 14 19 16 14 10 0 0 0
0 0 9 10 10 8 3 4 5 8 12 16 14 12 9 0 0 0
0 0 8 9 9 7 3 4 5 8 11 14 12 10 7 0 0 0
0 0 6 6 6 5 2 2 4 6 8 10 9 7 5 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
0.00 0.00 2.75 6.00 7.00 7.50 7.25 6.25 5.00 4.00 3.00 2.00 1.00 0.50 0.25 0.00 0.00 0.00
0.00 0.00 6.50 14.25 16.50 17.25 16.25 13.75 11.00 8.75 6.50 4.50 2.50 1.50 0.75 0.00 0.00 0.00
0.00 0.00 8.50 18.50 21.00 21.50 19.75 16.50 13.25 10.50 8.00 5.75 3.50 2.25 1.00 0.00 0.00 0.00
0.00 0.00 9.25 20.00 22.25 22.50 20.75 17.50 14.00 11.00 8.75 6.50 4.00 2.50 1.00 0.00 0.00 0.00
0.00 0.00 8.00 17.25 18.75 18.50 17.25 14.75 12.00 9.75 8.50 7.25 5.00 3.50 1.75 0.25 0.00 0.00
0.00 0.00 6.50 14.00 15.00 14.25 13.00 11.25 9.50 8.25 8.25 8.25 6.50 5.00 3.25 1.00 0.00 0.00
0.00 0.00 6.00 12.75 13.50 12.50 10.75 9.25 8.25 8.00 9.25 10.00 8.50 7.00 5.25 2.00 0.00 0.00
0.00 0.00 5.50 11.25 11.50 10.75 9.25 8.25 8.25 9.50 12.25 13.75 12.25 10.25 8.00 3.25 0.00 0.00
0.00 0.00 4.75 9.50 9.50 8.75 7.75 7.50 8.50 11.25 15.25 17.75 16.25 13.50 10.75 4.50 0.00 0.00
0.00 0.00 4.75 9.50 9.50 8.50 7.25 7.00 8.50 12.00 16.50 19.50 18.00 15.00 12.00 5.00 0.00 0.00
0.00 0.00 5.00 10.00 10.00 9.00 7.25 6.50 7.75 11.00 15.25 18.00 16.75 14.00 11.25 4.75 0.00 0.00
0.00 0.00 4.75 9.50 9.50 8.50 6.75 6.00 7.00 9.75 13.25 15.50 14.50 12.00 9.50 4.00 0.00 0.00
0.00 0.00 3.75 7.50 7.50 6.75 5.50 5.00 6.00 8.25 10.75 12.50 11.75 9.50 7.25 3.00 0.00 0.00
0.00 0.00 1.50 3.00 3.00 2.75 2.25 2.00 2.50 3.50 4.50 5.25 5.00 4.00 3.00 1.25 0.00 0.00
0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
「 上のはノードで、囲碁の石を置くとこの石密度評価値だぜ☆
下のはスクウェアで、将棋の駒を置くとこの石密度評価値だぜ☆」
def ccw90(p):
"""反時計回りに90°回転"""
return (-p[1], p[0])
direction_map = []
"""
西向きを手動で作ります。
-2 . . x
-1 . x x
0 @ x x
1 . x x
2 . . x
0 1 2
"""
direction_map.append([(2, -2), (1, -1), (2, -1), (1, 0),
(2, 0), (1, 1), (2, 1), (2, 2)])
"""
北西を手動で作ります。
-2 x x x
-1 x x x
0 @ x x
1 . . .
2 . . .
0 1 2
"""
direction_map.append([(0, -2), (1, -2), (2, -2), (0, -1),
(1, -1), (2, -1), (1, 0), (2, 0)])
# あとは90°回転で作るぜ☆(^~^)
direction_map.append(list(map(lambda p: ccw90(p), direction_map[0])))
direction_map.append(list(map(lambda p: ccw90(p), direction_map[1])))
direction_map.append(list(map(lambda p: ccw90(p), direction_map[2])))
direction_map.append(list(map(lambda p: ccw90(p), direction_map[3])))
direction_map.append(list(map(lambda p: ccw90(p), direction_map[4])))
direction_map.append(list(map(lambda p: ccw90(p), direction_map[5])))
# print(f'direction_map=len:{len(direction_map)} {direction_map}')
「 こんな感じで ループ文を書かなくても済むように テーブルを作成するんだぜ☆」
「 こんなことするから このプログラムは何をやっているんですか? とマニュアルを書くことになるわけじゃない、
数式なしの日本語で」
「 原理的に お金の話しの輪の中から あんたが 外へ、外へ 行かない?」
「 そら、業務のワークフローを構想するのが仕事の上部組織は わたしのブログや、こんなプログラム記事なんか 見ていてはいけないんだぜ☆
ここは 実装 なのだから☆ 実装をしないのが 構想なのだから☆」
「 他の人の実装と、あなたの実装。どこで価格の差が付くの?」
「 だいたい、お金を稼ぐスキルではないんだぜ、プログラミングは☆
お金を稼ぐスキルを持ってるやつが プログラミング覚えたら ブーストかかるだけで☆
プログラミング・スキルは 空っぽの配管みたいなもんだぜ☆ 中を 水なり ガスなりが通って 使い物になるんだぜ☆」
def scan_window_dir_node(f, ccw45, p):
"""起点を p とする方向性のあるウィンドウの各マスをイテレートして座標を返します。"""
for pp in direction_map[ccw45]:
f((p[0]+pp[0], p[1]+pp[1]))
def directed_stone_density_node(stone_board, ccw45: int):
"""
Parameters
----------
ccw45:
0 - 東
1 - 北東
2 - 北
3 - 北西
4 - 西
5 - 南西
6 - 南
7 - 南東
"""
num_board = [0] * (18*18)
def count_up(stone_board, p, pp):
nonlocal num_board
if stone_board[to_addr18x18(pp)] != '.':
num_board[to_addr18x18(p)] += 1
scan_pure_board(lambda p: scan_window_dir_node(
lambda pp: count_up(stone_board, p, pp), ccw45, p))
return num_board
「 ↑ ボードのループも簡単だろ☆ これで8方向対応終わり☆」
「 しかも 回転が 同型にならないやつだぜ☆ 右側と下側に1多いという……☆」
def drop_shadow(p):
"""右下に影が落ちるような座標"""
return (p, (p[0]+1, p[1]), (p[0], p[1]+1), (p[0]+1, p[1]+1))
「 ↑ じゃあ ドロップ・シャドウの動きを作ってしまえだぜ☆」
def flat(m):
"""重複とネストを解消します。順はばらばらになります。"""
s = set()
for items in m:
# items は ((),(),(),()) みたいに丸かっこがネストしている。
for item in items:
s.add(item)
return list(s)
dir_sq_map = []
dir_sq_map.append(flat(map(lambda p: drop_shadow(p), dir_node_map[0])))
dir_sq_map.append(flat(map(lambda p: drop_shadow(p), dir_node_map[1])))
dir_sq_map.append(flat(map(lambda p: drop_shadow(p), dir_node_map[2])))
dir_sq_map.append(flat(map(lambda p: drop_shadow(p), dir_node_map[3])))
dir_sq_map.append(flat(map(lambda p: drop_shadow(p), dir_node_map[4])))
dir_sq_map.append(flat(map(lambda p: drop_shadow(p), dir_node_map[5])))
dir_sq_map.append(flat(map(lambda p: drop_shadow(p), dir_node_map[6])))
dir_sq_map.append(flat(map(lambda p: drop_shadow(p), dir_node_map[7])))
# print(f'dir_sq_map=len:{len(dir_sq_map)} {dir_sq_map}')
「 ↑ 被ったところは set()
で省けだぜ☆ 変数名も長くなったから短くしようぜ☆」
def scan_window_dir_sq(f, ccw45, p):
"""起点を p とする方向性のあるウィンドウの各マスをイテレートして座標を返します。"""
for pp in dir_sq_map[ccw45]:
f((p[0]+pp[0], p[1]+pp[1]))
def directed_stone_density_sq(stone_board, ccw45: int):
"""
Parameters
----------
ccw45:
0 - 東
1 - 北東
2 - 北
3 - 北西
4 - 西
5 - 南西
6 - 南
7 - 南東
"""
num_board = [0] * (18*18)
def count_up(stone_board, p, pp):
nonlocal num_board
if stone_board[to_addr18x18(pp)] != '.':
num_board[to_addr18x18(p)] += 0.25
num_board[to_addr18x18((p[0]+1, p[1]))] += 0.25
num_board[to_addr18x18((p[0], p[1]+1))] += 0.25
num_board[to_addr18x18((p[0]+1, p[1]+1))] += 0.25
scan_pure_board(lambda p: scan_window_dir_sq(
lambda pp: count_up(stone_board, p, pp), ccw45, p))
return num_board
「 ↑ 似たような関数を作るのはイケてないが、動きが細かく違うんで 分けてしまうのも しかたないぜ☆」
「 これで コードは書けたつもりだが、テストが手間だぜ☆
次に進もうぜ☆」
06時56分
「 こんな風に 切り分け(parse)たいわけだぜ☆
パースというのは 使いやすい形に 変えることで 1つ1つの部品が よく見えるようになる、ぐらいの意味で、深い意味のある言葉ではないぜ☆
シリアライズは 1列にして 連番振って 先頭から順に 読み取りやすくすることだぜ☆」
「 連は英語で string だし、文字列も英語で string だし、紐も英語で string よ?」
「 stone string みたいな名前にすれば分かるだろ……☆」
7時16分
「 原因は 昨日の夕方に食った ラーメンとビールだけどな☆」
「 ↑ 連(Ren)を作るアルゴリズムというよりは、呼吸点(Liberty)を調べるアルゴリズムと認識されていると思うんで、
Liberty アルゴリズムなんだが……☆」
「 ↑ 差分更新と、盤面更新の手法があって、これから説明するのは 1995年頃からある古典的な 盤面更新の方だぜ☆
これは 実装が簡単というメリットがあり、最初に手を付けるには よい方法だぜ☆
ただし、2020年の コンピューター囲碁の強いやつを作ろうと思う時は、差分更新も 別に調べて作って 使いどころに合わせて利用しろだぜ☆」
「 ↑ 盤面をスキャンするときは、一度読み終わった 連 は もう再チェックしないようにする仕組みが必要だぜ☆
チェックが終わった石は 印でも付けておけだぜ☆
そして スキャンのとき 印が付いているところは 飛ばせだぜ☆」
def scan_pure_board(f):
"""枠を除いた、盤上を1マスずつイテレートして座標を返します。Y座標は行番号です。"""
for p in pure_board_map:
f(p)
def scan_pure_board_with_dirty(f):
"""チェックされていない番地だけ、関数を実行します。"""
dirty_board = [False] * (18*18) # 読み取ったらフラグを立てていきます。
def check(p):
if not dirty_board[to_addr18x18(p)]:
dirty_board[to_addr18x18(p)] = True # 読取済みのチェックをします。
f(p)
scan_pure_board(lambda p: check(p))
「 ↑ じゃあ あとは 発火条件で切り分ける関数を追加すればいいんだぜ☆ こんな短いの関数化しなくていいと思うが、
Python のラムダ関数 書きにくいんで 関数にしてしまおう☆」
「 Python のラムダ計算の構文が 読みにくいだけなんで☆
ラムダ関数は、こっちから渡した関数に 引数を入れてくれると思えだぜ☆」
<書きかけ>
Crieitは個人で開発中です。
興味がある方は是非記事の投稿をお願いします! どんな軽い内容でも嬉しいです。
なぜCrieitを作ろうと思ったか
また、「こんな記事が読みたいけど見つからない!」という方は是非記事投稿リクエストボードへ!
こじんまりと作業ログやメモ、進捗を書き残しておきたい方はボード機能をご利用ください!