控制台版扫雷,就是看起来费劲一点,和正常的扫雷木有区别,支持鼠标左、中、右键操作

jan-bar · 2019-10-17 16:08:42 · 911 次点击 · 大约8小时之前 开始浏览    置顶
这是一个创建于 2019-10-17 16:08:42 的主题,其中的信息可能已经有所发展或是发生改变。

码云地址,可以直接下载可执行程序 mine.jpg

package main

import (
    "fmt"
    "math/rand"
    "time"

    "github.com/jan-bar/golibs"
    "github.com/lxn/win"
)

func main() {
    var (
        mousePt1, mousePt2 win.POINT

        mouseSta  int32
        x, y, sta int
    )
    m := NewMinesweeping()
startFlag:
    m.Clear()
    for m.mine > 0 {
        m.Print(false)
        if m.api.GetCursorPos(&mousePt1, &mousePt2, &mouseSta) {
            x, y, sta = m.CheckClickPos(mousePt1)
            if sta < 0 {
                continue
            }
            switch mouseSta {
            case golibs.MouseLeft:
                if !m.HandleLeft(x, y) {
                    goto gameEnd
                }
            case golibs.MouseMid:
                if !m.HandleMid(x, y) {
                    goto gameEnd
                }
            case golibs.MouseRight:
                m.HandleRight(x, y)
            }
        }
    }
gameEnd:
    m.Print(true)
    for {
        if m.api.GetCursorPos(&mousePt1, &mousePt2, &mouseSta) {
            _, _, sta = m.CheckClickPos(mousePt1)
            if sta == ClickY {
                m.api.Clear()
                goto startFlag
            } else if sta == ClickN {
                break
            }
        }
    }
}

type GridDefine int /* 定义格子内容的类型,不直接用int避免使用时混乱 */

const ( /* 枚举类型,标记格子内容 */
    DefZero  GridDefine = iota // 0
    DefOne                     // 1
    DefTwo                     // 2
    DefThree                   // 3
    DefFour                    // 4
    DefFive                    // 5
    DefSix                     // 6
    DefSeven                   // 7
    DefEight                   // 8
    DefMine                    // 地雷
    DefFlag                    // 旗子
)

const (
    DefShowPos = 0x80 // 该位表示改点能否显示
    DefFlagPos = 0x40 // 该位表示改点是否标旗
    DefMaskPos = 0x0F // 改点具体值
)

type minesweeping struct {
    width, height, mine int // 宽,高,雷数

    api      *golibs.Win32Api
    gridSave [][]GridDefine
}

func NewMinesweeping() *minesweeping {
    rand.Seed(time.Now().UnixNano())
    m := &minesweeping{width: 30, height: 16, api: golibs.NewWin32Api()}
    m.api.SetWindowText("扫雷")
    m.api.CenterWindowOnScreen(1020, 600)
    m.api.ShowScrollBar(golibs.SB_BOTH, 0)
    mode := golibs.EnableMouseInput | golibs.EnableWindowInput | golibs.EnableExtendedFlags
    m.api.SetConsoleMode(mode) // 让控制台只能鼠标操作
    m.api.ShowHideCursor(false)
    m.gridSave = make([][]GridDefine, m.height)
    for i := 0; i < m.height; i++ {
        m.gridSave[i] = make([]GridDefine, m.width)
    }
    return m
}

const (
    ClickErr = -1
    ClickY   = -2
    ClickN   = -3
)

func (m *minesweeping) CheckClickPos(pos win.POINT) (int, int, int) {
    y, x := int(pos.X-4), int(pos.Y-8)
    if x < 0 || y < 0 {
        return 0, 0, ClickErr // 超过界限
    }
    x /= 32
    y /= 32
    if x == m.height {
        if y == 0 {
            return 0, 0, ClickY // 点击Y
        } else if y == 1 {
            return 0, 0, ClickN // 点击N
        }
        return 0, 0, ClickErr // 超过界限
    }
    if x >= m.height || y >= m.width {
        return 0, 0, ClickErr // 超过界限
    }
    return x, y, 0
}

// 重置所有数据
func (m *minesweeping) Clear() {
    m.mine = 99
    for i := 0; i < m.height; i++ {
        for j := 0; j < m.width; j++ {
            m.gridSave[i][j] = DefZero
        }
    }
    for i := 0; i < m.mine; i++ {
        for { // 先随机布雷
            x, y := rand.Intn(m.height), rand.Intn(m.width)
            if m.gridSave[x][y] == DefZero {
                m.gridSave[x][y] = DefMine
                break
            }
        }
    }
    for i := 0; i < m.height; i++ {
        for j := 0; j < m.width; j++ {
            if m.gridSave[i][j] == DefMine {
                m.AroundPos(i, j, func(x, y int) {
                    if m.gridSave[x][y] != DefMine {
                        m.gridSave[x][y]++
                    }
                })
            }
        }
    }
}

// 遍历x, y周围所有点
func (m *minesweeping) AroundPos(x, y int, f func(i, j int)) {
    for i := x - 1; i <= x+1; i++ {
        for j := y - 1; j <= y+1; j++ {
            if i >= 0 && j >= 0 && i < m.height && j < m.width && (i != x || j != y) {
                f(i, j)
            }
        }
    }
}

// 当前位置为空白,则点开一片为空白的区域
func (m *minesweeping) ClickAround(x, y int) {
    m.ShowPos(x, y)
    m.AroundPos(x, y, func(i, j int) {
        tmpNum := m.GetData(i, j)
        if tmpNum >= DefOne && tmpNum <= DefEight {
            m.ShowPos(i, j)
        } else if m.CanClick(i, j) && tmpNum == DefZero {
            m.ClickAround(i, j)
        }
    })
}

