Compare commits

...
Sign in to create a new pull request.

2 commits

15 changed files with 1334 additions and 156 deletions

View file

@ -4,9 +4,10 @@
"version": "0.0.1", "version": "0.0.1",
"scripts": { "scripts": {
"dev": "astro dev", "dev": "astro dev",
"build": "astro build", "build": "node scripts/generate-captains-index.js && astro build",
"preview": "astro preview", "preview": "astro preview",
"astro": "astro" "astro": "astro",
"generate-captains": "node scripts/generate-captains-index.js"
}, },
"dependencies": { "dependencies": {
"@radix-ui/react-dialog": "^1.1.6", "@radix-ui/react-dialog": "^1.1.6",

View file

@ -0,0 +1,41 @@
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const CAPTAINS_DIR = path.join(__dirname, '../src/assets/captains');
const INDEX_FILE = path.join(CAPTAINS_DIR, 'index.json');
function generateCaptainsIndex() {
try {
// Read all JSON files in the captains directory
const files = fs.readdirSync(CAPTAINS_DIR)
.filter(file => file.endsWith('.json') && file !== 'index.json');
// Parse each captain file to get their id and name
const captains = files.map(file => {
const content = fs.readFileSync(path.join(CAPTAINS_DIR, file), 'utf-8');
const captain = JSON.parse(content);
return {
id: captain.id,
name: captain.name
};
});
// Create the index object
const index = {
captains: captains
};
// Write the index file
fs.writeFileSync(INDEX_FILE, JSON.stringify(index, null, 4));
console.log('Successfully generated captains index');
} catch (error) {
console.error('Error generating captains index:', error);
process.exit(1);
}
}
generateCaptainsIndex();

View file

@ -0,0 +1,48 @@
{
"id": "burnham",
"name": "Burnham",
"image": "captain.jpg",
"specialtyThresholds": {
"research": {
"basic": [
{ "threshold": 4, "multiplier": "x2" },
{ "threshold": 8, "multiplier": "x3" },
{ "threshold": 12, "multiplier": "x4" }
],
"advanced": [
{ "threshold": 3, "multiplier": "x2" },
{ "threshold": 6, "multiplier": "x3" },
{ "threshold": 9, "multiplier": "x4" }
]
},
"influence": {
"basic": [
{ "threshold": 4, "multiplier": "x2" },
{ "threshold": 8, "multiplier": "x3" },
{ "threshold": 12, "multiplier": "x4" }
],
"advanced": [
{ "threshold": 3, "multiplier": "x2" },
{ "threshold": 6, "multiplier": "x3" },
{ "threshold": 9, "multiplier": "x4" }
]
},
"military": {
"basic": [
{ "threshold": 4, "multiplier": "x2" },
{ "threshold": 8, "multiplier": "x3" },
{ "threshold": 12, "multiplier": "x4" }
],
"advanced": [
{ "threshold": 3, "multiplier": "x2" },
{ "threshold": 6, "multiplier": "x3" },
{ "threshold": 9, "multiplier": "x4" }
]
}
},
"basicMission": {
"name": "Time Crystal Recovery",
"description": "Retrieve a valuable time crystal from a dangerous location.",
"points": 3
}
}

View file

@ -0,0 +1,28 @@
{
"captains": [
{
"id": "burnham",
"name": "Burnham"
},
{
"id": "koloth",
"name": "Koloth"
},
{
"id": "picard",
"name": "Picard"
},
{
"id": "sela",
"name": "Sela"
},
{
"id": "shran",
"name": "Shran"
},
{
"id": "sisko",
"name": "Sisko"
}
]
}

View file

@ -0,0 +1,48 @@
{
"id": "koloth",
"name": "Koloth",
"image": "captain.jpg",
"specialtyThresholds": {
"research": {
"basic": [
{ "threshold": 4, "multiplier": "x2" },
{ "threshold": 8, "multiplier": "x3" },
{ "threshold": 12, "multiplier": "x4" }
],
"advanced": [
{ "threshold": 3, "multiplier": "x2" },
{ "threshold": 6, "multiplier": "x3" },
{ "threshold": 9, "multiplier": "x4" }
]
},
"influence": {
"basic": [
{ "threshold": 4, "multiplier": "x2" },
{ "threshold": 8, "multiplier": "x3" },
{ "threshold": 12, "multiplier": "x4" }
],
"advanced": [
{ "threshold": 3, "multiplier": "x2" },
{ "threshold": 6, "multiplier": "x3" },
{ "threshold": 9, "multiplier": "x4" }
]
},
"military": {
"basic": [
{ "threshold": 4, "multiplier": "x2" },
{ "threshold": 8, "multiplier": "x3" },
{ "threshold": 12, "multiplier": "x4" }
],
"advanced": [
{ "threshold": 3, "multiplier": "x2" },
{ "threshold": 6, "multiplier": "x3" },
{ "threshold": 9, "multiplier": "x4" }
]
}
},
"basicMission": {
"name": "Klingon Honor",
"description": "Uphold Klingon honor in a challenging diplomatic situation.",
"points": 3
}
}

View file

@ -0,0 +1,48 @@
{
"id": "picard",
"name": "Picard",
"image": "captain.jpg",
"specialtyThresholds": {
"research": {
"basic": [
{ "threshold": 4, "multiplier": "x2" },
{ "threshold": 8, "multiplier": "x3" },
{ "threshold": 12, "multiplier": "x4" }
],
"advanced": [
{ "threshold": 3, "multiplier": "x2" },
{ "threshold": 6, "multiplier": "x3" },
{ "threshold": 9, "multiplier": "x4" }
]
},
"influence": {
"basic": [
{ "threshold": 4, "multiplier": "x2" },
{ "threshold": 8, "multiplier": "x3" },
{ "threshold": 12, "multiplier": "x4" }
],
"advanced": [
{ "threshold": 3, "multiplier": "x2" },
{ "threshold": 6, "multiplier": "x3" },
{ "threshold": 9, "multiplier": "x4" }
]
},
"military": {
"basic": [
{ "threshold": 4, "multiplier": "x2" },
{ "threshold": 8, "multiplier": "x3" },
{ "threshold": 12, "multiplier": "x4" }
],
"advanced": [
{ "threshold": 3, "multiplier": "x2" },
{ "threshold": 6, "multiplier": "x3" },
{ "threshold": 9, "multiplier": "x4" }
]
}
},
"basicMission": {
"name": "Diplomatic Mission",
"description": "Lead a diplomatic mission to establish peaceful relations with a new species.",
"points": 2
}
}

View file

@ -0,0 +1,48 @@
{
"id": "sela",
"name": "Sela",
"image": "captain.jpg",
"specialtyThresholds": {
"research": {
"basic": [
{ "threshold": 4, "multiplier": "x2" },
{ "threshold": 8, "multiplier": "x3" },
{ "threshold": 12, "multiplier": "x4" }
],
"advanced": [
{ "threshold": 3, "multiplier": "x2" },
{ "threshold": 6, "multiplier": "x3" },
{ "threshold": 9, "multiplier": "x4" }
]
},
"influence": {
"basic": [
{ "threshold": 4, "multiplier": "x2" },
{ "threshold": 8, "multiplier": "x3" },
{ "threshold": 12, "multiplier": "x4" }
],
"advanced": [
{ "threshold": 3, "multiplier": "x2" },
{ "threshold": 6, "multiplier": "x3" },
{ "threshold": 9, "multiplier": "x4" }
]
},
"military": {
"basic": [
{ "threshold": 4, "multiplier": "x2" },
{ "threshold": 8, "multiplier": "x3" },
{ "threshold": 12, "multiplier": "x4" }
],
"advanced": [
{ "threshold": 3, "multiplier": "x2" },
{ "threshold": 6, "multiplier": "x3" },
{ "threshold": 9, "multiplier": "x4" }
]
}
},
"basicMission": {
"name": "Romulan Infiltration",
"description": "Lead a covert operation to infiltrate a strategic Federation outpost.",
"points": 3
}
}

View file

@ -0,0 +1,60 @@
{
"id": "shran",
"name": "Shran",
"image": "captain.jpg",
"specialtyThresholds": {
"research": {
"basic": [
{ "threshold": 4, "multiplier": "x2" },
{ "threshold": 8, "multiplier": "x3" },
{ "threshold": 12, "multiplier": "x4" }
],
"advanced": [
{ "threshold": 3, "multiplier": "x2" },
{ "threshold": 6, "multiplier": "x3" },
{ "threshold": 9, "multiplier": "x4" }
]
},
"influence": {
"basic": [
{ "threshold": 4, "multiplier": "x2" },
{ "threshold": 8, "multiplier": "x3" },
{ "threshold": 12, "multiplier": "x4" }
],
"advanced": [
{ "threshold": 3, "multiplier": "x2" },
{ "threshold": 6, "multiplier": "x3" },
{ "threshold": 9, "multiplier": "x4" }
]
},
"military": {
"basic": [
{ "threshold": 4, "multiplier": "x2" },
{ "threshold": 8, "multiplier": "x3" },
{ "threshold": 12, "multiplier": "x4" }
],
"advanced": [
{ "threshold": 3, "multiplier": "x2" },
{ "threshold": 6, "multiplier": "x3" },
{ "threshold": 9, "multiplier": "x4" }
]
}
},
"basicMission": {
"name": "Andorian Defense",
"description": "Defend Andorian interests against external threats.",
"points": 3
},
"advancedMissions": [
{
"name": "Imperial Guard Alliance",
"description": "Form a strategic alliance with the Imperial Guard.",
"points": 5
},
{
"name": "Andorian Empire Expansion",
"description": "Expand Andorian influence into new territories.",
"points": 8
}
]
}

View file

@ -0,0 +1,60 @@
{
"id": "sisko",
"name": "Sisko",
"image": "captain.jpg",
"specialtyThresholds": {
"research": {
"basic": [
{ "threshold": 4, "multiplier": "x2" },
{ "threshold": 8, "multiplier": "x3" },
{ "threshold": 12, "multiplier": "x4" }
],
"advanced": [
{ "threshold": 3, "multiplier": "x2" },
{ "threshold": 6, "multiplier": "x3" },
{ "threshold": 9, "multiplier": "x4" }
]
},
"influence": {
"basic": [
{ "threshold": 4, "multiplier": "x2" },
{ "threshold": 8, "multiplier": "x3" },
{ "threshold": 12, "multiplier": "x4" }
],
"advanced": [
{ "threshold": 3, "multiplier": "x2" },
{ "threshold": 6, "multiplier": "x3" },
{ "threshold": 9, "multiplier": "x4" }
]
},
"military": {
"basic": [
{ "threshold": 4, "multiplier": "x2" },
{ "threshold": 8, "multiplier": "x3" },
{ "threshold": 12, "multiplier": "x4" }
],
"advanced": [
{ "threshold": 3, "multiplier": "x2" },
{ "threshold": 6, "multiplier": "x3" },
{ "threshold": 9, "multiplier": "x4" }
]
}
},
"basicMission": {
"name": "Bajoran Temple",
"description": "Protect and maintain the Bajoran Temple on Deep Space Nine.",
"points": 3
},
"advancedMissions": [
{
"name": "Dominion War",
"description": "Lead the Federation forces in a critical battle against the Dominion.",
"points": 5
},
{
"name": "Emissary of the Prophets",
"description": "Fulfill your role as the Emissary of the Prophets to save Bajor.",
"points": 8
}
]
}

View file

@ -1,8 +1,27 @@
--- ---
import Layout from '../layouts/Layout.astro'; import Layout from '../layouts/Layout.astro';
import { UIService } from '../utils/ui';
import { captainDataManager } from '../utils/captainData';
// Welcome to Astro! Wondering what to do next? Check out the Astro documentation at https://docs.astro.build // Welcome to Astro! Wondering what to do next? Check out the Astro documentation at https://docs.astro.build
// Don't want to use any of this? Delete everything in this file, the `assets`, `components`, and `layouts` directories, and start fresh. // Don't want to use any of this? Delete everything in this file, the `assets`, `components`, and `layouts` directories, and start fresh.
interface Captain {
id: string;
name: string;
}
const captains: Captain[] = [
{ id: 'picard', name: 'Picard' },
{ id: 'sisko', name: 'Sisko' },
{ id: 'sela', name: 'Sela' },
{ id: 'burnham', name: 'Burnham' },
{ id: 'shran', name: 'Shran' },
{ id: 'koloth', name: 'Koloth' }
];
// Initialize UI service
const uiService = UIService.getInstance();
--- ---
<Layout title="STCC - Token Counter"> <Layout title="STCC - Token Counter">
@ -10,18 +29,48 @@ import Layout from '../layouts/Layout.astro';
<h1 class="text-4xl font-bold text-center mb-8 text-blue-400">Star Trek Captain's Chair</h1> <h1 class="text-4xl font-bold text-center mb-8 text-blue-400">Star Trek Captain's Chair</h1>
<div class="grid grid-cols-1 md:grid-cols-2 gap-8"> <div class="grid grid-cols-1 md:grid-cols-2 gap-8">
<!-- Player 1 --> <!-- Player 1 Section -->
<div class="bg-gray-800 rounded-lg p-6 shadow-lg border border-blue-500"> <div class="bg-gray-800 p-6 rounded-lg shadow-lg">
<div class="mb-4"> <div class="space-y-4">
<input <!-- Missions Section -->
type="text" <div id="player1-missions" class="hidden">
id="player1-name" <h3 class="text-lg font-semibold text-gray-300 mb-2 text-center">Available Missions</h3>
class="w-full bg-gray-700 text-white rounded px-4 py-2 border border-blue-400 focus:outline-none focus:border-blue-300" <div id="player1-missions-list" class="space-y-2">
placeholder="Player 1 Name" <!-- Missions will be populated here -->
/> </div>
</div>
<div>
<label for="player1-captain" class="block text-sm font-medium text-gray-300 mb-1">Select Captain</label>
<select id="player1-captain" class="w-full bg-gray-700 text-white rounded px-3 py-2">
<option value="">Choose a captain...</option>
</select>
</div>
<div class="flex items-center space-x-4">
<div class="flex-1">
<label class="block text-sm font-medium text-gray-300 mb-2">Mode:</label>
<button
id="player1-mode-toggle"
class="relative inline-flex h-6 w-11 items-center rounded-full transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
role="switch"
aria-checked="false"
aria-label="Toggle mode"
>
<span class="sr-only">Toggle mode</span>
<span
class="absolute left-1 inline-block h-4 w-4 transform rounded-full bg-white transition-transform"
></span>
</button>
<span id="player1-mode-label" class="text-sm text-gray-300">Basic</span>
</div>
</div>
</div> </div>
<div class="space-y-4"> <div class="space-y-4">
<!-- Resources Section -->
<div>
<h3 class="text-lg font-semibold text-gray-300 mb-2 text-center">Resources</h3>
<div class="flex flex-col space-y-2">
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<label class="text-yellow-400">Latinum</label> <label class="text-yellow-400">Latinum</label>
<div class="flex items-center space-x-2"> <div class="flex items-center space-x-2">
@ -32,37 +81,114 @@ import Layout from '../layouts/Layout.astro';
</div> </div>
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<label class="text-blue-400">Dilithium</label> <label class="text-pink-400">Dilithium</label>
<div class="flex items-center space-x-2"> <div class="flex items-center space-x-2">
<button class="token-btn bg-blue-500 hover:bg-blue-600" data-player="1" data-token="dilithium" data-action="decrease">-</button> <button class="token-btn bg-pink-500 hover:bg-pink-600" data-player="1" data-token="dilithium" data-action="decrease">-</button>
<span id="player1-dilithium" class="text-2xl font-bold">0</span> <span id="player1-dilithium" class="text-2xl font-bold">0</span>
<button class="token-btn bg-blue-500 hover:bg-blue-600" data-player="1" data-token="dilithium" data-action="increase">+</button> <button class="token-btn bg-pink-500 hover:bg-pink-600" data-player="1" data-token="dilithium" data-action="increase">+</button>
</div> </div>
</div> </div>
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<label class="text-purple-400">Glory</label> <label class="text-blue-400">Glory</label>
<div class="flex items-center space-x-2"> <div class="flex items-center space-x-2">
<button class="token-btn bg-purple-500 hover:bg-purple-600" data-player="1" data-token="glory" data-action="decrease">-</button> <button class="token-btn bg-blue-500 hover:bg-blue-600" data-player="1" data-token="glory" data-action="decrease">-</button>
<span id="player1-glory" class="text-2xl font-bold">0</span> <span id="player1-glory" class="text-2xl font-bold">0</span>
<button class="token-btn bg-purple-500 hover:bg-purple-600" data-player="1" data-token="glory" data-action="increase">+</button> <button class="token-btn bg-blue-500 hover:bg-blue-600" data-player="1" data-token="glory" data-action="increase">+</button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<!-- Player 2 --> <!-- Horizontal Divider -->
<div class="bg-gray-800 rounded-lg p-6 shadow-lg border border-red-500"> <div class="w-full h-px bg-gray-700"></div>
<div class="mb-4">
<input <!-- Specialties Section -->
type="text" <div>
id="player2-name" <h3 class="text-lg font-semibold text-gray-300 mb-2 text-center">Specialties</h3>
class="w-full bg-gray-700 text-white rounded px-4 py-2 border border-red-400 focus:outline-none focus:border-red-300" <div class="flex flex-col space-y-2">
placeholder="Player 2 Name" <div class="flex items-center justify-between">
/> <div class="flex items-center w-32">
<label class="text-blue-400">Research</label>
<span id="player1-research-multiplier" class="text-sm text-gray-400 ml-2">x1</span>
</div>
<div class="flex items-center space-x-2">
<button class="token-btn bg-blue-500 hover:bg-blue-600" data-player="1" data-token="research" data-action="decrease">-</button>
<span id="player1-research" class="text-2xl font-bold">0</span>
<button class="token-btn bg-blue-500 hover:bg-blue-600" data-player="1" data-token="research" data-action="increase">+</button>
</div>
</div>
<div class="flex items-center justify-between">
<div class="flex items-center w-32">
<label class="text-yellow-400">Influence</label>
<span id="player1-influence-multiplier" class="text-sm text-gray-400 ml-2">x1</span>
</div>
<div class="flex items-center space-x-2">
<button class="token-btn bg-yellow-500 hover:bg-yellow-600" data-player="1" data-token="influence" data-action="decrease">-</button>
<span id="player1-influence" class="text-2xl font-bold">0</span>
<button class="token-btn bg-yellow-500 hover:bg-yellow-600" data-player="1" data-token="influence" data-action="increase">+</button>
</div>
</div>
<div class="flex items-center justify-between">
<div class="flex items-center w-32">
<label class="text-red-400">Military</label>
<span id="player1-military-multiplier" class="text-sm text-gray-400 ml-2">x1</span>
</div>
<div class="flex items-center space-x-2">
<button class="token-btn bg-red-500 hover:bg-red-600" data-player="1" data-token="military" data-action="decrease">-</button>
<span id="player1-military" class="text-2xl font-bold">0</span>
<button class="token-btn bg-red-500 hover:bg-red-600" data-player="1" data-token="military" data-action="increase">+</button>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Player 2 Section -->
<div class="bg-gray-800 p-6 rounded-lg shadow-lg">
<div class="space-y-4">
<!-- Missions Section -->
<div id="player2-missions" class="hidden">
<h3 class="text-lg font-semibold text-gray-300 mb-2 text-center">Available Missions</h3>
<div id="player2-missions-list" class="space-y-2">
<!-- Missions will be populated here -->
</div>
</div>
<div>
<label for="player2-captain" class="block text-sm font-medium text-gray-300 mb-1">Select Captain</label>
<select id="player2-captain" class="w-full bg-gray-700 text-white rounded px-3 py-2">
<option value="">Choose a captain...</option>
</select>
</div>
<div class="flex items-center space-x-4">
<div class="flex-1">
<label class="block text-sm font-medium text-gray-300 mb-2">Mode:</label>
<button
id="player2-mode-toggle"
class="relative inline-flex h-6 w-11 items-center rounded-full transition-colors focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2"
role="switch"
aria-checked="false"
aria-label="Toggle mode"
>
<span class="sr-only">Toggle mode</span>
<span
class="absolute left-1 inline-block h-4 w-4 transform rounded-full bg-white transition-transform"
></span>
</button>
<span id="player2-mode-label" class="text-sm text-gray-300">Basic</span>
</div>
</div>
</div> </div>
<div class="space-y-4"> <div class="space-y-4">
<!-- Resources Section -->
<div>
<h3 class="text-lg font-semibold text-gray-300 mb-2 text-center">Resources</h3>
<div class="flex flex-col space-y-2">
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<label class="text-yellow-400">Latinum</label> <label class="text-yellow-400">Latinum</label>
<div class="flex items-center space-x-2"> <div class="flex items-center space-x-2">
@ -73,20 +199,67 @@ import Layout from '../layouts/Layout.astro';
</div> </div>
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<label class="text-blue-400">Dilithium</label> <label class="text-pink-400">Dilithium</label>
<div class="flex items-center space-x-2"> <div class="flex items-center space-x-2">
<button class="token-btn bg-blue-500 hover:bg-blue-600" data-player="2" data-token="dilithium" data-action="decrease">-</button> <button class="token-btn bg-pink-500 hover:bg-pink-600" data-player="2" data-token="dilithium" data-action="decrease">-</button>
<span id="player2-dilithium" class="text-2xl font-bold">0</span> <span id="player2-dilithium" class="text-2xl font-bold">0</span>
<button class="token-btn bg-blue-500 hover:bg-blue-600" data-player="2" data-token="dilithium" data-action="increase">+</button> <button class="token-btn bg-pink-500 hover:bg-pink-600" data-player="2" data-token="dilithium" data-action="increase">+</button>
</div> </div>
</div> </div>
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<label class="text-purple-400">Glory</label> <label class="text-blue-400">Glory</label>
<div class="flex items-center space-x-2"> <div class="flex items-center space-x-2">
<button class="token-btn bg-purple-500 hover:bg-purple-600" data-player="2" data-token="glory" data-action="decrease">-</button> <button class="token-btn bg-blue-500 hover:bg-blue-600" data-player="2" data-token="glory" data-action="decrease">-</button>
<span id="player2-glory" class="text-2xl font-bold">0</span> <span id="player2-glory" class="text-2xl font-bold">0</span>
<button class="token-btn bg-purple-500 hover:bg-purple-600" data-player="2" data-token="glory" data-action="increase">+</button> <button class="token-btn bg-blue-500 hover:bg-blue-600" data-player="2" data-token="glory" data-action="increase">+</button>
</div>
</div>
</div>
</div>
<!-- Horizontal Divider -->
<div class="w-full h-px bg-gray-700"></div>
<!-- Specialties Section -->
<div>
<h3 class="text-lg font-semibold text-gray-300 mb-2 text-center">Specialties</h3>
<div class="flex flex-col space-y-2">
<div class="flex items-center justify-between">
<div class="flex items-center w-32">
<label class="text-blue-400">Research</label>
<span id="player2-research-multiplier" class="text-sm text-gray-400 ml-2">x1</span>
</div>
<div class="flex items-center space-x-2">
<button class="token-btn bg-blue-500 hover:bg-blue-600" data-player="2" data-token="research" data-action="decrease">-</button>
<span id="player2-research" class="text-2xl font-bold">0</span>
<button class="token-btn bg-blue-500 hover:bg-blue-600" data-player="2" data-token="research" data-action="increase">+</button>
</div>
</div>
<div class="flex items-center justify-between">
<div class="flex items-center w-32">
<label class="text-yellow-400">Influence</label>
<span id="player2-influence-multiplier" class="text-sm text-gray-400 ml-2">x1</span>
</div>
<div class="flex items-center space-x-2">
<button class="token-btn bg-yellow-500 hover:bg-yellow-600" data-player="2" data-token="influence" data-action="decrease">-</button>
<span id="player2-influence" class="text-2xl font-bold">0</span>
<button class="token-btn bg-yellow-500 hover:bg-yellow-600" data-player="2" data-token="influence" data-action="increase">+</button>
</div>
</div>
<div class="flex items-center justify-between">
<div class="flex items-center w-32">
<label class="text-red-400">Military</label>
<span id="player2-military-multiplier" class="text-sm text-gray-400 ml-2">x1</span>
</div>
<div class="flex items-center space-x-2">
<button class="token-btn bg-red-500 hover:bg-red-600" data-player="2" data-token="military" data-action="decrease">-</button>
<span id="player2-military" class="text-2xl font-bold">0</span>
<button class="token-btn bg-red-500 hover:bg-red-600" data-player="2" data-token="military" data-action="increase">+</button>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -105,97 +278,47 @@ import Layout from '../layouts/Layout.astro';
.token-btn { .token-btn {
@apply text-white font-bold py-2 px-4 rounded transition-colors duration-200; @apply text-white font-bold py-2 px-4 rounded transition-colors duration-200;
} }
/* Toggle switch styles */
button[role="switch"] {
@apply bg-gray-600;
}
button[role="switch"][aria-checked="true"] {
@apply bg-blue-500;
}
button[role="switch"][aria-checked="true"] span {
@apply translate-x-5;
}
/* Player 2 specific toggle styles */
#player2-mode-toggle[aria-checked="true"] {
@apply bg-red-500;
}
/* Mission card styles */
.mission-card {
@apply bg-gray-700 rounded-lg p-4 border border-gray-600;
}
.mission-card h4 {
@apply text-lg font-semibold text-gray-200 mb-2;
}
.mission-card p {
@apply text-gray-400 text-sm mb-2;
}
.mission-points {
@apply text-sm text-green-400 font-semibold;
}
</style> </style>
<script> <script>
// Initialize state from localStorage or default values import { UIService } from '../utils/ui';
const initialState = {
player1: {
name: localStorage.getItem('player1Name') || 'Player 1',
latinum: parseInt(localStorage.getItem('player1Latinum') || '0'),
dilithium: parseInt(localStorage.getItem('player1Dilithium') || '0'),
glory: parseInt(localStorage.getItem('player1Glory') || '0')
},
player2: {
name: localStorage.getItem('player2Name') || 'Player 2',
latinum: parseInt(localStorage.getItem('player2Latinum') || '0'),
dilithium: parseInt(localStorage.getItem('player2Dilithium') || '0'),
glory: parseInt(localStorage.getItem('player2Glory') || '0')
}
};
// Update UI with initial state document.addEventListener('DOMContentLoaded', () => {
function updateUI() { UIService.getInstance().initialize();
// Update player names
document.getElementById('player1-name').value = initialState.player1.name;
document.getElementById('player2-name').value = initialState.player2.name;
// Update token counts
document.getElementById('player1-latinum').textContent = initialState.player1.latinum;
document.getElementById('player1-dilithium').textContent = initialState.player1.dilithium;
document.getElementById('player1-glory').textContent = initialState.player1.glory;
document.getElementById('player2-latinum').textContent = initialState.player2.latinum;
document.getElementById('player2-dilithium').textContent = initialState.player2.dilithium;
document.getElementById('player2-glory').textContent = initialState.player2.glory;
}
// Save state to localStorage
function saveState() {
localStorage.setItem('player1Name', initialState.player1.name);
localStorage.setItem('player1Latinum', initialState.player1.latinum.toString());
localStorage.setItem('player1Dilithium', initialState.player1.dilithium.toString());
localStorage.setItem('player1Glory', initialState.player1.glory.toString());
localStorage.setItem('player2Name', initialState.player2.name);
localStorage.setItem('player2Latinum', initialState.player2.latinum.toString());
localStorage.setItem('player2Dilithium', initialState.player2.dilithium.toString());
localStorage.setItem('player2Glory', initialState.player2.glory.toString());
}
// Handle token button clicks
document.querySelectorAll('.token-btn').forEach(button => {
button.addEventListener('click', () => {
const player = button.dataset.player;
const token = button.dataset.token;
const action = button.dataset.action;
if (action === 'increase') {
initialState[`player${player}`][token]++;
} else {
initialState[`player${player}`][token] = Math.max(0, initialState[`player${player}`][token] - 1);
}
updateUI();
saveState();
}); });
});
// Handle player name changes
document.getElementById('player1-name').addEventListener('input', (e) => {
initialState.player1.name = e.target.value;
saveState();
});
document.getElementById('player2-name').addEventListener('input', (e) => {
initialState.player2.name = e.target.value;
saveState();
});
// Handle reset button
document.getElementById('reset-btn').addEventListener('click', () => {
if (confirm('Are you sure you want to reset all counters?')) {
initialState.player1.latinum = 0;
initialState.player1.dilithium = 0;
initialState.player1.glory = 0;
initialState.player2.latinum = 0;
initialState.player2.dilithium = 0;
initialState.player2.glory = 0;
updateUI();
saveState();
}
});
// Initial UI update
updateUI();
</script> </script>

