Skip to content

Instantly share code, notes, and snippets.

@xqm32
Created August 21, 2024 07:10
Show Gist options
  • Save xqm32/a5c85ce46cd51a4edc80e7f4479acf62 to your computer and use it in GitHub Desktop.
Save xqm32/a5c85ce46cd51a4edc80e7f4479acf62 to your computer and use it in GitHub Desktop.
Minesweeper Game in Go
package main
import (
"math/rand/v2"
"strconv"
)
// The game config
type Config struct {
Width int
Height int
Mines int
}
// The game states
type Game struct {
// Game config stored for g.G() to check border
Config *Config
// 0b0000_0000
// &0b1000_0000 = Mine
// &0b0100_0000 = Visible
// &0b0011_0000 = Flag
// &0b0000_1111 = Number
Grids []uint8
// 0b0000_0000 == Gaming
// 0b0000_0001 == Win
// 0b0000_0010 == Loss
Status uint8
}
// New creates a new game, initialize mines and number
func New(config *Config) *Game {
// Panic when code goes wrong
if config.Width <= 0 || config.Height <= 0 || config.Mines <= 0 {
panic("width, height and mines should be larger than 0")
} else if config.Mines > config.Width*config.Height {
panic("grids should be larger than mines")
}
// Create a new game instance
g := &Game{
Config: config,
Grids: make([]uint8, config.Width*config.Height),
}
// Initialize mines
for range config.Mines {
x, y := rand.IntN(config.Width), rand.IntN(config.Height)
for *g.G(x, y) != 0 {
x, y = rand.IntN(config.Width), rand.IntN(config.Height)
}
*g.G(x, y) = 0b1000_0000
// Add numbers arounds the mine grid
for _, a := range [][]int{{x - 1, y}, {x + 1, y}, {x, y - 1}, {x, y + 1}} {
if p := g.G(a[0], a[1]); p != nil {
*p += 1
}
}
}
return g
}
// G returns a pointer to the destination grid by (x, y) coordinate, starts from left top
func (g *Game) G(x, y int) *uint8 {
if x < 0 || x >= g.Config.Width || y < 0 || y >= g.Config.Height {
return nil
}
return &g.Grids[y*g.Config.Width+x]
}
// Click run a DFS algorithm for (x, y) coordinate to bring all empty grids visible
func (g *Game) Click(x, y int) *Game {
// Check if the coordinate is valid
c := g.G(x, y)
if c == nil {
panic("invalid coordinate")
}
// Make the grid visible
*c |= 0b0100_0000
// If the grid has number greater than 0, can go back
if *c&0b0000_1111 != 0 {
return g
}
// Click grids around the 0 number grid
for _, a := range [][]int{{x - 1, y}, {x + 1, y}, {x, y - 1}, {x, y + 1}} {
if p := g.G(a[0], a[1]); p != nil && *p&0b0100_0000 == 0 {
g.Click(a[0], a[1])
}
}
return g
}
// SetAllVisible simply set all grids visible
func (g *Game) SetAllVisible() *Game {
for y := range g.Config.Height {
for x := range g.Config.Width {
*g.G(x, y) |= 0b0100_0000
}
}
return g
}
// Print will output the game grids into console
func (g *Game) Print() *Game {
s := ""
for y := range g.Config.Height {
for x := range g.Config.Width {
if v := *g.G(x, y); v&0b0100_0000 != 0 {
if v&0b1000_0000 != 0 {
s += "*"
} else if f := v & 0b0011_0000; f != 0 {
if f == 0b0001_0000 {
s += "X"
} else if f == 0b0010_0000 {
s += "O"
} else if f == 0b0011_0000 {
s += "?"
}
} else {
s += strconv.Itoa(int(v & 0b0000_1111))
}
} else {
s += "."
}
}
s += "\n"
}
print(s)
return g
}
func main() {
New(&Config{
Width: 10,
Height: 10,
Mines: 10,
}).Click(1, 1).Print().SetAllVisible().Print()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment