Go語言版黑白棋(九):喫子

功能說明

這裏寫圖片描述

增加喫子規則算法。

在一個空格新落下一個棋子,並且翻轉對手一個或多個棋子,落子必須要喫子

原理說明

這裏寫圖片描述

示例代碼

package main

import (
    "fmt"
    "os"
    "strconv"
    "unsafe"

    "github.com/mattn/go-gtk/gdk"
    "github.com/mattn/go-gtk/gdkpixbuf"
    "github.com/mattn/go-gtk/glib"
    "github.com/mattn/go-gtk/gtk"
)

//控件結構體
type ChessWidet struct {
    window      *gtk.Window //窗口
    buttonMin   *gtk.Button //最小化按鈕
    buttonClose *gtk.Button //關閉按鈕
    labelBlack  *gtk.Label  //顯示黑子個數
    labelWhite  *gtk.Label  //顯示白子個數
    labelTime   *gtk.Label  //倒計時間顯示
    imageBlack  *gtk.Image  //黑子image,用於提示該誰落子
    imageWhite  *gtk.Image  //白子image,用於提示該誰落子
}

//屬性結構體
type ChessInfo struct {
    w, h int //窗口的寬度高度
    x, y int //鼠標點擊座標(相對於窗口)

    gridW  int //棋盤水平方向一個格子的寬度
    gridH  int //棋盤水平方向一個格子的高度
    startX int //棋盤起點x座標
    startY int //棋盤起點y座標
}

//枚舉,標誌棋盤棋子狀態
const (
    Empty = iota //當前棋盤格子沒有子
    Black        //當前棋盤格子爲黑子
    White        //當前棋盤格子爲白子
)

type Chessboard struct {
    //匿名字段
    ChessWidet
    ChessInfo

    currentRole int //當前落子角色(該誰落子)
    tipTimerId  int //定時器id,用於實現落子落子一閃一閃效果
    leftTimerId int //定時器id,用於倒計時
    timeNum     int //倒計時間

    chess [8][8]int //二維數組,標記棋盤棋子狀態
}

//函數,給按鈕設置圖片
func ButtonSetImageFromFile(button *gtk.Button, filename string) {
    //獲取按鈕的寬度和高度
    var w, h int
    button.GetSizeRequest(&w, &h)
    //獲取pixbuf,指定其大小
    pixbuf, _ := gdkpixbuf.NewPixbufFromFileAtScale(filename, w-10, h-10, false)

    //通過pixbuf新建image
    image := gtk.NewImageFromPixbuf(pixbuf)
    //按鈕設置圖片
    button.SetImage(image)

    //釋放pixbuf資源
    pixbuf.Unref()
}

//函數,給image設置圖片
func ImageSetPixbufFromFile(image *gtk.Image, filename string) {
    //獲取image的寬度和高度
    var w, h int
    image.GetSizeRequest(&w, &h)
    //獲取pixbuf,指定其大小
    pixbuf, _ := gdkpixbuf.NewPixbufFromFileAtScale(filename, w-5, h-5, false)

    //image設置pixbuf
    image.SetFromPixbuf(pixbuf)

    //釋放pixbuf資源
    pixbuf.Unref()
}

