Skip to content

Commit f5f17c8

Browse files
committed
✨ (mine-sweeper): add draw remaining flags
1 parent 82115c7 commit f5f17c8

File tree

2 files changed

+112
-14
lines changed

2 files changed

+112
-14
lines changed

internal/game/game.go

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ type Board struct {
1414
cols int // 總共列數
1515
cells [][]*Cell // 整格棋盤狀態
1616
minePositionShuffler positionShuffler // 亂序器用來安排地雷格子
17+
RemainingFlags int // 剩餘標記數
1718
}
1819

1920
// Game - 遊戲物件
@@ -34,7 +35,7 @@ type coord struct {
3435
type positionShuffler func(coords []coord)
3536

3637
func NewGame(rows, cols, mineCount int) *Game {
37-
board := NewBoard(rows, cols)
38+
board := NewBoard(rows, cols, mineCount)
3839
// 設定地雷
3940
board.PlaceMines(mineCount)
4041
// 計算結果
@@ -48,11 +49,12 @@ func NewGame(rows, cols, mineCount int) *Game {
4849
}
4950

5051
// NewBoard - 初始化盤面
51-
func NewBoard(rows, cols int) *Board {
52+
func NewBoard(rows, cols, mineCount int) *Board {
5253
board := &Board{
5354
rows: rows,
5455
cols: cols,
5556
minePositionShuffler: defaultPositionShuffler,
57+
RemainingFlags: mineCount,
5658
}
5759
board.cells = make([][]*Cell, rows)
5860
for row := range board.cells {
@@ -142,6 +144,33 @@ func (board *Board) GetCell(row, col int) *Cell {
142144
return board.cells[row][col]
143145
}
144146

147+
// ToggleFlag - 標記地雷
148+
func (board *Board) ToggleFlag(row, col int) {
149+
// 超出邊界
150+
if row < 0 || row >= board.rows ||
151+
col < 0 || col >= board.cols {
152+
return
153+
}
154+
155+
cell := board.cells[row][col]
156+
// 已經被揭開
157+
if cell.Revealed {
158+
return
159+
}
160+
161+
count := 0
162+
if cell.Flagged {
163+
count--
164+
} else {
165+
count++
166+
}
167+
if board.RemainingFlags-count < 0 {
168+
return
169+
}
170+
board.RemainingFlags -= count
171+
board.cells[row][col].Flagged = !cell.Flagged
172+
}
173+
145174
// Reveal - 從 row, col 開始翻開周圍不是地雷,直到遇到非零的格子
146175
func (board *Board) Reveal(row, col int) {
147176
// 超出邊界
@@ -158,6 +187,10 @@ func (board *Board) Reveal(row, col int) {
158187

159188
// 標注該格已經被揭開
160189
board.cells[row][col].Revealed = true
190+
if cell.Flagged {
191+
board.RemainingFlags++
192+
board.cells[row][col].Flagged = false
193+
}
161194

162195
// 如果是空白格 (AdjacenetMines = 0, 且不是地雷)
163196
if !cell.IsMine && cell.AdjacenetMines == 0 {

internal/layout/layout.go

Lines changed: 77 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"image/color"
66

77
"github.com/hajimehoshi/ebiten/v2"
8+
"github.com/hajimehoshi/ebiten/v2/inpututil"
89
"github.com/hajimehoshi/ebiten/v2/text/v2"
910
"github.com/hajimehoshi/ebiten/v2/vector"
1011
"github.com/leetcode-golang-classroom/mine-sweeper/internal/game"
@@ -14,8 +15,11 @@ const (
1415
gridSize = 32
1516
Rows = 10
1617
Cols = 10
17-
ScreenWidth = gridSize * Rows
18-
ScreenHeight = gridSize * Cols
18+
PanelHeight = 36 // 上方面板高度
19+
PaddingX = 60 // 面板內文字左邊距
20+
PaddingY = 20 // 面板
21+
ScreenHeight = PanelHeight + gridSize*Rows
22+
ScreenWidth = gridSize * Cols
1923
MineCounts = 9
2024
)
2125

@@ -31,11 +35,27 @@ func (g *GameLayout) Update() error {
3135
// 偵測 mouse click 事件
3236
if ebiten.IsMouseButtonPressed(ebiten.MouseButtonLeft) {
3337
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)
38+
// 當在面板下方才處理
39+
if yPos >= PanelHeight {
40+
row := (yPos - PanelHeight) / gridSize
41+
col := xPos / gridSize
42+
if row >= 0 && row < Rows && col >= 0 && col < Cols {
43+
// 執行 Flood Fill
44+
g.gameInstance.Board.Reveal(row, col)
45+
}
46+
}
47+
}
48+
// 偵測 mouse 右鍵 click 事件
49+
if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonRight) {
50+
xPos, yPos := ebiten.CursorPosition()
51+
// 當在面板下方才處理
52+
if yPos >= PanelHeight {
53+
row := (yPos - PanelHeight) / gridSize
54+
col := xPos / gridSize
55+
if row >= 0 && row < Rows && col >= 0 && col < Cols {
56+
// 執行 ToggleFlag
57+
g.gameInstance.Board.ToggleFlag(row, col)
58+
}
3959
}
4060
}
4161
return nil
@@ -46,7 +66,7 @@ func (g *GameLayout) drawUnTouchCell(screen *ebiten.Image, row, col int) {
4666
vector.DrawFilledRect(
4767
screen,
4868
float32(col*gridSize),
49-
float32(row*gridSize),
69+
float32(PanelHeight+row*gridSize),
5070
gridSize-1,
5171
gridSize-1,
5272
color.RGBA{100, 100, 100, 0xff},
@@ -59,7 +79,7 @@ func (g *GameLayout) drawTouchCellBackground(screen *ebiten.Image, row, col int)
5979
vector.DrawFilledRect(
6080
screen,
6181
float32(col*gridSize),
62-
float32(row*gridSize),
82+
float32(PanelHeight+row*gridSize),
6383
gridSize-1,
6484
gridSize-1,
6585
color.RGBA{200, 200, 200, 0xff},
@@ -72,7 +92,7 @@ func (g *GameLayout) drawTouchCellAdjacency(screen *ebiten.Image, row, col, valu
7292
// 繪製數字 (置中)
7393
textValue := fmt.Sprintf("%d", value)
7494
textXPos := col*gridSize + (gridSize)/2
75-
textYPos := row*gridSize + (gridSize)/2
95+
textYPos := PanelHeight + row*gridSize + (gridSize)/2
7696
textOpts := &text.DrawOptions{}
7797
textOpts.ColorScale.ScaleWithColor(getTileColor(value))
7898
textOpts.PrimaryAlign = text.AlignCenter
@@ -89,7 +109,7 @@ func (g *GameLayout) drawTouchCellMine(screen *ebiten.Image, row, col int) {
89109
// 繪製數字 (置中)
90110
textValue := "X"
91111
textXPos := col*gridSize + (gridSize)/2
92-
textYPos := row*gridSize + (gridSize)/2
112+
textYPos := PanelHeight + row*gridSize + (gridSize)/2
93113
textOpts := &text.DrawOptions{}
94114
textOpts.ColorScale.ScaleWithColor(getTileColor(-1))
95115
textOpts.PrimaryAlign = text.AlignCenter
@@ -101,7 +121,25 @@ func (g *GameLayout) drawTouchCellMine(screen *ebiten.Image, row, col int) {
101121
}, textOpts)
102122
}
103123

104-
func (g *GameLayout) Draw(screen *ebiten.Image) {
124+
// drawFlag - 標示 flag
125+
func (g *GameLayout) drawFlag(screen *ebiten.Image, row, col int) {
126+
// 繪製數字 (置中)
127+
textValue := "F"
128+
textXPos := col*gridSize + (gridSize)/2
129+
textYPos := PanelHeight + row*gridSize + (gridSize)/2
130+
textOpts := &text.DrawOptions{}
131+
textOpts.ColorScale.ScaleWithColor(getTileColor(-1))
132+
textOpts.PrimaryAlign = text.AlignCenter
133+
textOpts.SecondaryAlign = text.AlignCenter
134+
textOpts.GeoM.Translate(float64(textXPos), float64(textYPos))
135+
text.Draw(screen, textValue, &text.GoTextFace{
136+
Source: mplusFaceSource,
137+
Size: 30,
138+
}, textOpts)
139+
}
140+
141+
// drawBoard - 畫出目前盤面狀態
142+
func (g *GameLayout) drawBoard(screen *ebiten.Image) {
105143
for row := 0; row < Rows; row++ {
106144
for col := 0; col < Cols; col++ {
107145
// 取出格子狀態
@@ -111,6 +149,9 @@ func (g *GameLayout) Draw(screen *ebiten.Image) {
111149
// 當格子沒有被掀開時,畫出原本的灰階
112150
if !cell.Revealed {
113151
g.drawUnTouchCell(screen, row, col)
152+
if cell.Flagged {
153+
g.drawFlag(screen, row, col)
154+
}
114155
} else {
115156
g.drawTouchCellBackground(screen, row, col)
116157
if cell.AdjacenetMines != 0 {
@@ -124,6 +165,30 @@ func (g *GameLayout) Draw(screen *ebiten.Image) {
124165
}
125166
}
126167

168+
// drawRemainFlag
169+
func (g *GameLayout) drawRemainFlag(screen *ebiten.Image) {
170+
panel := ebiten.NewImage(ScreenWidth, PanelHeight)
171+
panel.Fill(color.RGBA{100, 100, 0x10, 0xFF})
172+
screen.DrawImage(panel, nil)
173+
// 畫旗子面板(固定在上方)
174+
textValue := fmt.Sprintf("Flags: %d / %d", g.gameInstance.Board.RemainingFlags, MineCounts)
175+
textXPos := PaddingX
176+
textYPos := PaddingY
177+
textOpts := &text.DrawOptions{}
178+
textOpts.ColorScale.ScaleWithColor(getTileColor(-1))
179+
textOpts.PrimaryAlign = text.AlignCenter
180+
textOpts.SecondaryAlign = text.AlignCenter
181+
textOpts.GeoM.Translate(float64(textXPos), float64(textYPos))
182+
text.Draw(screen, textValue, &text.GoTextFace{
183+
Source: mplusFaceSource,
184+
Size: 20,
185+
}, textOpts)
186+
}
187+
func (g *GameLayout) Draw(screen *ebiten.Image) {
188+
g.drawBoard(screen)
189+
g.drawRemainFlag(screen)
190+
}
191+
127192
func (g *GameLayout) Layout(outsideWidth, outsideHeight int) (int, int) {
128193
return ScreenWidth, ScreenHeight
129194
}

0 commit comments

Comments
 (0)