使用golang实现一个五子棋棋盘

1,437 阅读9分钟

前言

本来计划换工作,投了简历,人家让做面试题。不管能不能面上,多做点题,总归是好事。
面试题内容如下:

  • 题目1
6行7列棋盘, 如下图, 设计一种数据结构,可以表示棋盘?
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
请实现 void print(xxx) 函数,打印棋盘.
介绍:
假如有俩个玩家: player1 黑子,player2 白子
如果棋盘中位置(x,y) 的值 value:  0 表示空, 1 表示黑子, 2 表示白子

给一个M的instance m, 请实现 void print(m) 函数,打印棋盘

case 1> 棋盘全空情况, 创建实例,  然后调用 printM  打印结果如上图
case 2>  初始棋盘所有位置为空(0), 设置  m[0][3] m[3][2] 位置为黑子(1)  m[2][3] m[3][5] 为白子(2),
然后调用 print 函数打印棋盘,  打印结果如下图


. . . x . . .
. . . . . . .
. . . o . . .
. . x . . o .
. . . . . . .
. . . . . . .

  • 题目2
棋盘6行7列,两个玩家p1, p2 轮流放黑白棋子,
每个玩家  随机抽一列 放入自己的棋子,棋子掉入该列最后空白处,直到填满
(如果该列已经满,玩家此次机会失效,下一个玩家继续)

. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
o o . . . . x
o x x x o x o

胜出条件:棋盘被完全填满 (如下图)

x x o x x x o
x o o o x o o
o o x o o o o
x x o x o o x
o o x x x x x
o x o o o x o

  • 题目3
胜出条件:
其中一个玩家先出现
1.	一行连续4个子
2.	一列连续4个子
3.	对角线连续4个子
4.	棋盘被完全填满,还未出现胜负,则平局

例如:白子胜出 (第4列)
.  . . . . . .
. . . . . . .
. . . o . . .
o x x o x x .
x o x o o o .
x o o o x x .


黑子胜出 (对角线)
. . . . . . .
. . . . . . .
. x . o . . .
x x o x . o x
o o x x . o x
o o o o o x x

平局
o o x o o x o 
o o o x o o o
x x x o x x x
o o o x x o x
o o o x o o o
x o o x o o x

写一个程序模拟这个游戏,实现下面要求。
1.	定义Game Class
2.	初始化棋盘后,打印棋盘
3.	不需要human input
4.	游戏结束后,打印棋盘
5.	打印胜出方

开始

这显然是一个五子棋,我的第一反应是用二维数组表示该结构。不过仔细想了想,有没有其他的表达方式?于是就有了链表实现的数据结构。代码如下:

type hand uint

const (
    NilHand   hand = iota //空白
    BlackHand             //黑手
    WhiteHand             //白手
)

func (h hand) Str() string {
    switch h {
    case NilHand:
        return "."
    case BlackHand:
        return "X"
    case WhiteHand:
        return "O"
    default:
        return "."
    }
}

type Grid struct {
    value  hand
    left   *Grid
    right  *Grid
    top    *Grid
    bottom *Grid
}
  • 首先我们先定义几个函数,方便查找某个点的相邻位置的指针
func (g *Grid) LeftTop() *Grid {
	if g.left != nil {
		return g.left.top
	}
	return nil
}

func (g *Grid) LeftBottom() *Grid {
	if g.left != nil {
		return g.left.bottom
	}
	return nil
}

func (g *Grid) RightTop() *Grid {
	if g.right != nil {
		return g.right.top
	}
	return nil
}

func (g *Grid) RightBottom() *Grid {
	if g.right != nil {
		return g.right.bottom
	}
	return nil
}
  • 写一个相对偏移的函数Offset()
func (g *Grid) Offset(row, col int) *Grid {
	offset := g
	if g == nil {
		return nil
	}
	var i, j int
	i, j = 1, 1
	for i <= row {
		if row == i {
			for j <= col {
				if col == j {
					return offset
				}

				if offset == nil {
					return nil
				}
				offset = offset.right
				j++
			}
		}
		if offset == nil {
			return nil
		}
		offset = offset.bottom
		i++
	}
	return nil
}
  • 初始化棋盘
