BambooBasic Logo


Making Your First Game - BoomSweeper

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:



Step 1: Setting Up the Project

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.



Step 2: Define Constants

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.



Step 3: Declare Global Variables

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.



Step 4: Initialize 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).



Step 5: Place Mines Randomly

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).



Step 6: Calculate Adjacent Mine Numbers

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.



Step 7: Handle Mouse Input

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


Step 8: Reveal Tiles

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.



Step 9: Check Win Condition
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.



Step 10: Drawing

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


Step 11: Game Loop Structure

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:

  1. Clear the screen
  2. Update game state (process input, game logic)
  3. Render graphics
  4. Flip/present the screen
  5. Repeat


Step 12: Update Functions

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


Key Concepts Explained

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


Complete Source

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!



Next Steps

Now that you understand the basics, try these modifications:



Recommended Reading

Back to Documentation Index