158
src/utils/captainData.ts Normal file
View file

@ -0,0 +1,158 @@
import type { Captain } from './types';
interface ThresholdTable {
basic: {
threshold: number;
multiplier: string;
}[];
advanced: {
threshold: number;
multiplier: string;
}[];
}
/**
* @typedef {Object} Mission
* @property {string} name - The name of the mission
* @property {string} description - A brief description of the mission
* @property {number} points - The reward points for completing the mission
*/
interface Mission {
name: string;
description: string;
points: number;
}
/**
* @typedef {Object} CaptainConfig
* @property {string} id - Unique identifier for the captain
* @property {string} name - Display name of the captain
* @property {string} image - Filename of the captain's image
* @property {Object.<string, ThresholdTable>} specialtyThresholds - Threshold tables for each specialty
* @property {Mission} basicMission - The single mission available in basic mode
* @property {Mission[]} advancedMissions - Array of missions available in advanced mode
*/
interface CaptainConfig {
id: string;
name: string;
image: string;
specialtyThresholds: {
[key: string]: ThresholdTable;
};
basicMission: Mission;
advancedMissions?: Mission[];
}
class CaptainDataManager {
private static instance: CaptainDataManager;
private captainConfigs: Map<string, CaptainConfig> = new Map();
private availableCaptains: Captain[] = [];
private constructor() {}
static getInstance(): CaptainDataManager {
if (!CaptainDataManager.instance) {
CaptainDataManager.instance = new CaptainDataManager();
}
return CaptainDataManager.instance;
}
async loadAvailableCaptains(): Promise<Captain[]> {
if (this.availableCaptains.length > 0) {
console.log('Returning cached captains:', this.availableCaptains);
return this.availableCaptains;
}
try {
console.log('Loading captains from index.json...');
const response = await fetch('/src/assets/captains/index.json');
if (!response.ok) {
throw new Error('Failed to load captains index');
}
const data = await response.json();
this.availableCaptains = data.captains;
console.log('Set available captains:', this.availableCaptains);
return this.availableCaptains;
} catch (error) {
console.error('Error loading captains:', error);
throw error;
}
}
async loadCaptainConfig(captainId: string): Promise<CaptainConfig> {
if (this.captainConfigs.has(captainId)) {
return this.captainConfigs.get(captainId)!;
}
try {
const response = await fetch(`/src/assets/captains/${captainId}.json`);
if (!response.ok) {
throw new Error(`Failed to load captain config for ${captainId}`);
}
const config: CaptainConfig = await response.json();
this.captainConfigs.set(captainId, config);
return config;
} catch (error) {
console.error(`Error loading captain config for ${captainId}:`, error);
throw error;
}
}
getCaptainImagePath(captainId: string): string {
return `/src/assets/captains/${captainId}/captain.jpg`;
}
getSpecialtyThreshold(captainId: string, specialty: 'research' | 'influence' | 'military', mode: 'basic' | 'advanced', value: number): string {
const config = this.captainConfigs.get(captainId);
if (!config) {
throw new Error(`Captain config not found for ${captainId}`);
}
const thresholds = config.specialtyThresholds[specialty][mode];
console.log(`Calculating multiplier for ${captainId} ${specialty} in ${mode} mode:`, {
currentValue: value,
thresholds: thresholds
});
// Find the highest threshold that the current value meets or exceeds
for (let i = thresholds.length - 1; i >= 0; i--) {
if (value >= thresholds[i].threshold) {
console.log(`Found threshold ${thresholds[i].threshold} with multiplier ${thresholds[i].multiplier}`);
return thresholds[i].multiplier;
}
}
console.log('No thresholds met, returning default multiplier x1');
return 'x1';
}
/**
* Get available missions for a captain based on their current stats
* @param {string} captainId - The ID of the captain
* @param {Object} stats - Current stats of the player
* @param {number} stats.research - Current research value
* @param {number} stats.influence - Current influence value
* @param {number} stats.military - Current military value
* @param {string} mode - The current game mode ('basic' or 'advanced')
* @returns {Mission[]} Array of available missions
*/
getAvailableMissions(captainId: string, stats: { research: number; influence: number; military: number }, mode: 'basic' | 'advanced' = 'basic'): Mission[] {
const config = this.captainConfigs.get(captainId);
if (!config) {
throw new Error(`Captain config not found for ${captainId}`);
}
const missions: Mission[] = [];
// Always include the basic mission
missions.push(config.basicMission);
// In advanced mode, include additional missions
if (mode === 'advanced') {
missions.push(...config.advancedMissions || []);
}
return missions;
}
}
export const captainDataManager = CaptainDataManager.getInstance();

