を作るのはさすがに無理なので五目並べをつくろう……と思ったらわたしには難しかったんで三目並べ(いわゆるマルバツ)を作ってみました。
ウォーゲームによると三目並べで核戦争が回避されるらしいのでこれでいいのだ。
- ということでコンピュータ対コンピュータの対戦を見るだけです
- インタラクティブに実装するのがめんどうだったんで
- 一応ミニマックス法で全探索してます
- 全探索なので評価関数は非常に簡単にしてます
- 第一手はさすがに探索に時間(とメモリ)がかかるのでランダムにしてます(評価関数の値的に第一手はどこでも同じ)
- 敵がアホだった場合の考慮をしていないです
- 文献読んで自分なりに組んだのでミニマックス法の参考にはなりません
Go 言語の感想。
- 率直にいって好き嫌いでいうと嫌い
- スクリプト言語よりは C 言語に相当近い
- 使っていて D言語とか(←使ったことないけど)とか Lua とか Pascal とか思い起こした
- もちろんこのへんは言語経験によると思います
- 言語系に含まれるソートが、Sort される要素に Sortable な interface を実装するとか、その発想にビックリ
- でもはっきりいって比較関数をわたすほうが楽だと思った
なんか Go で書くのが流行ってるみたいなので - muddy brown thang や 2009-11-11 - nobu-qの日記 を参考にさせていただきました。
package main
import "fmt"
import "sort"
import "rand"
import "time"
type Board [3][3]int;
var cellImage = map[int]string { 0: " ", 1: "O", -1: "X" };
func (board *Board) render() {
fmt.Print("+-+-+-+\n");
for y := 0; y < len(board); y ++ {
line := board.hline(y);
fmt.Print("|");
for x := 0; x < len(line); x ++ {
fmt.Print(cellImage[line[x]]);
fmt.Print("|");
}
fmt.Print("\n");
fmt.Print("+-+-+-+\n");
}
}
func (board *Board) hline(y int) [3]int {
return [3]int { board[0][y], board[1][y], board[2][y] };
}
func (board *Board) vline(x int) [3]int {
return board[x]
}
func (board *Board) cross(r int) [3]int {
if r > 0 {
return [3]int { board[0][0], board[1][1], board[2][2] };
}
return [3]int { board[0][2], board[1][1], board[2][0] };
}
func score_for_line(line [3]int, me int) int {
if line[0] * me > 0 && line[1] * me > 0 && line[2] * me > 0 {
return 1
}
return 0
}
func (board *Board) score_of_me(me int) int {
switch true {
case score_for_line(board.cross(1), me) > 0: return 1;
case score_for_line(board.cross(-1), me) > 0: return 1;
}
for i := 0; i < 3; i ++ {
switch true {
case score_for_line(board.vline(i), me) > 0: return 1;
case score_for_line(board.hline(i), me) > 0: return 1;
}
}
return 0
}
func (board *Board) score_for(me int) int {
switch true {
case board.score_of_me(me) > 0: return 1;
case board.score_of_me(-me) > 0: return -1;
}
return 0
}
func (board *Board) filled() bool {
for y := 0; y < 3; y ++ {
for x := 0; x < 3; x ++ {
if board[x][y] == 0 { return false }
}
}
return true;
}
type GoStep struct {
x int;
y int;
score float;
};
type GoStepArray []GoStep;
func (steps GoStepArray) Len() int {
return len(steps);
}
func (steps GoStepArray) Less(i, j int) bool {
return steps[i].score > steps[j].score;
}
func (steps GoStepArray) Swap(i, j int) {
steps[i], steps[j] = steps[j], steps[i];
}
func (steps GoStepArray) Sort() {
sort.Sort(steps);
}
func find_child_steps(ch chan GoStep, board Board, me int, depth int, x, y int) {
board[x][y] = me;
score := board.score_for(me);
if score != 0 {
ch <- GoStep{ x, y, float(score) };
return;
}
op_cands := find_steps(board, -me, depth + 1);
if len(op_cands) <= 0 {
ch <- GoStep{ x, y, 0 };
}
else {
ch <- GoStep{ x, y, - op_cands[0].score * 0.9 };
}
}
func find_steps(board Board, me int, depth int) GoStepArray {
ch := make(chan GoStep);
num_cands := 0;
for y := 0; y < 3; y ++ {
for x := 0; x < 3; x ++ {
if board[x][y] != 0 { continue }
num_cands ++;
if depth == 0 { fmt.Print("."); }
go find_child_steps(ch, board, me, depth, x, y);
}
}
cands := make(GoStepArray, num_cands);
for i := 0; i < num_cands; i ++ {
cand := <- ch;
cands[i] = cand;
}
cands.Sort();
if depth == 0 { fmt.Print("\n"); }
return cands;
}
func decide_step(board Board, me int) (int, int) {
cands := find_steps(board, me, 0);
if len(cands) <= 0 {
return -1, -1;
}
score := cands[0].score;
n := 1;
for n < len(cands) {
if cands[n].score != score { break }
n ++;
}
cand := cands[rand.Intn(n)];
return cand.x, cand.y;
}
func main() {
rand.Seed(time.Nanoseconds());
var board Board;
me := 1;
board[rand.Intn(3)][rand.Intn(3)] = me;
me = -me;
for {
board.render();
if board.score_for(me) != 0 { break }
if board.filled() { break }
x, y := decide_step(board, me);
board[x][y] = me;
me = -me;
}
}無理やり go routine と channel を使ってます。
気のせいかもしれないけど、channel を作って、作った本人と go routine で呼び出される両者から channel に同時書き込むと不安定だった気がします。仕様とかちゃんと確認してませんが。