iT邦幫忙

2025 iThome 鐵人賽

DAY 17
2

主題

在地雷遊戲 (Minesweeper) 中,除了揭開格子之外,另一個非常重要的功能就是 旗子標記 (Flagging)。玩家可以透過右鍵點擊來標記懷疑有地雷的格子,避免誤觸。今天我們要完成的功能就是 右鍵旗子標記與取消,並讓遊戲能正確顯示剩餘旗子的數量。

功能需求

  • 玩家可以用 右鍵點擊格子 來標記或取消旗子。
  • 已揭開的格子不能再標記。
  • 畫面上能正確顯示旗子圖示。
  • 界面上的剩餘旗子數量會隨著操作而更新。

程式設計重點

1 紀錄目前所有剩餘可以標示的數目

// Board - 棋盤
type Board struct {
	rows                 int              // 總共格數
	cols                 int              // 總共列數
	cells                [][]*Cell        // 整格棋盤狀態
	minePositionShuffler positionShuffler // 亂序器用來安排地雷格子
	RemainingFlags       int              // 剩餘標記數
}

2 偵測滑鼠右鍵點擊

func (g *GameLayout) Update() error {
	// 偵測 mouse click 事件
	if ebiten.IsMouseButtonPressed(ebiten.MouseButtonLeft) {
		xPos, yPos := ebiten.CursorPosition()
		// 當在面板下方才處理
		if yPos >= PanelHeight {
			row := (yPos - PanelHeight) / gridSize
			col := xPos / gridSize
			if row >= 0 && row < Rows && col >= 0 && col < Cols {
				// 執行 Flood Fill
				g.gameInstance.Board.Reveal(row, col)
			}
		}
	}
	// 偵測 mouse 右鍵 click 事件
	if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonRight) {
		xPos, yPos := ebiten.CursorPosition()
		// 當在面板下方才處理
		if yPos >= PanelHeight {
			row := (yPos - PanelHeight) / gridSize
			col := xPos / gridSize
			if row >= 0 && row < Rows && col >= 0 && col < Cols {
				// 執行 ToggleFlag
				g.gameInstance.Board.ToggleFlag(row, col)
			}
		}
	}
	return nil
}
  1. 更新 flag 畫面
// drawBoard - 畫出目前盤面狀態
func (g *GameLayout) drawBoard(screen *ebiten.Image) {
	for row := 0; row < Rows; row++ {
		for col := 0; col < Cols; col++ {
			// 取出格子狀態
			cell := g.gameInstance.Board.GetCell(row, col)

			// 根據格子狀態,顯示對應的畫面
			// 當格子沒有被掀開時,畫出原本的灰階
			if !cell.Revealed {
				g.drawUnTouchCell(screen, row, col)
				if cell.Flagged {
					g.drawFlag(screen, row, col)
				}
			} else {
				g.drawTouchCellBackground(screen, row, col)
				if cell.AdjacenetMines != 0 {
					g.drawTouchCellAdjacency(screen, row, col, cell.AdjacenetMines)
				}
				if cell.IsMine {
					g.drawTouchCellMine(screen, row, col)
				}
			}
		}
	}
}
  1. 更新旗標個數在 panel
func (g *GameLayout) drawRemainFlag(screen *ebiten.Image) {
	panel := ebiten.NewImage(ScreenWidth, PanelHeight)
	panel.Fill(color.RGBA{100, 100, 0x10, 0xFF})
	screen.DrawImage(panel, nil)
	// 畫旗子面板(固定在上方)
	textValue := fmt.Sprintf("Flags: %d / %d", g.gameInstance.Board.RemainingFlags, MineCounts)
	textXPos := PaddingX
	textYPos := PaddingY
	textOpts := &text.DrawOptions{}
	textOpts.ColorScale.ScaleWithColor(getTileColor(-1))
	textOpts.PrimaryAlign = text.AlignCenter
	textOpts.SecondaryAlign = text.AlignCenter
	textOpts.GeoM.Translate(float64(textXPos), float64(textYPos))
	text.Draw(screen, textValue, &text.GoTextFace{
		Source: mplusFaceSource,
		Size:   20,
	}, textOpts)
}

github 測試結果

https://github.com/leetcode-golang-classroom/mine-sweeper-game/actions/runs/17334075303

執行結果

https://ithelp.ithome.com.tw/upload/images/20250830/201115809uoxajbIqt.png

驗收條件

  • 玩家可以右鍵點擊格子來 插旗 或 取消旗子。
  • 已揭開的格子無法再插旗。
  • 插旗後畫面會顯示「🚩」符號。
  • 下方文字會隨旗子數量變化正確更新。

本日收穫

今天完成了地雷遊戲中另一個關鍵功能 旗子標記,遊戲互動性大幅提升。玩家可以更有策略性地進行操作,而不是盲目翻格。

明日預告

  • 接下來我們會進一步處理 遊戲勝負判定:
  • 當所有地雷都被正確插旗時 → 玩家勝利。
  • 當玩家誤點到地雷 → 遊戲結束。

上一篇
採地雷遊戲:格子揭開與空白區域擴散揭開
下一篇
踩地雷遊戲:勝負判斷邏輯
系列文
在 ai 時代 gopher 遊戲開發者的 30 天自我養成20
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言