# Simulating and Solving Number Merge Puzzles with mergeGridR

    library(mergeGridR)

## Overview

`mergeGridR` provides a generic falling-block number merge puzzle as a Shiny interface and as a deterministic R engine. The Shiny app is the interactive interface; the R functions are useful for tests, reproducible play, autoplay experiments, and benchmarking.

Launch the app with:

    run_drop_number()

For a browser-only copy that does not need Shiny or a live R session, export the standalone HTML app:

    out <- file.path(tempdir(), "mergeGridR-static.html")
    export_static_app(out, overwrite = TRUE)

Use the Shiny app when you want the Rcpp-backed package engine, R objects, benchmarking, and package-level workflows. Use the static HTML app when you want a single local file for play or sharing on a machine with a modern WebGL-capable browser. Static-app high scores are browser-local and separated by preview horizon.

## Puzzle Rules

The default board has five columns and seven rows. Row `1` is the bottom row. The preview horizon defaults to one tile, so only the next provided tile is shown unless `game_config(next_count = ...)` selects a larger horizon. On each move, the next provided tile is dropped into a selected column and lands in the lowest open row.

After landing, the engine checks the active tile. Any equal-value orthogonally connected component that includes the active tile merges when its size is at least two. If the active tile is stable, the engine then scans the board from bottom to top and left to right for by-product equal-value components created by gravity or earlier cascade steps. Those components also merge, one at a time, until the board is stable. The created value is:

    #>   component_size multiplier
    #> 1              2          2
    #> 2              3          4
    #> 3              4          8
    #> 4              5         16

For example, three `64` tiles merge into `256`, because `64 * 2^(3 - 1) = 256`. The score increases by the created tile value for every merge. Gravity is applied after each merge, and cascades repeat from the newly created active tile before the next board-wide by-product scan.

A game is over when no column has an open top cell. New provided tiles are drawn from powers of two between `2` and the minimum of the largest tile observed so far and `max_spawn_value`, with a default cap of `256`. The spawn distribution can be weighted toward lower allowed values, weighted toward higher allowed values, or uniform.

By default, a game has three manual continues. A continue is available only after game over, clears the top three rows, and does not change score, move count, queue, or random-number state. The largest observed tile is preserved for future spawn eligibility even if that tile was cleared.

In the Shiny app, the preview horizon can be changed before play starts. If it is changed after the first move, the current game keeps its active horizon and the selected value applies on restart.

## Programmatic Play

Create a reproducible game with `new_game()`:

    state <- new_game(seed = 42)
    state$board
    #>      [,1] [,2] [,3] [,4] [,5]
    #> [1,]    0    0    0    0    0
    #> [2,]    0    0    0    0    0
    #> [3,]    0    0    0    0    0
    #> [4,]    0    0    0    0    0
    #> [5,]    0    0    0    0    0
    #> [6,]    0    0    0    0    0
    #> [7,]    0    0    0    0    0
    state$next_tiles
    #> [1] 2

Use `game_config(next_count = ...)` to create a game with a longer preview:

    three_preview <- new_game(game_config(next_count = 3), seed = 42)
    three_preview$next_tiles
    #> [1] 2 2 2

Drop the next tile into a one-based column:

    state <- drop_tile(state, column = 3)
    state$board
    #>      [,1] [,2] [,3] [,4] [,5]
    #> [1,]    0    0    2    0    0
    #> [2,]    0    0    0    0    0
    #> [3,]    0    0    0    0    0
    #> [4,]    0    0    0    0    0
    #> [5,]    0    0    0    0    0
    #> [6,]    0    0    0    0    0
    #> [7,]    0    0    0    0    0
    state$score
    #> [1] 0
    state$last_drop
    #> $column
    #> [1] 3
    #>
    #> $row
    #> [1] 1
    #>
    #> $value
    #> [1] 2
    #>
    #> $final_value
    #> [1] 2
    #>
    #> $merges
    #> [1] 0
    #>
    #> $created
    #> integer(0)
    #>
    #> $component_sizes
    #> integer(0)

Continue a game-over state with `continue_game()`:

    state <- continue_game(state)
    state$continues_remaining

You can also construct small states for reproducible rule checks. This example creates a three-tile component of `2`s:

    state <- new_game(seed = 1)
    state$board[1, 1] <- 2L
    state$board[1, 2] <- 2L
    state$next_tiles <- c(2L, 2L, 2L)

    merged <- drop_tile(state, column = 1)
    merged$board
    #>      [,1] [,2] [,3] [,4] [,5]
    #> [1,]    8    0    0    0    0
    #> [2,]    0    0    0    0    0
    #> [3,]    0    0    0    0    0
    #> [4,]    0    0    0    0    0
    #> [5,]    0    0    0    0    0
    #> [6,]    0    0    0    0    0
    #> [7,]    0    0    0    0    0
    merged$score
    #> [1] 8
    merged$last_drop[c("created", "component_sizes")]
    #> $created
    #> [1] 8
    #>
    #> $component_sizes
    #> [1] 3

## Autoplay Strategies

`autoplay_move()` evaluates legal columns and returns the recommended move without mutating the state.

    move <- autoplay_move(
      merged,
      strategy = "growth_lookahead",
      depth = 2,
      beam_width = 5,
      seed = 10
    )

    move[c("column", "strategy", "score_estimate")]
    #> $column
    #> [1] 2
    #>
    #> $strategy
    #> [1] "growth_lookahead"
    #>
    #> $score_estimate
    #> [1] 250.1
    head(move$candidates)
    #>   column score_estimate score_gain heuristic depth simulations growth_bonus
    #> 2      2          250.1          0     170.1     2          NA            0
    #> 3      3          250.1          0     167.1     2          NA            0
    #> 4      4          248.6          0     167.1     2          NA            0
    #> 5      5          248.6          0     168.6     2          NA            0
    #> 1      1          245.6          0     165.6     2          NA            0

The available strategies are:

- `greedy`: rank immediate outcomes using score gain and board-quality features.
- `lookahead`: depth-limited beam search using the base board-quality scorer.
- `growth_lookahead`: lookahead with an extra reward for three-or-more tile merges, especially when they create larger tiles.
- `monte_carlo`: average seeded rollout outcomes for each first move.

To play a full game automatically:

    game <- autoplay_game(
      strategy = "growth_lookahead",
      max_moves = 1000,
      depth = 3,
      beam_width = 10,
      seed = 1
    )

    game$final_state$score
    game$history

## Benchmarking

`benchmark_autoplay_strategies()` runs repeated seeded games for a settings grid and returns both per-game runs and aggregate summaries.

    bench <- benchmark_autoplay_strategies(
      n_games = 10,
      max_moves = 200,
      settings = autoplay_benchmark_settings("fast"),
      seed = 20260609,
      workers = 1
    )

    bench$summary

Set `workers > 1` to use base R PSOCK parallel workers. Parallel mode requires the package to be installed in the library paths visible to worker processes.

## Local High Score

The Shiny app persists the best score locally under `tools::R_user_dir("mergeGridR", "cache")`. Scores are separated by preview horizon, so a one-tile preview and a three-tile preview do not share the same best score.

    get_high_score()
    get_high_score(preview_horizon = 3)

Call `reset_high_score()` explicitly when you want to clear a local score file.