Created
August 21, 2024 07:10
-
-
Save xqm32/a5c85ce46cd51a4edc80e7f4479acf62 to your computer and use it in GitHub Desktop.
Minesweeper Game in Go
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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