Skip to content

Instantly share code, notes, and snippets.

@mode-mercury
Created October 18, 2025 00:47
Show Gist options
  • Select an option

  • Save mode-mercury/b7cded81fb56b167e98cf4c515be9612 to your computer and use it in GitHub Desktop.

Select an option

Save mode-mercury/b7cded81fb56b167e98cf4c515be9612 to your computer and use it in GitHub Desktop.
Untitled
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Nexus Chess - Cyberpunk Tower Defense</title>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
background: linear-gradient(135deg, #0a0a1a 0%, #151530 50%, #0a0a1a 100%);
color: white;
font-family: 'Orbitron', 'Courier New', monospace;
min-height: 100vh;
overflow-x: hidden;
}
:root {
--cyber-blue: #00f0ff;
--cyber-pink: #ff00aa;
--cyber-purple: #aa00ff;
--cyber-gold: #ffd700;
--cyber-green: #00ff88;
--cyber-red: #ff0044;
--cyber-darkblue: #1e3a8a;
--cyber-teal: #00ffcc;
--cyber-orange: #ff7700;
--cyber-violet: #cc00ff;
}
.particles {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: -1;
}
.particle {
position: absolute;
width: 2px;
height: 2px;
background: var(--cyber-blue);
animation: float 12s infinite linear;
}
.particle.pink { background: var(--cyber-pink); }
.particle.green { background: var(--cyber-green); }
.particle.purple { background: var(--cyber-purple); }
.particle.teal { background: var(--cyber-teal); }
.particle.orange { background: var(--cyber-orange); }
.particle.violet { background: var(--cyber-violet); }
@keyframes float {
0% { transform: translateY(100vh) translateX(0px) rotate(0deg); opacity: 0; }
10% { opacity: 1; }
90% { opacity: 1; }
100% { transform: translateY(-10vh) translateX(100px) rotate(180deg); opacity: 0; }
}
.game-container {
background: rgba(10, 10, 30, 0.4);
backdrop-filter: blur(15px);
border: 1px solid rgba(0, 240, 255, 0.4);
border-radius: 20px;
min-height: 100vh;
display: flex;
flex-direction: column;
}
.card {
width: 100px;
height: 160px;
cursor: pointer;
position: relative;
user-select: none;
transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
transform-origin: center;
}
@media (min-width: 768px) {
.card {
width: 110px;
height: 176px;
}
}
.card:hover {
transform: translateY(-8px) scale(1.05);
z-index: 10;
}
.card-disabled {
opacity: 0.5;
filter: grayscale(50%);
pointer-events: none;
}
@keyframes legendary-glow {
0%, 100% { filter: drop-shadow(0 0 10px #ffd700); }
50% { filter: drop-shadow(0 0 20px #ffd700) drop-shadow(0 0 30px #ff6b35); }
}
@keyframes epic-glow {
0%, 100% { filter: drop-shadow(0 0 10px #ff7700); }
50% { filter: drop-shadow(0 0 15px #ff7700) drop-shadow(0 0 25px #ff4400); }
}
@keyframes rare-glow {
0%, 100% { filter: drop-shadow(0 0 8px #c0c0c0); }
50% { filter: drop-shadow(0 0 12px #c0c0c0) drop-shadow(0 0 20px #ffffff); }
}
.card-legendary {
animation: legendary-glow 3s ease-in-out infinite;
}
.card-epic {
animation: epic-glow 3s ease-in-out infinite;
}
.card-rare {
animation: rare-glow 3s ease-in-out infinite;
}
/* 3D Game Board Styles */
#game-canvas {
width: 100%;
height: 500px;
border-radius: 12px;
overflow: hidden;
background: linear-gradient(135deg, rgba(5, 5, 15, 0.95) 0%, rgba(20, 10, 40, 0.95) 50%, rgba(5, 5, 15, 0.95) 100%);
box-shadow:
0 0 40px rgba(170, 0, 255, 0.5),
inset 0 0 30px rgba(170, 0, 255, 0.3),
0 0 80px rgba(0, 255, 204, 0.4);
}
/* Piece Menu */
.piece-menu {
position: fixed;
background: linear-gradient(135deg, rgba(0, 0, 0, 0.95) 0%, rgba(30, 30, 30, 0.9) 100%);
border: 2px solid #00f0ff;
border-radius: 8px;
padding: 8px;
min-width: 200px;
z-index: 100;
backdrop-filter: blur(10px);
box-shadow: 0 0 20px rgba(0, 240, 255, 0.4);
transform: translateX(-50%);
left: 50%;
bottom: 20px;
}
.piece-menu button {
display: block;
width: 100%;
padding: 6px 10px;
margin: 2px 0;
background: rgba(0, 240, 255, 0.2);
border: 1px solid #00f0ff;
color: #00f0ff;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
transition: all 0.3s ease;
}
.piece-menu button:hover {
background: rgba(0, 240, 255, 0.4);
box-shadow: 0 0 10px rgba(0, 240, 255, 0.6);
}
.piece-menu button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.modal-enter {
animation: modalEnter 0.3s ease-out;
}
@keyframes modalEnter {
from {
opacity: 0;
transform: scale(0.9) translateY(-20px);
}
to {
opacity: 1;
transform: scale(1) translateY(0);
}
}
.difficulty-btn {
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.difficulty-btn::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent);
transition: left 0.5s;
}
.difficulty-btn:hover::before {
left: 100%;
}
.floating {
animation: float 3s ease-in-out infinite;
}
@keyframes float {
0%, 100% {
transform: translateY(0px);
}
50% {
transform: translateY(-10px);
}
}
.compact-section {
background: linear-gradient(135deg, rgba(20, 25, 45, 0.8) 0%, rgba(10, 15, 30, 0.9) 100%);
border: 1px solid rgba(80, 90, 120, 0.6);
backdrop-filter: blur(10px);
}
.card-detail-modal {
backdrop-filter: blur(20px);
}
.logo-text {
background: linear-gradient(45deg, #00f0ff, #aa00ff, #ff00aa, #00ff88);
background-size: 300% 300%;
animation: gradientShift 4s ease infinite;
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
text-shadow: 0 0 20px rgba(0, 240, 255, 0.5);
}
@keyframes gradientShift {
0% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
}
.profile-icon {
width: 32px;
height: 32px;
background-color: #111827;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
border: 2px solid;
}
.profile-icon svg {
width: 18px;
height: 18px;
}
#player-info .profile-icon {
border-color: #00f0ff;
}
#opponent-info .profile-icon {
border-color: #ff00aa;
}
.board-container {
display: flex;
justify-content: center;
align-items: center;
flex: 1;
padding: 1rem;
position: relative;
}
.hand-fan {
display: flex;
justify-content: center;
align-items: flex-end;
min-height: 180px;
perspective: 1000px;
position: relative;
}
.card-fan-wrapper {
position: relative;
transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
margin: 0 -15px;
}
.card-fan-wrapper:hover {
transform: translateY(-20px) scale(1.05) !important;
z-index: 20;
}
#battle-log::-webkit-scrollbar {
width: 4px;
}
#battle-log::-webkit-scrollbar-thumb {
background-color: rgba(0, 240, 255, 0.4);
border-radius: 2px;
}
.stats-compact {
display: flex;
align-items: center;
gap: 8px;
font-size: 0.75rem;
flex-wrap: wrap;
max-width: 180px;
}
.health-bar {
background: linear-gradient(90deg, #ff0044 0%, #ff7700 50%, #00ff88 100%);
height: 4px;
border-radius: 2px;
transition: all 0.3s ease;
}
.exp-bar {
background: linear-gradient(90deg, #aa00ff 0%, #ff00aa 50%, #00f0ff 100%);
height: 6px;
border-radius: 3px;
transition: all 0.3s ease;
}
/* Player info layout fixes */
#player-info, #opponent-info {
max-width: 180px;
overflow: hidden;
}
#player-info .stats-compact, #opponent-info .stats-compact {
justify-content: flex-start;
}
#opponent-info .stats-compact {
justify-content: flex-end;
}
@media (max-width: 768px) {
#player-info, #opponent-info {
max-width: 140px;
}
.stats-compact {
font-size: 0.7rem;
gap: 4px;
}
}
/* Chest animation */
@keyframes chest-glow {
0%, 100% {
transform: translateY(0) rotateY(0deg);
box-shadow: 0 0 10px currentColor;
}
50% {
transform: translateY(-5px) rotateY(10deg);
box-shadow: 0 0 20px currentColor;
}
}
.chest-legendary {
animation: chest-glow 3s ease-in-out infinite;
color: #ffd700;
}
.chest-epic {
animation: chest-glow 3s ease-inout infinite;
color: #ff7700;
}
.chest-rare {
animation: chest-glow 3s ease-in-out infinite;
color: #c0c0c0;
}
/* Enhanced mode selection */
.mode-selection-bg {
background: radial-gradient(circle at center, #0a0a1a 0%, #050510 100%);
}
.nexus-glow {
box-shadow: 0 0 60px rgba(0, 240, 255, 0.4), 0 0 100px rgba(170, 0, 255, 0.3);
}
.selection-card {
background: linear-gradient(135deg, rgba(20, 20, 40, 0.9) 0%, rgba(10, 10, 25, 0.9) 100%);
border: 1px solid rgba(0, 240, 255, 0.3);
backdrop-filter: blur(10px);
}
.selection-card:hover {
border-color: rgba(0, 240, 255, 0.6);
box-shadow: 0 0 30px rgba(0, 240, 255, 0.3);
}
</style>
</head>
<body>
<div class="particles" id="particles"></div>
<div class="game-container container mx-auto p-2 max-w-7xl">
<!-- Header -->
<div class="text-center mb-2">
<h1 class="text-xl md:text-2xl font-bold logo-text floating">⚡ NEXUS CHESS ⚡</h1>
<p class="text-gray-300 text-xs">Cyberpunk Tower Defense</p>
</div>
<!-- Enhanced Cyberpunk Mode Selection Modal -->
<div id="mode-selection" class="fixed inset-0 mode-selection-bg flex items-center justify-center z-50 card-detail-modal">
<div class="selection-card rounded-2xl p-8 max-w-2xl w-full mx-4 border-2 border-cyan-400 shadow-2xl modal-enter backdrop-blur-xl nexus-glow">
<div class="text-center mb-8">
<div class="text-6xl mb-4 floating">🔮</div>
<h2 class="text-4xl font-bold mb-2 logo-text">NEXUS REALMS</h2>
<p class="text-cyan-300 text-sm">Choose your digital dominion</p>
<div class="w-32 h-1 bg-gradient-to-r from-cyan-500 to-purple-500 mx-auto mt-2 rounded-full"></div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div class="text-center">
<h3 class="text-xl font-bold mb-4 text-cyan-300">PLAYER BATTLE</h3>
<button onclick="startPvPMode()" class="w-full difficulty-btn bg-gradient-to-r from-cyan-600 via-blue-600 to-purple-600 hover:from-cyan-500 hover:via-blue-500 hover:to-purple-500 text-white py-6 px-8 rounded-xl font-bold transition-all transform hover:scale-105 border-2 border-cyan-400 shadow-lg nexus-glow">
<span class="text-3xl mr-3">⚔️</span>
<div class="text-lg">NEURAL DUEL</div>
<div class="text-xs opacity-75 mt-1">Player vs Player Combat</div>
</button>
</div>
<div class="text-center">
<h3 class="text-xl font-bold mb-4 text-purple-300">AI NEXUS</h3>
<div class="space-y-4">
<button onclick="startGame('easy')" class="w-full difficulty-btn bg-gradient-to-r from-green-600 to-emerald-700 hover:from-green-500 hover:to-emerald-600 text-white py-4 px-6 rounded-xl font-bold transition-all transform hover:scale-105 border-2 border-green-400 shadow-lg" style="box-shadow: 0 0 20px rgba(0, 255, 136, 0.3);">
<span class="text-xl mr-2">🟢</span>SCRIPT KIDDIE
<div class="text-xs opacity-75">Basic AI Protocols</div>
</button>
<button onclick="startGame('medium')" class="w-full difficulty-btn bg-gradient-to-r from-yellow-600 to-orange-700 hover:from-yellow-500 hover:to-orange-600 text-white py-4 px-6 rounded-xl font-bold transition-all transform hover:scale-105 border-2 border-yellow-400 shadow-lg" style="box-shadow: 0 0 20px rgba(255, 119, 0, 0.3);">
<span class="text-xl mr-2">🟡</span>CYBER OPERATIVE
<div class="text-xs opacity-75">Advanced AI Systems</div>
</button>
<button onclick="startGame('hard')" class="w-full difficulty-btn bg-gradient-to-r from-red-600 to-pink-700 hover:from-red-500 hover:to-pink-600 text-white py-4 px-6 rounded-xl font-bold transition-all transform hover:scale-105 border-2 border-red-400 shadow-lg" style="box-shadow: 0 0 20px rgba(255, 0, 68, 0.3);">
<span class="text-xl mr-2">🔴</span>NEXUS OVERLORD
<div class="text-xs opacity-75">Expert AI Dominion</div>
</button>
</div>
</div>
</div>
<div class="text-center mt-8">
<p class="text-gray-400 text-xs">Enter the grid and command your digital forces</p>
</div>
</div>
</div>
<!-- Player Info Header -->
<div class="flex justify-between items-center mb-2 compact-section rounded-lg p-2">
<div id="player-info" class="flex items-center space-x-2 bg-cyan-900/20 rounded-lg p-2">
<div id="player-profile-icon" class="profile-icon">
<svg class="text-cyan-400" fill="currentColor" viewBox="0 0 24 24"><path d="M12 2C6.47 2 2 6.47 2 12s4.47 10 10 10 10-4.47 10-10S17.53 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm-2-9h4v2h-2v2h2v2h-4v-2h2v-2h-2v-2zm-2-2h-2V7h2v2zm6 0V7h2v2h-2z"/></svg>
</div>
<div class="text-cyan-400">
<div class="text-xs font-bold">PLAYER 1</div>
<div class="stats-compact">
<span id="p1-health" class="text-sm font-bold">10</span>
<div class="w-16 bg-gray-700 rounded h-1"><div id="p1-health-bar" class="health-bar w-full h-full"></div></div>
<span class="text-xs">⚡<span id="p1-mana" class="text-cyan-400 font-bold">3</span></span>
<span class="text-xs">✨<span id="p1-exp" class="text-purple-400 font-bold">0</span></span>
</div>
</div>
</div>
<div class="text-center">
<div class="text-xs text-gray-300">TURN: <span id="current-turn" class="text-white font-bold">Player 1</span></div>
<div class="text-xs text-gray-300">TURN #<span id="turn-counter" class="text-white font-bold">1</span></div>
<div class="flex items-center gap-2 mt-1">
<button id="end-turn-btn" class="bg-purple-600 hover:bg-purple-700 px-3 py-1 rounded text-xs font-bold transition-colors border border-purple-400">END TURN</button>
<button id="music-toggle" class="bg-gray-600 hover:bg-gray-700 px-2 py-1 rounded text-xs transition-colors border border-gray-500">🔊</button>
</div>
</div>
<div id="opponent-info" class="flex items-center space-x-2 bg-pink-900/20 rounded-lg p-2">
<div class="text-pink-400 text-right">
<div class="text-xs font-bold">PLAYER 2</div>
<div class="stats-compact">
<span class="text-xs">✨<span id="p2-exp" class="text-purple-400 font-bold">0</span></span>
<span class="text-xs">⚡<span id="p2-mana" class="text-pink-400 font-bold">3</span></span>
<div class="w-16 bg-gray-700 rounded h-1"><div id="p2-health-bar" class="health-bar w-full h-full"></div></div>
<span id="p2-health" class="text-sm font-bold">10</span>
</div>
</div>
<div id="opponent-profile-icon" class="profile-icon">
<svg class="text-pink-400" fill="currentColor" viewBox="0 0 24 24"><path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z"/></svg>
</div>
</div>
</div>
<!-- Main Game Area -->
<div class="flex flex-col flex-1">
<!-- 3D Game Board -->
<div class="board-container flex-1 relative">
<canvas id="game-canvas"></canvas>
<!-- UI Overlays -->
<div id="nexus-info" style="position: absolute; top: 10px; left: 10px; background: rgba(0,0,0,0.8); padding: 8px; border-radius: 4px; font-size: 12px; border: 1px solid #00f0ff;">
<div>Enemy Nexus HP: <span id="enemy-nexus-health">10</span>/10</div>
</div>
<!-- Chest Info -->
<div id="chest-info" style="position: absolute; top: 10px; right: 10px; background: rgba(0,0,0,0.8); padding: 8px; border-radius: 4px; font-size: 12px; border: 1px solid #ffd700;">
<div>Chests: <span id="chest-count">0</span> remaining</div>
</div>
</div>
<!-- Battle Log -->
<div class="mb-2">
<h4 class="text-xs font-bold text-cyan-300 mb-1">📜 BATTLE LOG</h4>
<div id="battle-log" class="bg-gray-900 rounded p-2 h-12 overflow-y-auto text-xs text-gray-300 border border-gray-700"></div>
</div>
<!-- Player Hand - Fanned Display -->
<div class="hand-fan">
<div id="p1-hand" class="flex justify-center items-end"></div>
</div>
<!-- Deck Info -->
<div class="text-center text-xs text-cyan-300 compact-section rounded p-1 mt-2 border border-cyan-800">
📚: <span id="p1-deck-count">50</span> | 📚: <span id="p2-deck-count">50</span> (Opponent)
</div>
</div>
<!-- Card Detail Modal -->
<div id="card-detail-modal" class="fixed inset-0 bg-black bg-opacity-70 hidden flex items-center justify-center z-50 card-detail-modal">
<div class="bg-gradient-to-br from-gray-800 to-gray-900 rounded-lg p-6 max-w-sm w-full mx-4 border-2 border-cyan-400 modal-enter" style="max-height: 90vh; overflow-y: auto;">
<div id="card-detail-content"></div>
<div class="flex space-x-3 mt-4">
<button id="play-card-btn" class="flex-1 bg-cyan-600 hover:bg-cyan-700 px-4 py-2 rounded-lg font-bold transition-colors border border-cyan-400">🎯 Play Card</button>
<button onclick="hideCardDetailModal()" class="flex-1 bg-gray-600 hover:bg-gray-700 px-4 py-2 rounded-lg transition-colors border border-gray-500">❌ Cancel</button>
</div>
</div>
</div>
<!-- Piece Menu -->
<div id="piece-menu" class="piece-menu" style="display: none;"></div>
<!-- Chest Loot Modal -->
<div id="chest-loot-modal" class="fixed inset-0 bg-black bg-opacity-70 hidden flex items-center justify-center z-50 card-detail-modal">
<div class="bg-gradient-to-br from-gray-800 to-gray-900 rounded-lg p-6 max-w-sm w-full mx-4 border-2 border-yellow-500 modal-enter" style="max-height: 90vh; overflow-y: auto;">
<div id="chest-loot-content"></div>
<div class="flex space-x-3 mt-4">
<button onclick="hideChestLootModal()" class="flex-1 bg-yellow-600 hover:bg-yellow-700 px-4 py-2 rounded-lg font-bold transition-colors text-black border border-yellow-400">🎁 Take Loot</button>
</div>
</div>
</div>
</div>
<script>
// Game state and configuration
let gameState = {};
let soundEnabled = true;
let selectedCardIndex = null;
let selectedCardPlayer = null;
let currentOscillators = [];
let musicPlaying = false;
let audioContext;
let selectedPieceForMenu = null;
// Three.js variables
let scene, camera, renderer, raycaster, mouse;
let boardSquares = [];
let pieces3D = [];
let nexusCores = [];
let chests3D = [];
let defenseTowers = [];
let walls3D = [];
let boardGroup;
let selectedPiece3D = null;
let validMoveSquares = [];
// Expanded board size with stairway areas
const BOARD_ROWS = 14; // 10 main board + 2 stairway rows per player
const BOARD_COLS = 10;
const SQUARE_SIZE = 1;
const SQUARE_SPACING = 1.1;
// Defense tower positions (outside main board)
const TOWER_POSITIONS = [
{ row: -1, col: 3, player: 1 }, { row: -1, col: 6, player: 1 },
{ row: BOARD_ROWS, col: 3, player: 2 }, { row: BOARD_ROWS, col: 6, player: 2 }
];
const CARD_TYPES = { PIECE: 'piece', INSTANT: 'instant', SUMMON: 'summon', ENHANCEMENT: 'enhancement', HEALING: 'healing', EXP_BOOST: 'exp_boost', GEAR: 'gear' };
// Chest types
const CHEST_TYPES = {
LEGENDARY: { name: 'Legendary Chest', color: 0xffd700, glow: 0xffaa00, rarity: 'legendary', minItems: 2, maxItems: 3 },
EPIC: { name: 'Epic Chest', color: 0xff7700, glow: 0xff4400, rarity: 'epic', minItems: 2, maxItems: 3 },
RARE: { name: 'Rare Chest', color: 0xc0c0c0, glow: 0xffffff, rarity: 'rare', minItems: 1, maxItems: 2 }
};
// Gear items
const GEAR_ITEMS = {
WEAPONS: [
{ name: 'Quantum Blade', type: 'weapon', attack: 3, rarity: 'legendary', description: 'A blade that cuts through dimensions', flavor: '"Reality is just another layer to slice through."' },
{ name: 'Plasma Saber', type: 'weapon', attack: 2, rarity: 'epic', description: 'High-energy plasma weapon', flavor: '"Hot enough to melt through any defense."' },
{ name: 'Nano Sword', type: 'weapon', attack: 1, rarity: 'rare', description: 'Molecular-edged blade', flavor: '"Sharpened to the atomic level."' },
{ name: 'Data Dagger', type: 'weapon', attack: 1, rarity: 'rare', description: 'Quick digital strike weapon', flavor: '"Information is the deadliest weapon."' }
],
ARMOR: [
{ name: 'Quantum Shield', type: 'armor', defense: 3, rarity: 'legendary', description: 'Shield that exists in multiple dimensions', flavor: '"Can block attacks from any reality."' },
{ name: 'Plasma Barrier', type: 'armor', defense: 2, rarity: 'epic', description: 'Energy-based defensive field', flavor: '"Nothing gets through this wall of pure energy."' },
{ name: 'Nano Mesh', type: 'armor', defense: 1, rarity: 'rare', description: 'Self-repairing protective layer', flavor: '"Adapts to any threat automatically."' },
{ name: 'Data Cloak', type: 'armor', defense: 1, rarity: 'rare', description: 'Information-scrambling defense', flavor: '"Confuses enemy targeting systems."' }
]
};
// Special chest-only cards
const CHEST_CARDS = [
{ name: '⚡ Time Warp', cost: 0, rarity: 'legendary', description: '🕒 Take an extra turn immediately.', flavor: '"Time bends to your will."', effect_desc: 'Take an additional turn after this one' },
{ name: '🌀 Black Hole', cost: 0, rarity: 'legendary', description: '🕳️ Remove all enemy pieces from a 3x3 area.', flavor: '"The ultimate reset button."', effect_desc: 'Destroy all pieces in target area' },
{ name: '✨ Phoenix Protocol', cost: 0, rarity: 'epic', description: '🔥 Resurrect your last destroyed piece at full health.', flavor: '"From the ashes, we rise again."', effect_desc: 'Revive last destroyed friendly piece' },
{ name: '🛡️ Impervious Matrix', cost: 0, rarity: 'epic', description: '💎 Make your Nexus invulnerable for 2 turns.', flavor: '"Absolute protection."', effect_desc: 'Nexus takes no damage for 2 turns' },
{ name: '⚙️ Overclock', cost: 0, rarity: 'rare', description: '🚀 Double the attack of all your pieces for one turn.', flavor: '"Pushing systems beyond their limits."', effect_desc: 'Double attack for all friendly pieces' },
{ name: '🔋 Power Core', cost: 0, rarity: 'rare', description: '💡 Gain 5 permanent maximum mana.', flavor: '"Unlimited power!"', effect_desc: 'Increase max mana by 5' }
];
const PIECE_TYPES = {
NEXUS_CORE: { name: '🏛️ Nexus Core', symbol: '♔', attack: 0, defense: 10, cost: 0, moves: 'none', rarity: 'legendary', description: '💎 The heart of your digital empire. Its destruction means total system failure.', flavor: '"In the grid, all paths lead to the Core."' },
DATA_EMPRESS: { name: '👑 Data Empress', symbol: '♕', attack: 9, defense: 9, cost: 9, moves: 'queen', rarity: 'legendary', description: '✨ Supreme digital sovereign with unlimited network access and devastating power.', flavor: '"She commands the datastreams like a symphony."' },
FORTRESS_NODE: { name: '🏰 Fortress Node', symbol: '♖', attack: 5, defense: 5, cost: 5, moves: 'rook', rarity: 'rare', description: '🛡️ Heavily fortified server cluster. Moves in straight lines, crushing everything in its path.', flavor: '"Impenetrable walls in cyberspace."' },
SIGNAL_HACKER: { name: '🔮 Signal Hacker', symbol: '♗', attack: 3, defense: 3, cost: 3, moves: 'bishop', rarity: 'common', description: '📡 Expert in diagonal data manipulation. Slips through network defenses at odd angles.', flavor: '"The shortest path is rarely straight."' },
CYBER_STALKER: { name: '🐴 Cyber Stalker', symbol: '♘', attack: 3, defense: 3, cost: 3, moves: 'knight', rarity: 'common', description: '⚡ Unpredictable digital hunter. Its erratic movement patterns confuse enemy algorithms.', flavor: '"Two steps forward, one step sideways."' },
BYTE_PAWN: { name: '🟦 Byte Pawn', symbol: '♙', attack: 1, defense: 1, cost: 1, moves: 'pawn', rarity: 'common', description: '🧩 Basic data packet soldier. Moves forward and attacks diagonally up to 2 spaces.', flavor: '"Small but essential."' },
STASIS_GUARD: { name: '🔒 Stasis Guard', symbol: '⬟', attack: 0, defense: 1, cost: 0, moves: 'pawn', rarity: 'special', description: '🛡️ Temporary defense matrix. Dissolves after first impact or temporal decay.', flavor: '"A barrier of frozen time."' },
DEFENSE_TOWER: { name: '🗼 Defense Tower', symbol: '🗼', attack: 2, defense: 4, cost: 3, moves: 'none', rarity: 'rare', description: '🎯 Automated defense system. Attacks adjacent enemy pieces each turn.', flavor: '"Always watching, always ready."' }
};
const INSTANT_CARDS = [
{ name: '🛡️ Firewall Boost', cost: 2, effect: 'shield', rarity: 'common', description: '🛡️ Temporarily increase defense of a piece.', flavor: '"Stronger walls for a short time."', effect_desc: 'Add +2 defense to target piece this turn' },
{ name: '💥 Data Spike', cost: 3, effect: 'damage', rarity: 'common', description: '⚡ Deliver a sudden burst of damage to an enemy piece.', flavor: '"A sharp jab to their system."', effect_desc: 'Deal 3 damage to target piece' },
{ name: '🔄 Control Override', cost: 5, effect: 'control', rarity: 'legendary', description: '🕹️ Take control of an enemy piece for one turn.', flavor: '"Your pawn becomes mine."', effect_desc: 'Control target enemy piece for 1 turn' },
{ name: '🚀 Speed Hack', cost: 3, effect: 'buff', rarity: 'rare', description: '⚡ Increase movement for a piece.', flavor: '"Faster than the blink of an eye."', effect_desc: 'Target piece can move twice this turn' },
{ name: '🔍 Security Breach', cost: 1, effect: 'draw', rarity: 'common', description: '📄 Access restricted data archives for additional resources.', flavor: '"Knowledge is the ultimate weapon."', effect_desc: 'Draw 2 additional cards from your deck' },
{ name: '⚡ Power Surge', cost: 3, effect: 'mana', rarity: 'common', description: '🔋 Redirect energy from auxiliary systems.', flavor: '"More power to the processing cores!"', effect_desc: 'Gain +2 mana at start of next turn' },
{ name: '💀 System Crash', cost: 4, effect: 'destroy', rarity: 'rare', description: '🔥 Cause catastrophic system failure in target piece.', flavor: '"Error 404: Piece not found."', effect_desc: 'Destroy target piece with defense 3 or less' },
{ name: '🔗 Neural Link', cost: 2, effect: 'bond', rarity: 'rare', description: '🧠 Create psychic connection between two pieces.', flavor: '"Minds think as one."', effect_desc: 'Two target pieces share damage taken' }
];
const SUMMON_CARDS = [
{ name: '📡 Deploy Protocol', cost: 1, rarity: 'common', description: '🚀 Initialize rapid deployment sequence for lightweight units.', flavor: '"Small programs, big impact."', effect_desc: 'Summon any piece costing 3 mana or less for free' },
{ name: '♻️ Emergency Backup', cost: 2, rarity: 'rare', description: '💾 Restore deleted programs from backup archives.', flavor: '"Nothing is ever truly lost in the digital realm."', effect_desc: 'Return any destroyed piece from discard pile to hand' },
{ name: '⚡ Resource Allocation', cost: 0, rarity: 'common', description: '🔄 Optimize system resources for maximum efficiency.', flavor: '"Work smarter, not harder."', effect_desc: 'Gain +2 mana immediately this turn' },
{ name: '🧠 Neural Link', cost: 1, rarity: 'common', description: '📡 Establish direct interface with data repositories.', flavor: '"Direct download to consciousness."', effect_desc: 'Draw 3 cards from your deck' },
{ name: '🏰 Digital Fortress', cost: 2, rarity: 'common', description: '🛡️ Reinforce network security across all nodes.', flavor: '"Strong walls make strong empires."', effect_desc: 'All friendly pieces gain +1 defense permanently' },
{ name: '🌀 Quantum Surge', cost: 4, rarity: 'legendary', description: '⚛️ Harness quantum fluctuations for massive power boost.', flavor: '"Reality bends to digital will."', effect_desc: 'All friendly pieces gain +2 attack and +1 defense this turn' }
];
const ENHANCEMENT_CARDS = [
{ name: '⚔️ Combat Protocol', cost: 2, rarity: 'common', description: '🗡️ Upload advanced combat algorithms to target piece.', flavor: '"Precision strikes through superior code."', effect_desc: 'Target piece gains +2 attack permanently' },
{ name: '🛡️ Armor Plating', cost: 2, rarity: 'common', description: '🔧 Install additional defensive layers.', flavor: '"Better safe than sorry."', effect_desc: 'Target piece gains +2 defense permanently' },
{ name: '🔄 Adaptive Core', cost: 3, rarity: 'rare', description: '🧬 Grant piece ability to evolve and adapt.', flavor: '"Evolution through digital selection."', effect_desc: 'Target piece gains +1 attack and +1 defense each turn' },
{ name: '👁️ Omniscience Net', cost: 4, rarity: 'legendary', description: '🌐 Connect piece to global surveillance network.', flavor: '"All seeing, all knowing."', effect_desc: 'Target piece can see and attack any enemy piece on board' }
];
const HEALING_CARDS = [
{ name: '💚 Nano Repair', cost: 2, rarity: 'common', description: '🔧 Deploy microscopic repair bots to fix damaged systems.', flavor: '"Healing from the inside out."', effect_desc: 'Restore 3 health to target piece' },
{ name: '✨ System Restore', cost: 4, rarity: 'rare', description: '💽 Restore piece to previous backup state.', flavor: '"Like it never happened."', effect_desc: 'Fully heal target piece and remove debuffs' },
{ name: '🌟 Mass Regeneration', cost: 6, rarity: 'legendary', description: '🔄 Network-wide healing protocol.', flavor: '"All systems nominal."', effect_desc: 'Heal all friendly pieces by 2 health' },
{ name: '⚕️ Emergency Medical', cost: 1, rarity: 'common', description: '🚑 Quick field repairs for critical damage.', flavor: '"Patching the holes."', effect_desc: 'Heal target piece by 1 and give +1 defense this turn' }
];
const EXP_BOOST_CARDS = [
{ name: '📈 Data Mining', cost: 2, rarity: 'common', description: '⛏️ Extract valuable experience from system operations.', flavor: '"Knowledge is power."', effect_desc: 'Gain 10 experience points immediately' },
{ name: '🎓 Training Protocol', cost: 3, rarity: 'rare', description: '📚 Accelerated learning subroutines.', flavor: '"Practice makes perfect."', effect_desc: 'Gain 15 experience and draw 1 card' },
{ name: '💎 Master Code', cost: 5, rarity: 'legendary', description: '🧠 Access to advanced programming techniques.', flavor: '"Transcendent understanding."', effect_desc: 'Gain 25 experience and upgrade target piece level' },
{ name: '⚡ Quick Study', cost: 1, rarity: 'common', description: '📖 Rapid information absorption.', flavor: '"Learning at light speed."', effect_desc: 'Gain 5 experience points' }
];
// Audio system
function initAudio() {
if (!audioContext) {
audioContext = new (window.AudioContext || window.webkitAudioContext)();
}
}
function playBackgroundMusic() {
if (!soundEnabled || musicPlaying) return;
initAudio();
musicPlaying = true;
// Create layered ambient cyberpunk music
const createOscillatorLayer = (freq, type, gain, delay = 0) => {
setTimeout(() => {
if (!musicPlaying) return;
const osc = audioContext.createOscillator();
const gainNode = audioContext.createGain();
const filter = audioContext.createBiquadFilter();
osc.connect(filter);
filter.connect(gainNode);
gainNode.connect(audioContext.destination);
osc.frequency.value = freq;
osc.type = type;
filter.type = 'lowpass';
filter.frequency.value = 800;
gainNode.gain.setValueAtTime(0, audioContext.currentTime);
gainNode.gain.linearRampToValueAtTime(gain, audioContext.currentTime + 2);
// Add subtle frequency modulation
const lfo = audioContext.createOscillator();
const lfoGain = audioContext.createGain();
lfo.connect(lfoGain);
lfoGain.connect(osc.frequency);
lfo.frequency.value = 0.2;
lfoGain.gain.value = 5;
osc.start();
lfo.start();
currentOscillators.push({ osc, lfo, gainNode });
}, delay);
};
// Create multiple layers for rich ambient sound
createOscillatorLayer(110, 'sine', 0.02, 0); // Bass drone
createOscillatorLayer(220, 'triangle', 0.015, 1000); // Mid layer
createOscillatorLayer(440, 'sawtooth', 0.008, 2000); // High layer
createOscillatorLayer(330, 'sine', 0.012, 3000); // Harmony
// Restart the music loop
setTimeout(() => {
if (musicPlaying) {
stopCurrentOscillators();
playBackgroundMusic();
}
}, 12000);
}
function stopCurrentOscillators() {
currentOscillators.forEach(({ osc, lfo, gainNode }) => {
try {
gainNode.gain.linearRampToValueAtTime(0, audioContext.currentTime + 1);
setTimeout(() => {
osc.stop();
lfo.stop();
}, 1000);
} catch (e) {}
});
currentOscillators = [];
}
function stopBackgroundMusic() {
musicPlaying = false;
stopCurrentOscillators();
}
function toggleMusic() {
soundEnabled = !soundEnabled;
const btn = document.getElementById('music-toggle');
btn.textContent = soundEnabled ? '🔊' : '🔇';
if (soundEnabled) {
playBackgroundMusic();
} else {
stopBackgroundMusic();
}
}
function playSound(soundId) {
if (!soundEnabled) return;
initAudio();
const oscillator = audioContext.createOscillator();
const gainNode = audioContext.createGain();
oscillator.connect(gainNode);
gainNode.connect(audioContext.destination);
oscillator.frequency.value = soundId === 'cardPlay' ? 800 : soundId === 'pieceMove' ? 600 : soundId === 'battle' ? 400 : soundId === 'chest' ? 1000 : 500;
oscillator.type = 'sine';
gainNode.gain.setValueAtTime(0.1, audioContext.currentTime);
gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.2);
oscillator.start(audioContext.currentTime);
oscillator.stop(audioContext.currentTime + 0.2);
}
// Enhanced particle system
function createParticles() {
const container = document.getElementById('particles');
const colors = ['', 'pink', 'green', 'purple', 'teal', 'orange', 'violet'];
for (let i = 0; i < 40; i++) {
const p = document.createElement('div');
const colorClass = colors[Math.floor(Math.random() * colors.length)];
p.className = `particle ${colorClass}`;
p.style.left = Math.random() * 100 + '%';
p.style.top = Math.random() * 100 + '%';
p.style.animationDelay = Math.random() * 12 + 's';
p.style.animationDuration = (12 + Math.random() * 6) + 's';
container.appendChild(p);
}
}
// Three.js setup
function init3D() {
const canvas = document.getElementById('game-canvas');
const container = canvas.parentElement;
// Scene setup
scene = new THREE.Scene();
scene.background = new THREE.Color(0x050515);
// Camera setup
camera = new THREE.PerspectiveCamera(75, container.clientWidth / container.clientHeight, 0.1, 1000);
camera.position.set(0, 10, 10);
camera.lookAt(0, 0, 0);
// Renderer setup
renderer = new THREE.WebGLRenderer({ canvas: canvas, antialias: true });
renderer.setSize(container.clientWidth, container.clientHeight);
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
// Raycaster for mouse interaction
raycaster = new THREE.Raycaster();
mouse = new THREE.Vector2();
// Lighting - more vibrant colors
const ambientLight = new THREE.AmbientLight(0x404040, 0.6);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xcc00ff, 0.9);
directionalLight.position.set(5, 12, 5);
directionalLight.castShadow = true;
directionalLight.shadow.mapSize.width = 2048;
directionalLight.shadow.mapSize.height = 2048;
scene.add(directionalLight);
const pointLight = new THREE.PointLight(0x00ffcc, 0.7, 25);
pointLight.position.set(-5, 8, -5);
scene.add(pointLight);
// Create board
createBoard3D();
// Mouse event handlers
canvas.addEventListener('mousemove', onMouseMove, false);
canvas.addEventListener('click', onMouseClick, false);
// Handle window resize
window.addEventListener('resize', onWindowResize, false);
// Start animation loop
animate();
}
function createBoard3D() {
boardGroup = new THREE.Group();
scene.add(boardGroup);
// Create board squares with stairway effect
for (let row = 0; row < BOARD_ROWS; row++) {
boardSquares[row] = [];
for (let col = 0; col < BOARD_COLS; col++) {
const square = createBoardSquare(row, col);
boardSquares[row][col] = square;
boardGroup.add(square);
}
}
// Create nexus cores
createNexusCores();
// Create defense towers
createDefenseTowers();
// Create random walls
createRandomWalls();
}
function createBoardSquare(row, col) {
// Hexagonal board square
const geometry = new THREE.CylinderGeometry(0.4, 0.4, 0.1, 6);
// More vibrant alternating colors with stairway effect
const isLight = (row + col) % 2 === 0;
let color, yPos, emissive;
// Stairway effect for player bases
if (row < 2) { // Player 1 stairway
color = isLight ? 0x1a1a4a : 0x0f0f3f;
yPos = row === 0 ? 0.3 : 0.15; // Higher for row 0
emissive = isLight ? 0x0a0a33 : 0x050522;
} else if (row >= BOARD_ROWS - 2) { // Player 2 stairway
color = isLight ? 0x4a1a1a : 0x3f0f0f;
yPos = row === BOARD_ROWS - 1 ? 0.3 : 0.15; // Higher for last row
emissive = isLight ? 0x330a0a : 0x220505;
} else { // Main board
color = isLight ? 0x2a1a4a : 0x1a0f3f;
yPos = 0;
emissive = isLight ? 0x1a0033 : 0x0d0022;
}
const material = new THREE.MeshPhongMaterial({
color: color,
emissive: emissive,
transparent: true,
opacity: 0.9
});
const square = new THREE.Mesh(geometry, material);
square.position.set(
(col - BOARD_COLS / 2 + 0.5) * SQUARE_SPACING,
yPos,
(row - BOARD_ROWS / 2 + 0.5) * SQUARE_SPACING
);
square.receiveShadow = true;
// Store board coordinates
square.userData = { row, col, type: 'square' };
return square;
}
function createNexusCores() {
// Player 1 Nexus (bottom) - positioned at the end of the stairway
const nexus1 = createNexus(1);
nexus1.position.set(0, 0.5, (BOARD_ROWS / 2 + 1) * SQUARE_SPACING);
nexusCores[1] = nexus1;
scene.add(nexus1);
// Player 2 Nexus (top) - positioned at the end of the stairway
const nexus2 = createNexus(2);
nexus2.position.set(0, 0.5, -(BOARD_ROWS / 2 + 1) * SQUARE_SPACING);
nexusCores[2] = nexus2;
scene.add(nexus2);
}
function createNexus(player) {
const group = new THREE.Group();
// Main nexus structure with more refined design
const geometry = new THREE.OctahedronGeometry(0.8);
const color = player === 1 ? 0x00ffcc : 0xff00aa;
const material = new THREE.MeshPhongMaterial({
color: color,
emissive: color,
emissiveIntensity: 0.6,
transparent: true,
opacity: 0.9
});
const nexus = new THREE.Mesh(geometry, material);
nexus.position.y = 0.75;
nexus.castShadow = true;
nexus.userData = { type: 'nexus', player: player };
group.add(nexus);
// Add pulsing glow effect
const glowGeometry = new THREE.SphereGeometry(1.2, 16, 16);
const glowMaterial = new THREE.MeshBasicMaterial({
color: color,
transparent: true,
opacity: 0.2
});
const glow = new THREE.Mesh(glowGeometry, glowMaterial);
glow.position.y = 0.75;
group.add(glow);
// Add energy core
const coreGeometry = new THREE.SphereGeometry(0.3, 12, 12);
const coreMaterial = new THREE.MeshBasicMaterial({
color: 0xffffff,
emissive: 0xffffff,
emissiveIntensity: 0.8
});
const core = new THREE.Mesh(coreGeometry, coreMaterial);
core.position.y = 0.75;
group.add(core);
return group;
}
function createDefenseTowers() {
TOWER_POSITIONS.forEach(towerPos => {
const tower = createTower3D(towerPos.player, towerPos.row, towerPos.col);
defenseTowers.push(tower);
scene.add(tower);
});
}
function createTower3D(player, row, col) {
const group = new THREE.Group();
const towerColor = player === 1 ? 0x00ffcc : 0xff00aa;
// Tower base
const baseGeometry = new THREE.CylinderGeometry(0.4, 0.5, 0.3, 8);
const baseMaterial = new THREE.MeshPhongMaterial({
color: towerColor,
emissive: towerColor,
emissiveIntensity: 0.3
});
const base = new THREE.Mesh(baseGeometry, baseMaterial);
base.position.y = 0.15;
base.castShadow = true;
group.add(base);
// Tower body
const bodyGeometry = new THREE.CylinderGeometry(0.3, 0.4, 1.5, 8);
const bodyMaterial = new THREE.MeshPhongMaterial({
color: towerColor,
emissive: towerColor,
emissiveIntensity: 0.2
});
const body = new THREE.Mesh(bodyGeometry, bodyMaterial);
body.position.y = 1.0;
body.castShadow = true;
group.add(body);
// Tower top with cannon
const topGeometry = new THREE.CylinderGeometry(0.2, 0.3, 0.5, 6);
const top = new THREE.Mesh(topGeometry, bodyMaterial);
top.position.y = 1.8;
group.add(top);
// Cannon barrel
const barrelGeometry = new THREE.CylinderGeometry(0.08, 0.08, 0.8, 6);
const barrelMaterial = new THREE.MeshPhongMaterial({
color: 0x333333,
emissive: 0x111111
});
const barrel = new THREE.Mesh(barrelGeometry, barrelMaterial);
barrel.position.y = 1.8;
barrel.position.z = player === 1 ? 0.5 : -0.5;
barrel.rotation.x = Math.PI / 2;
group.add(barrel);
// Position tower
group.position.set(
(col - BOARD_COLS / 2 + 0.5) * SQUARE_SPACING,
0,
(row - BOARD_ROWS / 2 + 0.5) * SQUARE_SPACING
);
// Store tower data
group.userData = {
type: 'defense_tower',
player: player,
row: row,
col: col,
attack: () => Math.floor(Math.random() * 3) + 1 // 1-3 damage
};
return group;
}
function createRandomWalls() {
const wallCount = 8; // Number of random walls to generate
for (let i = 0; i < wallCount; i++) {
let row, col;
let attempts = 0;
// Find an empty spot for the wall (avoid edges and player bases)
do {
row = Math.floor(Math.random() * (BOARD_ROWS - 4)) + 2;
col = Math.floor(Math.random() * (BOARD_COLS - 2)) + 1;
attempts++;
} while ((gameState.board[row][col] !== null ||
isNearNexus(row, col) ||
walls3D.some(w => w.userData.row === row && w.userData.col === col)) &&
attempts < 100);
if (attempts < 100) {
const wall = createWall3D(row, col);
walls3D.push(wall);
scene.add(wall);
// Add wall to game state
gameState.board[row][col] = {
type: 'wall',
owner: 0, // Neutral
health: 5,
isWall: true,
id: Math.random().toString(36).substr(2, 9)
};
}
}
}
function isNearNexus(row, col) {
// Check if position is too close to nexus (avoid blocking paths)
const player1NexusRow = 0;
const player2NexusRow = BOARD_ROWS - 1;
const nexusCol = BOARD_COLS / 2;
return (Math.abs(row - player1NexusRow) < 3 && Math.abs(col - nexusCol) < 3) ||
(Math.abs(row - player2NexusRow) < 3 && Math.abs(col - nexusCol) < 3);
}
function createWall3D(row, col) {
const group = new THREE.Group();
// Wall base
const wallGeometry = new THREE.BoxGeometry(0.8, 0.8, 0.8);
const wallMaterial = new THREE.MeshPhongMaterial({
color: 0x555555,
emissive: 0x222222,
emissiveIntensity: 0.1
});
const wall = new THREE.Mesh(wallGeometry, wallMaterial);
wall.position.y = 0.4;
wall.castShadow = true;
group.add(wall);
// Wall details
const detailGeometry = new THREE.BoxGeometry(0.6, 0.1, 0.6);
const detailMaterial = new THREE.MeshPhongMaterial({
color: 0x777777,
emissive: 0x333333
});
for (let i = 0; i < 3; i++) {
const detail = new THREE.Mesh(detailGeometry, detailMaterial);
detail.position.y = 0.2 + i * 0.2;
group.add(detail);
}
// Position wall
group.position.set(
(col - BOARD_COLS / 2 + 0.5) * SQUARE_SPACING,
0,
(row - BOARD_ROWS / 2 + 0.5) * SQUARE_SPACING
);
// Store wall data
group.userData = {
type: 'wall',
row: row,
col: col
};
return group;
}
function createChest3D(chestData, row, col) {
const group = new THREE.Group();
// Chest base
const baseGeometry = new THREE.BoxGeometry(0.5, 0.3, 0.4);
const baseMaterial = new THREE.MeshPhongMaterial({
color: chestData.type.color,
emissive: chestData.type.color,
emissiveIntensity: 0.4
});
const base = new THREE.Mesh(baseGeometry, baseMaterial);
base.position.y = 0.15;
base.castShadow = true;
group.add(base);
// Chest lid
const lidGeometry = new THREE.BoxGeometry(0.5, 0.1, 0.4);
const lid = new THREE.Mesh(lidGeometry, baseMaterial);
lid.position.y = 0.35;
lid.castShadow = true;
group.add(lid);
// Chest lock
const lockGeometry = new THREE.CylinderGeometry(0.05, 0.05, 0.05, 8);
const lockMaterial = new THREE.MeshPhongMaterial({
color: 0x000000,
emissive: chestData.type.glow,
emissiveIntensity: 0.7
});
const lock = new THREE.Mesh(lockGeometry, lockMaterial);
lock.position.y = 0.4;
lock.position.z = 0.15;
group.add(lock);
// Position on board
group.position.set(
(col - BOARD_COLS / 2 + 0.5) * SQUARE_SPACING,
0,
(row - BOARD_ROWS / 2 + 0.5) * SQUARE_SPACING
);
// Store chest data
group.userData = {
type: 'chest',
chestData: chestData,
row: row,
col: col
};
return group;
}
function createPiece3D(pieceData, row, col) {
const group = new THREE.Group();
// Base with more vibrant colors
const baseGeometry = new THREE.CylinderGeometry(0.3, 0.3, 0.1, 8);
const playerColor = pieceData.owner === 1 ? 0x00ffcc : 0xff00aa;
const baseMaterial = new THREE.MeshPhongMaterial({
color: playerColor,
emissive: playerColor,
emissiveIntensity: 0.4
});
const base = new THREE.Mesh(baseGeometry, baseMaterial);
base.position.y = 0.05;
base.castShadow = true;
group.add(base);
// Chess-like piece body based on piece type with more refined designs
let bodyGeometry;
const pieceHeight = 0.8;
switch (pieceData.pieceType) {
case 'DATA_EMPRESS': // Queen
// Create queen with refined crown design
const queenGroup = new THREE.Group();
const queenBase = new THREE.CylinderGeometry(0.2, 0.25, 0.6, 10);
const queenMaterial = new THREE.MeshPhongMaterial({
color: playerColor,
emissive: playerColor,
emissiveIntensity: 0.3,
transparent: true,
opacity: 0.9
});
const queenMesh = new THREE.Mesh(queenBase, queenMaterial);
queenMesh.position.y = 0.3;
// Crown details with more refinement
const crownGeometry = new THREE.ConeGeometry(0.12, 0.25, 5);
for (let i = 0; i < 5; i++) {
const crownSpike = new THREE.Mesh(crownGeometry, queenMaterial);
crownSpike.position.y = 0.6;
crownSpike.rotation.y = (i * Math.PI * 2) / 5;
crownSpike.position.x = 0.15 * Math.cos((i * Math.PI * 2) / 5);
crownSpike.position.z = 0.15 * Math.sin((i * Math.PI * 2) / 5);
queenGroup.add(crownSpike);
}
// Central gem
const gemGeometry = new THREE.SphereGeometry(0.08, 8, 6);
const gemMaterial = new THREE.MeshPhongMaterial({
color: 0xffffff,
emissive: 0xffffff,
emissiveIntensity: 0.5
});
const gem = new THREE.Mesh(gemGeometry, gemMaterial);
gem.position.y = 0.7;
queenGroup.add(gem);
queenGroup.add(queenMesh);
queenGroup.position.y = 0.4;
group.add(queenGroup);
break;
case 'FORTRESS_NODE': // Rook
// Create rook with more refined castle design
const rookGeometry = new THREE.CylinderGeometry(0.22, 0.25, 0.7, 10);
const rookMaterial = new THREE.MeshPhongMaterial({
color: playerColor,
emissive: playerColor,
emissiveIntensity: 0.3,
transparent: true,
opacity: 0.9
});
const rook = new THREE.Mesh(rookGeometry, rookMaterial);
rook.position.y = 0.35;
// Battlements with more detail
const battlementGeometry = new THREE.BoxGeometry(0.12, 0.12, 0.12);
for (let i = 0; i < 4; i++) {
const battlement = new THREE.Mesh(battlementGeometry, rookMaterial);
battlement.position.y = 0.7;
battlement.position.x = 0.18 * Math.cos((i * Math.PI) / 2);
battlement.position.z = 0.18 * Math.sin((i * Math.PI) / 2);
group.add(battlement);
}
group.add(rook);
break;
case 'SIGNAL_HACKER': // Bishop
// Create bishop with more refined mitre design
const bishopGroup = new THREE.Group();
const bishopBase = new THREE.CylinderGeometry(0.15, 0.2, 0.4, 10);
const bishopMaterial = new THREE.MeshPhongMaterial({
color: playerColor,
emissive: playerColor,
emissiveIntensity: 0.3,
transparent: true,
opacity: 0.9
});
const bishopBaseMesh = new THREE.Mesh(bishopBase, bishopMaterial);
bishopBaseMesh.position.y = 0.2;
// Bishop head with more refined shape
const bishopHead = new THREE.ConeGeometry(0.1, 0.35, 8);
const bishopHeadMesh = new THREE.Mesh(bishopHead, bishopMaterial);
bishopHeadMesh.position.y = 0.55;
// Slit in bishop head
const slitGeometry = new THREE.BoxGeometry(0.06, 0.18, 0.02);
const slitMaterial = new THREE.MeshPhongMaterial({ color: 0x000000 });
const slit = new THREE.Mesh(slitGeometry, slitMaterial);
slit.position.y = 0.55;
slit.position.z = 0.1;
bishopGroup.add(bishopBaseMesh);
bishopGroup.add(bishopHeadMesh);
bishopGroup.add(slit);
bishopGroup.position.y = 0.4;
group.add(bishopGroup);
break;
case 'CYBER_STALKER': // Knight
// Create knight with more refined horse design
const knightGroup = new THREE.Group();
const knightBase = new THREE.CylinderGeometry(0.15, 0.18, 0.5, 10);
const knightMaterial = new THREE.MeshPhongMaterial({
color: playerColor,
emissive: playerColor,
emissiveIntensity: 0.3,
transparent: true,
opacity: 0.9
});
const knightBaseMesh = new THREE.Mesh(knightBase, knightMaterial);
knightBaseMesh.position.y = 0.25;
// Horse head with more detail
const headGeometry = new THREE.SphereGeometry(0.1, 10, 8);
const headMesh = new THREE.Mesh(headGeometry, knightMaterial);
headMesh.position.set(0.12, 0.6, 0.05);
headMesh.scale.set(1.3, 1.1, 0.9);
// Horse ears
const earGeometry = new THREE.ConeGeometry(0.035, 0.1, 6);
const leftEar = new THREE.Mesh(earGeometry, knightMaterial);
leftEar.position.set(0.06, 0.7, 0.05);
leftEar.rotation.x = -0.5;
const rightEar = new THREE.Mesh(earGeometry, knightMaterial);
rightEar.position.set(0.18, 0.7, 0.05);
rightEar.rotation.x = -0.5;
// Horse mane
const maneGeometry = new THREE.BoxGeometry(0.08, 0.05, 0.1);
const mane = new THREE.Mesh(maneGeometry, knightMaterial);
mane.position.set(0.12, 0.65, -0.05);
knightGroup.add(knightBaseMesh);
knightGroup.add(headMesh);
knightGroup.add(leftEar);
knightGroup.add(rightEar);
knightGroup.add(mane);
knightGroup.position.y = 0.4;
group.add(knightGroup);
break;
case 'DEFENSE_TOWER':
// Tower with more refined cyberpunk elements
const towerGeometry = new THREE.CylinderGeometry(0.15, 0.2, 1.0, 10);
const towerMaterial = new THREE.MeshPhongMaterial({
color: playerColor,
emissive: playerColor,
emissiveIntensity: 0.3,
transparent: true,
opacity: 0.9
});
const tower = new THREE.Mesh(towerGeometry, towerMaterial);
tower.position.y = 0.5;
// Tower top with more detail
const topGeometry = new THREE.CylinderGeometry(0.1, 0.15, 0.3, 8);
const top = new THREE.Mesh(topGeometry, towerMaterial);
top.position.y = 0.9;
// Antenna with glow
const antennaGeometry = new THREE.CylinderGeometry(0.02, 0.02, 0.3, 6);
const antennaMaterial = new THREE.MeshPhongMaterial({
color: playerColor,
emissive: playerColor,
emissiveIntensity: 0.8
});
const antenna = new THREE.Mesh(antennaGeometry, antennaMaterial);
antenna.position.y = 1.15;
// Radar dish
const dishGeometry = new THREE.SphereGeometry(0.08, 8, 4);
const dish = new THREE.Mesh(dishGeometry, antennaMaterial);
dish.position.y = 1.3;
dish.scale.z = 0.3;
group.add(tower);
group.add(top);
group.add(antenna);
group.add(dish);
break;
default: // BYTE_PAWN and others - Standard pawn shape with refinement
const pawnGroup = new THREE.Group();
const pawnBase = new THREE.CylinderGeometry(0.15, 0.18, 0.3, 10);
const pawnMaterial = new THREE.MeshPhongMaterial({
color: playerColor,
emissive: playerColor,
emissiveIntensity: 0.3,
transparent: true,
opacity: 0.9
});
const pawnBaseMesh = new THREE.Mesh(pawnBase, pawnMaterial);
pawnBaseMesh.position.y = 0.15;
const pawnHead = new THREE.SphereGeometry(0.12, 10, 8);
const pawnHeadMesh = new THREE.Mesh(pawnHead, pawnMaterial);
pawnHeadMesh.position.y = 0.45;
// Pawn collar
const collarGeometry = new THREE.TorusGeometry(0.15, 0.02, 8, 12);
const collar = new THREE.Mesh(collarGeometry, pawnMaterial);
collar.position.y = 0.3;
collar.rotation.x = Math.PI / 2;
pawnGroup.add(pawnBaseMesh);
pawnGroup.add(pawnHeadMesh);
pawnGroup.add(collar);
pawnGroup.position.y = 0.4;
group.add(pawnGroup);
}
// Position on board with stairway adjustment
let yOffset = 0;
if (row < 2) yOffset = row === 0 ? 0.3 : 0.15;
else if (row >= BOARD_ROWS - 2) yOffset = row === BOARD_ROWS - 1 ? 0.3 : 0.15;
group.position.set(
(col - BOARD_COLS / 2 + 0.5) * SQUARE_SPACING,
yOffset,
(row - BOARD_ROWS / 2 + 0.5) * SQUARE_SPACING
);
// Store piece data
group.userData = {
type: 'piece',
pieceData: pieceData,
row: row,
col: col
};
return group;
}
function onMouseMove(event) {
const rect = renderer.domElement.getBoundingClientRect();
mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
// Highlight hovered objects
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(scene.children, true);
// Reset all materials
resetSquareColors();
if (intersects.length > 0) {
const object = intersects[0].object;
const userData = object.userData || object.parent.userData;
if (userData && userData.type === 'square') {
// Highlight square
object.material.emissive.setHex(0x333366);
} else if (userData && userData.type === 'piece') {
// Highlight piece
if (object.material && object.material.emissive) {
object.material.emissive.setHex(0x666666);
}
} else if (userData && userData.type === 'chest') {
// Highlight chest
if (object.material && object.material.emissive) {
object.material.emissive.setHex(0x888888);
}
}
}
}
function onMouseClick(event) {
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(scene.children, true);
if (intersects.length > 0) {
const object = intersects[0].object;
const userData = object.userData || object.parent.userData;
if (userData && userData.type === 'square') {
handleSquareClick(userData.row, userData.col);
} else if (userData && userData.type === 'piece') {
handlePieceClick(userData.pieceData, userData.row, userData.col);
} else if (userData && userData.type === 'nexus') {
handleNexusClick(userData.player);
} else if (userData && userData.type === 'chest') {
handleChestClick(userData.chestData, userData.row, userData.col);
}
}
}
function resetSquareColors() {
for (let row = 0; row < BOARD_ROWS; row++) {
for (let col = 0; col < BOARD_COLS; col++) {
const square = boardSquares[row][col];
const isLight = (row + col) % 2 === 0;
let baseColor, emissiveColor;
if (row < 2) { // Player 1 stairway
baseColor = isLight ? 0x1a1a4a : 0x0f0f3f;
emissiveColor = isLight ? 0x0a0a33 : 0x050522;
} else if (row >= BOARD_ROWS - 2) { // Player 2 stairway
baseColor = isLight ? 0x4a1a1a : 0x3f0f0f;
emissiveColor = isLight ? 0x330a0a : 0x220505;
} else { // Main board
baseColor = isLight ? 0x2a1a4a : 0x1a0f3f;
emissiveColor = isLight ? 0x1a0033 : 0x0d0022;
}
square.material.color.setHex(baseColor);
square.material.emissive.setHex(emissiveColor);
}
}
}
function showValidMoves3D(selectedPiece) {
clearValidMoves3D();
for (let r = 0; r < BOARD_ROWS; r++) {
for (let c = 0; c < BOARD_COLS; c++) {
if (canMovePiece(selectedPiece, r, c)) {
const square = boardSquares[r][c];
square.material.color.setHex(0x00ff00);
square.material.emissive.setHex(0x004400);
validMoveSquares.push(square);
}
}
}
}
function showValidAttacks3D(selectedPiece) {
clearValidMoves3D();
for (let r = 0; r < BOARD_ROWS; r++) {
for (let c = 0; c < BOARD_COLS; c++) {
if (canAttackSquare(selectedPiece, r, c)) {
const square = boardSquares[r][c];
square.material.color.setHex(0xff0000);
square.material.emissive.setHex(0x440000);
validMoveSquares.push(square);
}
}
}
}
function clearValidMoves3D() {
validMoveSquares.forEach(square => {
const row = square.userData.row;
const col = square.userData.col;
const isLight = (row + col) % 2 === 0;
let baseColor, emissiveColor;
if (row < 2) { // Player 1 stairway
baseColor = isLight ? 0x1a1a4a : 0x0f0f3f;
emissiveColor = isLight ? 0x0a0a33 : 0x050522;
} else if (row >= BOARD_ROWS - 2) { // Player 2 stairway
baseColor = isLight ? 0x4a1a1a : 0x3f0f0f;
emissiveColor = isLight ? 0x330a0a : 0x220505;
} else { // Main board
baseColor = isLight ? 0x2a1a4a : 0x1a0f3f;
emissiveColor = isLight ? 0x1a0033 : 0x0d0022;
}
square.material.color.setHex(baseColor);
square.material.emissive.setHex(emissiveColor);
});
validMoveSquares = [];
}
function update3DBoard() {
// Remove all pieces from scene
pieces3D.forEach(piece => {
if (piece.parent) {
piece.parent.remove(piece);
}
});
pieces3D = [];
// Add current pieces
for (let row = 0; row < BOARD_ROWS; row++) {
for (let col = 0; col < BOARD_COLS; col++) {
const piece = gameState.board[row][col];
if (piece && !piece.isWall) {
const piece3D = createPiece3D(piece, row, col);
scene.add(piece3D);
pieces3D.push(piece3D);
}
}
}
}
function onWindowResize() {
const container = document.getElementById('game-canvas').parentElement;
camera.aspect = container.clientWidth / container.clientHeight;
camera.updateProjectionMatrix();
renderer.setSize(container.clientWidth, container.clientHeight);
}
function animate() {
requestAnimationFrame(animate);
// Animate nexus cores
nexusCores.forEach(nexus => {
if (nexus) {
nexus.rotation.y += 0.005;
// Pulsing effect
const scale = 1 + Math.sin(Date.now() * 0.002) * 0.05;
nexus.scale.set(scale, scale, scale);
}
});
// Animate pieces
pieces3D.forEach(piece => {
if (piece.userData.pieceData.justSummoned) {
piece.rotation.y += 0.02;
} else {
piece.rotation.y += 0.001;
}
// Idle floating animation
const time = Date.now() * 0.001;
const yOffset = piece.userData.row < 2 ? (piece.userData.row === 0 ? 0.3 : 0.15) :
piece.userData.row >= BOARD_ROWS - 2 ? (piece.userData.row === BOARD_ROWS - 1 ? 0.3 : 0.15) : 0;
piece.position.y = yOffset + 0.05 + Math.sin(time + piece.userData.row + piece.userData.col) * 0.02;
});
// Animate chests
chests3D.forEach(chest => {
chest.rotation.y += 0.01;
const time = Date.now() * 0.001;
chest.position.y = 0.15 + Math.sin(time + chest.userData.row + chest.userData.col) * 0.05;
});
// Animate defense towers
defenseTowers.forEach(tower => {
tower.rotation.y += 0.005;
const time = Date.now() * 0.001;
tower.position.y = 0.05 + Math.sin(time + tower.userData.row + tower.userData.col) * 0.02;
});
renderer.render(scene, camera);
}
// Defense tower attack system
function executeTowerAttacks() {
defenseTowers.forEach(tower => {
const towerData = tower.userData;
if (towerData.player === gameState.currentPlayer) {
const damage = towerData.attack();
const enemyPieces = [];
// Find all enemy pieces
for (let r = 0; r < BOARD_ROWS; r++) {
for (let c = 0; c < BOARD_COLS; c++) {
const piece = gameState.board[r][c];
if (piece && piece.owner !== towerData.player && !piece.isWall) {
enemyPieces.push({ piece, row: r, col: c });
}
}
}
// Attack a random enemy piece
if (enemyPieces.length > 0) {
const target = enemyPieces[Math.floor(Math.random() * enemyPieces.length)];
target.piece.health -= damage;
log(`🗼 Defense Tower attacks ${target.piece.name} for ${damage} damage!`);
awardExperience(towerData.player, 1);
if (target.piece.health <= 0) {
log(`💥 ${target.piece.name} destroyed by tower fire!`);
gameState.board[target.row][target.col] = null;
awardExperience(towerData.player, 2);
}
}
}
});
}
// Chest system
function generateChests(state) {
const chests = [];
const chestCount = 6; // Number of chests to generate
for (let i = 0; i < chestCount; i++) {
let row, col;
let attempts = 0;
// Find an empty spot for the chest (avoid stairway areas)
do {
row = Math.floor(Math.random() * (BOARD_ROWS - 6)) + 3;
col = Math.floor(Math.random() * (BOARD_COLS - 2)) + 1;
attempts++;
} while ((state.board[row][col] !== null ||
chests.some(c => c.row === row && c.col === col) ||
state.board[row][col]?.isWall) &&
attempts < 100);
if (attempts < 100) {
// Determine chest type based on rarity
const rand = Math.random();
let chestType;
if (rand < 0.1) { // 10% legendary
chestType = CHEST_TYPES.LEGENDARY;
} else if (rand < 0.3) { // 20% epic
chestType = CHEST_TYPES.EPIC;
} else { // 70% rare
chestType = CHEST_TYPES.RARE;
}
const chest = {
type: chestType,
row: row,
col: col,
id: Math.random().toString(36).substr(2, 9)
};
chests.push(chest);
// Create 3D chest
const chest3D = createChest3D(chest, row, col);
scene.add(chest3D);
chests3D.push(chest3D);
}
}
return chests;
}
function handleChestClick(chestData, row, col) {
// Only allow current player to open chests
if (gameState.currentPlayer !== 1) return;
// Check if there's a piece on this square that belongs to current player
const piece = gameState.board[row][col];
if (!piece || piece.owner !== gameState.currentPlayer) {
log("🚫 You need a piece on this square to open the chest!");
return;
}
openChest(chestData, piece);
}
function openChest(chestData, piece) {
playSound('chest');
// Remove chest from board
gameState.chests = gameState.chests.filter(c => c.id !== chestData.id);
// Remove 3D chest
const chestIndex = chests3D.findIndex(c => c.userData.chestData.id === chestData.id);
if (chestIndex !== -1) {
scene.remove(chests3D[chestIndex]);
chests3D.splice(chestIndex, 1);
}
// Generate loot based on chest type
const loot = generateChestLoot(chestData.type);
// Show loot modal
showChestLootModal(loot, chestData.type);
log(`🎁 ${piece.name} opened a ${chestData.type.name}!`);
updateChestCount();
}
function generateChestLoot(chestType) {
const loot = [];
const itemCount = Math.floor(Math.random() * (chestType.maxItems - chestType.minItems + 1)) + chestType.minItems;
for (let i = 0; i < itemCount; i++) {
const rand = Math.random();
if (rand < 0.4) { // 40% chance for gear
const gearType = Math.random() < 0.5 ? 'WEAPONS' : 'ARMOR';
const availableGear = GEAR_ITEMS[gearType].filter(item =>
item.rarity === chestType.rarity ||
(chestType.rarity === 'legendary' && item.rarity === 'epic') || // Legendary chests can contain epic gear
(chestType.rarity === 'epic' && item.rarity === 'rare') // Epic chests can contain rare gear
);
if (availableGear.length > 0) {
const gear = {...availableGear[Math.floor(Math.random() * availableGear.length)]};
gear.id = Math.random().toString(36).substr(2, 9);
loot.push({ type: 'gear', item: gear });
}
} else { // 60% chance for special card
const availableCards = CHEST_CARDS.filter(card =>
card.rarity === chestType.rarity ||
(chestType.rarity === 'legendary' && card.rarity === 'epic') || // Legendary chests can contain epic cards
(chestType.rarity === 'epic' && card.rarity === 'rare') // Epic chests can contain rare cards
);
if (availableCards.length > 0) {
const card = {...availableCards[Math.floor(Math.random() * availableCards.length)]};
card.id = Math.random().toString(36).substr(2, 9);
card.type = CARD_TYPES.INSTANT; // Mark as instant for playability
loot.push({ type: 'card', item: card });
}
}
}
return loot;
}
function showChestLootModal(loot, chestType) {
const modal = document.getElementById('chest-loot-modal');
const content = document.getElementById('chest-loot-content');
let lootHTML = `
<div class="text-center">
<h2 class="text-xl font-bold mb-2" style="color: ${chestType.rarity === 'legendary' ? '#ffd700' : chestType.rarity === 'epic' ? '#ff7700' : '#c0c0c0'}">${chestType.name} Loot!</h2>
<div class="text-4xl mb-4">${chestType.rarity === 'legendary' ? '💰' : chestType.rarity === 'epic' ? '🎁' : '📦'}</div>
<p class="text-cyan-300 mb-4">You found:</p>
`;
loot.forEach(item => {
if (item.type === 'gear') {
const gear = item.item;
lootHTML += `
<div class="mb-3 p-2 rounded border ${gear.rarity === 'legendary' ? 'border-yellow-500 bg-yellow-900/20' : gear.rarity === 'epic' ? 'border-orange-500 bg-orange-900/20' : 'border-gray-400 bg-gray-800/20'}">
<div class="font-bold ${gear.rarity === 'legendary' ? 'text-yellow-400' : gear.rarity === 'epic' ? 'text-orange-400' : 'text-gray-300'}">${gear.name}</div>
<div class="text-sm">${gear.type === 'weapon' ? '⚔️ Weapon' : '🛡️ Armor'}</div>
<div class="text-xs">${gear.type === 'weapon' ? `+${gear.attack} Attack` : `+${gear.defense} Defense`}</div>
<div class="text-xs text-cyan-300 mt-1">${gear.description}</div>
<div class="text-xs italic text-gray-400">"${gear.flavor}"</div>
</div>
`;
} else if (item.type === 'card') {
const card = item.item;
lootHTML += `
<div class="mb-3 p-2 rounded border ${card.rarity === 'legendary' ? 'border-yellow-500 bg-yellow-900/20' : card.rarity === 'epic' ? 'border-orange-500 bg-orange-900/20' : 'border-gray-400 bg-gray-800/20'}">
<div class="font-bold ${card.rarity === 'legendary' ? 'text-yellow-400' : card.rarity === 'epic' ? 'text-orange-400' : 'text-gray-300'}">${card.name}</div>
<div class="text-sm">Special Card</div>
<div class="text-xs text-cyan-300 mt-1">${card.description}</div>
<div class="text-xs italic text-gray-400">"${card.flavor}"</div>
</div>
`;
}
});
lootHTML += `</div>`;
content.innerHTML = lootHTML;
modal.classList.remove('hidden');
// Store loot for claiming
gameState.pendingLoot = loot;
}
function hideChestLootModal() {
const modal = document.getElementById('chest-loot-modal');
modal.classList.add('hidden');
// Add loot to player's inventory
if (gameState.pendingLoot) {
gameState.pendingLoot.forEach(item => {
if (item.type === 'card') {
// Add to player's hand
gameState.players[1].hand.push(item.item);
} else if (item.type === 'gear') {
// Add to player's gear inventory
if (!gameState.players[1].gear) gameState.players[1].gear = [];
gameState.players[1].gear.push(item.item);
}
});
log(`🎉 Loot added to your inventory!`);
gameState.pendingLoot = null;
updateUI();
}
}
function updateChestCount() {
const chestCount = gameState.chests ? gameState.chests.length : 0;
document.getElementById('chest-count').textContent = chestCount;
}
// Game initialization
function getInitialGameState() {
const state = {
gameMode: 'pvp',
currentPlayer: 1,
turnCounter: 1,
players: {
1: { health: 10, mana: 3, hand: [], deck: [], movedPieces: [], isAI: false, nexusHealth: 10, experience: 0 },
2: { health: 10, mana: 3, hand: [], deck: [], movedPieces: [], isAI: false, nexusHealth: 10, experience: 0 }
},
board: Array(BOARD_ROWS).fill().map(() => Array(BOARD_COLS).fill(null)),
selectedPiece: null,
gameOver: false,
log: ["🎮 Game started. Player 1's turn."]
};
createDecks(state);
drawInitialHands(state);
setupInitialBoard(state);
// Generate chests
state.chests = generateChests(state);
return state;
}
function startPvPMode() {
gameState = getInitialGameState();
gameState.gameMode = 'pvp';
$('#mode-selection').fadeOut(300);
initGame();
}
function startGame(difficulty) {
gameState = getInitialGameState();
gameState.gameMode = difficulty;
gameState.players[2].isAI = true;
$('#mode-selection').fadeOut(300);
initGame();
}
function initGame() {
init3D();
updateUI();
updateChestCount();
playBackgroundMusic();
}
function setupInitialBoard(state) {
// Place defense towers on the board - adjusted for expanded board
const towerPositions1 = [[2,2], [2,7]];
const towerPositions2 = [[BOARD_ROWS-3,2], [BOARD_ROWS-3,7]];
towerPositions1.forEach(([r, c]) => {
if (r >= 0 && r < BOARD_ROWS && c >= 0 && c < BOARD_COLS) {
state.board[r][c] = {
...PIECE_TYPES.DEFENSE_TOWER,
owner: 1,
health: PIECE_TYPES.DEFENSE_TOWER.defense,
isTower: true,
level: 1,
id: Math.random().toString(36).substr(2, 9)
};
}
});
towerPositions2.forEach(([r, c]) => {
if (r >= 0 && r < BOARD_ROWS && c >= 0 && c < BOARD_COLS) {
state.board[r][c] = {
...PIECE_TYPES.DEFENSE_TOWER,
owner: 2,
health: PIECE_TYPES.DEFENSE_TOWER.defense,
isTower: true,
level: 1,
id: Math.random().toString(36).substr(2, 9)
};
}
});
// Place stasis guards - adjusted for expanded board
const stasisPositions1 = [[1,1], [1,2], [1,3], [1,4], [1,5], [1,6], [1,7], [1,8]];
const stasisPositions2 = [[BOARD_ROWS-2,1], [BOARD_ROWS-2,2], [BOARD_ROWS-2,3], [BOARD_ROWS-2,4], [BOARD_ROWS-2,5], [BOARD_ROWS-2,6], [BOARD_ROWS-2,7], [BOARD_ROWS-2,8]];
stasisPositions1.forEach(([r, c]) => {
if (r >= 0 && r < BOARD_ROWS && c >= 0 && c < BOARD_COLS) {
state.board[r][c] = {
...PIECE_TYPES.STASIS_GUARD,
owner: 1,
health: PIECE_TYPES.STASIS_GUARD.defense,
isStasis: true,
level: 1,
id: Math.random().toString(36).substr(2, 9)
};
}
});
stasisPositions2.forEach(([r, c]) => {
if (r >= 0 && r < BOARD_ROWS && c >= 0 && c < BOARD_COLS) {
state.board[r][c] = {
...PIECE_TYPES.STASIS_GUARD,
owner: 2,
health: PIECE_TYPES.STASIS_GUARD.defense,
isStasis: true,
level: 1,
id: Math.random().toString(36).substr(2, 9)
};
}
});
}
function createDecks(state) {
Object.keys(state.players).forEach(player => {
const deck = [];
// Add pieces with increased quantities
Object.entries(PIECE_TYPES).forEach(([type, piece]) => {
if (type === 'NEXUS_CORE' || type === 'STASIS_GUARD') return; // Skip these
let count = 1;
if (type === 'DATA_EMPRESS') count = 1;
else if (type === 'FORTRESS_NODE') count = 4;
else if (type === 'SIGNAL_HACKER') count = 4;
else if (type === 'CYBER_STALKER') count = 4;
else if (type === 'BYTE_PAWN') count = 6;
else if (type === 'DEFENSE_TOWER') count = 3;
for (let i = 0; i < count; i++) {
deck.push({
type: CARD_TYPES.PIECE,
pieceType: type,
...piece,
id: Math.random().toString(36).substr(2, 9)
});
}
});
// Add other card types
INSTANT_CARDS.forEach(card => {
for (let i = 0; i < 2; i++) {
deck.push({
type: CARD_TYPES.INSTANT,
...card,
id: Math.random().toString(36).substr(2, 9)
});
}
});
SUMMON_CARDS.forEach(card => {
for (let i = 0; i < 2; i++) {
deck.push({
type: CARD_TYPES.SUMMON,
...card,
id: Math.random().toString(36).substr(2, 9)
});
}
});
ENHANCEMENT_CARDS.forEach(card => {
deck.push({
type: CARD_TYPES.ENHANCEMENT,
...card,
id: Math.random().toString(36).substr(2, 9)
});
});
// Add healing cards
HEALING_CARDS.forEach(card => {
for (let i = 0; i < 2; i++) {
deck.push({
type: CARD_TYPES.HEALING,
...card,
id: Math.random().toString(36).substr(2, 9)
});
}
});
// Add experience boost cards
EXP_BOOST_CARDS.forEach(card => {
for (let i = 0; i < 2; i++) {
deck.push({
type: CARD_TYPES.EXP_BOOST,
...card,
id: Math.random().toString(36).substr(2, 9)
});
}
});
// Shuffle deck
for (let i = deck.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[deck[i], deck[j]] = [deck[j], deck[i]];
}
state.players[player].deck = deck;
});
}
// ... (rest of the game logic functions remain similar but adjusted for the expanded board)
// Make sure to update the handleSquareClick function to handle chests and walls
function handleSquareClick(row, col) {
// Check if clicking on a wall
if (gameState.board[row][col]?.isWall) {
log("🚧 This square is blocked by a wall!");
return;
}
// Check if clicking on a chest
const chest = gameState.chests ? gameState.chests.find(c => c.row === row && c.col === col) : null;
if (chest) {
handleChestClick(chest, row, col);
return;
}
// Check if clicking on nexus attack zones (now at row 0 and BOARD_ROWS-1)
if ((row === 0 && gameState.currentPlayer === 1) || (row === BOARD_ROWS - 1 && gameState.currentPlayer === 2)) {
if (gameState.selectedPiece) {
attackNexusCore(gameState.selectedPiece, gameState.currentPlayer === 1 ? 2 : 1);
clearSelection();
hidePieceMenu();
updateUI();
return;
}
}
const piece = gameState.board[row][col];
if (gameState.selectedPiece) {
// Handle move or attack
if (canMovePiece(gameState.selectedPiece, row, col)) {
movePiece(gameState.selectedPiece, row, col);
} else if (canAttackSquare(gameState.selectedPiece, row, col)) {
attackPiece(gameState.selectedPiece, row, col);
}
clearSelection();
hidePieceMenu();
clearValidMoves3D();
} else {
hidePieceMenu();
}
updateUI();
}
// Update movement logic to account for walls
function canMovePiece(selectedPiece, targetRow, targetCol) {
const piece = selectedPiece.piece;
const fromRow = selectedPiece.row;
const fromCol = selectedPiece.col;
const targetPiece = gameState.board[targetRow][targetCol];
// Check if target is a wall
if (targetPiece && targetPiece.isWall) return false;
if (targetPiece && targetPiece.owner === piece.owner) return false;
const rowDiff = Math.abs(targetRow - fromRow);
const colDiff = Math.abs(targetCol - fromCol);
switch (piece.moves) {
case 'king': return rowDiff <= 1 && colDiff <= 1;
case 'queen': return rowDiff === 0 || colDiff === 0 || rowDiff === colDiff;
case 'rook': return rowDiff === 0 || colDiff === 0;
case 'bishop': return rowDiff === colDiff;
case 'knight': return (rowDiff === 2 && colDiff === 1) || (rowDiff === 1 && colDiff === 2);
case 'pawn':
const dir = piece.owner === 1 ? -1 : 1;
if (targetPiece !== null && !targetPiece.isWall) {
return false;
} else {
return (targetRow - fromRow === dir) && (colDiff === 0);
}
case 'none': return false;
default: return false;
}
}
// Update turn management to include tower attacks
function endTurn() {
if (gameState.gameOver) return;
hidePieceMenu();
clearValidMoves3D();
// Execute tower attacks and board tower attacks
executeTowerAttacks();
executeBoardTowerAttacks();
gameState.players[gameState.currentPlayer].movedPieces = [];
for (let r = 0; r < BOARD_ROWS; r++) {
for (let c = 0; c < BOARD_COLS; c++) {
const p = gameState.board[r][c];
if (p && p.owner === gameState.currentPlayer) {
p.justSummoned = false;
p.tempAttackBoost = 0;
p.tempDefenseBoost = 0;
}
}
}
if (gameState.turnCounter >= 6) {
removeStasisPieces();
}
gameState.currentPlayer = gameState.currentPlayer === 1 ? 2 : 1;
if (gameState.currentPlayer === 1) {
gameState.turnCounter++;
}
gameState.players[gameState.currentPlayer].mana = Math.min(10, (gameState.players[gameState.currentPlayer].mana || 0) + 1);
drawCard(gameState.currentPlayer);
clearSelection();
awardExperience(gameState.currentPlayer === 1 ? 2 : 1, 1);
log(`🎮 Turn ${gameState.turnCounter} - Player ${gameState.currentPlayer}'s turn`);
updateUI();
if (gameState.players[gameState.currentPlayer].isAI) {
setTimeout(makeAIMove, 1500);
}
}
// Board tower attacks (existing tower logic)
function executeBoardTowerAttacks() {
for (let r = 0; r < BOARD_ROWS; r++) {
for (let c = 0; c < BOARD_COLS; c++) {
const piece = gameState.board[r][c];
if (piece && piece.isTower && piece.owner === gameState.currentPlayer) {
attackAdjacentSquares(r, c, piece);
}
}
}
}
function attackAdjacentSquares(towerRow, towerCol, tower) {
const directions = [[-1,-1], [-1,0], [-1,1], [0,-1], [0,1], [1,-1], [1,0], [1,1]];
let attacked = false;
directions.forEach(([dr, dc]) => {
const targetRow = towerRow + dr;
const targetCol = towerCol + dc;
if (targetRow >= 0 && targetRow < BOARD_ROWS && targetCol >= 0 && targetCol < BOARD_COLS) {
const target = gameState.board[targetRow][targetCol];
if (target && target.owner !== tower.owner && !target.isWall) {
const damage = tower.attack;
target.health = (target.health || target.defense) - damage;
log(`🗼 ${tower.name} attacks ${target.name} for ${damage} damage!`);
attacked = true;
awardExperience(tower.owner, 2);
if (target.health <= 0) {
log(`💥 ${target.name} destroyed by tower fire!`);
gameState.board[targetRow][targetCol] = null;
awardExperience(tower.owner, 3);
}
}
}
});
}
// Update the UI function to show chest count
function updateUI() {
if (!gameState || !gameState.players) return;
const player1 = gameState.players[1];
const player2 = gameState.players[2];
$('#p1-health').text(player1.health);
$('#p2-health').text(player2.health);
$('#p1-health-bar').css('width', `${(player1.health / 10) * 100}%`);
$('#p2-health-bar').css('width', `${(player2.health / 10) * 100}%`);
$('#current-turn').text(`Player ${gameState.currentPlayer}`);
$('#turn-counter').text(gameState.turnCounter);
$('#p1-mana').text(player1.mana);
$('#p2-mana').text(player2.mana);
$('#p1-exp').text(player1.experience);
$('#p2-exp').text(player2.experience);
$('#p1-deck-count').text(player1.deck?.length || 0);
$('#p2-deck-count').text(player2.deck?.length || 0);
// Update enemy nexus health display
const enemyPlayer = gameState.currentPlayer === 1 ? 2 : 1;
$('#enemy-nexus-health').text(gameState.players[enemyPlayer].nexusHealth);
updatePlayerHand(player1.hand || [], 1);
update3DBoard();
updateLog();
updateChestCount();
}
// The rest of the existing functions (drawCard, awardExperience, etc.) remain similar
// Initialize game
$(document).ready(() => {
createParticles();
$('#mode-selection').show();
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment