プログラミング
記事内に商品プロモーションを含む場合があります

3目並べ(tic-toc-toe)のプログラムをGo言語で書く|Go言語学習

tadanori

プログラミングの学習用に3目並べ(俗にいう○×ゲーム)をPythonで作成してみましたが、同じものをGo言語で書き直してみました。

この記事では、Pythonで作成した3目並べ(tic-toc-toe)をGo言語で書き直したものを紹介します。

詳しいプログラムの解説はPythonで作成した記事を参考にしてください。

あわせて読みたい
3目並べ(tic-toc-toe)のプログラムをPythonで書く|Python学習
3目並べ(tic-toc-toe)のプログラムをPythonで書く|Python学習

3目並べとは

英語ではtic-tac-toeと呼ばれるゲームです。日本では○×ゲームといった方がわかりやすいかもしれません。

ルールは、2人のプレイヤーが3×3のグリッド上に交互にマーク(○または×)をつけていくというシンプルなものです。最初のプレイヤーは○か×を選び、次のプレイヤーは残ったマークを使います。プレイヤーは自分の番が来たら、グリッドの空いているマスに自分のマークを置きます。

勝利条件は簡単です。先に縦、横、または斜めのいずれかに自分のマークを3つ並べたプレイヤーが勝ちとなります。もし、全てのマスが埋まってもどちらも3つ並べることができなかった場合、そのゲームは引き分けとなります。

思考部分のプログラミングも比較的簡単に作成できるので、プログラミングの練習にピッタリです。

Go言語による3目並べのコード

このプログラムですが、以下の手順で進めて行けば初心者〜中級者の学習に活用できます。

  1. ボードの表示、キーボード入力などの基本部分を作成する
  2. 勝ち判定を加えて、人対人の対戦ができるプログラムにする
  3. コンピュータの思考ルーチンを考えて実装してみる
    コンピュータの思考ルーチンは、現在の盤面+どちらのターンかを受け取って、置く場所を返すものにします。中身は自由に考えると楽しいです。
  4. 探索で手を読み切る思考ルーチンを作成してみる

①、②、③までは初級レベルでも可能だと思います。④は中級レベルのプログラムで結構考える必要があります。どこまで自力で作れるかでプログラミングスキルがある程度わかります。ぜひ、自力で作ってみてください。

参考コード

以下、コード全体になります。このプログラムは人対コンピュータの対戦ができるもので、コンピュータの思考ルーチンを4つ用意しています。

以下、各関数の処理です。

  • printBoard()
    3マス並べの盤面を描画する関数です。マス目と(a,b,c)と(1,2,3)のラベルを表示します。
  • getInput()
    1aといった形で、マスの位置をしていする入力を受け取る関数です。1aでもa1でもどちらでも受け取る様にしています。
  • checkWin()
    勝ちを判定する関数です。どちらかが勝っている場合は勝っている方(-1 or 1)を返します。どちらもまだ勝っていない場合は0を返します。

以下思考処理です

  • thinkSeq()
    左上から見ていって、最初に置ける場所を返します。弱いです。
  • thinkRandom()
    まだ○も×も書かれていないマスを探して、ランダムに返します。これも弱いです。
  • thinkStop2()
    相手が2マス連続になっている場合(リーチしている場合)に、3マス目が置けない様に塞ぎます。リーチがなければ、ランダムに空いたマスを返します。そこそこ遊べます(乱数しだいで勝てます)
  • think()
    深さ優先探索を行なって、勝ち、または、引き分けにできるマスにコマをおきます。人間がミスしない限り、引き分けになります。

3マス並べは、両者が間違えずに打った場合、引き分けになります

package main

import (
	"fmt"
	"math/rand"
)

func printBoard(board [][]int) {
	fmt.Println("--------------------")
	fmt.Println("  a b c")
	for i, row := range board {
		fmt.Print(i+1, " ")
		for _, v := range row {
			switch v {
			case 0:
				fmt.Print(". ")
			case 1:
				fmt.Print("X ")
			case -1:
				fmt.Print("O ")
			}
		}
		fmt.Println()
	}
}

func getInput() (int, int) {
	var s string
	fmt.Scan(&s)
	x, y := 0, 0
	if '0' <= s[0] && s[0] <= '9' {
		y = int(s[0]-'0') - 1
		x = int(s[1] - 'a')
	} else {
		y = int(s[1]-'0') - 1
		x = int(s[0] - 'a')
	}
	return x, y
}

