中国象棋的棋盘是9×10的格局,棋子png像素是56×56,很容易得出棋盘的大小。

棋盘大小

打开define.go,增加如下定义:

//窗口
const (
	SquareSize  = 56
	BoardEdge   = 8
	BoardWidth  = BoardEdge + SquareSize*9 + BoardEdge
	BoardHeight = BoardEdge + SquareSize*10 + BoardEdge
)

SquareSize 就是棋子所占区域大小

BoardEdge 边界大小

BoardWidth、BoardHeight 就是棋盘的宽和高

//棋盘范围
const (
	Top    = 3
	Bottom = 12
	Left   = 3
	Right  = 11
)

修改game.go中Layout

func (g *Game) Layout(outsideWidth, outsideHeight int) (screenWidth, screenHeight int) {
	return BoardWidth, BoardHeight
}

定义棋盘

中国象棋的棋盘是一个9×10格局,我们把这个棋盘扩大,用一个16×16的数组来表示这个棋盘,在define.go中预置一个常量数组 cucpcStartup[256]:

// 棋盘初始设置
var cucpcStartup = [256]int{
	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, 20, 19, 18, 17, 16, 17, 18, 19, 20, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 21, 0, 0, 0, 0, 0, 21, 0, 0, 0, 0, 0,
	0, 0, 0, 22, 0, 22, 0, 22, 0, 22, 0, 22, 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, 14, 0, 14, 0, 14, 0, 14, 0, 0, 0, 0,
	0, 0, 0, 0, 13, 0, 0, 0, 0, 0, 13, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 12, 11, 10, 9, 8, 9, 10, 11, 12, 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}

如下图所示:

上图就是9×10的象棋棋盘在16×16的数组中的位置,这样做有很多好处。

首先,在define.go中可以定义一个数组ccInBoard:

//ccInBoard 判断棋子是否在棋盘中的数组
var ccInBoard = [256]int{
	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, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0,
	0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0,
	0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0,
	0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0,
	0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0,
	0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0,
	0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0,
	0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0,
	0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0,
	0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 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),哪些格子在棋盘内(填1),所以就没有必要使用 x >= LEFT && x <= RIGHT && y >= TOP && y <= BOTTOM 之类的语句了,取而代之的是 ccInBoard[sq] != 0。

其次,一维数组的上下左右关系非常简明——上面一格减16,下面一格是加16,左面一格是减1,右面一格是加1。

比如马可以跳的点只有8个,终点相对起点的偏移值是固定的:

//马的步长,以帅(将)的步长作为马腿
var ccMaDelta = [4][2]int{{-33, -31}, {-18, 14}, {-14, 18}, {31, 33}} 

而对应的马腿的偏移值是:

//帅(将)的步长
var ccJiangDelta = [4]int{-16, -1, 1, 16} 

这个数组之所以命名为 ccJiangDelta ,是因为它也是帅(将)的偏移值。

这样找到一个马的所有走法就容易很多了,首先判断某个方向上的马腿是否有子,然后判断该方向上的两个走法是否能走。

用类似的访求可以获得其他棋子的走法。

基本操作

基于上面的棋盘定义,在define.go中增加一些基本操作:

//获得格子的Y
func getY(sq int) int {
	return sq >> 4
}

//获得格子的X
func getX(sq int) int {
	return sq & 15
}

//根据纵坐标和横坐标获得格子
func squareXY(x, y int) int {
	return x + (y << 4)
}

//翻转格子
func squareFlip(sq int) int {
	return 254 - sq
}

//X水平镜像
func xFlip(x int) int {
	return 14 - x
}

//Y垂直镜像
func yFlip(y int) int {
	return 15 - y
}

//获得红黑标记(红子是8,黑子是16)
func sideTag(sd int) int {
	return 8 + (sd << 3)
}

//获得对方红黑标记
func oppSideTag(sd int) int {
	return 16 - (sd << 3)
}

//获得走法的起点
func src(mv int) int {
	return mv & 255
}

//获得走法的终点
func dst(mv int) int {
	return mv >> 8
}

//根据起点和终点获得走法
func move(sqSrc, sqDst int) int {
	return sqSrc + sqDst*256
}

这里的sq就是cucpcStartup对应的下标,例如sq=52,那对应棋盘的位置就是x=4,y=3。

sd就是棋子的常量值,例如黑马(ImgBlackMa=19)

棋局结构体

在chess文件夹下新建rule.go,定义棋局的结构体:

package chess

//PositionStruct 局面结构
type PositionStruct struct {
	sdPlayer    int // 轮到谁走,0=红方,1=黑方
	ucpcSquares [256]int              // 棋盘上的棋子
}

//NewPositionStruct 初始化棋局
func NewPositionStruct() *PositionStruct {
	p := &PositionStruct{
}
	if p == nil {
		return nil
	}
	return p
}

//startup 初始化棋盘
func (p *PositionStruct) startup() {
	p.sdPlayer = 0
	for sq := 0; sq < 256; sq++ {
		p.ucpcSquares[sq] = cucpcStartup[sq]
	}
}

//changeSide 交换走子方
func (p *PositionStruct) changeSide() {
	p.sdPlayer = 1 - p.sdPlayer
}

//addPiece 在棋盘上放一枚棋子
func (p *PositionStruct) addPiece(sq, pc int) {
	p.ucpcSquares[sq] = pc
}

//delPiece 从棋盘上拿走一枚棋子
func (p *PositionStruct) delPiece(sq int) {
	p.ucpcSquares[sq] = 0
}

//movePiece 搬一步棋的棋子
func (p *PositionStruct) movePiece(mv int) {
	sqSrc := src(mv)
	sqDst := dst(mv)
	p.delPiece(sqDst)
	pc := p.ucpcSquares[sqSrc]
	p.delPiece(sqSrc)
	p.addPiece(sqDst, pc);
}

//makeMove 走一步棋
func (p *PositionStruct) makeMove(mv int) {
	p.movePiece(mv)
	p.changeSide()
}

棋局界面

打开game.go,定义棋局的界面操作,修改Game结构体:

type Game struct {
	sqSelected     int                   // 选中的格子
	mvLast         int                   // 上一步棋
	bFlipped       bool                  //是否翻转棋盘
	images         map[int]*ebiten.Image //图片资源
	audios         map[int]*audio.Player //音效
	audioContext   *audio.Context        //音效器
	singlePosition *PositionStruct       //棋局单例
}

增加sqSelected、mvLast、bFlipped和singlePosition。

修改NewGame函数:

func NewGame() bool {
	game := &Game{
		images:         make(map[int]*ebiten.Image),
		audios:         make(map[int]*audio.Player),
		singlePosition: NewPositionStruct(),
	}
	if game == nil || game.singlePosition == nil {
		return false
	}

	var err error
	//音效器
	game.audioContext, err = audio.NewContext(48000)
	if err != nil {
		fmt.Print(err)
		return false
	}

	//加载资源
	if ok := game.loadResource(); !ok {
		return false
	}

	game.singlePosition.startup()

	ebiten.SetWindowSize(BoardWidth, BoardHeight)
	ebiten.SetWindowTitle("中国象棋")
	if err := ebiten.RunGame(game); err != nil {
		log.Fatal(err)
		return false
	}

	return true
}

增加资源加载的处理,到这里基本的数据结构已经定义好。

在下一节中,我们将学习如何绘制棋子以及响应鼠标事件。

1+

本文为原创文章,转载请注明出处,欢迎访问作者网站(和而不同)

发表评论

error: Content is protected !!