This tutorial will guide you through creating a complete minesweeper game called BoomSweeper. You'll learn the fundamental concepts of game programming in BambooBasic.
What You'll Learn:
Create a new BambooBasic file and start with the imports:
; BoomSweeper - MineSweeper clone
Import "BBRuntimeLinux.decls"
Include "BBR_INCLUDE.bam"
The Import statement loads all the runtime functions you'll need for graphics, input, and more.
The Include "BBR_INCLUDE.bam" file contains all the constants required for the Bamboo Runtime (like BBR_WINDOW_MODE_WT, VKEY_ESCAPE, etc.). This file can be found in the BambooBasic/userlibs folder, and it's recommended you copy it into all new project folders when using the Bamboo Runtime.
Constants make your code easier to understand and modify:
; Graphics setup
Const GRAPHICS_WIDTH:Int = 480
Const GRAPHICS_HEIGHT:Int = 620
Const GRAPHICS_MODE:Int = BBR_WINDOW_MODE_WT
; Game states
Const SCREEN_TITLE:Int = 0
Const SCREEN_GAME:Int = 1
Const SCREEN_WIN:Int = 2
Const SCREEN_LOSE:Int = 3
; Game constants
Const GRID_WIDTH:Int = 16
Const GRID_HEIGHT:Int = 16
Const TILE_SIZE:Int = 30
Const MINE_COUNT:Int = 40
; Tile states
Const TILE_HIDDEN:Int = 0
Const TILE_REVEALED:Int = 1
Const TILE_FLAGGED:Int = 2
Game States control which screen is showing (title, gameplay, win, or lose).
Grid Constants define the size of your minesweeper grid.
Tile States track whether each tile is hidden, revealed, or flagged.
Global variables store game data that multiple functions need to access:
; Game state
Global gameScreen:Int = SCREEN_TITLE
Global running:Int = True
; Grid data
Global grid:Int[16][16] ; 0 = empty, 1 = mine
Global state:Int[16][16] ; TILE_HIDDEN, TILE_REVEALED, TILE_FLAGGED
Global numbers:Int[16][16] ; Count of adjacent mines
Global minesRemaining:Int
Global tilesRevealed:Int
Global firstClick:Int
; Graphics
Global font:Int
Global imgHidden:Int
Global imgRevealed:Int
Global imgMine:Int
; Mouse tracking
Global mouseLeftPressed:Int
Global clickTileX:Int
Global clickTileY:Int
2D Arrays like grid[16][16] store data for each tile in the grid.
This function resets the game board:
Function InitGrid()
Local x:Int, y:Int
For y = 0 To GRID_HEIGHT - 1
For x = 0 To GRID_WIDTH - 1
grid[y][x] = 0 ; No mine
state[y][x] = TILE_HIDDEN ; Hidden
numbers[y][x] = 0 ; No adjacent mines yet
Next
Next
minesRemaining = MINE_COUNT
tilesRevealed = 0
firstClick = True
EndFunction
Nested loops iterate through every tile in the grid. The outer loop goes through rows (y), the inner loop through columns (x).
Mines are placed after the first click to ensure you never click a mine first:
Function PlaceMines(avoidX:Int, avoidY:Int)
Local placed:Int = 0
Local x:Int, y:Int
While placed < MINE_COUNT
x = mathRand(0, GRID_WIDTH - 1)
y = mathRand(0, GRID_HEIGHT - 1)
; Only place if not already a mine and not the first click
If grid[y][x] = 0 Then
If x <> avoidX Or y <> avoidY Then
grid[y][x] = 1 ; Place mine
placed = placed + 1
EndIf
EndIf
Wend
EndFunction
mathRand(min, max) generates a random number between min and max (inclusive).
For each empty tile, count how many mines surround it:
Function CalculateNumbers()
Local x:Int, y:Int, dx:Int, dy:Int, nx:Int, ny:Int, count:Int
For y = 0 To GRID_HEIGHT - 1
For x = 0 To GRID_WIDTH - 1
If grid[y][x] = 0 Then ; If not a mine
count = 0
; Check all 8 neighbors
For dy = -1 To 1
For dx = -1 To 1
If dx <> 0 Or dy <> 0 Then
nx = x + dx
ny = y + dy
; Check bounds
If nx >= 0 And nx < GRID_WIDTH And ny >= 0 And ny < GRID_HEIGHT Then
If grid[ny][nx] = 1 Then
count = count + 1
EndIf
EndIf
EndIf
Next
Next
numbers[y][x] = count
EndIf
Next
Next
EndFunction
This checks all 8 tiles around each empty tile, counting mines.
Convert mouse coordinates to grid coordinates:
Function GetTileAtMouse:Int(mx:Int, my:Int)
Local gridStartX:Int = (GRAPHICS_WIDTH - GRID_WIDTH * TILE_SIZE) / 2
Local gridStartY:Int = 80
Local gridEndX:Int = gridStartX + GRID_WIDTH * TILE_SIZE
Local gridEndY:Int = gridStartY + GRID_HEIGHT * TILE_SIZE
; Check if mouse is within grid bounds
If mx >= gridStartX And mx < gridEndX And my >= gridStartY And my < gridEndY Then
clickTileX = (mx - gridStartX) / TILE_SIZE
clickTileY = (my - gridStartY) / TILE_SIZE
Return True
EndIf
Return False
EndFunction
This function reveals a tile and implements "flood fill" for empty areas:
Function RevealTile(x:Int, y:Int)
; Check bounds
If x < 0 Or x >= GRID_WIDTH Or y < 0 Or y >= GRID_HEIGHT Then Return
If state[y][x] <> TILE_HIDDEN Then Return
state[y][x] = TILE_REVEALED
tilesRevealed = tilesRevealed + 1
; Hit a mine? Game over!
If grid[y][x] = 1 Then
gameScreen = SCREEN_LOSE
Return
EndIf
; If empty (no adjacent mines), reveal neighbors (flood fill)
If numbers[y][x] = 0 Then
Local dx:Int, dy:Int
For dy = -1 To 1
For dx = -1 To 1
If dx <> 0 Or dy <> 0 Then
RevealTile(x + dx, y + dy) ; Recursive call
EndIf
Next
Next
EndIf
EndFunction
Recursion: The function calls itself to reveal adjacent tiles when an empty tile is clicked.
Function CheckWin:Int()
Local totalTiles:Int = GRID_WIDTH * GRID_HEIGHT
Local safeTiles:Int = totalTiles - MINE_COUNT
If tilesRevealed = safeTiles Then
Return True
EndIf
Return False
EndFunction
You win when all non-mine tiles are revealed.
Draw a single tile based on its state:
Function DrawTile(x:Int, y:Int, revealAll:Int)
Local screenX:Int = (GRAPHICS_WIDTH - GRID_WIDTH * TILE_SIZE) / 2 + x * TILE_SIZE
Local screenY:Int = 80 + y * TILE_SIZE
Local tileState:Int = state[y][x]
If revealAll Then
; Show all tiles (for win/lose screen)
If grid[y][x] = 1 Then
b2dSetColor(255, 0, 0)
b2dDrawImage(imgMine, screenX, screenY)
Else
b2dSetColor(100, 200, 100)
b2dDrawImage(imgRevealed, screenX, screenY)
If numbers[y][x] > 0 Then
b2dSetColor(255, 255, 255)
b2dDrawText(ToString(numbers[y][x]), screenX + TILE_SIZE / 2, screenY + TILE_SIZE / 2 - 14, 1, font)
EndIf
EndIf
ElseIf tileState = TILE_HIDDEN Then
b2dSetColor(180, 180, 180)
b2dDrawImage(imgHidden, screenX, screenY)
Else
b2dSetColor(100, 200, 100)
b2dDrawImage(imgRevealed, screenX, screenY)
If numbers[y][x] > 0 Then
b2dSetColor(255, 255, 255)
b2dDrawText(ToString(numbers[y][x]), screenX + TILE_SIZE / 2, screenY + TILE_SIZE / 2 - 14, 1, font)
EndIf
EndIf
EndFunction
The main loop follows the classic game loop pattern:
Function Main()
SetupFramework()
While running
b2dCls() ; Clear screen
; Update game logic
Select gameScreen
Case SCREEN_TITLE
UpdateTitleScreen()
Exit
Case SCREEN_GAME
UpdateMainGame()
Exit
Case SCREEN_WIN
UpdateWinScreen()
Exit
Case SCREEN_LOSE
UpdateLoseScreen()
Exit
EndSelect
; Render graphics
Select gameScreen
Case SCREEN_TITLE
RenderTitleScreen()
Exit
Case SCREEN_GAME
RenderMainGame()
Exit
Case SCREEN_WIN
RenderWinScreen()
Exit
Case SCREEN_LOSE
RenderLoseScreen()
Exit
EndSelect
b2dFlip() ; Display what we drew
Wend
b2dEnd()
Return False
EndFunction
Game Loop Pattern:
Each game state has its own update function. Here's the main game update:
Function UpdateMainGame()
Local mouseX:Int = inpGetMouseX()
Local mouseY:Int = inpGetMouseY()
Local currentLeftButton:Int = inpIsMouseDown(VMOUSE_LBUTTON)
; ESC to return to title
If inpIsKeyHit(VKEY_ESCAPE) Then
gameScreen = SCREEN_TITLE
EndIf
; Left click to reveal
If currentLeftButton And mouseLeftPressed = False Then
mouseLeftPressed = True
If GetTileAtMouse(mouseX, mouseY) Then
; First click? Place mines
If firstClick Then
PlaceMines(clickTileX, clickTileY)
CalculateNumbers()
firstClick = False
EndIf
If state[clickTileY][clickTileX] = TILE_HIDDEN Then
RevealTile(clickTileX, clickTileY)
If CheckWin() Then
gameScreen = SCREEN_WIN
EndIf
EndIf
EndIf
EndIf
If currentLeftButton = False Then
mouseLeftPressed = False
EndIf
EndFunction
1. Game States
Using constants to represent different screens makes code clearer:
gameScreen = SCREEN_TITLE ; Much clearer than gameScreen = 0
2. 2D Arrays
Perfect for grid-based games:
grid[y][x] = 1 ; Access row y, column x
3. Separation of Update and Render
Game logic (Update) is separate from drawing (Render). This makes code organized and easier to debug.
4. Input Handling
Track button states to detect clicks (pressed but not yet released):
If currentLeftButton And mouseLeftPressed = False Then
; Just pressed!
mouseLeftPressed = True
EndIf
5. Bounds Checking
Always check if coordinates are valid before accessing arrays:
If x >= 0 And x < GRID_WIDTH And y >= 0 And y < GRID_HEIGHT Then
; Safe to access grid[y][x]
EndIf
The complete BoomSweeper source code is located at:
BambooBasic/booms/Dabzy/2D/BoomSweeper/BoomSweeper.bam
Study the full source to see how all the pieces fit together!
Now that you understand the basics, try these modifications: