tetris_engine 1.3.0 copy "tetris_engine: ^1.3.0" to clipboard
tetris_engine: ^1.3.0 copied to clipboard

A complete, customizable Tetris engine for Flutter: Guideline rules (SRS, 7-bag, hold, lock delay, T-Spins), themes, touch and keyboard controls, replays and save/restore.

tetris_engine #

pub package CI License: MIT

A complete, reusable Tetris engine for Flutter. It has no backend and no dependencies beyond Flutter, and runs on Android, iOS, Web, Windows, Linux and macOS.

Screenshot

Features #

  • Tetris Guideline rules: 7-bag randomizer, SRS rotation with wall kicks, hold, ghost piece, next queue and lock delay with move reset
  • Scoring: singles to Tetrises, T-Spins (full and mini), back-to-back, combos, perfect clears, soft and hard drop points
  • Levels and gravity: configurable start level, lines per level and gravity curve
  • Widgets: board renderer, next and hold previews, score, level and lines panels, statistics panel, pause and game-over overlays, on-screen control pad
  • Input: keyboard with remappable keys (desktop/web), swipe gestures and auto-repeating touch buttons (mobile)
  • Events stream for sound effects, haptics and animations
  • Themes: light, dark and colorblind-safe themes, all customizable, plus per-level color palettes that change as the player levels up
  • Seeded games and exact replays: record a game as JSON and play it back move for move
  • Save and restore: serialize the full game state to any storage
  • Statistics: games, scores, lines, levels, play time and piece usage

Installation #

dependencies:
  tetris_engine: ^1.3.0

Quick start #

import 'package:tetris_engine/tetris_engine.dart';

final game = TetrisGame()..start();

// In your widget tree:
TetrisBoard(
  game: game,
  theme: darkTetrisTheme,
  onGameOver: () => print('Game over! Score: ${game.state.scoreState.score}'),
)

Remember to call game.dispose() when you're done with it.

Layout example #

Column(
  children: [
    Expanded(
      child: Row(
        children: [
          Column(
            children: [
              HoldPiecePreview(game: game),
              ScorePanel(game: game),
              LevelPanel(game: game),
              LinesClearedPanel(game: game),
            ],
          ),
          SizedBox(width: 200, child: TetrisBoard(game: game)),
          NextPiecePreview(game: game, count: 4),
        ],
      ),
    ),
    TetrisControlPad(game: game), // on-screen buttons for touch devices
  ],
)

Game options #

TetrisGame(
  boardRows: 20,
  boardCols: 10,
  nextQueueSize: 5,
  startLevel: 1,
  linesPerLevel: 10,
  lockDelay: const Duration(milliseconds: 500), // Duration.zero = classic
  maxLockResets: 15,
  gravityCurve: (level) => Duration(milliseconds: 800 ~/ level),
  seed: 42,                // same seed → same pieces (e.g. daily challenge)
  statistics: myStats,     // share lifetime stats between games
)

Sound effects, haptics and animations #

Every action is reported on game.events:

game.events.listen((event) {
  switch (event.type) {
    case TetrisEventType.hardDropped:
      HapticFeedback.mediumImpact();
    case TetrisEventType.linesCleared:
      if (event.tSpin != TSpinType.none) playSound('tspin');
      else if (event.lines == 4) playSound('tetris');
      else playSound('clear');
      if (event.perfectClear) showBanner('PERFECT CLEAR');
    case TetrisEventType.levelUp:
      showBanner('Level ${event.level}');
    default:
      break;
  }
});

The simple callbacks onScoreChanged, onLevelUp, onLinesCleared and onGameOver are still available.

Themes #

// Built-in themes
TetrisBoard(game: game, theme: defaultTetrisTheme);    // light
TetrisBoard(game: game, theme: darkTetrisTheme);       // dark
TetrisBoard(game: game, theme: colorblindTetrisTheme); // Wong palette

// Custom theme
final myTheme = darkTetrisTheme.copyWith(
  boardBackground: Colors.black,
  tetrominoColors: TetrominoColors(
    fill: {TetrominoType.I: Colors.cyan, /* ... */},
    border: {TetrominoType.I: Colors.blue, /* ... */},
  ),
);

Level colors #