func checkWin(board [][]int) int {
	for i := 0; i < 3; i++ {
		if board[i][0] != 0 && board[i][0] == board[i][1] && board[i][0] == board[i][2] {
			return board[i][0]
		}
		if board[0][i] != 0 && board[0][i] == board[1][i] && board[0][i] == board[2][i] {
			return board[0][i]
		}
	}
	if board[0][0] != 0 && board[0][0] == board[1][1] && board[0][0] == board[2][2] {
		return board[0][0]
	}
	if board[0][2] != 0 && board[0][2] == board[1][1] && board[0][2] == board[2][0] {
		return board[0][2]
	}
	return 0
}

// 最初に見つけた場所に置く
func thinkSeq(board [][]int) (int, int) {

	for i := 0; i < 3; i++ {
		for j := 0; j < 3; j++ {
			if board[i][j] == 0 {
				return j, i
			}
		}
	}
	return 0, 0
}

type pos struct {
	x, y int
}

// ランダムに置く場所を決める
func thinkRandom(board [][]int) (int, int) {
	p := []pos{}
	for i := 0; i < 3; i++ {
		for j := 0; j < 3; j++ {
			if board[i][j] == 0 {
				p = append(p, pos{j, i})
			}
		}
	}
	sel := rand.Intn(len(p))
	return p[sel].x, p[sel].y
}

// 負けない様ににする
func thinkStop2(boaard [][]int, turn int) (int, int) {
	fmt.Println("turn", turn)
	p := []pos{}
	for i := 0; i < 3; i++ {
		for j := 0; j < 3; j++ {
			if boaard[i][j] != 0 {
				continue
			}
			boaard[i][j] = -turn
			if checkWin(boaard) == -turn {
				boaard[i][j] = 0
				return j, i
			}
			boaard[i][j] = 0
			p = append(p, pos{j, i})
		}
	}
	sel := rand.Intn(len(p))
	return p[sel].x, p[sel].y
}

// 深さ優先探索で、最終的な勝者を返す
func think(board [][]int, turn int) (int, int, int) {
	// 9マス全てが埋まっているかどうか確認
	cnt := 0
	for i := 0; i < 3; i++ {
		for j := 0; j < 3; j++ {
			if board[i][j] == 0 {
				cnt++
			}
		}
	}
	if cnt == 0 {
		return -1, -1, 0 // 全て埋まっていればDRAW
	}

	x, y := 0, 0
	win := false
	res := -turn
	for i := 0; i < 3; i++ {
		for j := 0; j < 3; j++ {
			if board[i][j] != 0 {
				continue // 既に置かれている場合はスキップ
			}
			board[i][j] = turn
			winner := checkWin(board)
			if winner == turn {
				board[i][j] = 0
				return j, i, turn
			}
			_, _, r := think(board, -turn)
			board[i][j] = 0

			if r == turn { // 自分が勝つ場合は、その手を覚える
				win = true
				x, y = j, i
				res = turn
			}
			if win == false { // 自分の価値が決定してない場合
				if r == 0 { // 引き分けの場合は、手を覚える
					x, y = j, i
					res = 0
				}
				if r == -turn && res == -turn { //現在まだ引き分け以上の手が見つかっていなければ、場所を記録
					x, y = j, i
				}
			}
		}
	}
	return x, y, res
}

func main() {
	board := [][]int{
		{0, 0, 0},
		{0, 0, 0},
		{0, 0, 0},
	}

	for i := 0; i < 9; i++ {
		printBoard(board)
		winner := checkWin(board)
		if winner != 0 {
			fmt.Println("--------------------")
			if winner == 1 {
				fmt.Println("Winner is player1")
			}
			if winner == -1 {
				fmt.Println("Winner is player2")
			}
			fmt.Println("--------------------")

			return
		}

		var x, y int
		if i%2 == 0 {
			for true {
				fmt.Print("Enter position(ex. 1a):")
				x, y = getInput()
				if board[y][x] != 0 {
					fmt.Println("already occupied. try again.")
				} else {
					break
				}
			}
		} else {
			// x, y = thinkSeq(board)
			// x, y = thinkRandom(board)
			// x, y = thinkStop2(board, 1)
			x, y, _ = think(board, 1)
		}
		if i%2 == 0 {
			board[y][x] = -1
		} else {
			board[y][x] = 1
		}

	}

	printBoard(board)

	fmt.Println("--------------------")
	fmt.Println("Draw")
	fmt.Println("--------------------")
}

PythonからGo言語への移植はそれほど難しくありません。戻り値も複数個受け取ることができるので、pythonで書いたプログラムまんまでOKです。

まとめ

Go言語でも3目並べのプログラムを作成してみました。結構簡単なプログラムですが、深さ優先探索まで行う場合はそれなりのスキルが必要になるため、初級〜中級者までが練習用にプログラムする素材としてはちょうど良い気がします。

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

記事URLをコピーしました