Files
mine-sweeper/game.go

182 lines
4.1 KiB
Go

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
}