As in classic Tetris, colors can change every level. LevelThemes applies a palette per level on top of a base theme (ten built-in palettes that cycle), and LevelThemeBuilder rebuilds only when the level's theme changes:

final levelThemes = LevelThemes(base: darkTetrisTheme);

LevelThemeBuilder(
  game: game,
  levelThemes: levelThemes,
  builder: (context, theme, _) => Column(
    children: [
      LevelPanel(game: game, theme: theme),
      Expanded(child: TetrisBoard(game: game, theme: theme)),
    ],
  ),
);

// Change palette every 3 levels, or supply your own palettes
LevelThemes(
  base: darkTetrisTheme,
  levelsPerPalette: 3,
  palettes: [
    LevelPalette.fromFills({for (final t in TetrominoType.values) t: Colors.cyan}),
    LevelPalette.fromFills({for (final t in TetrominoType.values) t: Colors.pink}),
  ],
);

With a light base theme only the piece colors change by default; pass recolorBoard: true to also use the palettes' dark board colors.

Levels advance every linesPerLevel cleared lines (default 10) and gravity speeds up with each level.

Replays #

Attach a recorder to the game. It captures every input, gravity step and lock together with the game's seed, so playback reproduces the game exactly.

// Record
final recorder = ReplayRecorder();
final game = TetrisGame(recorder: recorder)..start();
// ... play ...
final json = recorder.exportJson();

// Replay onto any game
final saved = ReplayRecorder()..importJson(json);
ReplayPlayer(InputController(replayGame)).play(
  saved.frames,
  seed: saved.seed,
  onComplete: () => print('Replay finished'),
);

Saving state #

// Serialize
prefs.setString('tetris_save', jsonEncode(game.toJson()));

// Restore (comes back paused, so call resume() to continue)
game.loadFromJson(jsonDecode(prefs.getString('tetris_save')!));
game.resume();

Controls #

Keyboard (desktop and web) #

Key Action
← / A Move left
→ / D Move right
↓ / S Soft drop
↑ / W / X Rotate clockwise
Z Rotate counter-clockwise
Space Hard drop
C / Left Shift Hold
P / Esc Pause

Remap keys with keyMap:

TetrisBoard(
  game: game,
  keyMap: {
    ...TetrisKeyboardHandler.defaultKeyMap,
    LogicalKeyboardKey.keyK: 'hardDrop',
  },
)

Touch #

On TetrisBoard itself:

  • Tap: rotate clockwise
  • Swipe left/right: move
  • Swipe down: soft drop
  • Fast swipe down: hard drop
  • Long press: hold

TetrisControlPad adds on-screen buttons. Holding move or soft drop repeats the action (tune it with repeatDelay and repeatInterval).

TetrisBoard pauses the game automatically when the app goes to the background. Set pauseOnBackground: false to turn this off.

Custom game loops #

Turn off the internal timers to step the game yourself, for example from a fixed-step loop, an AI or a test:

final game = TetrisGame()..useInternalClock = false;
game.start();
game.applyGravity();      // one gravity step
game.lockActivePiece();   // lock if the piece is resting on the stack

Architecture #

TetrisGame (ChangeNotifier, events stream)
├── CollisionSystem    — collision and ghost piece
├── RotationSystem     — SRS wall kicks, T-Spin detection
├── BoardManager       — lock and line clear
├── PieceManager       — seeded 7-bag randomizer
├── ScoringSystem      — Guideline scoring
└── LevelSystem        — progression

Widgets
├── TetrisBoard        — CustomPainter renderer + keyboard/gesture input
├── TetrisControlPad   — on-screen buttons
├── NextPiecePreview / HoldPiecePreview
├── ScorePanel / LevelPanel / LinesClearedPanel / StatisticsPanel
└── TetrisPauseOverlay / TetrisGameOverOverlay

Contributing #

Bug reports and pull requests are welcome. See CONTRIBUTING.md.

License #

MIT

2
likes
160
points
175
downloads
screenshot

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A complete, customizable Tetris engine for Flutter: Guideline rules (SRS, 7-bag, hold, lock delay, T-Spins), themes, touch and keyboard controls, replays and save/restore.

Repository (GitHub)
View/report issues
Contributing

Topics

#tetris #game #puzzle #game-engine #widget

License

MIT (license)

Dependencies

flutter

More

Packages that depend on tetris_engine