Skip to content
dagrigorevPublic

About

BlockBlast inspired game for iOS

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

2 Commits

Folders and files

Repository files navigation

Blockfall

A block-placement puzzle game for iPhone and iPad. Drag pieces onto an 8×8 board, fill rows and columns to clear them, and chain clears into combos. It's built with SwiftUI, the Observation framework, SwiftData and Swift Charts, and has no third-party dependencies.

The name, visuals and sounds are all original. Blocks are drawn in code and sound effects are synthesized at runtime, so the app has no image or audio assets.

Home screen with the Blockfall title, best score and Play button Game screen mid-combo showing Combo x3, +84 and Great! Game Over card with a New Best badge and confetti

Contents

Features

  • Classic 8×8 block puzzle. Three pieces per round, no rotation, and rows and columns clear at the same time.
  • Fair dealing. Each new set of three contains at least one piece that fits whenever any piece can fit. Difficulty rises gently as your score grows.
  • Combos and bonuses. Consecutive clears multiply your points, and emptying the board earns a +300 perfect-clear bonus.
  • Responsive drag and drop. The piece lifts above your finger, snaps to the grid with a ghost preview, and highlights the lines it will clear.
  • Polished effects. Placement pop, clears that sweep outward into particles, bouncing combo text, rolling score digits, and confetti for a new best.
  • Haptics and sound. Distinct feedback for pickup, placement, clears, combos and invalid drops. Sound follows the silent switch.
  • Persistence. Unfinished games auto-save when the app goes to the background. Best score, settings and the history of finished games are stored in SwiftData.
  • Stats. Games played, best and average score, total lines, longest combo, and a chart of your last 20 scores.
  • Themes. Dark by default, with System and Light options and three block palettes, including High Contrast.
  • Accessible. Dynamic Type, VoiceOver, Reduce Motion and a high-contrast palette are all supported.

How to play

  1. Drag any of the three pieces from the tray onto the board. Pieces can't be rotated.
  2. Fill a whole row or column to clear it. Filling a row and a column with one piece clears both.
  3. After you place all three pieces, a new set is dealt.
  4. The game ends when none of your remaining pieces fits anywhere on the board.

Pause sheet with Resume, Restart, Home and sound and haptics toggles

Tap the pause button to resume, restart (with confirmation), return home, or toggle sound and haptics.

Requirements

Deployment target iOS 17.0 (iPhone and iPad)
Xcode 16 or later (the project uses synchronized folders)
Swift 5.10+ (the project builds in Swift 5 language mode)
Dependencies None

The animated mesh-gradient background needs iOS 18. On iOS 17 the app falls back to a static navy-to-indigo gradient.

Getting started

  1. Open Blockfall.xcodeproj in Xcode.
  2. Select the Blockfall scheme and any iPhone or iPad simulator.
  3. Press Run (⌘R).

To build and run the tests from the command line:

xcodebuild test -project Blockfall.xcodeproj -scheme Blockfall -destination 'platform=iOS Simulator,name=iPhone 17 Pro'

Use any simulator name installed on your Mac.

The project builds with zero compiler warnings. To run on a physical device, set your development team under Signing & Capabilities and change the bundle identifier (com.example.blockfall) to one you own.

Project structure

Blockfall/
├── App/
│   ├── BlockfallApp.swift       App entry point, SwiftData container, root view, scene-phase auto-save
│   ├── AppModel.swift           Navigation, profile/settings, recording games, saved-game handling
│   └── DemoScenario.swift       Debug-only screenshot and test scenarios
├── Engine/                      Pure game logic (Foundation only, no UI imports)
│   ├── Piece.swift              Shape catalog defined as ASCII patterns
│   ├── Board.swift              8×8 grid, placement checks, line detection and clearing
│   ├── ScoreCalculator.swift    Scoring and combo rules
│   ├── PieceGenerator.swift     Seedable RNG and fair, difficulty-weighted dealing
│   └── GameState.swift          Codable game state and move handling
├── Features/
│   ├── Home/                    Animated title, best score, Play/Continue
│   ├── Game/                    Game screen, board canvas, tray, drag and drop, floating text, pause sheet
│   ├── GameOver/                Result card with confetti
│   ├── Settings/                Sound, haptics, theme, palette, reset stats
│   └── Stats/                   Stat cards and Swift Charts history
├── DesignSystem/
│   ├── Palette.swift            Block palettes, theme colors, rounded fonts
│   ├── BlockRenderer.swift      Glossy block drawing shared by board, tray and effects
│   ├── Components.swift         Buttons, animated background, confetti, stat card
│   └── SoundEngine.swift        AVAudioEngine synthesized sound effects
└── Persistence/
    ├── Models.swift             SwiftData models: PlayerProfile, GameRecord
    └── SavedGameStore.swift     JSON save file for the in-progress game
