# Arrows Escape

A 500-level chain-lock puzzle PWA. Each grid contains several bent
arrow-shaped pieces, but at any moment EXACTLY ONE is extractable —
tap it to slide it out in the direction its arrowhead points, which
unlocks exactly the next piece in a fixed sequence. Work through the
entire chain to free the green target arrow, which always comes last.
No guessing which piece to try: only one is ever an option.

## Deployment

Upload the entire `arrowpuzzle/` folder to:

```
public_html/arrowpuzzle/
```

Live at:

```
https://gamesji.com/arrowpuzzle/
```

Visiting `gamesji.com/arrowpuzzle` (no trailing slash, no `index.html`)
works automatically — the included `.htaccess` guarantees `index.html`
is served as the default document.

**Also upload `ads.txt` separately to your domain root**
(`public_html/ads.txt`, so it's reachable at `gamesji.com/ads.txt`) —
not inside the `arrowpuzzle/` folder. See "Google AdSense Setup" below.

No build step required — plain HTML/CSS/JS.

## File Structure

```
/ArrowsEscape
  index.html
  manifest.json
  service-worker.js
  robots.txt
  sitemap.xml
  levels.json          <- 100 generated puzzles
  generate_levels.py   <- regenerate levels.json if needed
  css/
    style.css
  js/
    config.js   <- ALL tunable settings (lives, coins, ads, colors, hint cost...)
    storage.js  <- localStorage persistence layer
    levels.js   <- loads/serves levels.json
    game.js     <- core game engine (piece rendering, tap-to-extract, audio, popups)
  assets/
    icons/      <- PWA icons (generated placeholder arrow mark)
    sounds/     <- EMPTY: drop click.mp3, success.mp3, fail.mp3, coin.mp3 here
```

## Game Mechanic & Level Data

Each level's `pieces` array holds arrow-shaped polylines:

```json
{
  "id": 0,
  "cells": [[r,c], [r,c], ...],  // tail-to-head order
  "headDir": "N" | "S" | "E" | "W"
}
```

A piece can be extracted (tap to remove) only if every cell from its
head to the board edge, in `headDir`, is currently empty of other
pieces. Unlike a looser "any valid order" puzzle, this generator
enforces a **strict chain-lock**: at every point in the game, exactly
one piece is extractable. `solutionOrder` is the only valid clearing
sequence — piece 0 unlocks first, extracting it frees exactly piece 1,
and so on, until the final id (`targetId`, always green) unlocks last.
The Hint button highlights whichever single piece is currently free.

Levels are generated by placing pieces in **reverse chain order**: the
target is placed first with a long exit lane, then each predecessor is
grown specifically to cross the exit lane of the piece placed just
before it — while explicitly steering clear of every other already-
placed piece's lane, so no piece can accidentally free up out of turn.
A full simulation (`simulate_chain_is_strict`) re-verifies, for every
generated level, that exactly one piece is free at every step before
it's accepted; this hard guarantee is why piece counts are capped at
6-8 per level regardless of grid size (see Difficulty Tiers below).

## Difficulty Tiers

| Levels    | Difficulty | Grid   | Pieces |
|-----------|-----------|--------|--------|
| 1-100     | Easy      | 12x9   | 6      |
| 101-200   | Medium    | 14x10  | 7      |
| 201-300   | Hard      | 16x11  | 7      |
| 301-400   | Expert    | 18x12  | 8      |
| 401-500   | Master    | 20x13  | 8      |

Piece count is capped at 6-8 across all tiers — this is a structural
reliability limit of the strict chain-lock construction (every piece's
exit lane is reserved for exactly one predecessor, with no accidental
crossings allowed). Difficulty instead scales via larger grids (more
complex, longer paths to visually trace) and longer individual pieces.

## Audio

Synthesized Web Audio tones by default (no files needed). To use real
mp3s: drop them into `assets/sounds/`, set `useSynthesizedTones: false`
in `js/config.js`, and wire `GAME_CONFIG.audioFiles` into the `Audio_`
object in `js/game.js`.

## Admin Config

Edit `js/config.js` — lives, coin rewards, hint cost, ad toggles,
theme colors, total level count — without touching game logic.

## Google AdSense Setup

Ads are wired up but inactive until you add your real Publisher ID.
Once you have one from your AdSense account (format `ca-pub-XXXXXXXXXXXXXXXX`),
update these 4 spots:

1. **`index.html`** — two places in `<head>`:
   - `<meta name="google-adsense-account" content="ca-pub-...">`
   - the `adsbygoogle.js?client=ca-pub-...` script `src`
2. **`index.html`** — the two `<ins class="adsbygoogle">` ad units
   (top and bottom banners): set `data-ad-client="ca-pub-..."` and
   `data-ad-slot="..."` (the slot ID comes from AdSense > Ads > By ad
   unit, after you create a Display ad unit there).
3. **`ads.txt`** — replace `pub-0000000000000000` with your ID
   (without the `ca-` prefix). **This file must be uploaded to your
   domain ROOT** (`gamesji.com/ads.txt`), not inside `/arrowpuzzle/` —
   AdSense checks the root regardless of which subfolder serves ads.
4. **`js/config.js`** — set `adsEnabled: true` to turn the banners on
   (they stay hidden while this is `false`, regardless of the other
   steps above).

## Regenerating Levels

```
python3 generate_levels.py
```

Fixed random seed for reproducibility. Edit grid size / piece count /
piece length per difficulty tier inside the script to change puzzle
characteristics.

## PWA

`manifest.json` + `service-worker.js` provide installability and
offline play. Service worker uses network-first for JS/HTML/JSON so
future updates show immediately, falling back to cache when offline.
