[码云地址,可以直接下载可执行程序](https://gitee.com/janbar/minesweeping/tree/master/consoleMinesweeping)
![mine.jpg](https://static.studygolang.com/191017/9d9a9db1f90898da89f7fb77fc3bb524.jpg)
```go
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编程而已。
有疑问加站长微信联系(非本文作者)