BlockfallTests/
└── EngineTests.swift            Swift Testing suites for the engine and saved games
Screenshots/                     Images used in this README

Architecture

Blockfall uses MVVM with a pure engine.

┌──────────────┐   gestures / intents   ┌─────────────────┐   place(slot:row:col:)   ┌─────────────┐
│ SwiftUI views│ ─────────────────────▶ │  GameViewModel  │ ───────────────────────▶ │  GameState  │
│  (Features)  │ ◀───────────────────── │  (@Observable)  │ ◀─────────────────────── │  (engine)   │
└──────────────┘   observed properties  └─────────────────┘        MoveOutcome        └─────────────┘
                                                 │
                                                 ▼
                                     AppModel → SwiftData / SavedGameStore
  • Engine (Engine/). Value types with no UI imports. GameState.place(slot:row:col:) validates a move, updates the board, scores it, deals a new tray when needed, and returns a MoveOutcome describing everything that happened: cleared cells, points breakdown, combo, perfect clear and game over. The whole state is Codable, including the random number generator, so a restored game deals exactly the pieces it would have dealt.
  • View model (GameViewModel). An @Observable class that owns the GameState, drag state (dragged slot, translation, ghost position, highlighted lines), effect timestamps, floating text, and counters that drive .sensoryFeedback. Layout measurements such as the board frame are @ObservationIgnored so geometry updates don't trigger re-renders.
  • App model (AppModel). Handles navigation between Home and Game, the PlayerProfile (settings and best score), recording finished games, and saving and restoring the in-progress game.
  • Persistence. SwiftData stores the profile and one GameRecord per finished game. The in-progress game is saved as JSON in Application Support, so it's easy to inspect and test.

Game rules in detail

Pieces

The catalog has 41 fixed shapes. Every orientation is a separate entry, because pieces never rotate:

Group Shapes
Single and lines 1×1; 1×2 through 1×5, horizontal and vertical
Squares and rectangles 2×2, 3×3, 2×3, 3×2
Small L/J (3 cells) 4 orientations
L and J (4 cells) 4 orientations each
T 4 orientations
S and Z Horizontal and vertical each
3×3 corners (5 cells) 4 orientations
Diagonals 2-cell and 3-cell, both directions

Scoring

Event Points
Each cell placed +1
Line clear 10 × lines² (1 line = 10, 2 = 40, 3 = 90, 4 = 160)
Combo multiplier Clear points × (1 + combo × 0.5)
Perfect clear (board left empty) +300

Combo rules:

  • Every move that clears at least one line adds 1 to the combo counter.
  • The multiplier uses the combo value from before the current move, so a first clear scores the base amount. "Combo xN" appears from the second clear in a streak onward.
  • Three placements in a row without a clear reset the combo to 0. A clear within those three moves keeps the streak alive.

Example: two lines on the third clear of a streak score 10 × 2² × (1 + 2 × 0.5) = 80.

Dealing

  • Pieces that currently fit somewhere are weighted 3×, and pieces that don't fit are weighted 0.25×.
  • Guarantee: if none of the three dealt pieces fits but any piece in the catalog does, one slot is replaced with a fitting piece.
  • Difficulty: as the score rises toward 6,000, small pieces become slightly less likely and large or awkward pieces (5-cell shapes, 3×3 squares, 3-cell diagonals) become more likely.
  • The generator uses a seedable SplitMix64 RNG, so tests are reproducible.

Game over

When none of the remaining tray pieces fits, the game shows a short "No more space" banner, records the result, and then shows the Game Over card. If you beat your previous best, the card adds a New Best! badge and confetti.

Visual effects

These frames come from a screen recording of a single move. Dropping a 1×2 piece completes the bottom row and the right column together, which leaves the board empty:

Dragging Placed +80ms +200ms +320ms
Lines about to clear are highlighted in the piece's color The piece pops into place "Perfect!" and "+342" appear; particles start in the middle Cells shrink outward from the center of each line The outermost cells finish; particles scatter
  • Placement pop: a damped spring scale on the newly placed cells.
  • Line clear: each cell's delay depends on its distance from the center of its line. Cells shrink and fade, then break into particles in their own color.
  • Floating text: "+score", "Combo xN", "Great!" (2 lines), "Excellent!" (3+ lines), "Perfect!" and "New Best!". Combo text scales, bounces and wobbles using KeyframeAnimator. Text size scales with the board, adjusted by the user's Dynamic Type setting within a capped range.
  • Score: the score rolls up digit by digit with .contentTransition(.numericText()).
  • Tray: each new set of pieces springs in one after another.
  • Background: a slowly drifting MeshGradient on iOS 18+.

