Skip to content

Commit f95bfe6

Browse files
committed
Slow down piece slide and portal warp animations; wait longer between turns so bot warps are visible
1 parent 9148d76 commit f95bfe6

3 files changed

Lines changed: 105 additions & 7 deletions

File tree

.github/copilot-instructions.md

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
# SpeedChess — Copilot Instructions
2+
3+
Project conventions and gotchas for AI agents working in this repository.
4+
5+
## Stack
6+
7+
- React 19 + TypeScript (strict), Vite 8, `vite-plugin-pwa`
8+
- Vitest 3.2.4 (1400+ tests)
9+
- Deployed to GitHub Pages at `https://swon404.github.io/SpeedChess/` (base `/SpeedChess/`)
10+
- Dev server: `npm run dev` (port 5180)
11+
- `npm install` requires `--legacy-peer-deps`
12+
13+
## Repo Layout
14+
15+
- `src/engine/` — custom chess engine (no external chess library)
16+
- Board is `[rank][file]` 8×8 array; **rank 0 = white back rank**
17+
- `rules.ts` exports `legalMovesFrom`, `allLegalMoves`, `makeMove`, `gameResult`, `inCheck`, `teleportTargets`, `portalAt`, `findKing`
18+
- `bot.ts` — opponent AI; `notation.ts` — SAN/UCI helpers
19+
- `src/puzzles/` — puzzle databases (standard + portal) and React wrappers
20+
- `src/screens/` — top-level screens (`NewGameScreen`, `PuzzlesScreen`, etc.)
21+
- `src/components/` — UI (`Board.tsx`, etc.)
22+
- `src/GameContext.tsx` — global game state context
23+
- `scripts/` — one-off tsx scripts (puzzle import, asset cropping)
24+
25+
## FEN Conventions
26+
27+
- FEN strings must have **at least 4 fields**: `placement turn castling ep` (halfmove/fullmove optional).
28+
- Square color: `(file + rank) % 2 === 0 ? "dark" : "light"` (file/rank are 0-indexed).
29+
30+
## Portal Chess Mechanics
31+
32+
Portal Chess is a custom variant. Key rules when generating/validating puzzles:
33+
34+
- Each side has at most one active portal square (`state.portals[color]`).
35+
- A piece type is designated `creator` per color (`state.portalCreators[color]`). The creator is **exempt** from teleporting through its own portal.
36+
- With `creator: "K"`, all Q/R/B/N pieces from that side teleport on entering their portal.
37+
- When a non-creator piece moves onto its own portal square, teleport is **forced** (not optional).
38+
- `teleportTargets(state, from, color)` returns the portal square plus all empty squares (excluding `from` and the portal square). Bishops are restricted to squares matching the portal-square color.
39+
- When the **creator** piece moves and `state.portals[color] === null`, a portal **auto-drops** at the landing square.
40+
- Extended UCI for portal moves: `"e2e4@d8"` = move to `e4` (which is the portal), then teleport to `d8`.
41+
42+
## Puzzle Generation Pattern (`portal-puzzle-db.ts`)
43+
44+
Generators emit `RawCandidate[]` and `build()` validates each one by replaying moves through the engine. Invalid candidates (illegal moves, intermediate mate, non-mate at end) are silently dropped — design speculatively, let `validate()` filter.
45+
46+
```ts
47+
function validate(c: RawCandidate): boolean {
48+
let s: GameState | null = setupState(c);
49+
for (let i = 0; i < c.moves.length; i++) {
50+
s = applyMove(s, c.moves[i]);
51+
if (!s) return false;
52+
const r = gameResult(s);
53+
if (i === c.moves.length - 1) {
54+
if (r.kind !== "checkmate") return false;
55+
} else {
56+
if (r.kind !== "ongoing") return false;
57+
}
58+
}
59+
return true;
60+
}
61+
```
62+
63+
Pitfalls observed:
64+
- Direct (non-portal) mates leak: e.g. rook on a-file slides to `a7` without using portal. Exclude conflicting source files when generating.
65+
- Defenders may block: long-diagonal bishop mates fail when a defender rook can interpose. Prefer adjacent diagonal mates with pawn/queen support.
66+
- Creator-king dropping a portal at its own landing square blocks the rook's path (king occupies the portal). Workarounds are non-trivial.
67+
68+
## Testing & Shipping
69+
70+
Standard ship workflow (PowerShell):
71+
72+
```powershell
73+
npm run build 2>&1 | Select-Object -Last 4
74+
git add -A
75+
git commit -m "..."
76+
git push
77+
```
78+
79+
- Run all tests: `npx vitest run 2>&1 | Select-Object -Last 8`
80+
- Run a file: `npx vitest run path/to/file.test.ts 2>&1 | Select-Object -Last 10`
81+
- Tests live under `src/**/__tests__/*.test.ts`
82+
- Don't leave throwaway debug tests committed (`debug-*.test.ts`, `*-stats.test.ts`)
83+
84+
## Coding Conventions
85+
86+
- TypeScript strict mode — no implicit `any`, no unchecked nulls.
87+
- Engine code is pure/functional: state transitions return new objects rather than mutating.
88+
- React components use hooks + context (`GameContext`); no Redux.
89+
- Avoid adding chess libraries; extend the in-house engine.
90+
- Don't add docstrings/comments to code you didn't change.
91+
92+
## Operational
93+
94+
- OS: Windows; shell: PowerShell. Chain commands with `;`, never `&&`.
95+
- Don't `Start-Sleep` after async commands — terminal output notifications arrive automatically.