// 左键点击,遇到雷游戏结束
func (m *minesweeping) HandleLeft(x, y int) bool {
    if m.CanClick(x, y) {
        m.ShowPos(x, y)
        tmpNum := m.GetData(x, y)
        if tmpNum == DefMine {
            return false
        }
        if tmpNum == DefZero {
            m.ClickAround(x, y)
        }
    }
    return true
}

// 点出地雷返回false,游戏结束
func (m *minesweeping) HandleMid(x, y int) bool {
    tmpNum := m.GetData(x, y)
    if m.IsShow(x, y) && tmpNum >= DefOne && tmpNum <= DefEight {
        flagCnt := DefZero
        m.AroundPos(x, y, func(i, j int) {
            if m.IsFlag(i, j) { // 计算该点周围标旗个数
                flagCnt++
            }
        })
        if flagCnt != tmpNum {
            return true
        }
        // 标旗个数等于该点个数,表示剩余位置一定不是雷,可以自动点开
        flagCnt = DefZero
        m.AroundPos(x, y, func(i, j int) {
            if m.CanClick(i, j) { // 周围可点击点
                m.ShowPos(i, j)
                switch m.GetData(i, j) {
                case DefMine: // 由于错误标旗,导致点开一个雷,因此算游戏结束
                    flagCnt = DefMine
                case DefZero: // 中键点开一个空白,自动点开一片空白
                    m.ClickAround(i, j)
                }
            }
        })
        return flagCnt != DefMine
    }
    return true
}

// 右键标旗,对应地雷个数需要变化
func (m *minesweeping) HandleRight(x, y int) {
    if !m.IsShow(x, y) { // 翻转标旗
        if m.gridSave[x][y]&DefFlagPos != 0 {
            m.gridSave[x][y] &= ^DefFlagPos
            m.mine++
        } else {
            m.gridSave[x][y] |= DefFlagPos
            m.mine--
        }
    }
}

func (m *minesweeping) CanClick(x, y int) bool {
    return m.gridSave[x][y]&^DefMaskPos == 0 // 该点能被点击
}

func (m *minesweeping) IsFlag(x, y int) bool {
    return m.gridSave[x][y]&DefFlagPos != 0 // 该点是否已经标雷
}

func (m *minesweeping) IsShow(x, y int) bool {
    return m.gridSave[x][y]&DefShowPos != 0 // 返回是否显示
}

func (m *minesweeping) ShowPos(x, y int) {
    m.gridSave[x][y] |= DefShowPos // 设置点显示
}

func (m *minesweeping) GetData(x, y int) GridDefine {
    return m.gridSave[x][y] & DefMaskPos // 返回该点值
}

var (
    ColorMap = map[GridDefine]int{ /* 每种标记的颜色值 */
        DefZero:  golibs.ForegroundRed | golibs.ForegroundGreen | golibs.ForegroundBlue,
        DefOne:   golibs.ForegroundRed,
        DefTwo:   golibs.ForegroundBlue | golibs.ForegroundRed,
        DefThree: golibs.ForegroundGreen,
        DefFour:  golibs.ForegroundRed | golibs.ForegroundGreen,
        DefFive:  golibs.ForegroundGreen | golibs.ForegroundBlue,
        DefSix:   golibs.ForegroundRed,
        DefSeven: golibs.ForegroundBlue | golibs.ForegroundGreen | golibs.ForegroundIntensity,
        DefEight: golibs.ForegroundGreen,
        DefMine:  golibs.ForegroundRed | golibs.ForegroundGreen | golibs.ForegroundIntensity,
        DefFlag:  golibs.ForegroundGreen | golibs.ForegroundBlue,
    }
)

func (m *minesweeping) Print(gameOver bool) {
    m.api.GotoXY(0, 0)
    for i := 0; i < m.height; i++ {
        fmt.Print(" -----------------------------------------------------------------------------------------------------------------------\n|")
        for j := 0; j < m.width; j++ {
            if m.IsFlag(i, j) {
                fmt.Print(" & |")
            } else if m.IsShow(i, j) {
                switch t := m.GetData(i, j); t {
                case DefZero:
                    fmt.Print("   |")
                case DefMine:
                    fmt.Print(" # |")
                default:
                    m.api.TextBackground(ColorMap[t])
                    fmt.Printf(" %d ", t)
                    m.api.TextBackground(ColorMap[0]) /* 重置为白色 */
                    fmt.Print("|")
                }
            } else {
                fmt.Print(" * |")
            }
        }
        fmt.Println()
    }
    fmt.Println(" -----------------------------------------------------------------------------------------------------------------------")
    var s string
    if gameOver {
        s = "You Win"
        if m.mine > 0 {
            s = "Game Over"
        }
    }
    fmt.Printf("| Y | N |\t[flag:&,mine:#]\tmines:%2d\t%s\n", m.mine, s)
}

当游戏成功或踩雷结束时可以用鼠标点击左下角的“Y”重新来一局,点击左下角的“N”退出游戏
鼠标左键:点开一个可点击位置,如果是雷则结束
鼠标中键:当该点个数和附近标旗数一致时,将其他所有可点击位置点开(如果错误标雷,可能会游戏结束)
鼠标右键:在可点击位置右键则标旗,再次右键取消标旗
以上就是源码和操作方法,写出来玩玩,主要是体会win32编程而已。


有疑问加站长微信联系(非本文作者)

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

911 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传