# Payas Omi — Documentation

> Sri Lankan trick-taking card game · Browser-based · Open Source  
> Developer: **Saman Wijesinghe** · License: MIT

---

## Table of Contents

1. [Project Overview](#1-project-overview)
2. [Quick Start](#2-quick-start)
3. [Game Rules](#3-game-rules)
4. [User Interface Guide](#4-user-interface-guide)
5. [Settings Reference](#5-settings-reference)
6. [Keyboard Shortcuts](#6-keyboard-shortcuts)
7. [Save & Load System](#7-save--load-system)
8. [AI Behaviour](#8-ai-behaviour)
9. [Architecture & Code Structure](#9-architecture--code-structure)
10. [CSS Design System](#10-css-design-system)
11. [JavaScript API Reference](#11-javascript-api-reference)
12. [Data Structures](#12-data-structures)
13. [Contributing](#13-contributing)
14. [License](#14-license)

---

## 1. Project Overview

**Payas Omi** is a fully client-side, single-file web implementation of Omi — a traditional Sri Lankan trick-taking card game. It requires no server, no build step, and no installation. Open `payas-omi.html` in any modern browser and play immediately.

### Key Facts

| Property | Value |
|---|---|
| File | `payas-omi.html` |
| Stack | HTML5 · CSS3 · Vanilla JS (ES2022) |
| Dependencies | Bootstrap 5.3 · Font Awesome 6.5 · Google Fonts |
| Storage | `localStorage` (auto-save) · `.md` file export/import |
| Players | 4 (1 human + 3 AI bots) |
| Teams | Us (South + North) vs Them (West + East) |
| Deck | 32 cards — A K Q J 10 9 8 7 per suit |
| Tricks per round | 8 |
| Scoring | Token-based (1 / 2 / draw) |
| Browser support | Chromium-based · Firefox |
| Theme | Light and Dark mode |
| Responsive | Desktop · Tablet · Mobile (portrait & landscape) |
| Fullscreen | Enter / exit via button or F11 |

### Design Language

Payas Omi follows the **Payas design system** shared across all Payas web tools (Reader, Editor, Player, Imager, FM, Days). It uses the same colour tokens, typography, and component patterns.

---

## 2. Quick Start

### Running the Game

No build or server needed. Just open the file:

```bash
# Option 1 — open directly
xdg-open payas-omi.html          # Linux
open payas-omi.html              # macOS
start payas-omi.html             # Windows

# Option 2 — serve locally (optional, avoids any file:// quirks)
npx serve .
python3 -m http.server 8080
```

Then navigate to the file or `http://localhost:8080/payas-omi.html`.

### First Game

1. Open `payas-omi.html` in your browser.
2. Click the **↺ (refresh)** button in the top-right corner to start a new game.
3. Each player is dealt **4 cards** first. The **Trump Maker** (player to the dealer's right) names the trump suit. If that's you, a suit-selection panel will appear.
4. After trump is named, each player receives **4 more cards** (8 total). Your hand appears at the bottom.
5. The Trump Maker leads the first trick. Click a card to play it. Follow the rules below.

---

## 3. Game Rules

### Overview

Omi is Sri Lanka's most popular trick-taking card game for **4 players** split into **2 partnerships**:

- **Us** — South (you) + North (AI partner)
- **Them** — West (AI) + East (AI)

A **32-card deck** is used (Ace, King, Queen, Jack, 10, 9, 8, 7 of each suit — the 2–6 are removed). Each player receives **8 cards** and plays **8 tricks** per round.

### Card Rankings

Within any suit, cards rank from highest to lowest:

```
A > K > Q > J > 10 > 9 > 8 > 7
```

### Round Flow

```
Deal 4  →  Trump Maker Names Trump  →  Deal 4 more  →  Play 8 Tricks  →  Score Tokens  →  Next Round
```

#### Step 1 — First Deal (4 Cards)

The deck is shuffled and **4 cards** dealt to each player. Your hand is shown face-up at the bottom; opponent hands are face-down.

#### Step 2 — Trump Maker Names Trump

The **Trump Maker** is the player to the **dealer's right**. They examine their 4 cards and choose a trump suit:

- If an **AI is the Trump Maker**, it automatically picks the suit it holds most cards of.
- If **you (South) are the Trump Maker**, a panel appears with four suit buttons — click to choose.

The chosen suit is announced and displayed in the score bar for the entire round.

> **Trump suit** — a designated suit that beats all other suits in trick resolution.

#### Step 3 — Second Deal (4 More Cards)

After trump is named, each player receives **4 more cards** to complete their 8-card hand. Cards are re-sorted with trump placed last (right side of your hand) for visual clarity.

#### Step 4 — Playing Tricks

- The **Trump Maker leads the first trick** (the player who named trump).
- Each player plays **one card** per trick, moving clockwise (South → West → North → East → South…).
- **Follow-suit rule**: if you hold a card of the led suit, you **must** play one. You may only play another suit (including trump) if you have no cards of the led suit.
- After all 4 players have played:
  - If any **trump cards** were played, the **highest trump** wins the trick.
  - Otherwise, the **highest card of the led suit** wins.
- The trick winner **leads the next trick**.

#### Step 5 — Scoring Tokens

After all 8 tricks are played, tokens are awarded:

| Result | Tokens Awarded |
|---|---|
| Winning team takes **5, 6, or 7 tricks** | **1 token** to the winning team |
| Winning team takes **all 8 tricks** *(Kapothi)* | **2 tokens** to the winning team |
| **4–4 draw** | **No tokens** — next round's winner earns a **bonus token** |

> **Ambaya** — if the Trump Maker's team fails to win at least 5 tricks, it is a notable loss. Scoring still proceeds normally (opponents receive their token), but the round is flagged as Ambaya in the history.

> **Draw bonus** — when a round ends 4–4, the bonus token carries over. The *next* round's winner earns +1 extra token on top of the normal 1 or 2.

#### Step 6 — End of Game

The first team to reach or exceed the **winning token target** (default: 10) wins the game. The dealer rotates clockwise each round.

---

## 4. User Interface Guide

### Titlebar

```
[ Payas Omi ]  [ Game ] [ Settings ] [ About ] [ License ]  [ 🌙 ] [ ⛶ ] [ ↺ ]
```

| Element | Action |
|---|---|
| **Payas Omi** | Brand logo (Fraunces serif font) |
| **Game** | Switch to the game board |
| **Settings** | Open settings panel |
| **About** | About the game and developer |
| **License** | MIT license text |
| **🌙 / ☀️** | Toggle dark / light mode (Ctrl+T) |
| **⛶ / ⊠** | Enter / exit fullscreen (F11) |
| **↺** | Start a new game (Ctrl+N) |

> **Mobile note** — on screens narrower than 576 px, navigation button labels are hidden and only icons are shown, keeping the titlebar compact.

### Score Bar (in-game)

Displayed at the top of the game board:

```
Us  3  |  Them  2  |  Trump ♥  |  Tricks: Us 5 – 3 Them  |  Round 3 · South's turn
```

| Element | Meaning |
|---|---|
| **Us / Them** | Cumulative **token** totals |
| **Trump** | Current round's trump suit (red for ♥♦, white for ♠♣) |
| **Tricks** | Live trick count for this round (out of 8) |
| **Status message** | Current phase / whose turn it is |

### Game Table

The table uses a **4-position grid layout**:

```
         [ North AI hand — face down ]
                       
[ West AI ]   [ Center trick area ]   [ East AI ]
                       
         [ Your hand — face up   ]
```

- **AI hands** — shown as face-down card backs (green with inner pattern)
- **Your hand** — face-up cards; playable cards are fully opaque, unplayable cards are dimmed (50% opacity + greyscale)
- **Trick area center** — circular chip showing how many tricks have been played
- **Trick slots** — one per player; cards land here when played
- **Active player** — name pill glows gold to show whose turn it is

### Card Anatomy

```
┌───────────┐
│ A         │   ← rank (top-left)
│ ♠         │   ← suit (below rank)
│           │
│     ♠     │   ← large centre suit
│           │
│         A │   ← rank (bottom-right, rotated 180°)
└───────────┘
```

- **Trump cards** glow with a gold border
- **Hovering** lifts the card and enlarges it (spring easing)
- **Unplayable cards** are faded and show a `not-allowed` cursor

### Round Result Overlay

Shown after all 8 tricks. Displays:

- Win / Lose / Draw / Kapothi result for the round
- Tricks won by each team (and Ambaya flag if applicable)
- Tokens gained this round
- Running token totals
- Full round history table (scrollable)

Buttons: **Save** (export `.md`) · **Next Round**

### Game Over Overlay

Shown when a team reaches the winning score. Displays final scores and a **New Game** button.

---

## 5. Settings Reference

Access via the **Settings** nav item.

### Appearance

| Setting | Options | Default | Description |
|---|---|---|---|
| Dark Mode | On / Off | Off | Toggles dark/light theme. Persisted to localStorage. |

### Game Rules

| Setting | Options | Default | Description |
|---|---|---|---|
| Winning Tokens | 5 / 10 / 15 | 10 | First team to reach this many tokens wins the game. |

Scoring is fixed by the real Omi rules: 1 token for 5–7 tricks, 2 tokens (Kapothi) for all 8, and a carry-over bonus for a 4–4 draw.

### Players

| Setting | Type | Default | Description |
|---|---|---|---|
| Your Name | Text (max 12 chars) | You | Displayed on your player name pill (South position). |
| AI Difficulty | Easy / Medium / Hard | Medium | Controls AI card-play strategy. See [AI Behaviour](#8-ai-behaviour). |
| Turn Timer | Off / 15s / 30s / 60s | 30s | (UI only — timer display not yet enforced in v1.0) |

### Data

| Action | Description |
|---|---|
| **Import Save** | Load a previously exported `.md` save file via file picker. Restores scores and round history. |
| **Export Save** | Download the current game state as a `.md` file. |
| **Reset Everything** | Clears all localStorage data and reloads the page. Requires confirmation. |

---

## 6. Keyboard Shortcuts

| Shortcut | Action |
|---|---|
| `Ctrl + N` | Start a new game |
| `Ctrl + T` | Toggle dark / light theme |
| `F11` | Toggle fullscreen |

---

## 7. Save & Load System

### Auto-Save

After every round ends, game state is automatically saved to `localStorage` under the key `omi-autosave`. On page load, if a save is found, scores and round history are restored and a "Resumed" message is shown.

**Auto-saved fields:**
- `teamScore` — cumulative team scores
- `roundHistory` — array of all past rounds
- `round` — current round number
- `dealer` — current dealer position

> Note: the current hand in progress is **not** auto-saved mid-round. Only completed round data persists.

### Manual Export (`.md` file)

Click **Save** in the round result overlay or **Export Save** in Settings. Downloads a human-readable Markdown file named:

```
payas-omi-save-round{N}.md
```

#### Save File Format

```markdown
---
title: Payas Omi Save
date: 2025-04-19T10:30:00.000Z
round: 5
scoreUs: 6
scoreThem: 4
winScore: 10
---

# Payas Omi Save File

## Scores
- Us: 6 tokens
- Them: 4 tokens

## Round History

| Round | Trump | By | Us Tricks | Them Tricks | Us +tok | Them +tok | Us Total | Them Total | Note |
|---|---|---|---|---|---|---|---|---|---|
| 1 | ♠ | West | 6 | 2 | 1 | 0 | 1 | 0 | |
| 2 | ♥ | North | 8 | 0 | 2 | 0 | 3 | 0 | Kapothi! |
| 3 | ♦ | South | 4 | 4 | 0 | 0 | 3 | 0 | Draw |
...

## Settings
\`\`\`json
{
  "winScore": 10,
  "playerName": "You",
  "aiDiff": "medium",
  "timer": 30,
  "dark": false
}
\`\`\`
```

### Manual Import

In Settings, click **Import Save** and select a `.md` file. The parser reads the YAML frontmatter (`round`, `scoreUs`, `scoreThem`, `winScore`) and restores those values. The current round resets so you start fresh from the restored score.

### Settings Persistence

All settings are stored separately in localStorage under `omi-settings` as a JSON object. They survive page reloads and are applied immediately on load.

---

## 8. AI Behaviour

All three AI players (North, West, East) share the same decision engine, parameterised by the **AI Difficulty** setting.

### Difficulty Levels

#### Easy
- Plays a **random legal card** on every turn.
- No strategic awareness.

#### Medium *(default)*
- **Leading a trick**: Plays the highest non-trump card available. Falls back to trump only if no non-trump cards remain.
- **Following**:
  - If the partner is currently winning the trick (and it is not the last card to play), plays the **lowest card** to conserve strong cards.
  - Otherwise, tries to **win the trick** by playing the lowest card that beats the current winner.
  - If unable to win, plays the **lowest card** to minimise loss.

#### Hard
- Same as Medium, plus:
- **Leading**: if holding more than one trump card, leads with the **highest trump** to draw out opponent trumps aggressively.
- Otherwise identical to Medium (further improvements reserved for future versions).

### AI Timing

AI moves are delayed by a random interval to simulate thinking:

- First move of a trick: `800ms`
- Subsequent moves: `700ms + random(0–400ms)`
- After trick resolution: `700ms + random(0–400ms)` before leading the next trick

---

## 9. Architecture & Code Structure

The entire application is a **single HTML file** (`payas-omi.html`) with no external JavaScript files or build process. Structure within the file:

```
payas-omi.html
├── <head>
│   ├── CDN links (Bootstrap 5.3, Font Awesome 6.5, Google Fonts)
│   └── <style> — all CSS (~480 lines)
│       ├── CSS custom properties (design tokens)
│       ├── Base / reset
│       ├── Titlebar
│       ├── Page layout
│       ├── Game page (table, score bar, zones)
│       ├── 3D card styles (play cards, trick cards, card backs)
│       ├── Overlay / modal styles
│       ├── Settings / About / License page styles
│       └── Responsive breakpoints (≤900px tablet · ≤640px mobile · ≤420px small phone · landscape)
│
├── <body>
│   ├── #titlebar — nav bar with brand + page buttons
│   ├── #pages — container for all pages
│   │   ├── #game-page
│   │   │   ├── #score-bar
│   │   │   ├── #table (CSS grid: north / west / center / east / south)
│   │   │   │   ├── #zone-north, #zone-west, #zone-east, #zone-south
│   │   │   │   └── #trick-area (CSS grid: trick slots + center chip)
│   │   │   ├── #result-overlay
│   │   │   └── #gameover-overlay
│   │   ├── #settings-page
│   │   ├── #about-page
│   │   └── #license-page
│   ├── #toast-container
│   └── <script> — all JavaScript (~430 lines)
│       ├── Constants (SUITS, RANKS, RANK_VAL, RED_SUITS)
│       ├── Settings (loadSettings, saveSettings, applySettings)
│       ├── Navigation (showPage, toggleTheme, toggleFullscreen)
│       ├── Game state (initState)
│       ├── Card helpers (mkDeck, shuffle, cardVal, winnerOf, legalPlays…)
│       ├── Render helpers (makePlayCard, makeTrickCard, renderHands…)
│       ├── Game flow (newGame, startRound, startPlay, playCard…)
│       ├── AI (aiPlay, aiChooseCard)
│       ├── Round end (resolveTrick, endRound, showResultOverlay…)
│       ├── Overlay helpers
│       ├── Toast system
│       ├── Save / load (stateToMarkdown, exportSave, importSave…)
│       └── Init
```

### Page System

Pages are absolutely-positioned `<div class="page">` elements inside `#pages`. Only one has the `active` class at a time. `showPage(name)` switches between them by toggling `active` and updating the nav button state.

### Rendering Model

The game uses **imperative DOM rendering** — no virtual DOM or framework. On each state change, the relevant render function is called:

| Function | Redraws |
|---|---|
| `renderHands()` | Your face-up cards + all AI face-down backs |
| `renderTrick()` | The 4 trick slots in the center area |
| `renderScoreBar()` | Scores, trump badge, live trick count |
| `highlightCurrentPlayer()` | Active player pill glow |

---

## 10. CSS Design System

Payas Omi uses the shared **Payas design token set** defined as CSS custom properties on `:root`.

### Colour Tokens

#### Light Mode (default)

| Token | Value | Usage |
|---|---|---|
| `--bg-app` | `#eeeae2` | Page / settings background |
| `--bg-bar` | `#ffffff` | Titlebar, modal backgrounds |
| `--bg-sidebar` | `#f5f2eb` | Modal headers/footers |
| `--bg-chip` | `#f0ece3` | Input backgrounds |
| `--bg-hover` | `#e8e4db` | Button hover state |
| `--bg-active` | `#dedad1` | Pressed state |
| `--text-1` | `#181816` | Primary text |
| `--text-2` | `#52504a` | Secondary text |
| `--text-3` | `#90907e` | Labels, muted text |
| `--border` | `#dbd7ce` | Borders, dividers |
| `--accent` | `#2d6a4f` | Forest green — primary accent |
| `--accent-lt` | `#52b788` | Light green |
| `--accent-dk` | `#1b4332` | Dark green (hover on accent) |
| `--accent-dim` | `rgba(45,106,79,.12)` | Active bg tint |
| `--danger` | `#dc2626` | Destructive actions |

#### Dark Mode (`[data-theme="dark"]`)

| Token | Value |
|---|---|
| `--bg-app` | `#0f0f0e` |
| `--bg-bar` | `#191917` |
| `--bg-sidebar` | `#141412` |
| `--text-1` | `#e8e4d6` |
| `--text-2` | `#a4a099` |
| `--accent` | `#52b788` |
| `--border` | `#2a2a28` |

### Typography

| Usage | Font | Size | Weight |
|---|---|---|---|
| UI body text | DM Sans | 13–14px | 400–500 |
| Brand / logo | Fraunces (serif) | 15px | 600 |
| Brand sub-label | Fraunces italic | 11px | 400 |
| Numbers / mono | JetBrains Mono | 10–15px | 400–600 |
| Card ranks | DM Sans | 19px | 800 |

### Responsive Breakpoints

| Breakpoint | Condition | Key Changes |
|---|---|---|
| **Tablet** | `max-width: 900px` | Cards scale to 80×114 px, trick cards 66×94 px, card backs 58×82 px |
| **Mobile** | `max-width: 640px` | Score bar no-wrap + scroll, compact titlebar, cards 68×98 px, brand subtitle hidden |
| **Small phone** | `max-width: 420px` | Cards 58×84 px, trick cards 52×76 px, tighter padding throughout |
| **Landscape phone** | `max-height: 500px` + landscape | Titlebar shrinks to 36 px, very compact cards 56×80 px for maximum vertical space |

On screens ≤576 px, Bootstrap's `d-none d-sm-inline` hides navigation button labels — only icons remain, keeping the titlebar usable at any width.

### 3D Card Technique

Cards achieve depth through layered CSS:

```css
/* Base shadow stack */
box-shadow:
  0 6px 18px rgba(0,0,0,.38),   /* ambient drop shadow */
  0 2px 4px rgba(0,0,0,.22),    /* contact shadow */
  inset 0 1px 0 rgba(255,255,255,.9); /* top-edge highlight */

/* Gloss streak (::before pseudo-element) */
background: linear-gradient(180deg, rgba(255,255,255,.45) 0%, transparent 100%);

/* Right-edge thickness (::after pseudo-element) */
width: 3px; background: rgba(0,0,0,.18);
```

---

## 11. JavaScript API Reference

### Constants

```js
const SUITS    = ['♠','♥','♦','♣']
// 32-card deck — only A K Q J 10 9 8 7 per suit
const RANKS    = ['7','8','9','10','J','Q','K','A']
const RANK_VAL = { '7':0, '8':1, '9':2, '10':3, 'J':4, 'Q':5, 'K':6, 'A':7 }
const RED_SUITS = new Set(['♥','♦'])
const PLAYERS  = ['south','west','north','east']  // south = human
const TEAMS    = { us:['south','north'], them:['west','east'] }
```

### Settings Functions

| Function | Description |
|---|---|
| `loadSettings()` | Reads `omi-settings` from localStorage and calls `applySettings()`. Called on page load. |
| `saveSettings()` | Reads all settings form elements, writes to `settings` object and localStorage, then calls `applySettings()`. |
| `applySettings()` | Pushes `settings` object values to DOM elements and applies theme. |

### Navigation Functions

| Function | Description |
|---|---|
| `showPage(name)` | Activates the named page (`'game'`, `'settings'`, `'about'`, `'license'`). |
| `toggleTheme()` | Flips `data-theme` attribute between `'dark'` and absent (light). Persists to localStorage. |
| `toggleFullscreen()` | Requests or exits browser fullscreen via the Fullscreen API. Updates the titlebar icon. |

### Game Flow Functions

| Function | Description |
|---|---|
| `newGame()` | Resets scores to 0, creates fresh state, starts first round. |
| `nextRound()` | Hides result overlay and calls `startRound()`. |
| `startRound()` | Increments round counter, deals 4 cards each, identifies Trump Maker (dealer's right), triggers trump selection. |
| `aiChooseTrump(hand4)` | AI selects the trump suit with the most cards in the initial 4-card hand. |
| `onTrumpChosen(suit)` | Called when the human (South) picks a trump suit from the selection overlay. |
| `finaliseTrump(suit)` | Sets `G.trump`, deals remaining 4 cards, sorts hands, calls `startPlay()`. |
| `startPlay()` | Sets phase to `'play'`, Trump Maker leads the first trick, starts turn cycle. |
| `playCard(player, card)` | Records card to `G.trick`, removes from hand, renders, advances to next player or resolves trick. |
| `resolveTrick()` | Determines trick winner, increments `G.trickWins`, handles next lead or round end. |
| `endRound()` | Tallies tricks, applies token scoring (1/2/draw), updates `G.teamScore`, pushes to history, rotates dealer, shows result overlay. |

### Card Helper Functions

| Function | Signature | Description |
|---|---|---|
| `mkDeck()` | `→ Card[]` | Returns a fresh 52-card array `[{s, r}, …]`. |
| `shuffle(arr)` | `(Card[]) → Card[]` | In-place Fisher-Yates shuffle. Returns same array. |
| `cardVal(card, trump, ledSuit)` | `→ number` | Returns numeric trick priority. Trump = 1000+rank, led suit = 100+rank, other = rank. |
| `winnerOf(trick, trump)` | `(Object, string) → string` | Returns the player name who wins the given trick object. |
| `legalPlays(hand, ledSuit)` | `(Card[], string\|null) → Card[]` | Returns subset of hand that are legal to play (must follow suit if possible). |
| `teamOf(player)` | `(string) → 'us'\|'them'` | Returns the team key for a given player name. |
| `partnerOf(player)` | `(string) → string` | Returns the player name of the given player's partner. |

### Render Functions

| Function | Description |
|---|---|
| `renderHands()` | Clears and rebuilds south's face-up hand and all AI face-down backs. Marks playable cards. |
| `renderTrick()` | Clears and rebuilds the 4 trick card slots from `G.trick`. |
| `renderScoreBar()` | Updates score values, trump badge, and live trick counter in the score bar. |
| `makePlayCard(card, idx, playable)` | Creates and returns a `.play-card` DOM element. |
| `makeTrickCard(card, isWinner)` | Creates and returns a `.trick-card` DOM element. |
| `highlightCurrentPlayer()` | Adds/removes `.active-player` on all player name elements. |
| `setStatus(msg)` | Updates the status message text in the score bar. |

### AI Functions

| Function | Signature | Description |
|---|---|---|
| `aiPlay(player)` | `(string) → void` | Guards against stale calls, then picks and plays a card for the given AI player. |
| `aiChooseCard(player, legal, led)` | `(string, Card[], string\|null) → Card` | Core AI decision function. Behaviour varies by `settings.aiDiff`. |

### Save / Load Functions

| Function | Description |
|---|---|
| `stateToMarkdown()` | Serialises current game state to a Markdown string with YAML frontmatter. |
| `exportSave()` | Calls `stateToMarkdown()`, creates a Blob, triggers download. |
| `importSave()` | Programmatically clicks the hidden `<input type="file">`. |
| `handleImport(event)` | File change handler — reads `.md` file, parses frontmatter, restores scores. |
| `saveAutoState()` | Writes minimal state to `localStorage['omi-autosave']`. |
| `loadAutoState()` | Reads `omi-autosave` and restores `teamScore`, `roundHistory`, `round`, `dealer`. Returns `true` if found. |

### Overlay & Toast Functions

| Function | Description |
|---|---|
| `showOverlay(id)` | Adds `.show` class to overlay element. |
| `hideOverlay(id)` | Removes `.show` class. |
| `hideAllOverlays()` | Removes `.show` from every `.overlay` element. |
| `toast(msg, dur?)` | Creates a toast notification. Default duration: `2200ms`. |

---

## 12. Data Structures

### Card Object

```js
{ s: '♠' | '♥' | '♦' | '♣',  r: '2'|'3'|...'K'|'A' }
```

### Game State Object (`G`)

```js
{
  teamScore:    { us: number, them: number },  // token totals
  roundHistory: RoundRecord[],
  round:        number,          // current round number (1-based)
  phase:        'idle' | 'deal' | 'play' | 'result',
  hands: {
    south: Card[],  // human player (face-up)
    west:  Card[],  // AI
    north: Card[],  // AI (partner of south)
    east:  Card[],  // AI
  },
  dealer:     string,          // 'south' | 'west' | 'north' | 'east'
  trump:      string | null,   // e.g. '♠'
  trumpMaker: string | null,   // player who chose trump (dealer's right)
  drawBonus:  boolean,         // carry-over token flag from a 4-4 draw
  trick: {
    [player: string]: Card     // cards played in current trick, keyed by player name
  },
  trickLead:    string | null, // player who led the current trick
  trickWins: {
    south: number, west: number, north: number, east: number
  },
  currentPlayer: string | null,
}
```

### Round Record Object

```js
{
  round:      number,
  trump:      string,      // e.g. '♥'
  trumpMaker: string,      // player who named the trump
  usTricks:   number,      // tricks won by Us this round
  themTricks: number,      // tricks won by Them this round
  usGain:     number,      // tokens added to Us
  themGain:   number,      // tokens added to Them
  roundLabel: string,      // 'Kapothi!' | 'Ambaya' | 'Draw' | ''
  usTotal:    number,      // cumulative Us tokens after this round
  themTotal:  number,      // cumulative Them tokens after this round
}
```

### Settings Object

```js
{
  winScore:   number,   // 5 | 10 | 15  (tokens target)
  playerName: string,   // display name for South player
  aiDiff:     string,   // 'easy' | 'medium' | 'hard'
  timer:      number,   // 0 | 15 | 30 | 60
  dark:       boolean,
}
```

---

## 13. Contributing

Payas Omi is open source under the MIT license. Contributions are welcome.

### How to Contribute

1. **Fork** the repository.
2. **Edit** `payas-omi.html` directly — no build step needed.
3. Test in both **Chromium** and **Firefox**, and on **mobile** screen sizes.
4. Test both **light and dark modes**.
5. Submit a **pull request** with a clear description of what changed and why.

### Coding Guidelines

- **No frameworks** — vanilla JS only. No npm dependencies in the game file itself.
- **Single file** — keep everything in `payas-omi.html`. Do not split into separate `.js` or `.css` files.
- **Design tokens** — always use `var(--token-name)` for colours and shadows. Do not hardcode colours in new CSS rules.
- **Dark mode** — every visual change must work in both themes. Test by toggling with `Ctrl+T`.
- **Mobile first** — test at 375px wide. Cards must remain readable and tappable.
- **No comments for obvious code** — only comment non-obvious logic (e.g. AI heuristics, shuffle algorithm quirks).

### Suggested Improvements

The following features are candidates for future versions:

- [ ] **Turn timer enforcement** — auto-play a random legal card when the timer expires
- [ ] **Card animations** — CSS transitions for cards sliding to the trick area
- [ ] **Sound effects** — subtle card-play and win sounds (Web Audio API)
- [ ] **PWA / offline support** — Service Worker caching for offline play
- [ ] **Hot-seat multiplayer** — allow a second human player to take over East or West
- [ ] **Hard AI improvements** — track played cards and adjust strategy accordingly
- [ ] **Accessibility** — keyboard navigation through the hand, ARIA labels on cards

---

## 14. License

```
MIT License

Copyright (c) 2025 Saman Wijesinghe

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
```

---

*Payas Omi — built with care by Saman Wijesinghe*