src/GameContext.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -245,10 +245,12 @@ export function GameProvider({ children }: { children: ReactNode }) {
245245
try {
246246
// Compute the bot move and ensure enough time has elapsed so the
247247
// human's own animation has time to play before the board re-renders
248-
// with the bot's response. Teleport animations are longer (~1.6s)
249-
// so we wait extra when the previous move was a portal entry.
248+
// with the bot's response. Teleport animations are longer (~2.8s
249+
// total: demat + gap + remat) so we wait extra when the previous
250+
// move was a portal entry. Slide animation is ~650ms, so we wait
251+
// ~750ms after a normal human move.
250252
const prevMove = state.history[state.history.length - 1];
251-
const minThinkMs = prevMove?.isPortalEntry ? 1700 : 320;
253+
const minThinkMs = prevMove?.isPortalEntry ? 2900 : 750;
252254
const t0 = performance.now();
253255
const move = await chooseBotMove(state, lvl);
254256
const elapsed = performance.now() - t0;

src/styles.css

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -274,20 +274,21 @@ section label { display: block; margin: 6px 0; }
274274
height: 100%;
275275
}
276276
.piece-sliding {
277-
animation: piece-slide 480ms cubic-bezier(0.22, 0.61, 0.36, 1);
277+
animation: piece-slide 650ms cubic-bezier(0.22, 0.61, 0.36, 1);
278278
will-change: transform;
279279
}
280-
/* Portal teleport: piece dematerialises at origin, rematerialises at destination. */
280+
/* Portal teleport: piece dematerialises at origin, rematerialises at destination.
281+
Total warp duration: demat (1300ms) + gap (200ms) + remat (1300ms) = ~2.8s. */
281282
.piece-dematerialize {
282283
position: absolute;
283284
inset: 0;
284285
pointer-events: none;
285-
animation: piece-demat 900ms ease-in forwards;
286+
animation: piece-demat 1300ms ease-in forwards;
286287
will-change: transform, opacity, filter;
287288
z-index: 2;
288289
}
289290
.piece-rematerialize {
290-
animation: piece-remat 900ms 700ms cubic-bezier(0.16, 1, 0.3, 1) backwards;
291+
animation: piece-remat 1300ms 1500ms cubic-bezier(0.16, 1, 0.3, 1) backwards;
291292
will-change: transform, opacity, filter;
292293
}
293294
@keyframes piece-demat {

0 commit comments

Comments
 (0)