83
src/utils/storage.ts Normal file
View file

@ -0,0 +1,83 @@
import type { GameState, PlayerNumber } from './types';
export class StorageService {
private static readonly STORAGE_PREFIX = 'stcc_';
public static saveGameState(state: GameState): void {
// Save player 1 state
localStorage.setItem(`${this.STORAGE_PREFIX}player1_captain`, state.player1.captain);
localStorage.setItem(`${this.STORAGE_PREFIX}player1_mode`, state.player1.mode);
localStorage.setItem(`${this.STORAGE_PREFIX}player1_latinum`, state.player1.latinum.toString());
localStorage.setItem(`${this.STORAGE_PREFIX}player1_dilithium`, state.player1.dilithium.toString());
localStorage.setItem(`${this.STORAGE_PREFIX}player1_glory`, state.player1.glory.toString());
localStorage.setItem(`${this.STORAGE_PREFIX}player1_research`, state.player1.research.toString());
localStorage.setItem(`${this.STORAGE_PREFIX}player1_influence`, state.player1.influence.toString());
localStorage.setItem(`${this.STORAGE_PREFIX}player1_military`, state.player1.military.toString());
// Save player 2 state
localStorage.setItem(`${this.STORAGE_PREFIX}player2_captain`, state.player2.captain);
localStorage.setItem(`${this.STORAGE_PREFIX}player2_mode`, state.player2.mode);
localStorage.setItem(`${this.STORAGE_PREFIX}player2_latinum`, state.player2.latinum.toString());
localStorage.setItem(`${this.STORAGE_PREFIX}player2_dilithium`, state.player2.dilithium.toString());
localStorage.setItem(`${this.STORAGE_PREFIX}player2_glory`, state.player2.glory.toString());
localStorage.setItem(`${this.STORAGE_PREFIX}player2_research`, state.player2.research.toString());
localStorage.setItem(`${this.STORAGE_PREFIX}player2_influence`, state.player2.influence.toString());
localStorage.setItem(`${this.STORAGE_PREFIX}player2_military`, state.player2.military.toString());
}
public static loadGameState(): GameState {
const state: GameState = {
player1: {
captain: '',
mode: 'basic',
latinum: 0,
dilithium: 0,
glory: 0,
research: 0,
influence: 0,
military: 0
},
player2: {
captain: '',
mode: 'basic',
latinum: 0,
dilithium: 0,
glory: 0,
research: 0,
influence: 0,
military: 0
}
};
// Load player 1 state
state.player1.captain = localStorage.getItem(`${this.STORAGE_PREFIX}player1_captain`) || '';
state.player1.mode = (localStorage.getItem(`${this.STORAGE_PREFIX}player1_mode`) as 'basic' | 'advanced') || 'basic';
state.player1.latinum = parseInt(localStorage.getItem(`${this.STORAGE_PREFIX}player1_latinum`) || '0', 10);
state.player1.dilithium = parseInt(localStorage.getItem(`${this.STORAGE_PREFIX}player1_dilithium`) || '0', 10);
state.player1.glory = parseInt(localStorage.getItem(`${this.STORAGE_PREFIX}player1_glory`) || '0', 10);
state.player1.research = parseInt(localStorage.getItem(`${this.STORAGE_PREFIX}player1_research`) || '0', 10);
state.player1.influence = parseInt(localStorage.getItem(`${this.STORAGE_PREFIX}player1_influence`) || '0', 10);
state.player1.military = parseInt(localStorage.getItem(`${this.STORAGE_PREFIX}player1_military`) || '0', 10);
// Load player 2 state
state.player2.captain = localStorage.getItem(`${this.STORAGE_PREFIX}player2_captain`) || '';
state.player2.mode = (localStorage.getItem(`${this.STORAGE_PREFIX}player2_mode`) as 'basic' | 'advanced') || 'basic';
state.player2.latinum = parseInt(localStorage.getItem(`${this.STORAGE_PREFIX}player2_latinum`) || '0', 10);
state.player2.dilithium = parseInt(localStorage.getItem(`${this.STORAGE_PREFIX}player2_dilithium`) || '0', 10);
state.player2.glory = parseInt(localStorage.getItem(`${this.STORAGE_PREFIX}player2_glory`) || '0', 10);
state.player2.research = parseInt(localStorage.getItem(`${this.STORAGE_PREFIX}player2_research`) || '0', 10);
state.player2.influence = parseInt(localStorage.getItem(`${this.STORAGE_PREFIX}player2_influence`) || '0', 10);
state.player2.military = parseInt(localStorage.getItem(`${this.STORAGE_PREFIX}player2_military`) || '0', 10);
return state;
}
public static clearGameState(): void {
// Clear all state entries
Object.keys(localStorage).forEach(key => {
if (key.startsWith(this.STORAGE_PREFIX)) {
localStorage.removeItem(key);
}
});
}
}

