buddy-battle 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,75 @@
1
+ import React, { useState, useEffect } from 'react';
2
+ import { Box, Text } from 'ink';
3
+ import { renderSprite } from '../engine/sprites.js';
4
+ export function BuddySprite({ buddy, color = "yellow", isAttacking = false, isHit = false }) {
5
+ const [frame, setFrame] = useState(0);
6
+ const [shakeOffset, setShakeOffset] = useState(0);
7
+ useEffect(() => {
8
+ const t = setInterval(() => setFrame(f => f + 1), 1000);
9
+ return () => clearInterval(t);
10
+ }, []);
11
+ useEffect(() => {
12
+ let t;
13
+ if (isHit) {
14
+ let ticks = 0;
15
+ t = setInterval(() => {
16
+ ticks++;
17
+ setShakeOffset(ticks % 2 === 0 ? 2 : 0);
18
+ }, 100);
19
+ }
20
+ else {
21
+ setShakeOffset(0);
22
+ }
23
+ return () => clearInterval(t);
24
+ }, [isHit]);
25
+ const lines = renderSprite(buddy, frame);
26
+ const renderColor = isAttacking ? "cyanBright" : (isHit ? "redBright" : color);
27
+ return (React.createElement(Box, { flexDirection: "column", alignItems: "center" }, lines.map((l, i) => {
28
+ const prefix = " ".repeat(shakeOffset);
29
+ return React.createElement(Text, { key: i, color: renderColor, bold: isAttacking },
30
+ prefix,
31
+ l);
32
+ })));
33
+ }
34
+ export function BattleProjectiles({ activeMove, isPlayer1 }) {
35
+ const [tick, setTick] = useState(0);
36
+ useEffect(() => {
37
+ if (!activeMove) {
38
+ setTick(0);
39
+ return;
40
+ }
41
+ const timer = setInterval(() => setTick(t => t + 1), 60);
42
+ return () => clearInterval(timer);
43
+ }, [activeMove]);
44
+ if (!activeMove)
45
+ return React.createElement(Box, { width: 30, justifyContent: "center", alignItems: "center" },
46
+ React.createElement(Text, null, " "));
47
+ let avatar = "*";
48
+ let color = "whiteBright";
49
+ if (activeMove === 'Chaos Bomb') {
50
+ avatar = "◉";
51
+ color = "magentaBright";
52
+ }
53
+ else if (activeMove.includes('Smash')) {
54
+ avatar = "☄️";
55
+ color = "yellowBright";
56
+ }
57
+ else if (activeMove === 'Firewall') {
58
+ avatar = "🛡️";
59
+ color = "cyanBright";
60
+ }
61
+ else if (activeMove === 'Reboot') {
62
+ avatar = "➕";
63
+ color = "greenBright";
64
+ }
65
+ const isSelfCast = activeMove === 'Firewall' || activeMove === 'Reboot';
66
+ const maxDistance = 26;
67
+ let distance = Math.min(tick * 3, maxDistance);
68
+ if (isSelfCast) {
69
+ // Self-cast moves don't cross the field, they hover next to the caster!
70
+ distance = tick % 2 === 0 ? 0 : 2;
71
+ }
72
+ const padding = isPlayer1 ? distance : (maxDistance - distance);
73
+ return (React.createElement(Box, { width: 30, paddingLeft: Math.max(0, padding), justifyContent: "flex-start", alignItems: "center" },
74
+ React.createElement(Text, { bold: true, color: color }, avatar)));
75
+ }
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "buddy-battle",
3
+ "version": "1.0.0",
4
+ "license": "MIT",
5
+ "bin": "dist/cli.js",
6
+ "type": "module",
7
+ "engines": {
8
+ "node": ">=16"
9
+ },
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "dev": "tsc --watch",
13
+ "test": "prettier --check . && xo && ava"
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "dependencies": {
19
+ "@supabase/supabase-js": "^2.101.1",
20
+ "dotenv": "^17.3.1",
21
+ "ink": "^4.1.0",
22
+ "ink-text-input": "^5.0.1",
23
+ "meow": "^11.0.0",
24
+ "open": "^11.0.0",
25
+ "react": "^18.2.0",
26
+ "zod": "^4.3.6"
27
+ },
28
+ "devDependencies": {
29
+ "@sindresorhus/tsconfig": "^3.0.1",
30
+ "@types/react": "^18.0.32",
31
+ "@vdemedes/prettier-config": "^2.0.1",
32
+ "ava": "^5.2.0",
33
+ "chalk": "^5.2.0",
34
+ "eslint-config-xo-react": "^0.27.0",
35
+ "eslint-plugin-react": "^7.32.2",
36
+ "eslint-plugin-react-hooks": "^4.6.0",
37
+ "ink-testing-library": "^3.0.0",
38
+ "prettier": "^2.8.7",
39
+ "ts-node": "^10.9.1",
40
+ "typescript": "^5.0.3",
41
+ "xo": "^0.53.1"
42
+ },
43
+ "ava": {
44
+ "extensions": {
45
+ "ts": "module",
46
+ "tsx": "module"
47
+ },
48
+ "nodeArguments": [
49
+ "--loader=ts-node/esm"
50
+ ]
51
+ },
52
+ "xo": {
53
+ "extends": "xo-react",
54
+ "prettier": true,
55
+ "rules": {
56
+ "react/prop-types": "off"
57
+ }
58
+ },
59
+ "prettier": "@vdemedes/prettier-config"
60
+ }
package/readme.md ADDED
@@ -0,0 +1,125 @@
1
+ # 🤖 Buddy Battle
2
+
3
+ **PvP Arena Combat for Claude Code Buddies**
4
+
5
+ Buddy Battle brings your Claude Code companion to life in a real-time, turn-based terminal fighting game. Your buddy's species, rarity, and stats are pulled directly from your Claude Code installation — the same little creature that sits beside your prompt is now your champion in the arena.
6
+
7
+ ![Node.js](https://img.shields.io/badge/Node.js-18+-green) ![Supabase](https://img.shields.io/badge/Supabase-Realtime-blue) ![License](https://img.shields.io/badge/License-MIT-yellow)
8
+
9
+ ## ✨ Features
10
+
11
+ - **🎮 Real-Time PvP** — Fight other Claude Code users over Supabase Realtime
12
+ - **🧬 Deterministic Buddies** — Your buddy's species, rarity, and stats are derived from your Anthropic account via `Bun.hash`
13
+ - **⚔️ 4-Move Combat System** — Debug Smash, Chaos Bomb, Firewall, and Reboot — each powered by your buddy's unique stats
14
+ - **💥 Battle Animations** — Projectiles fly across the terminal, hits shake the screen
15
+ - **🏆 Global Leaderboard** — ELO-tracked rankings stored in Supabase Postgres
16
+ - **🔐 Cloud Profiles** — Choose a Trainer Name, synced across all your machines via SHA-256 hashed Anthropic UUID
17
+ - **📡 Disconnect Protection** — Supabase Presence detects ragequits and awards the survivor a win
18
+ - **🛡️ Privacy First** — No Anthropic credentials or account IDs ever leave your machine
19
+
20
+ ## 📋 Prerequisites
21
+
22
+ - **[Claude Code](https://docs.anthropic.com/en/docs/claude-code)** — You need a hatched buddy (`/buddy` command)
23
+ - **[Node.js](https://nodejs.org/) 18+**
24
+ - **[Bun](https://bun.sh/)** — Required for accurate buddy stat extraction (Claude Code already installs this)
25
+ - **A Supabase project** — Free tier works fine
26
+
27
+ ## 🚀 Quick Start
28
+
29
+ ### 1. Clone & Install
30
+
31
+ ```bash
32
+ git clone https://github.com/Grenghis-Khan/buddy-battle.git
33
+ cd buddy-battle
34
+ npm install
35
+ ```
36
+
37
+ ### 2. Set Up Supabase
38
+
39
+ Create a new [Supabase](https://supabase.com) project, then run this SQL in the SQL Editor:
40
+
41
+ ```sql
42
+ CREATE TABLE public.leaderboard (
43
+ id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
44
+ trainer_name text UNIQUE NOT NULL,
45
+ hashed_id text UNIQUE,
46
+ wins integer DEFAULT 0,
47
+ losses integer DEFAULT 0,
48
+ elo integer DEFAULT 1000,
49
+ created_at timestamptz DEFAULT now()
50
+ );
51
+
52
+ ALTER TABLE public.leaderboard ENABLE ROW LEVEL SECURITY;
53
+ CREATE POLICY "Public read" ON public.leaderboard FOR SELECT USING (true);
54
+ CREATE POLICY "Public insert" ON public.leaderboard FOR INSERT WITH CHECK (true);
55
+ CREATE POLICY "Public update" ON public.leaderboard FOR UPDATE USING (true);
56
+ ```
57
+
58
+ ### 3. Configure Environment
59
+
60
+ ```bash
61
+ cp .env.example .env
62
+ ```
63
+
64
+ Edit `.env` with your Supabase project URL and anon key (found in Settings → API).
65
+
66
+ ### 4. Build & Play!
67
+
68
+ ```bash
69
+ npm run build
70
+ node dist/cli.js
71
+ ```
72
+
73
+ ## 🎯 Combat System
74
+
75
+ | Move | Stat | Effect |
76
+ |------|------|--------|
77
+ | **Debug Smash** | DEBUGGING | Reliable damage |
78
+ | **Chaos Bomb** | CHAOS | Massive damage, 30% miss chance |
79
+ | **Firewall** | WISDOM | Blocks 50% of next incoming hit |
80
+ | **Reboot** | PATIENCE | Heals your buddy |
81
+
82
+ Each buddy's stats are unique and deterministic — a high-CHAOS buddy will devastate with Chaos Bombs, while a high-PATIENCE buddy can outlast opponents with Reboots.
83
+
84
+ ## 🏗️ Architecture
85
+
86
+ ```
87
+ buddy-battle/
88
+ ├── source/
89
+ │ ├── app.tsx # Root application + state machine
90
+ │ ├── cli.tsx # Entry point
91
+ │ ├── extract-bones.ts # Bun script for accurate stat extraction
92
+ │ ├── config/
93
+ │ │ └── reader.ts # Reads ~/.claude.json + shells out to Bun
94
+ │ ├── engine/
95
+ │ │ ├── companion.ts # Deterministic buddy generation (mirrored from Claude Code)
96
+ │ │ ├── types.ts # Species, rarities, stats
97
+ │ │ └── sprites.ts # ASCII art for all 18 species
98
+ │ ├── network/
99
+ │ │ └── client.ts # Supabase Realtime + Presence + Cloud Profiles
100
+ │ └── ui/
101
+ │ ├── Arena.tsx # Battle screen with animations
102
+ │ ├── Menu.tsx # Main menu
103
+ │ ├── Matchmaking.tsx # Lobby + opponent discovery
104
+ │ ├── Leaderboard.tsx # Global rankings
105
+ │ ├── Setup.tsx # First-time Trainer Name registration
106
+ │ └── Shared.tsx # Sprites + projectile components
107
+ ```
108
+
109
+ ### Key Technical Decisions
110
+
111
+ - **Bun.hash bridge**: Claude Code runs on Bun and uses `Bun.hash()` (wyhash) to seed buddy generation. Since Node.js uses a different hash, we shell out to `bun source/extract-bones.ts` at boot to get bit-identical results.
112
+ - **SHA-256 identity**: Your Anthropic `accountUuid` is hashed before touching the network — Supabase never sees your real account ID.
113
+ - **Peer-to-peer combat**: No server-side game logic. Both clients independently compute damage from broadcast payloads, keeping the architecture simple.
114
+
115
+ ## ⚠️ Bun Warning
116
+
117
+ If Bun is not installed, the game will still work but your buddy's species, rarity, and combat stats may not match what Claude Code shows. Install Bun to fix this:
118
+
119
+ ```bash
120
+ curl -fsSL https://bun.sh/install | bash
121
+ ```
122
+
123
+ ## 📄 License
124
+
125
+ MIT