//Chessboard的方法,獲取控件,設置控件屬性,返回主窗口控件指針
func (obj *Chessboard) CreateWindow() *gtk.Window {
    builder := gtk.NewBuilder()     //新建builder
    builder.AddFromFile("ui.glade") //讀取glade文件

    //獲取glade對應的控件
    obj.window = gtk.WindowFromObject(builder.GetObject("window1"))      //獲取窗口控件
    obj.buttonMin = gtk.ButtonFromObject(builder.GetObject("buttonMin")) //按鈕
    obj.buttonClose = gtk.ButtonFromObject(builder.GetObject("buttonClose"))
    obj.labelBlack = gtk.LabelFromObject(builder.GetObject("labelBlack")) //標籤
    obj.labelWhite = gtk.LabelFromObject(builder.GetObject("labelWhite"))
    obj.labelTime = gtk.LabelFromObject(builder.GetObject("labelTime"))
    obj.imageBlack = gtk.ImageFromObject(builder.GetObject("imageBlack")) //image
    obj.imageWhite = gtk.ImageFromObject(builder.GetObject("imageWhite")) //image

    //設置屬性
    //窗口屬性
    obj.w = 800
    obj.h = 480
    obj.window.SetSizeRequest(obj.w, obj.h)    //設置窗口大小
    obj.window.SetPosition(gtk.WIN_POS_CENTER) //居中顯示
    obj.window.SetAppPaintable(true)           //允許窗口能繪圖(重要)
    obj.window.SetDecorated(false)             //去表框
    //添加鼠標按下事件
    obj.window.SetEvents(int(gdk.BUTTON_PRESS_MASK | gdk.BUTTON1_MOTION_MASK))

    //按鈕屬性
    ButtonSetImageFromFile(obj.buttonMin, "image/min.png") //給按鈕設置圖片,此爲自定義函數
    ButtonSetImageFromFile(obj.buttonClose, "image/close.png")
    obj.buttonMin.SetCanFocus(false) //去掉按鈕上的聚焦框
    obj.buttonClose.SetCanFocus(false)

    //標籤屬性
    obj.labelBlack.ModifyFontSize(50) //修改字體大小
    obj.labelWhite.ModifyFontSize(50) //修改字體大小
    obj.labelTime.ModifyFontSize(30)  //修改字體大小

    //修改字體顏色爲白色
    obj.labelBlack.ModifyFG(gtk.STATE_NORMAL, gdk.NewColor("white"))
    obj.labelWhite.ModifyFG(gtk.STATE_NORMAL, gdk.NewColor("white"))
    obj.labelTime.ModifyFG(gtk.STATE_NORMAL, gdk.NewColor("white"))

    //image屬性
    ImageSetPixbufFromFile(obj.imageBlack, "image/black.png")
    ImageSetPixbufFromFile(obj.imageWhite, "image/white.png")

    //obj.imageBlack.Hide()
    //obj.imageWhite.Hide()

    //棋盤格子尺寸信息
    obj.startX = 200
    obj.startY = 60
    obj.gridW = 50
    obj.gridH = 40

    return obj.window
}

//繪圖事件處理函數,"expose-event"的回調函數
func PaintEvent(ctx *glib.CallbackContext) {
    arg := ctx.Data()            //獲取用戶傳遞的參數,是空接口類型
    obj, ok := arg.(*Chessboard) //類型斷言
    if false == ok {             //如果ok爲false,說明類型斷言錯誤
        fmt.Println("arg.(*Chessboard) err")
        return
    }

    //指定窗口爲繪圖區域,在窗口上繪圖
    painter := obj.window.GetWindow().GetDrawable()
    gc := gdk.NewGC(painter)

    //設置背景圖的pixbuf,其寬高和窗口一樣,最後一個參數固定爲false
    bg, _ := gdkpixbuf.NewPixbufFromFileAtScale("./image/bg.jpg", obj.w, obj.h, false)

    //畫圖,畫背景圖
    painter.DrawPixbuf(gc, bg, 0, 0, 0, 0, -1, -1, gdk.RGB_DITHER_NONE, 0, 0)

    //設置黑白子圖片pixbuf
    blackPixbuf, _ := gdkpixbuf.NewPixbufFromFileAtScale("./image/black.png", obj.gridW, obj.gridH, false)
    whitePixbuf, _ := gdkpixbuf.NewPixbufFromFileAtScale("./image/white.png", obj.gridW, obj.gridH, false)

    for i := 0; i < 8; i++ {
        for j := 0; j < 8; j++ {
            if obj.chess[i][j] == Black { //畫黑子
                painter.DrawPixbuf(gc, blackPixbuf, 0, 0, obj.startX+i*obj.gridW, obj.startY+j*obj.gridH, -1, -1, gdk.RGB_DITHER_NONE, 0, 0)
            } else if obj.chess[i][j] == White { //畫黑子
                painter.DrawPixbuf(gc, whitePixbuf, 0, 0, obj.startX+i*obj.gridW, obj.startY+j*obj.gridH, -1, -1, gdk.RGB_DITHER_NONE, 0, 0)
            }
        }
    }

    //釋放圖片資源,必須,否則會導致內存泄露,內存越用越多
    bg.Unref()
    blackPixbuf.Unref()
    whitePixbuf.Unref()
}

