# Falling Tetromino Engine

[![Crates.io](https://img.shields.io/crates/v/falling-tetromino-engine.svg)](https://crates.io/crates/falling-tetromino-engine)
[![Documentation](https://docs.rs/falling-tetromino-engine/badge.svg)](https://docs.rs/falling-tetromino-engine)
[![License](https://img.shields.io/crates/l/falling-tetromino-engine)](https://github.com/Strophox/falling-tetromino-engine#license)

A backend with ergonomic API for games where tetrominos fall and stack.

## Installation

Add this to your Cargo.toml:

```toml
[dependencies]
falling-tetromino-engine = "1.2.0"
```


# About

The engine is completely frontend-agnostic, although calls to `Game::update` may optionally return additional info to facilitate implementation of visual effects.

The engine allows for compile-time mods which may arbitrarily access and modify game state during gameplay.

The engine aims to compete on the order of modern tetromino stackers;
It incorporates many features found in such games.
Experienced players may be familiar with most of the following mechanics:
- **Variable gravity/fall delay** (frame-agnostic); '20G' (= 0s fall delay),
- Simple but flexible programming of **custom fall and lock delay progressions** (`DelayParameters`),
- (Arbitrary) **piece preview**,
- **Pre-spawn actions** toggle ('Initial Hold/Rotation System'),
- **Rotation systems**: 'Ocular' (engine-specific, playtested), 'Classic', 'Super',
- **Tetromino generators**: 'Uniform', 'Stock' (generalized Bag), 'Recency' (history), 'Balancerelative',
- **Spawn delay** (ARE),
- **Delayed auto-shift** (DAS),
- **Auto-repeat rate** (ARR),
- **Soft drop factor** (SDF),
- **Lenient lock delay reset** toggle (reset lock delay even if rotate/move fails),
- **Ensure move delay less than lock delay** toggle (DAS/ARR automatically shortened when lock delay is very low),
- **Lock-reset-cap factor** (~maximum time before lock delay cannot be reset),
- **Line clear duration** (LCD),
- Custom **win/loss conditions based on stats**: time, pieces, lines, score,
- **Hold** piece,
- Higher **score** for larger lineclears and spins ('allspin')
- Game **reproducibility** (PRNG),
- Available player actions: MoveLeft, MoveRight; RotateLeft, RotateRight, Rotate180; DropSoft, DropHard, TeleDown ('Sonic drop'), TeleLeft, TeleRight, HoldPiece.


# Example Usage

```rust
use falling_tetromino_engine::*;

// Starting up a game - note that in-game time starts at 0s.
let mut game = Game::builder()
    .seed(1234)
    /* ...Further optional configuration possible... */
    .build();

// Updating the game with the info that 'left' should be pressed at second 4.2;
// If a piece is in the game, it will try to move left.
let input = Input::Activate(Button::MoveLeft);
game.update(InGameTime::from_secs(4.2), Some(input));

// ...

// Updating the game with the info that no input change has occurred up to second 6.79;
// This updates the game, e.g., pieces fall and lock.
game.update(InGameTime::from_secs(6.79), None);

// Read most recent game state;
// This is how a UI can know how to render the board, etc.
let State { board, .. } = game.state();
```

Internally, the game keeps its own timeline:
```
    0s - Game start; Piece is spawned.
   /        4.2s - Player input; Piece starts moving left.
  /        /            6.79s - Piece will have moved left some more,
 /        /            /        has been affected by gravity etc.
[--------|------------¦--------------- - - - ->
```


# Overview by Types

Much of the implementation is tightly encoded into types.

```rust
// The central engine type.
struct Game {
    config: Configuration, // Can be safely modified during play.
    state_init: StateInitialization, // For reproducibility.
    state: State,
    phase: Phase, // Macro-state-machine.
    modifiers: Vec<Modifier>, // Modding facility.
}
```

It may be instructive to simply consider the main type definitions as a tentative overview of the deeper engine mechanics.

### Types used as `Game` fields

```rust
struct Configuration {
    piece_preview_count: usize,
    allow_initial_actions: bool,
    rotation_system: RotationSystem,
    spawn_delay: Duration,
    delayed_auto_shift: Duration,
    auto_repeat_rate: Duration,
    fall_delay_params: DelayParameters,
    soft_drop_factor: ExtNonNegF64,
    lock_delay_params: DelayParameters,
    ensure_move_delay_lt_lock_delay: bool,
    allow_lenient_lock_reset: bool,
    lock_reset_cap_factor: ExtNonNegF64,
    line_clear_duration: Duration,
    update_delays_every_n_lineclears: u32,
    game_limits: Vec<(Stat, bool)>,
    notification_level: NotificationLevel,
}

struct StateInitialization {
    seed: u64,
    tetromino_generator: TetrominoGenerator,
}

struct State {
    time: InGameTime,
    buttons_pressed: [Option<InGameTime>; Button::VARIANTS.len()],
    rng: GameRng,
    piece_generator: TetrominoGenerator,
    piece_preview: VecDeque<Tetromino>,
    piece_held: Option<(Tetromino, bool)>,
    board: [[Option<TileTypeID>; Game::WIDTH]; Game::HEIGHT],
    fall_delay: ExtDuration,
    fall_delay_lowerbound_hit_at_n_lineclears: Option<u32>,
    lock_delay: ExtDuration,
    pieces_locked: [u32; Tetromino::VARIANTS.len()],
    lineclears: u32,
    consecutive_line_clears: u32,
    score: u32,
}

enum Phase {
    Spawning { spawn_time: InGameTime },
    PieceInPlay {
        piece: Piece,
        auto_move_scheduled: Option<InGameTime>,
        fall_or_lock_time: InGameTime,
        lock_time_cap: InGameTime,
        lowest_y: isize,
    },
    LinesClearing { clears_finish_time: InGameTime },
    GameEnd { cause: GameEndCause, is_win: bool },
}

struct Modifier {
    descriptor: String,
    mod_function: Box<GameModFn>,
}
```

### Other Important Types

```rust
enum Tetromino {
    O, I, S, Z, T, L, J
}

struct Piece {
    tetromino: Tetromino,
    orientation: Orientation,
    position: Coord,
}

enum Button {
    MoveLeft, MoveRight,
    RotateLeft, RotateRight, Rotate180,
    DropSoft, DropHard,
    TeleLeft, TeleRight, TeleDown, 
    HoldPiece,
}

struct DelayParameters {
    base_delay: ExtDuration,
    factor: ExtNonNegF64,
    subtrahend: ExtDuration,
    lowerbound: ExtDuration,
}
```

See [full documentation](https://docs.rs/falling-tetromino-engine).