31
src/utils/types.ts Normal file
View file

@ -0,0 +1,31 @@
export interface Captain {
id: string;
name: string;
}
export interface GameState {
player1: PlayerState;
player2: PlayerState;
}
export interface PlayerState {
captain: string;
mode: 'basic' | 'advanced';
latinum: number;
dilithium: number;
glory: number;
research: number;
influence: number;
military: number;
}
export const TOKEN_TYPES = {
RESOURCES: ['latinum', 'dilithium', 'glory'] as const,
SPECIALTIES: ['research', 'influence', 'military'] as const
} as const;
export type ResourceType = typeof TOKEN_TYPES.RESOURCES[number];
export type SpecialtyType = typeof TOKEN_TYPES.SPECIALTIES[number];
export type TokenType = ResourceType | SpecialtyType;
export type PlayerNumber = 1 | 2;

392
src/utils/ui.ts Normal file
View file

@ -0,0 +1,392 @@
import type { GameState, PlayerNumber, TokenType } from './types';
import { captainDataManager } from './captainData';
import { StorageService } from './storage';
export class UIService {
private static instance: UIService;
private gameState: GameState;
private constructor() {
this.gameState = {
player1: {
captain: '',
mode: 'basic',
latinum: 0,
dilithium: 0,
glory: 0,
research: 0,
influence: 0,
military: 0
},
player2: {
captain: '',
mode: 'basic',
latinum: 0,
dilithium: 0,
glory: 0,
research: 0,
influence: 0,
military: 0
}
};
}
static getInstance(): UIService {
if (!UIService.instance) {
UIService.instance = new UIService();
}
return UIService.instance;
}
private getElement<T extends HTMLElement>(id: string): T | null {
const element = document.getElementById(id) as T | null;
if (!element) {
console.error(`Element with id '${id}' not found`);
}
return element;
}
private async updateCaptainOptions(playerNum: PlayerNumber): Promise<void> {
const select = this.getElement<HTMLSelectElement>(`player${playerNum}-captain`);
if (!select) return;
try {
console.log('Loading available captains...');
const captains = await captainDataManager.loadAvailableCaptains();
console.log('Loaded captains:', captains);
const currentValue = select.value;
// Clear all existing options
select.innerHTML = '';
// Get the other player's selected captain
const otherPlayerNum = playerNum === 1 ? 2 : 1;
const otherPlayerKey = `player${otherPlayerNum}` as keyof GameState;
const otherPlayerCaptain = this.gameState[otherPlayerKey].captain;
// Filter out the other player's selected captain
const availableCaptains = captains.filter(captain => captain.id !== otherPlayerCaptain);
// Add captain options
availableCaptains.forEach(captain => {
console.log('Adding captain option:', captain);
const option = document.createElement('option');
option.value = captain.id;
option.textContent = captain.name;
select.appendChild(option);
});
// If no captain is currently selected, select the first one
if (!currentValue && availableCaptains.length > 0) {
select.value = availableCaptains[0].id;
this.gameState[`player${playerNum}` as keyof GameState].captain = availableCaptains[0].id;
try {
await captainDataManager.loadCaptainConfig(availableCaptains[0].id);
} catch (error) {
console.error('Error loading captain config:', error);
}
} else if (currentValue && availableCaptains.some(c => c.id === currentValue)) {
// Restore the previously selected value if it exists and is still available
select.value = currentValue;
} else if (availableCaptains.length > 0) {
// If the current selection is no longer available, select the first available captain
select.value = availableCaptains[0].id;
this.gameState[`player${playerNum}` as keyof GameState].captain = availableCaptains[0].id;
try {
await captainDataManager.loadCaptainConfig(availableCaptains[0].id);
} catch (error) {
console.error('Error loading captain config:', error);
}
}
} catch (error) {
console.error('Error updating captain options:', error);
}
}
private calculateMultiplier(playerNum: PlayerNumber, specialty: 'research' | 'influence' | 'military'): string {
const playerKey = `player${playerNum}` as keyof GameState;
const player = this.gameState[playerKey];
console.log('Calculating multiplier:', {
playerNum,
specialty,
captain: player.captain,
mode: player.mode,
value: player[specialty]
});
if (!player.captain) {
console.log('No captain selected, returning x1');
return 'x1';
}
try {
const multiplier = captainDataManager.getSpecialtyThreshold(
player.captain,
specialty,
player.mode,
player[specialty]
);
console.log('Calculated multiplier:', multiplier);
return multiplier;
} catch (error) {
console.error(`Error calculating multiplier: ${error}`);
return 'x1';
}
}
private updateUI(): void {
// Update player selections
this.updateCaptainOptions(1);
this.updateCaptainOptions(2);
// Update mode toggles and counter buttons
[1, 2].forEach(playerNum => {
const playerKey = `player${playerNum}` as keyof GameState;
const player = this.gameState[playerKey];
const toggle = this.getElement<HTMLButtonElement>(`player${playerNum}-mode-toggle`);
const label = this.getElement<HTMLSpanElement>(`player${playerNum}-mode-label`);
// Disable/enable mode toggle based on captain selection
const isCaptainSelected = player.captain !== '' && player.captain !== 'Choose a captain...';
if (toggle) {
toggle.disabled = !isCaptainSelected;
toggle.setAttribute('aria-checked', (player.mode === 'advanced').toString());
if (!isCaptainSelected) {
toggle.classList.add('opacity-50', 'cursor-not-allowed');
} else {
toggle.classList.remove('opacity-50', 'cursor-not-allowed');
}
}
if (label) label.textContent = player.mode === 'advanced' ? 'Advanced' : 'Basic';
// Update missions display
const missionsContainer = this.getElement<HTMLDivElement>(`player${playerNum}-missions`);
const missionsList = this.getElement<HTMLDivElement>(`player${playerNum}-missions-list`);
if (missionsContainer && missionsList) {
missionsList.innerHTML = '';
if (isCaptainSelected) {
const missions = captainDataManager.getAvailableMissions(
player.captain,
{
research: player.research,
influence: player.influence,
military: player.military
},
player.mode
);
missions.forEach(mission => {
const li = document.createElement('li');
li.textContent = `${mission.name} (${mission.points} points)`;
missionsList.appendChild(li);
});
missionsContainer.classList.remove('hidden');
} else {
missionsContainer.classList.add('hidden');
}
}
// Disable/enable counter buttons based on captain selection
const playerButtons = document.querySelectorAll(`.token-btn[data-player="${playerNum}"]`);
playerButtons.forEach(button => {
(button as HTMLButtonElement).disabled = !isCaptainSelected;
if (!isCaptainSelected) {
button.classList.add('opacity-50', 'cursor-not-allowed');
} else {
button.classList.remove('opacity-50', 'cursor-not-allowed');
}
});
// Reset counters if no captain is selected
if (!isCaptainSelected) {
player.latinum = 0;
player.dilithium = 0;
player.glory = 0;
player.research = 0;
player.influence = 0;
player.military = 0;
}
});
// Update resource counters
['latinum', 'dilithium', 'glory'].forEach(resource => {
[1, 2].forEach(playerNum => {
const playerKey = `player${playerNum}` as keyof GameState;
const player = this.gameState[playerKey];
const counter = this.getElement<HTMLSpanElement>(`player${playerNum}-${resource}`);
if (counter) counter.textContent = player[resource as keyof typeof player].toString();
});
});
// Update specialty counters and multipliers
['research', 'influence', 'military'].forEach(specialty => {
[1, 2].forEach(playerNum => {
const playerKey = `player${playerNum}` as keyof GameState;
const player = this.gameState[playerKey];
const counter = this.getElement<HTMLSpanElement>(`player${playerNum}-${specialty}`);
const multiplier = this.getElement<HTMLSpanElement>(`player${playerNum}-${specialty}-multiplier`);
if (counter) counter.textContent = player[specialty as keyof typeof player].toString();
if (multiplier) multiplier.textContent = this.calculateMultiplier(playerNum as PlayerNumber, specialty as 'research' | 'influence' | 'military');
});
});
}
private updateMissions(): void {
[1, 2].forEach(playerNum => {
const playerKey = `player${playerNum}` as keyof GameState;
const player = this.gameState[playerKey];
const missionsContainer = this.getElement<HTMLDivElement>(`player${playerNum}-missions`);
const missionsList = this.getElement<HTMLDivElement>(`player${playerNum}-missions-list`);
if (!missionsContainer || !missionsList) return;
// Clear existing missions
missionsList.innerHTML = '';
// Get available missions
const missions = captainDataManager.getAvailableMissions(
player.captain,
{
research: player.research,
influence: player.influence,
military: player.military
},
player.mode
);
// Add missions to the list
missions.forEach(mission => {
const li = document.createElement('li');
li.textContent = `${mission.name} (${mission.points} points)`;
missionsList.appendChild(li);
});
});
}
private handleTokenChange(playerNum: PlayerNumber, token: TokenType, action: 'increment' | 'decrement'): void {
const playerKey = `player${playerNum}` as keyof GameState;
const currentValue = this.gameState[playerKey][token];
console.log('Handling token change:', {
playerNum,
token,
action,
currentValue
});
if (action === 'increment') {
this.gameState[playerKey][token] = Math.min(15, currentValue + 1);
} else {
this.gameState[playerKey][token] = Math.max(0, currentValue - 1);
}
console.log('New value:', this.gameState[playerKey][token]);
this.updateUI();
StorageService.saveGameState(this.gameState);
}
private resetCounters(): void {
if (confirm('Are you sure you want to reset all counters?')) {
['1', '2'].forEach(playerNum => {
const playerKey = `player${playerNum}` as keyof GameState;
const tokens: TokenType[] = ['latinum', 'dilithium', 'glory', 'research', 'influence', 'military'];
tokens.forEach(token => {
this.gameState[playerKey][token] = 0;
});
});
this.updateUI();
StorageService.saveGameState(this.gameState);
}
}
public initialize(): void {
// Load saved state
const savedState = StorageService.loadGameState();
this.gameState = savedState;
// Captain selection
const player1CaptainSelect = this.getElement<HTMLSelectElement>('player1-captain');
const player2CaptainSelect = this.getElement<HTMLSelectElement>('player2-captain');
if (player1CaptainSelect) {
player1CaptainSelect.addEventListener('change', async (e: Event) => {
const target = e.target as HTMLSelectElement;
this.gameState.player1.captain = target.value;
try {
await captainDataManager.loadCaptainConfig(target.value);
// Update player 2's options to remove the selected captain
await this.updateCaptainOptions(2);
} catch (error) {
console.error('Error loading captain config:', error);
}
this.updateUI();
StorageService.saveGameState(this.gameState);
});
}
if (player2CaptainSelect) {
player2CaptainSelect.addEventListener('change', async (e: Event) => {
const target = e.target as HTMLSelectElement;
this.gameState.player2.captain = target.value;
try {
await captainDataManager.loadCaptainConfig(target.value);
// Update player 1's options to remove the selected captain
await this.updateCaptainOptions(1);
} catch (error) {
console.error('Error loading captain config:', error);
}
this.updateUI();
StorageService.saveGameState(this.gameState);
});
}
// Mode toggles
[1, 2].forEach(playerNum => {
const toggle = this.getElement<HTMLButtonElement>(`player${playerNum}-mode-toggle`);
if (toggle) {
toggle.addEventListener('click', () => {
const playerKey = `player${playerNum}` as keyof GameState;
this.gameState[playerKey].mode = this.gameState[playerKey].mode === 'basic' ? 'advanced' : 'basic';
this.updateUI();
StorageService.saveGameState(this.gameState);
});
}
});
// Token button handlers
document.querySelectorAll('.token-btn').forEach(button => {
const element = button as HTMLElement;
const player = element.dataset.player;
const type = element.dataset.token;
const action = element.dataset.action;
if (player && type && action) {
element.addEventListener('click', () => {
const playerNum = parseInt(player, 10) as PlayerNumber;
const tokenType = type as TokenType;
const actionType = action === 'increase' ? 'increment' : 'decrement';
console.log('Token button clicked:', { playerNum, tokenType, actionType });
this.handleTokenChange(playerNum, tokenType, actionType);
});
}
});
// Reset button
const resetButton = this.getElement<HTMLButtonElement>('reset-btn');
if (resetButton) {
resetButton.addEventListener('click', () => this.resetCounters());
}
// Initial UI update
this.updateUI();
}
}

View file

@ -1,5 +1,14 @@
{ {
"extends": "astro/tsconfigs/strict", "compilerOptions": {
"include": [".astro/types.d.ts", "**/*"], "target": "ES2017",
"exclude": ["dist"] "module": "ESNext",
"moduleResolution": "node",
"esModuleInterop": true,
"strict": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"lib": ["ES2017", "DOM"]
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
} }