func InitGrid(row, col int, head *Grid) *Grid {
	l := &Grid{}

	l = head
	c := col
	for c > 1 {
		var tmp Grid
		tmp.left = l
		l.right = &tmp
		l = &tmp
		c--
	}

	l = head
	r := row
	for r > 1 {
		var tmp Grid
		tmp.top = l
		l.bottom = &tmp
		l = &tmp
		r--
	}

	for i := 2; i <= row; i++ {
		for j := 2; j <= col; j++ {
			top := head.Offset(i-1, j)
			left := head.Offset(i, j-1)
			var tmp Grid
			tmp.top = top
			tmp.left = left

			top.bottom = &tmp
			left.right = &tmp
		}
	}
	return head
  • 写一个落子动作
func (g *Grid) Set(row, col int, value hand) {
	offset := g
	offset = g.Offset(row, col)
	if offset == nil {
		return
	} else if offset.value == NilHand {
		offset.value = value
	}
	return
}
  • 解题:
    • TestGrid()、 Print()实现题目1的要求
    • GameOne函数,实现题目2的要求
    • GameTwo函数,实现题目3的要求

完整代码

package main

import (
	"fmt"
	"math/rand"
	"strconv"
	"strings"
	"time"
)

type hand uint

const (
	NilHand   hand = iota //空白
	BlackHand             //黑手
	WhiteHand             //白手
)

func (h hand) Str() string {
	switch h {
	case NilHand:
		return "."
	case BlackHand:
		return "X"
	case WhiteHand:
		return "O"
	default:
		return "."
	}
}

type Grid struct {
	value  hand
	left   *Grid
	right  *Grid
	top    *Grid
	bottom *Grid
}

func (g *Grid) LeftTop() *Grid {
	if g.left != nil {
		return g.left.top
	}
	return nil
}

func (g *Grid) LeftBottom() *Grid {
	if g.left != nil {
		return g.left.bottom
	}
	return nil
}

func (g *Grid) RightTop() *Grid {
	if g.right != nil {
		return g.right.top
	}
	return nil
}

func (g *Grid) RightBottom() *Grid {
	if g.right != nil {
		return g.right.bottom
	}
	return nil
}

//设置row,col坐标的值, 即 落棋子
func (g *Grid) Set(row, col int, value hand) {
	offset := g
	offset = g.Offset(row, col)
	if offset == nil {
		return
	} else if offset.value == NilHand {
		offset.value = value
	}
	return
}

//获取向右偏移x位的指针
func (g *Grid) RightOffset(col int) *Grid {
	var tmp *Grid
	tmp = g
	if g == nil {
		return nil
	}
	for i := 1; i <= col; i++ {
		if i == col && tmp != nil {
			return tmp
		}
		if tmp == nil {
			return nil
		}
		if tmp.right != nil {
			tmp = tmp.right
		}
	}
	return nil
}

//获取向下偏移y位的指针
func (g *Grid) BottomOffset(row int) *Grid {
	var tmp *Grid
	tmp = g
	if g == nil {
		return nil
	}
	for i := 1; i <= row; i++ {
		if i == row && tmp != nil {
			return tmp
		}
		if tmp == nil {
			return nil
		}
		if tmp.bottom != nil {
			tmp = tmp.bottom
		}
	}
	return nil
}

//获取该表格有几行
func (g *Grid) GetRowLen() int {
	var row int
	tmp := g
	for tmp != nil {
		row++
		tmp = tmp.bottom
	}
	return row
}

//获取该表格有几列
func (g *Grid) GetColLen() int {
	var col int
	tmp := g
	for tmp != nil {
		col++
		tmp = tmp.right
	}
	return col
}

func (g *Grid) Print() {
	fmt.Println("当前棋盘布局为:")
	var colNumStr = ""
	col := g.GetColLen()
	fillC := strconv.Itoa(g.GetColLen())
	for i := 1; i <= col; i++ {
		colNumStr += " " + StrLeftFill(len(fillC), i)
	}
	fmt.Println(StrLeftFill(len(strconv.Itoa(g.GetRowLen())), ""), strings.TrimLeft(colNumStr, " "))

	for row := 1; row <= g.GetRowLen(); row++ {
		var rowStr = ""
		for col := 1; col <= g.GetColLen(); col++ {
			//rowStr += StrLeftFill(len(strconv.Itoa(g.GetColLen())), "") + g.Offset(row, col).value.Str()
			rowStr += " " + StrLeftFill(len(strconv.Itoa(g.GetColLen())), g.Offset(row, col).value.Str())
		}
		fmt.Println(StrLeftFill(len(strconv.Itoa(g.GetRowLen())), row), strings.TrimLeft(rowStr, " "))
	}

	fmt.Println("")
}

/*
获取坐标处的指针
*/
func (g *Grid) Offset(row, col int) *Grid {
	offset := g
	if g == nil {
		return nil
	}
	var i, j int
	i, j = 1, 1
	for i <= row {
		if row == i {
			for j <= col {
				if col == j {
					return offset
				}

				if offset == nil {
					return nil
				}
				offset = offset.right
				j++
			}
		}
		if offset == nil {
			return nil
		}
		offset = offset.bottom
		i++
	}
	return nil
}

//获取某列最后一行的棋子的指针
func (g *Grid) GetLastRow(col int) *Grid {
	offset := g.Offset(1, col)
	for row := 1; row <= g.GetRowLen(); row++ {
		if row == g.GetRowLen() {
			return offset
		}
		offset = offset.bottom
	}
	return nil
}

//获取某一行的最后一列没有落棋子的指针
func (g *Grid) GetEmptyLastRow(col int) *Grid {
	if col <= 0 || col > g.GetColLen() {
		return nil
	}
	last := g.GetLastRow(col)
	if last == nil {
		return nil
	}
	for row := 1; row <= g.GetRowLen(); row++ {
		if last.value == NilHand {
			return last
		}
		last = last.top
		if last == nil {
			return nil
		}
	}
	return nil
}

//棋盘是否已满?
func (g *Grid) IsFull() bool {
	for row := 1; row <= g.GetRowLen(); row++ {
		for col := 1; col <= g.GetColLen(); col++ {
			if g.Offset(row, col).value == NilHand {
				return false
			}
		}
	}
	return true
}

//统计黑手,白手的棋子数量
func (g *Grid) Count() (black, white int) {
	for row := 1; row <= g.GetRowLen(); row++ {
		for col := 1; col <= g.GetColLen(); col++ {
			if g.Offset(row, col).value == BlackHand {
				black++
			} else if g.Offset(row, col).value == WhiteHand {
				white++
			}
		}
	}
	return
}

//检查是否已分出胜负
func (g *Grid) IsWin(row, col int) bool {
	offset := g.Offset(row, col)
	h := offset.value
	if h == NilHand {
		return false
	}
	//检查行
	count := 1
	left := offset.left
	right := offset.right
	for {
		if left == nil {
			break
		}
		if left.value == h {
			count++
		} else {
			break
		}
		left = left.left
	}
	for {
		if right == nil {
			break
		}
		if right.value == h {
			count++
		} else {
			break
		}
		right = right.right
	}
	if count >= 4 {
		switch h {
		case WhiteHand:
			fmt.Println("白手赢", fmt.Sprintf("最后一个落子点为(row:%d,col:%d)", row, col))
		case BlackHand:
			fmt.Println("黑手赢", fmt.Sprintf("最后一个落子点为(row:%d,col:%d)", row, col))
		}
		return true
	}

	//检查列
	count = 1
	top := offset.top
	bottom := offset.bottom
	for {
		if top == nil {
			break
		}
		if top.value == h {
			count++
		} else {
			break
		}
		top = top.top
	}
	for {
		if bottom == nil {
			break
		}
		if bottom.value == h {
			count++
		} else {
			break
		}
		bottom = bottom.bottom
	}
	if count >= 4 {
		switch h {
		case WhiteHand:
			fmt.Println("白手赢", fmt.Sprintf("最后一个落子点为(row:%d,col:%d)", row, col))
		case BlackHand:
			fmt.Println("黑手赢", fmt.Sprintf("最后一个落子点为(row:%d,col:%d)", row, col))
		}
		return true
	}

	//检查左斜边
	count = 1
	leftTop := offset.LeftTop()
	rightBottom := offset.RightBottom()
	for {
		if leftTop == nil {
			break
		}
		if leftTop.value == h {
			count++
		} else {
			break
		}
		leftTop = leftTop.LeftTop()
	}
	for {
		if rightBottom == nil {
			break
		}
		if rightBottom.value == h {
			count++
		} else {
			break
		}
		rightBottom = rightBottom.RightBottom()
	}
	if count >= 4 {
		switch h {
		case WhiteHand:
			fmt.Println("白手赢", fmt.Sprintf("最后一个落子点为(row:%d,col:%d)", row, col))
		case BlackHand:
			fmt.Println("黑手赢", fmt.Sprintf("最后一个落子点为(row:%d,col:%d)", row, col))
		}
		return true
	}

	//检查右斜边
	count = 1
	rightTop := offset.RightTop()
	leftBottom := offset.LeftBottom()
	for {
		if rightTop == nil {
			break
		}
		if rightTop.value == h {
			count++
		} else {
			break
		}
		rightTop = rightTop.RightTop()
	}
	for {
		if leftBottom == nil {
			break
		}
		if leftBottom.value == h {
			count++
		} else {
			break
		}
		leftBottom = leftBottom.LeftBottom()
	}
	if count >= 4 {
		switch h {
		case WhiteHand:
			fmt.Println("白手赢", fmt.Sprintf("最后一个落子点为(row:%d,col:%d)", row, col))
		case BlackHand:
			fmt.Println("黑手赢", fmt.Sprintf("最后一个落子点为(row:%d,col:%d)", row, col))
		}
		return true
	}
	if g.IsFull() {
		fmt.Println("平手")
		return true
	} else {
		return false
	}
}

/*
约定: row,col的起始值为1
*/
func InitGrid(row, col int, head *Grid) *Grid {
	l := &Grid{}

	l = head
	c := col
	for c > 1 {
		var tmp Grid
		tmp.left = l
		l.right = &tmp
		l = &tmp
		c--
	}

	l = head
	r := row
	for r > 1 {
		var tmp Grid
		tmp.top = l
		l.bottom = &tmp
		l = &tmp
		r--
	}

	for i := 2; i <= row; i++ {
		for j := 2; j <= col; j++ {
			top := head.Offset(i-1, j)
			left := head.Offset(i, j-1)
			var tmp Grid
			tmp.top = top
			tmp.left = left

			top.bottom = &tmp
			left.right = &tmp
		}
	}
	return head
}

func TestGrid(row, col int) {
	/*
		简单的测试下棋子
		设置:
				(1,4),(4,3)为黑手
				(3,4),(4,6)为白手
	*/
	grid := InitGrid(row, col, &Grid{})

	grid.Set(1, 4, BlackHand)
	grid.Set(4, 3, BlackHand)
	grid.Set(3, 4, WhiteHand)
	grid.Set(4, 6, WhiteHand)
	grid.Print()
}
func GameOne(row, col int) {
	/*
		游戏规则:
			两个玩家p1,p2轮流放黑白棋子,每个玩家随机抽取一列放入自己的棋子,棋子调入该列最后空白处(可理解为压栈),直到填满.
			(如果该列已经满了,玩家此次机会失效,下一个玩家继续).
		胜出条件:
			棋盘被完全占满(最多的为胜?)
	*/
	grid := InitGrid(row, col, &Grid{})
	rand.Seed(time.Now().Unix())
	i := 0 //
	for {
		if grid.IsFull() {
			black, white := grid.Count()
			if black > white {
				fmt.Println("黑手胜", fmt.Sprintf("黑手:%d;白手:%d", black, white))
			} else if black < white {
				fmt.Println("白手胜", fmt.Sprintf("黑手:%d;白手:%d", black, white))
			} else {
				fmt.Println("平手", fmt.Sprintf("黑手:%d;白手:%d", black, white))
			}
			grid.Print()
			break
		}
		//规定偶数为: 黑手
		h := grid.GetEmptyLastRow(rand.Intn(grid.GetColLen()) + 1)
		if h == nil {
		} else if i%2 == 0 {
			h.value = BlackHand
		} else {
			h.value = WhiteHand
		}
		i++
	}
}

//落子的坐标
type XY struct {
	row int
	col int
}

func GameTwo(row, col int) {
	/*
		游戏规则:(和我们平时玩的规则一样)
			1. 一行连续4个子
			2. 一列连续4个子
			3. 对角线连续4个子
			4. 棋盘被完全填满,还未出现胜负,则平局
	*/
	grid := InitGrid(row, col, &Grid{})

	//生成所有的棋子位置
	xy := map[int]XY{}
	var loop int // 第几次循环
	for r := 1; r <= row; r++ {
		for c := 1; c <= col; c++ {
			xy[loop] = XY{row: r, col: c}
			loop++
		}
	}

	rand.Seed(time.Now().Unix())
	p := XY{1, 1}
	i := 0
	for {
		//if grid.IsFull() {
		//	break
		//}
		if grid.IsWin(p.row, p.col) {
			grid.Print()
			break
		}

		//随机落棋
		for {
			if v, ok := xy[rand.Intn(loop+1)]; ok {
				p = v
				delete(xy, loop) //已取出,删除该坐标
				break
			}
			//棋子坐标非法, continue
		}

		if i%2 == 0 {
			//黑手落棋子
			grid.Set(p.row, p.col, BlackHand)
		} else {
			//白手落棋子
			grid.Set(p.row, p.col, WhiteHand)
		}
		i++
	}
}

//字符串左边填充
func StrLeftFill(s int, value interface{}) string {
	var format = ""
	format = "%" + strconv.Itoa(s) + "v"
	return fmt.Sprintf(format, value)
}

func main() {
	grid := InitGrid(6, 7, &Grid{})

	//空棋盘
	grid.Print()

	TestGrid(6, 7)
	GameOne(6, 7)
	GameTwo(20, 20)
}

运行结果

当前棋盘布局为:
  1 2 3 4 5 6 7
1 . . . . . . .
2 . . . . . . .
3 . . . . . . .
4 . . . . . . .
5 . . . . . . .
6 . . . . . . .

当前棋盘布局为:
  1 2 3 4 5 6 7
1 . . . X . . .
2 . . . . . . .
3 . . . O . . .
4 . . X . . O .
5 . . . . . . .
6 . . . . . . .

黑手胜 黑手:22;白手:20
当前棋盘布局为:
  1 2 3 4 5 6 7
1 O O O X X O X
2 O O O X X X X
3 X O O O X X X
4 O X X X X O O
5 O O X X X X O
6 X O O O O X X

白手赢 最后一个落子点为(row:19,col:8)
当前棋盘布局为:
   1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20
 1 .  X  .  .  .  .  .  .  .  X  .  .  .  .  .  .  O  .  X  X
 2 .  .  .  .  X  X  .  X  O  .  .  .  O  .  .  .  X  .  X  .
 3 .  .  .  .  .  .  .  .  X  .  .  .  .  .  O  .  X  X  .  X
 4 .  .  O  X  .  .  .  O  .  X  O  X  O  .  .  .  .  .  .  .
 5 .  .  .  .  .  .  X  .  O  .  .  O  .  .  .  .  .  X  X  .
 6 .  O  O  .  O  O  .  .  .  .  .  O  .  X  O  X  .  .  .  O
 7 .  .  .  .  X  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .
 8 X  .  .  X  .  O  .  .  .  .  .  X  X  .  .  .  O  X  O  O
 9 O  O  .  O  .  O  .  .  O  .  X  X  .  .  .  O  .  .  .  .
10 O  .  .  X  X  .  O  .  O  .  .  X  X  .  .  X  .  .  .  X
11 .  .  .  .  .  O  .  X  O  .  O  .  O  .  .  .  .  .  .  .
12 .  O  X  X  .  X  X  .  .  .  .  .  .  X  .  .  X  O  .  .
13 X  .  X  X  .  .  .  .  .  .  .  X  .  .  O  O  .  .  .  .
14 .  .  .  .  .  X  .  .  .  X  .  X  .  .  .  .  .  .  .  X
15 .  .  .  O  .  .  .  .  .  .  O  O  .  .  .  .  .  X  .  X
16 .  O  .  O  O  X  .  O  O  O  .  .  .  .  .  .  .  O  .  .
17 .  .  O  O  X  .  .  O  .  .  X  X  .  X  .  .  .  O  O  X
18 .  .  .  X  .  .  .  O  X  .  X  .  .  .  O  O  .  X  .  X
19 .  .  .  .  O  O  .  O  O  .  .  O  .  .  O  .  .  .  .  O
20 .  X  .  .  X  X  .  X  .  .  .  .  .  .  .  .  O  O  O  .