オセロの全手順を出力
オセロの全手順を出力するシェルスクリプトとCソースを作りました。というか、AIに作らせました。
https://hasera.net/othello/archive/search-all.zip
・初手は f5 固定
・MAX_DEPTH が探索の深さで、初手 f5 を打った状態からの深さなので、MAX_DEPTH + 1 手までの全手順を出力します。
シェルスクリプト
#!/bin/bash
# オセロの全手順出力
# 初手 f5 固定
# MAX_DEPTH + 1手まで
# パスは手数にも文字列にも含めない
# 終局したら、その時点で探索終了
#
# 出力例:
# f5d6c3d3c4# ——————————–
# 設定
# ——————————–MAX_DEPTH=2
declare -a board
# ——————————–
# 初期盤面
#
# d4 = W
# e4 = B
# d5 = B
# e5 = W
# ——————————–init_board() {
for i in {0..63}; do
board[$i]=”.”
doneboard[27]=”W” # d4
board[28]=”B” # e4
board[35]=”B” # d5
board[36]=”W” # e5
}# ——————————–
# 座標文字
# ——————————–files=(a b c d e f g h)
coord() {
local r=$1
local c=$2echo -n “${files[$c]}$((r+1))”
}# ——————————–
# 合法手判定
# ——————————–legal() {
local r=$1
local c=$2
local p=$3local o
if [[ “$p” == “B” ]]; then
o=”W”
else
o=”B”
fi# 空きマスでなければ不可
[[ “${board[$((r*8+c))]}” != “.” ]] && return 1local dr dc rr cc n
for dr in -1 0 1; do
for dc in -1 0 1; do[[ $dr -eq 0 && $dc -eq 0 ]] && continue
rr=$((r+dr))
cc=$((c+dc))
n=0# 相手石が連続しているか
while ((rr>=0 && rr<8 && cc>=0 && cc<8)) &&
[[ “${board[$((rr*8+cc))]}” == “$o” ]]; don=$((n+1))
rr=$((rr+dr))
cc=$((cc+dc))
done# 相手石の先に自分の石があれば合法
if ((n>0)) &&
((rr>=0 && rr<8 && cc>=0 && cc<8)) &&
[[ “${board[$((rr*8+cc))]}” == “$p” ]]; thenreturn 0
fidone
donereturn 1
}# ——————————–
# 着手+石を反転
# ——————————–put() {
local r=$1
local c=$2
local p=$3local o
if [[ “$p” == “B” ]]; then
o=”W”
else
o=”B”
fiboard[$((r*8+c))]=”$p”
local dr dc rr cc n
for dr in -1 0 1; do
for dc in -1 0 1; do[[ $dr -eq 0 && $dc -eq 0 ]] && continue
rr=$((r+dr))
cc=$((c+dc))
n=0# 相手石を数える
while ((rr>=0 && rr<8 && cc>=0 && cc<8)) &&
[[ “${board[$((rr*8+cc))]}” == “$o” ]]; don=$((n+1))
rr=$((rr+dr))
cc=$((cc+dc))
done# 自分の石で挟めている
if ((n>0)) &&
((rr>=0 && rr<8 && cc>=0 && cc<8)) &&
[[ “${board[$((rr*8+cc))]}” == “$p” ]]; then# 挟んだ相手石を反転
rr=$((r+dr))
cc=$((c+dc))while [[ “${board[$((rr*8+cc))]}” == “$o” ]]; do
board[$((rr*8+cc))]=”$p”
rr=$((rr+dr))
cc=$((cc+dc))
done
fidone
done
}# ——————————–
# 合法手が1つでもあるか
# ——————————–has_legal_move() {
local player=$1
local r cfor r in {0..7}; do
for c in {0..7}; doif legal “$r” “$c” “$player”; then
return 0
fidone
donereturn 1
}# ——————————–
# 再帰探索
# ——————————–search() {
local depth=$1
local player=$2
local sequence=$3local next
if [[ “$player” == “B” ]]; then
next=”W”
else
next=”B”
fi# ——————————–
# 現在のプレイヤーの合法手を探索
# ——————————–local found=0
local r c mfor r in {0..7}; do
for c in {0..7}; doif legal “$r” “$c” “$player”; then
found=1
# 最大手数に到達したら、
# この手順を出力して終了
if (( depth == MAX_DEPTH )); thenecho “${sequence}$(coord “$r” “$c”)”
continue
fi# 盤面保存
local -a saved
saved=(“${board[@]}”)# 着手
put “$r” “$c” “$player”# 座標追加
m=”${files[$c]}$((r+1))”# 次の手
search \
$((depth+1)) \
“$next” \
“${sequence}${m}”# 盤面復元
board=(“${saved[@]}”)fi
done
done# ——————————–
# 合法手がない
# ——————————–if (( found == 0 )); then
# 相手にも合法手がない
# → 完全に終局
if ! has_legal_move “$next”; thenecho “$sequence”
return
fi# ——————————–
# 相手には合法手がある
# → パス
#
# パスなので
# depth も sequence も変更しない
# ——————————–search “$depth” “$next” “$sequence”
fi
}# ——————————–
# メイン
# ——————————–init_board
# 黒の初手 f5
put 4 5 B# 白の2手目から探索
search 1 W “f5”
Cソース
#include <stdio.h>
#include <stdlib.h>
#include <string.h>#define BOARD_SIZE 8
char board[64];
const char files[] = “abcdefgh”;
/*
* Othello 全手順出力
*
* 初手 f5 固定
* 最大 depth + 1 手まで
*
* 例:
* ./othello 2
*
* 出力:
* f5d6c3
* f5d6c4
* …
*
* パスは手数にも文字列にも含めない。
* 終局したら、その時点で探索終了。
*//* ——————————–
* 座標文字
* ——————————– */void coord(int r, int c, char *buf)
{
buf[0] = files[c];
buf[1] = ‘1’ + r;
buf[2] = ‘\0’;
}/* ——————————–
* 初期盤面
*
* d4 = W
* e4 = B
* d5 = B
* e5 = W
* ——————————– */void init_board(void)
{
int i;for (i = 0; i < 64; i++)
board[i] = ‘.’;board[27] = ‘W’; /* d4 */
board[28] = ‘B’; /* e4 */
board[35] = ‘B’; /* d5 */
board[36] = ‘W’; /* e5 */
}/* ——————————–
* 合法手判定
* ——————————– */int legal(int r, int c, char player)
{
char opponent;
int dr, dc;
int rr, cc;
int n;opponent = (player == ‘B’) ? ‘W’ : ‘B’;
/* 空きマスでなければ不可 */
if (board[r * 8 + c] != ‘.’)
return 0;for (dr = -1; dr <= 1; dr++) {
for (dc = -1; dc <= 1; dc++) {if (dr == 0 && dc == 0)
continue;rr = r + dr;
cc = c + dc;
n = 0;/* 相手石が連続しているか */
while (rr >= 0 && rr < 8 &&
cc >= 0 && cc < 8 &&
board[rr * 8 + cc] == opponent) {n++;
rr += dr;
cc += dc;
}/* 相手石の先に自分の石があれば合法 */
if (n > 0 &&
rr >= 0 && rr < 8 &&
cc >= 0 && cc < 8 &&
board[rr * 8 + cc] == player) {return 1;
}
}
}return 0;
}/* ——————————–
* 着手+石を反転
* ——————————– */void put(int r, int c, char player)
{
char opponent;
int dr, dc;
int rr, cc;
int n;opponent = (player == ‘B’) ? ‘W’ : ‘B’;
board[r * 8 + c] = player;
for (dr = -1; dr <= 1; dr++) {
for (dc = -1; dc <= 1; dc++) {if (dr == 0 && dc == 0)
continue;rr = r + dr;
cc = c + dc;
n = 0;/* 相手石を数える */
while (rr >= 0 && rr < 8 &&
cc >= 0 && cc < 8 &&
board[rr * 8 + cc] == opponent) {n++;
rr += dr;
cc += dc;
}/* 自分の石で挟めている */
if (n > 0 &&
rr >= 0 && rr < 8 &&
cc >= 0 && cc < 8 &&
board[rr * 8 + cc] == player) {/* 最初の相手石へ戻る */
rr = r + dr;
cc = c + dc;/* 挟んだ相手石を反転 */
while (rr >= 0 && rr < 8 &&
cc >= 0 && cc < 8 &&
board[rr * 8 + cc] == opponent) {board[rr * 8 + cc] = player;
rr += dr;
cc += dc;
}
}
}
}
}/* ——————————–
* 合法手が1つでもあるか
* ——————————– */int has_legal_move(char player)
{
int r, c;for (r = 0; r < 8; r++) {
for (c = 0; c < 8; c++) {if (legal(r, c, player))
return 1;
}
}return 0;
}/* ——————————–
* 再帰探索
* ——————————– */void search(int depth, char player, const char *sequence,
int max_depth)
{
char next;
int found = 0;
int r, c;char new_sequence[1024];
char move[3];next = (player == ‘B’) ? ‘W’ : ‘B’;
/* ——————————–
* 現在のプレイヤーの合法手を探索
* ——————————– */for (r = 0; r < 8; r++) {
for (c = 0; c < 8; c++) {if (!legal(r, c, player))
continue;found = 1;
/*
* 最大手数に到達したら、
* この手順を出力して終了
*/
if (depth == max_depth) {coord(r, c, move);
printf(“%s%s\n”, sequence, move);
continue;
}/* 盤面保存 */
char saved[64];
memcpy(saved, board, sizeof(board));/* 着手 */
put(r, c, player);/* 座標追加 */
coord(r, c, move);snprintf(new_sequence,
sizeof(new_sequence),
“%s%s”,
sequence,
move);/* 次の手 */
search(depth + 1,
next,
new_sequence,
max_depth);/* 盤面復元 */
memcpy(board, saved, sizeof(board));
}
}/* ——————————–
* 合法手がない
* ——————————– */if (!found) {
/*
* 相手にも合法手がない
* → 完全に終局
*/
if (!has_legal_move(next)) {printf(“%s\n”, sequence);
return;
}/*
* 相手には合法手がある
* → パス
*
* パスなので
* depth も sequence も変更しない
*/
search(depth,
next,
sequence,
max_depth);
}
}/* ——————————–
* メイン
* ——————————– */int main(int argc, char *argv[])
{
int max_depth;if (argc != 2) {
fprintf(stderr,
“使い方: %s <探索深さ>\n”,
argv[0]);
return 1;
}max_depth = atoi(argv[1]);
if (max_depth < 1) {
fprintf(stderr,
“探索深さは1以上を指定してください。\n”);
return 1;
}init_board();
/* 黒の初手 f5 */
put(4, 5, ‘B’);/* 白の2手目から探索 */
search(1, ‘W’, “f5”, max_depth);return 0;
}
結果
2手までの全手順 3通り
3手までの全手順 14通り
4手までの全手順 61通り
5手までの全手順 349通り
6手までの全手順 2050通り
7手までの全手順 13773通り
8手までの全手順 97554通り
9手までの全手順 751330通り
10手までの全手順 6142855通り
11手までの全手順 53065220通り
12手までの全手順は 484974802通り
「60手までの全手順」も動きますが、最後まで動かすには余りにも莫大な時間がかかり、実質不可能だと思います。