//方法,改變落子角色
func (obj *Chessboard) ChangeRole() {
    //重新設置時間
    obj.timeNum = 20
    obj.labelTime.SetText(strconv.Itoa(obj.timeNum)) //標籤顯示時間

    //先隱藏提示圖片
    obj.imageBlack.Hide()
    obj.imageWhite.Hide()

    if obj.currentRole == Black { //白子下
        obj.currentRole = White
    } else { //黑子下
        obj.currentRole = Black
    }

}

// 喫子的規則
// 喫子規則的參數:棋盤數組座標位置(x y) role 當前落子角色
// eatChess爲true,代表改變原來的數組, false不改變數組內容,只判斷此位置能喫多少個子
// 返回值:喫子個數
func (obj *Chessboard) JudgeRule(x, y int, role int, eatChess bool) (eatNum int) {
    // 棋盤的八個方向
    dir := [8][2]int{{1, 0}, {1, -1}, {0, -1}, {-1, -1}, {-1, 0}, {-1, 1}, {0, 1}, {1, 1}}

    tempX, tempY := x, y // 臨時保存棋盤數組座標位置

    if obj.chess[tempX][tempY] != Empty { // 如果此方格內已有棋子,返回
        return 0
    }

    // 棋盤的8個方向
    for i := 0; i < 8; i++ {
        tempX += dir[i][0]
        tempY += dir[i][1] // 準備判斷相鄰棋子

        // 如果沒有出界,且相鄰棋子是對方棋子,纔有喫子的可能.
        if (tempX < 8 && tempX >= 0 && tempY < 8 && tempY >= 0) && (obj.chess[tempX][tempY] != role) && (obj.chess[tempX][tempY] != Empty) {
            tempX += dir[i][0]
            tempY += dir[i][1] // 繼續判斷下一個,向前走一步
            for tempX < 8 && tempX >= 0 && tempY < 8 && tempY >= 0 {
                if obj.chess[tempX][tempY] == Empty { // 遇到空位跳出
                    break
                }

                if obj.chess[tempX][tempY] == role { // 找到自己的棋子,代表可以喫子
                    if eatChess == true { // 確定喫子
                        obj.chess[x][y] = role // 開始點標誌爲自己的棋子
                        tempX -= dir[i][0]
                        tempY -= dir[i][1] // 後退一步
                        for (tempX != x) || (tempY != y) {
                            // 只要沒有回到開始的位置就執行
                            obj.chess[tempX][tempY] = role // 標誌爲自己的棋子
                            tempX -= dir[i][0]
                            tempY -= dir[i][1] // 繼續後退一步
                            eatNum++           // 累計
                        }
                    } else { //不喫子,只是判斷這個位置能不能喫子
                        tempX -= dir[i][0]
                        tempY -= dir[i][1]                 // 後退一步
                        for (tempX != x) || (tempY != y) { // 只計算可以喫子的個數
                            tempX -= dir[i][0]
                            tempY -= dir[i][1] // 繼續後退一步
                            eatNum++
                        }
                    }

                    break // 跳出循環
                } // 沒有找到自己的棋子,就向前走一步

                tempX += dir[i][0]
                tempY += dir[i][1] // 向前走一步
            }
        } // 如果這個方向不能喫子,就換一個方向
        tempX, tempY = x, y
    }

    return // 返回能喫子的個數
}

//鼠標按下事件處理,MousePressEvent爲其回調函數,把obj傳遞給回調函數
func MousePressEvent(ctx *glib.CallbackContext) {
    arg := ctx.Data()            //獲取用戶傳遞的參數,是空接口類型
    obj, ok := arg.(*Chessboard) //類型斷言
    if false == ok {             //如果ok爲false,說明類型斷言錯誤
        fmt.Println("arg.(*Chessboard) err")
        return
    }

    //獲取鼠鍵按下屬性結構體變量,系統內部的變量,不是用戶傳參變量
    tmp := ctx.Args(0)
    event := *(**gdk.EventButton)(unsafe.Pointer(&tmp))
    if event.Button == 1 { //左鍵
        obj.x, obj.y = int(event.X), int(event.Y) //保存點擊的起點座標

        // 要保證點擊點在棋盤範圍裏面
        if obj.x >= obj.startX && obj.x <= obj.startX+8*obj.gridW && obj.y >= obj.startY && obj.y <= obj.startX+8*obj.gridH {
            // 棋盤的位置轉換轉換爲座標下標值
            i := (obj.x - obj.startX) / obj.gridW
            j := (obj.y - obj.startY) / obj.gridH

            //保證i, j在0~7範圍裏
            if i >= 0 && i <= 7 && j >= 0 && j <= 7 {
                //fmt.Printf("i = %d, j = %d\n", i, j)

                if obj.JudgeRule(i, j, obj.currentRole, true) > 0 { //能喫子才更新棋盤
                    obj.window.QueueDraw() //刷新繪圖區域
                    obj.ChangeRole()       //改變落子角色
                }
            }
        }

    }
}

