Skip to content

Commit c47a838

Browse files
committed
✨ (mine-sweeper): add floodfill algorithm
1 parent 40b3d34 commit c47a838

File tree

10 files changed

+206
-2
lines changed

10 files changed

+206
-2
lines changed

cmd/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import (
1010

1111
func main() {
1212
ebiten.SetWindowSize(layout.ScreenWidth, layout.ScreenHeight)
13-
ebiten.SetWindowTitle("Mine Sweeper Grid")
13+
ebiten.SetWindowTitle("Mine Sweeper Grid - Flood Filled")
1414
gameInstance := game.NewGame(layout.Rows, layout.Cols, layout.MineCounts)
1515
gameLayout := layout.NewGameLayout(gameInstance)
1616
if err := ebiten.RunGame(gameLayout); err != nil {

go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,13 @@ require (
88
github.com/ebitengine/gomobile v0.0.0-20240911145611-4856209ac325 // indirect
99
github.com/ebitengine/hideconsole v1.0.0 // indirect
1010
github.com/ebitengine/purego v0.8.0 // indirect
11+
github.com/go-text/typesetting v0.2.0 // indirect
1112
github.com/jezek/xgb v1.1.1 // indirect
1213
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
14+
golang.org/x/image v0.20.0 // indirect
1315
golang.org/x/sync v0.8.0 // indirect
1416
golang.org/x/sys v0.25.0 // indirect
17+
golang.org/x/text v0.18.0 // indirect
1518
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect
1619
)
1720

go.sum

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@ github.com/ebitengine/hideconsole v1.0.0 h1:5J4U0kXF+pv/DhiXt5/lTz0eO5ogJ1iXb8Yj
66
github.com/ebitengine/hideconsole v1.0.0/go.mod h1:hTTBTvVYWKBuxPr7peweneWdkUwEuHuB3C1R/ielR1A=
77
github.com/ebitengine/purego v0.8.0 h1:JbqvnEzRvPpxhCJzJJ2y0RbiZ8nyjccVUrSM3q+GvvE=
88
github.com/ebitengine/purego v0.8.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
9+
github.com/go-text/typesetting v0.2.0 h1:fbzsgbmk04KiWtE+c3ZD4W2nmCRzBqrqQOvYlwAOdho=
10+
github.com/go-text/typesetting v0.2.0/go.mod h1:2+owI/sxa73XA581LAzVuEBZ3WEEV2pXeDswCH/3i1I=
11+
github.com/go-text/typesetting-utils v0.0.0-20240317173224-1986cbe96c66 h1:GUrm65PQPlhFSKjLPGOZNPNxLCybjzjYBzjfoBGaDUY=
12+
github.com/go-text/typesetting-utils v0.0.0-20240317173224-1986cbe96c66/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o=
13+
github.com/hajimehoshi/bitmapfont/v3 v3.2.0 h1:0DISQM/rseKIJhdF29AkhvdzIULqNIIlXAGWit4ez1Q=
14+
github.com/hajimehoshi/bitmapfont/v3 v3.2.0/go.mod h1:8gLqGatKVu0pwcNCJguW3Igg9WQqVXF0zg/RvrGQWyg=
915
github.com/hajimehoshi/ebiten/v2 v2.8.8 h1:xyMxOAn52T1tQ+j3vdieZ7auDBOXmvjUprSrxaIbsi8=
1016
github.com/hajimehoshi/ebiten/v2 v2.8.8/go.mod h1:durJ05+OYnio9b8q0sEtOgaNeBEQG7Yr7lRviAciYbs=
1117
github.com/jezek/xgb v1.1.1 h1:bE/r8ZZtSv7l9gk6nU0mYx51aXrvnyb44892TwSaqS4=
@@ -15,6 +21,8 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
1521
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
1622
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
1723
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
24+
github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
25+
github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
1826
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
1927
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
2028
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
@@ -25,6 +33,8 @@ golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
2533
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
2634
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
2735
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
36+
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
37+
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
2838
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
2939
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U=
3040
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

internal/fonts/embed.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright 2022 The Ebitengine Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package fonts
16+
17+
import (
18+
_ "embed"
19+
)
20+
21+
var (
22+
//go:embed mplus-1p-regular.ttf
23+
MPlus1pRegular_ttf []byte
24+
25+
//go:embed pressstart2p.ttf
26+
PressStart2P_ttf []byte
27+
)

internal/fonts/license.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# License
2+
3+
## mplus-1p-regular.ttf
4+
5+
```
6+
M+ FONTS Copyright (C) 2002-2015 M+ FONTS PROJECT
7+
8+
-
9+
10+
LICENSE_E
11+
12+
13+
14+
15+
These fonts are free software.
16+
Unlimited permission is granted to use, copy, and distribute them, with
17+
or without modification, either commercially or noncommercially.
18+
THESE FONTS ARE PROVIDED "AS IS" WITHOUT WARRANTY.
19+
20+
21+
http://mplus-fonts.sourceforge.jp/mplus-outline-fonts/
22+
```
23+
24+
## PressStart2P-vaV7.ttf
25+
26+
```
27+
Copyright (c) 2011, Cody "CodeMan38" Boisclair (cody@zone38.net),
28+
with Reserved Font Name "Press Start".
29+
30+
This Font Software is licensed under the SIL Open Font License, Version 1.1.
31+
```
1.58 MB
Binary file not shown.

internal/fonts/pressstart2p.ttf

80.5 KB
Binary file not shown.

internal/game/game.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,3 +141,35 @@ func (b *Board) CalculateAdjacentMines() {
141141
func (board *Board) GetCell(row, col int) *Cell {
142142
return board.cells[row][col]
143143
}
144+
145+
// Reveal - 從 row, col 開始翻開周圍不是地雷,直到遇到非零的格子
146+
func (board *Board) Reveal(row, col int) {
147+
// 超出邊界
148+
if row < 0 || row >= board.rows ||
149+
col < 0 || col >= board.cols {
150+
return
151+
}
152+
153+
cell := board.cells[row][col]
154+
// 已經被揭開
155+
if cell.Revealed {
156+
return
157+
}
158+
159+
// 標注該格已經被揭開
160+
board.cells[row][col].Revealed = true
161+
162+
// 如果是空白格 (AdjacenetMines = 0, 且不是地雷)
163+
if !cell.IsMine && cell.AdjacenetMines == 0 {
164+
// 鄰近所有方向
165+
neighborDirections := [8]coord{
166+
{Row: -1, Col: -1}, {Row: -1, Col: 0}, {Row: -1, Col: 1},
167+
{Row: 0, Col: -1}, {Row: 0, Col: 1},
168+
{Row: 1, Col: -1}, {Row: 1, Col: 0}, {Row: 1, Col: 1},
169+
}
170+
for _, direction := range neighborDirections {
171+
neighborRow, neighborCol := row+direction.Row, col+direction.Col
172+
board.Reveal(neighborRow, neighborCol)
173+
}
174+
}
175+
}

internal/layout/font.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package layout
2+
3+
import (
4+
"bytes"
5+
"image/color"
6+
"log"
7+
8+
"github.com/hajimehoshi/ebiten/v2/text/v2"
9+
"github.com/leetcode-golang-classroom/mine-sweeper/internal/fonts"
10+
)
11+
12+
var (
13+
mplusFaceSource *text.GoTextFaceSource
14+
)
15+
16+
func init() {
17+
s, err := text.NewGoTextFaceSource(bytes.NewReader(fonts.MPlus1pRegular_ttf))
18+
if err != nil {
19+
log.Fatal(err)
20+
}
21+
mplusFaceSource = s
22+
}
23+
24+
func getTileColor(value int) color.Color {
25+
switch value {
26+
case 0:
27+
return color.RGBA{0x77, 0x6e, 0x65, 0xff}
28+
default:
29+
return color.RGBA{0xf9, 0xf6, 0xf2, 0xff}
30+
}
31+
}

internal/layout/layout.go

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package layout
22

33
import (
4+
"fmt"
45
"image/color"
56

67
"github.com/hajimehoshi/ebiten/v2"
8+
"github.com/hajimehoshi/ebiten/v2/text/v2"
79
"github.com/hajimehoshi/ebiten/v2/vector"
810
"github.com/leetcode-golang-classroom/mine-sweeper/internal/game"
911
)
@@ -24,31 +26,99 @@ type GameLayout struct {
2426
func NewGameLayout(gameInstance *game.Game) *GameLayout {
2527
return &GameLayout{gameInstance: gameInstance}
2628
}
29+
2730
func (g *GameLayout) Update() error {
31+
// 偵測 mouse click 事件
32+
if ebiten.IsMouseButtonPressed(ebiten.MouseButtonLeft) {
33+
xPos, yPos := ebiten.CursorPosition()
34+
row := yPos / gridSize
35+
col := xPos / gridSize
36+
if row >= 0 && row < Rows && col >= 0 && col < Cols {
37+
// 執行 Flood Fill
38+
g.gameInstance.Board.Reveal(row, col)
39+
}
40+
}
2841
return nil
2942
}
3043

3144
// drawUnTouchCell - 畫出沒有被掀開的格子
3245
func (g *GameLayout) drawUnTouchCell(screen *ebiten.Image, row, col int) {
3346
vector.DrawFilledRect(
3447
screen,
48+
float32(col*gridSize),
3549
float32(row*gridSize),
50+
gridSize-1,
51+
gridSize-1,
52+
color.RGBA{100, 100, 100, 0xff},
53+
false,
54+
)
55+
}
56+
57+
// drawTouchCellBackground - 畫出 click 之後背景
58+
func (g *GameLayout) drawTouchCellBackground(screen *ebiten.Image, row, col int) {
59+
vector.DrawFilledRect(
60+
screen,
3661
float32(col*gridSize),
62+
float32(row*gridSize),
3763
gridSize-1,
3864
gridSize-1,
39-
color.RGBA{0xcc, 0xcc, 0xcc, 0xff},
65+
color.RGBA{200, 200, 200, 0xff},
4066
false,
4167
)
4268
}
4369

70+
// drawTouchCellAdjacency - 畫出 click 之後顯示出來的值
71+
func (g *GameLayout) drawTouchCellAdjacency(screen *ebiten.Image, row, col, value int) {
72+
// 繪製數字 (置中)
73+
textValue := fmt.Sprintf("%d", value)
74+
textXPos := col*gridSize + (gridSize)/2
75+
textYPos := row*gridSize + (gridSize)/2
76+
textOpts := &text.DrawOptions{}
77+
textOpts.ColorScale.ScaleWithColor(getTileColor(value))
78+
textOpts.PrimaryAlign = text.AlignCenter
79+
textOpts.SecondaryAlign = text.AlignCenter
80+
textOpts.GeoM.Translate(float64(textXPos), float64(textYPos))
81+
text.Draw(screen, textValue, &text.GoTextFace{
82+
Source: mplusFaceSource,
83+
Size: 30,
84+
}, textOpts)
85+
}
86+
87+
// drawTouchCellMine - 畫出地雷
88+
func (g *GameLayout) drawTouchCellMine(screen *ebiten.Image, row, col int) {
89+
// 繪製數字 (置中)
90+
textValue := "X"
91+
textXPos := col*gridSize + (gridSize)/2
92+
textYPos := row*gridSize + (gridSize)/2
93+
textOpts := &text.DrawOptions{}
94+
textOpts.ColorScale.ScaleWithColor(getTileColor(-1))
95+
textOpts.PrimaryAlign = text.AlignCenter
96+
textOpts.SecondaryAlign = text.AlignCenter
97+
textOpts.GeoM.Translate(float64(textXPos), float64(textYPos))
98+
text.Draw(screen, textValue, &text.GoTextFace{
99+
Source: mplusFaceSource,
100+
Size: 30,
101+
}, textOpts)
102+
}
103+
44104
func (g *GameLayout) Draw(screen *ebiten.Image) {
45105
for row := 0; row < Rows; row++ {
46106
for col := 0; col < Cols; col++ {
47107
// 取出格子狀態
48108
cell := g.gameInstance.Board.GetCell(row, col)
109+
110+
// 根據格子狀態,顯示對應的畫面
49111
// 當格子沒有被掀開時,畫出原本的灰階
50112
if !cell.Revealed {
51113
g.drawUnTouchCell(screen, row, col)
114+
} else {
115+
g.drawTouchCellBackground(screen, row, col)
116+
if cell.AdjacenetMines != 0 {
117+
g.drawTouchCellAdjacency(screen, row, col, cell.AdjacenetMines)
118+
}
119+
if cell.IsMine {
120+
g.drawTouchCellMine(screen, row, col)
121+
}
52122
}
53123
}
54124
}

0 commit comments

Comments
 (0)