package minesweeper import ( "bytes" "fmt" "image/color" "github.com/hajimehoshi/ebiten/examples/resources/fonts" "github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2/ebitenutil" "github.com/hajimehoshi/ebiten/v2/inpututil" "github.com/hajimehoshi/ebiten/v2/text/v2" ) type Game struct { Width, Height int CellSize int rows, columns int bombPercentage int cells []Cell firstMove bool lost bool won bool bombIndexes map[int]struct{} faceSource *text.GoTextFaceSource } func NewGame(width, height, cellSize, bombPercentage int) (*Game, error) { s, err := text.NewGoTextFaceSource(bytes.NewReader(fonts.MPlus1pRegular_ttf)) if err != nil { return nil, err } rows := width / cellSize columns := height / cellSize g := &Game{ Width: width, Height: height, CellSize: cellSize, rows: rows, columns: columns, bombPercentage: bombPercentage, cells: make([]Cell, rows*columns), firstMove: false, lost: false, won: false, bombIndexes: map[int]struct{}{}, faceSource: s, } g.Reset() return g, nil } func (g *Game) CheckWin() { for _, cell := range g.cells { if cell.cellType != BOMB && cell.state == FLAG { return } else if cell.cellType == BOMB && cell.state != FLAG { return } else if cell.state == DARK { return } } g.won = true return } func (g *Game) HandleFirstMove(i int) { if !g.firstMove { return } indexes := make([]int, 0) g.applyNeighbours(i, func(index int) { indexes = append(indexes, index) r := RandomUntilNoBomb(g.rows*g.columns, g.bombIndexes) g.cells[r].cellType = BOMB }) for _, i := range indexes { g.cells[i].cellType = NO_BOMB } for i := range g.cells { g.cells[i].bombNeighbours = g.CalcNeighbourBombs(i) } g.SetBomblessNeighboursToOpen(i) g.firstMove = false } func (g *Game) Reset() { cells, indexes := g.SetupCells() g.cells = cells g.bombIndexes = indexes g.won = false g.lost = false g.firstMove = true } func (g *Game) Update() error { if !g.won && !g.lost { if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) { x, y := ebiten.CursorPosition() index := g.CursorPosToIndex(x, y) g.HandleFirstMove(index) if g.cells[index].cellType == BOMB && g.cells[index].state != FLAG { g.lost = true } else if g.cells[index].state != FLAG { g.cells[index].state = OPEN if g.cells[index].bombNeighbours == 0 { g.SetBomblessNeighboursToOpen(index) } } } else if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonRight) { x, y := ebiten.CursorPosition() index := g.CursorPosToIndex(x, y) if g.cells[index].state == DARK { g.cells[index].state = FLAG } else if g.cells[index].state == FLAG { g.cells[index].state = DARK } } } if inpututil.IsKeyJustPressed(ebiten.KeyR) { g.Reset() } g.CheckWin() return nil } func (g *Game) Draw(screen *ebiten.Image) { showBombs := false if g.lost { showBombs = true ebitenutil.DebugPrint(screen, "Game Over!") } else if g.won { ebitenutil.DebugPrint(screen, "Game Won :) YAAY!") return } g.DrawCells(screen, showBombs) } func (g *Game) DrawCells(screen *ebiten.Image, showBombs bool) { for _, cell := range g.cells { op := &ebiten.DrawImageOptions{} op.GeoM.Translate(float64(cell.left), float64(cell.top)) borderImage := ebiten.NewImage(g.CellSize, g.CellSize) borderImage.Fill(color.Black) cellImage := ebiten.NewImage(g.CellSize-2, g.CellSize-2) if showBombs && cell.cellType == BOMB { cellImage.Fill(color.RGBA{255, 0, 0, 255}) } else { cellImage.Fill(MatchColor(cell.state)) } borderImage.DrawImage(cellImage, nil) if cell.state == OPEN && cell.bombNeighbours != 0 { txOp := &text.DrawOptions{} txOp.GeoM.Translate(float64(g.CellSize-24)/2, float64(g.CellSize-24)/2) text.Draw(borderImage, fmt.Sprintf("%v", cell.bombNeighbours), &text.GoTextFace{Source: g.faceSource, Size: 24}, txOp) } screen.DrawImage(borderImage, op) } } func (g *Game) Layout(outsideWidth, outsideHeight int) (screenWidth, screenHeight int) { return outsideWidth, outsideHeight }