//方法,事件、信號處理,回調函數如果簡單使用匿名函數,否則自定義函數
func (obj *Chessboard) HandleSignal() {

    //鼠標按下事件處理,MousePressEvent爲其回調函數,把obj傳遞給回調函數
    obj.window.Connect("button-press-event", MousePressEvent, obj)

    //鼠標移動事件處理,實現窗口的移動
    obj.window.Connect("motion-notify-event", func(ctx *glib.CallbackContext) {
        //獲取鼠標屬性結構體變量,系統內部的變量,不是用戶傳參變量
        arg := ctx.Args(0)
        //還是EventButton
        event := *(**gdk.EventButton)(unsafe.Pointer(&arg))

        //移動窗口
        obj.window.Move(int(event.XRoot)-obj.x, int(event.YRoot)-obj.y)
    })

    //改變窗口大小時,觸發"configure-event",然後手動刷新繪圖區域,否則圖片會重疊
    obj.window.Connect("configure-event", func() {
        obj.window.QueueDraw() //刷新繪圖區域
    })

    //繪圖(曝光)事件,其回調函數PaintEvent做繪圖操作,把obj傳遞給回調函數
    obj.window.Connect("expose-event", PaintEvent, obj)

    //最小化按鈕信號處理
    obj.buttonMin.Connect("clicked", func() {
        obj.window.Iconify() //窗口最小化
    })

    //關閉按鈕信號處理
    obj.buttonClose.Connect("clicked", func() {
        gtk.MainQuit() //程序結束
    })
}

//定時器處理函數,角色提示,達到一閃一閃的效果
func ShowTip(obj *Chessboard) {
    if obj.currentRole == Black { //當前該黑子下
        //白子提示圖片隱藏
        obj.imageWhite.Hide()

        if obj.imageBlack.GetVisible() == true { //原來顯示的,則隱藏
            obj.imageBlack.Hide()
        } else {
            obj.imageBlack.Show()
        }

    } else if obj.currentRole == White { //當前該白子下
        //黑子提示圖片隱藏
        obj.imageBlack.Hide()

        if obj.imageWhite.GetVisible() == true { //原來顯示的,則隱藏
            obj.imageWhite.Hide()
        } else {
            obj.imageWhite.Show()
        }
    }
}

//方法,主要做些初始化操作
func (obj *Chessboard) InitChess() {
    //當前落子角色
    obj.currentRole = Black
    //obj.currentRole = White

    //全部標記爲Empty
    for i := 0; i < 8; i++ {
        for j := 0; j < 8; j++ {
            obj.chess[i][j] = Empty
        }
    }

    //中間位置
    obj.chess[3][3] = Black
    obj.chess[4][4] = Black
    obj.chess[4][3] = White
    obj.chess[3][4] = White

    //先隱藏提示圖片
    obj.imageBlack.Hide()
    obj.imageWhite.Hide()

    //落子提示
    obj.tipTimerId = glib.TimeoutAdd(500, func() bool {
        ShowTip(obj) //調用函數處理
        return true
    })

    //倒計時
    obj.timeNum = 20
    obj.labelTime.SetText(strconv.Itoa(obj.timeNum)) //標籤顯示時間

    //啓動倒計時定時器
    obj.leftTimerId = glib.TimeoutAdd(1000, func() bool {
        obj.timeNum--
        obj.labelTime.SetText(strconv.Itoa(obj.timeNum)) //標籤顯示時間
        if obj.timeNum == 0 {                            //時間到
            obj.ChangeRole() //改變落子角色
        }

        return true
    })
}

func main() {
    gtk.Init(&os.Args)

    var obj Chessboard //創建結構體變量

    //創建控件,設計屬性
    window := obj.CreateWindow()

    //初始化數據
    obj.InitChess()

    //事件、信號處理
    obj.HandleSignal()

    //顯示控件
    window.Show()

    gtk.Main()
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章