Haptics and sound

Event Haptic Sound
Pick up a piece Light impact Short rising blip
Place a piece Medium impact Soft thud
Clear lines Rigid impact Three-note arpeggio
Combo Success + heavy impact Faster four-note arpeggio
Invalid drop / game over Error Low double tone / descending phrase

Sound effects are synthesized with AVAudioEngine when the app launches. The audio session uses the .ambient category, so the silent switch mutes the game and it doesn't interrupt music. Sound and haptics can be turned off in Settings or from the pause sheet.

Accessibility

  • Dynamic Type: all UI text uses SF Pro Rounded text styles and scales with the user's setting. At the largest accessibility sizes, layouts adapt: buttons and stat cards stack vertically, long screens scroll, and the palette swatches move below their names. The in-game top bar and the logo stop growing at a fixed size so they don't wrap or clip.
  • VoiceOver: each board cell has a label like "Row 3, column 5, empty" or "Row 8, column 2, filled, blue". Tray pieces describe their color, size and whether they fit. A double-tap places a piece in the first open spot, so the game is playable without dragging.
  • Reduce Motion: turns off the placement pop, particles, floating-text bounce and rise, title animation, mesh drift and confetti. Clears use a simple fade instead.
  • High Contrast palette: strongly separated hues and brightness levels, selectable in Settings.

Devices and layout

The board is sized from the available space: up to 520pt on iPhone and 760pt on iPad, centered, with the tray scaled to match. iPhone is portrait only, while iPad supports all orientations.

iPhone SE (iOS 18) iPad Pro 13" iPad mini

Testing

The engine is covered by Swift Testing (@Test) suites in BlockfallTests/EngineTests.swift:

Suite What it checks
Catalog Unique ids and shapes, normalized cell offsets, dimensions
Placement Valid and invalid positions at every edge and corner, overlaps, diagonal gaps, fitsAnywhere
Clearing Single rows, simultaneous row + column crosses, multiple rows and columns at once, and a 500-board randomized check that the drag highlight matches the actual clear
Scoring The 10 × lines² formula, combo multipliers, the combo reset after three placements without a clear, the perfect-clear bonus
Game state New tray after all three pieces are placed, game-over detection, game over reported by a move
Generator Same seed gives the same deals; at least one piece fits in each of 10,000 seeded deals on random boards; difficulty weighting; colors and unique ids
Saved games Encoding and decoding round-trips, including the RNG continuing identically; corrupt boards are rejected; the store saves, loads and clears, including in a directory whose name contains spaces

Drag and drop, line clears and the effects above were also tested by hand in the iOS Simulator.

Debug demo scenarios

Debug builds accept a -demo launch argument that sets up a specific scene. Set it under Edit Scheme › Run › Arguments, or pass it with xcrun simctl launch:

Argument Scene
-demo combo A board mid-combo that automatically plays a two-row clear ("Combo x3", "Great!")
-demo gameover A full board with no moves left, ending on a New Best with confetti
-demo clear A board one move from a row-and-column perfect clear, for testing by hand

Demo games never touch your real data: they don't record Stats, change the best score, or overwrite a saved game. They're compiled out of Release builds.

Adding a new piece shape

Add one line to Piece.catalog in Blockfall/Engine/Piece.swift:

Piece(id: "plus", pattern: [".X.", "XXX", ".X."]),
  • X marks a filled cell and . an empty one. The shape is normalized automatically, so leading empty rows or columns don't matter.
  • Pieces never rotate. Add each orientation you want as its own entry with a unique id.
  • The difficulty tier comes from the cell count: 3 or fewer cells → tier 0, 4 → tier 1, 5 or more → tier 2. Pass tier: to override it for awkward shapes, as the diagonals do.
  • Run the tests. The catalog suite checks that ids and shapes are unique, and the generator suite re-checks the fairness guarantee with the new shape included.
  • Saved games store full piece data, so existing saves keep working.

Performance notes

  • The board is drawn in a single Canvas. A TimelineView drives redraws only while a placement or clear effect is running, so the board is static the rest of the time.
  • Drag updates don't allocate memory. Placement checks use index math on a flat 64-cell array, and line highlights use row and column bitmasks (LineMask).
  • Game-over detection is O(pieces × 64 × cells).

About

BlockBlast inspired game for iOS

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages