harmony/presentation_no_iframe.html

1332 lines
51 KiB
HTML
Raw Permalink Normal View History

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>🎯 Harmony Game Presentation 🎯</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Arial', sans-serif;
height: 100vh;
overflow: hidden;
position: relative;
background: #000;
}
/* Background game - embedded directly from harmony.html */
.game-background {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
z-index: 1;
display: flex;
justify-content: center;
align-items: center;
pointer-events: none;
}
/* Game styles from harmony.html - adapted for presentation mode */
.game-container {
display: flex;
gap: 20px;
max-width: 1200px;
}
.board-container {
position: relative;
}
.board {
position: relative;
width: 560px;
height: 560px;
background: #111;
border: 2px solid #0f0;
margin: 10px;
}
.grid-lines {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
}
.grid-line {
position: absolute;
background: #444;
}
.grid-line.horizontal {
width: 100%;
height: 1px;
}
.grid-line.vertical {
height: 100%;
width: 1px;
}
.intersection {
position: absolute;
width: 20px;
height: 20px;
border: 2px solid #666;
border-radius: 50%;
background: #222;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
font-size: 12px;
transition: all 0.3s;
transform: translate(-50%, -50%);
pointer-events: none;
}
.intersection.unplayable {
display: none;
}
.intersection.heart-only {
border-color: #ff0 !important;
background: #330 !important;
}
.intersection.heart-only:hover {
background: #660 !important;
}
.intersection.highlight {
background: #ff0 !important;
animation: pulse 1s infinite;
}
.intersection.winning {
background: #ff0 !important;
animation: winning-pulse 2s infinite;
z-index: 1000;
}
.intersection.exploding {
background: #f00 !important;
animation: explode 1s ease-out forwards;
}
.intersection.imploding {
background: #800 !important;
animation: implode 1s ease-in forwards;
}
@keyframes explode {
0% {
transform: translate(-50%, -50%) scale(1);
background: #ff0;
box-shadow: 0 0 5px #ff0;
}
50% {
transform: translate(-50%, -50%) scale(2);
background: #f80;
box-shadow: 0 0 20px #f80;
}
100% {
transform: translate(-50%, -50%) scale(3);
background: #f00;
box-shadow: 0 0 40px #f00;
opacity: 0;
}
}
@keyframes implode {
0% {
transform: translate(-50%, -50%) scale(1);
background: #800;
opacity: 1;
}
50% {
transform: translate(-50%, -50%) scale(0.5);
background: #400;
}
100% {
transform: translate(-50%, -50%) scale(0);
opacity: 0;
}
}
@keyframes pulse {
0%, 100% { opacity: 0.5; }
50% { opacity: 1; }
}
@keyframes winning-pulse {
0%, 100% {
background: #ff0 !important;
transform: scale(1);
}
50% {
background: #f80 !important;
transform: scale(1.2);
}
}
/* Player Stone Differentiation */
.intersection.player-1 {
background-color: rgba(0, 150, 255, 0.3) !important; /* Blue background for P1 */
border-color: #0096ff !important;
}
.intersection.player-2 {
background-color: rgba(255, 100, 0, 0.3) !important; /* Orange background for P2 */
border-color: #ff6400 !important;
}
.intersection.player-1:hover {
background-color: rgba(0, 150, 255, 0.5) !important;
}
.intersection.player-2:hover {
background-color: rgba(255, 100, 0, 0.5) !important;
}
.intersection.harmony-piece {
border-color: #0ff !important; /* Cyan border for locked pieces */
box-shadow: 0 0 8px #0ff;
cursor: not-allowed;
}
/* Presentation Mode Styles - force presentation mode styles */
.game-container {
max-width: none !important;
gap: 0 !important;
width: 100vw !important;
height: 100vh !important;
display: flex !important;
justify-content: center !important;
align-items: center !important;
position: fixed !important;
top: 0 !important;
left: 0 !important;
}
.board-container {
margin: 0 !important;
width: 100vw !important;
height: 100vh !important;
display: flex !important;
justify-content: center !important;
align-items: center !important;
position: fixed !important;
top: 0 !important;
left: 0 !important;
}
.board {
margin: 0 !important;
border: 3px solid #0f0 !important;
box-shadow: 0 0 20px rgba(0, 255, 0, 0.3) !important;
max-width: none !important;
max-height: none !important;
/* Size will be set by JavaScript */
}
/* Presentation overlay */
#presentation-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 10;
background: transparent;
display: flex;
align-items: center;
justify-content: center;
}
/* Slide container */
.slide {
display: none;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding: 60px;
border-radius: 20px;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
max-width: 90vw;
max-height: 90vh;
text-align: center;
color: white;
overflow: auto;
}
.slide.active {
display: block;
animation: slideIn 0.5s ease-in-out;
}
@keyframes slideIn {
from {
opacity: 0;
transform: translateY(50px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.slide h1 {
font-size: 4em;
margin-bottom: 30px;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
}
.slide h2 {
font-size: 3em;
margin-bottom: 20px;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
}
.slide p {
font-size: 1.8em;
line-height: 1.6;
margin-bottom: 20px;
}
.slide ul {
font-size: 1.6em;
text-align: left;
max-width: 800px;
margin: 0 auto;
}
.slide li {
margin-bottom: 15px;
list-style: none;
position: relative;
padding-left: 30px;
}
.slide li:before {
content: "🎯";
position: absolute;
left: 0;
}
.slide img {
max-width: 100%;
max-height: 60vh;
border-radius: 15px;
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.3);
margin: 20px 0;
}
.slide-counter {
position: fixed;
top: 30px;
right: 30px;
z-index: 20;
background: rgba(0, 0, 0, 0.5);
color: white;
padding: 10px 20px;
border-radius: 25px;
font-size: 1.2em;
backdrop-filter: blur(10px);
}
/* Special styling for different slide types */
.title-slide {
background: linear-gradient(135deg, #ff6b6b 0%, #ee5a24 100%);
}
.image-slide {
background: linear-gradient(135deg, #2c3e50 0%, #3498db 100%);
}
.content-slide {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.hindu-slide {
background: linear-gradient(135deg, #ff9a9e 0%, #fecfef 100%);
}
.game-only-slide {
background: transparent;
padding: 0;
}
</style>
</head>
<body>
<!-- Background game - embedded directly from harmony.html -->
<div class="game-background">
<div class="game-container">
<div class="board-container">
<div class="board" id="board"></div>
</div>
</div>
</div>
<!-- Presentation overlay -->
<div id="presentation-overlay">
<!-- Slide 1: Title -->
<div class="slide active title-slide" id="slide-1">
<h1>🎯 Hacking Games and Leveraging/Managing A.I</h1>
<div style="font-size: 2.5em; margin-top: 50px; display: inline-block; text-align: left;">
<span>by Jolly + </span>
<div style="display: inline-block; vertical-align: top; text-align: left;">
<div style="text-align: left;">shift</div>
<div style="font-size: 0.6em; margin-top: 5px; white-space: nowrap; text-align: left;">(who got dragged along 😄)</div>
</div>
</div>
</div>
<!-- Slide 2: Why hack games? -->
<div class="slide content-slide" id="slide-2">
<h2>Why Hack Games??</h2>
<ul>
<li>To improve them…</li>
<li>Increase or decrease complexity</li>
<li>To make them more fun</li>
<li>To make them more social</li>
<li>For example: make a one player game two player</li>
<li>Or a 4 player game 6 player</li>
</ul>
</div>
<!-- Slide 3: Increased Complexity -->
<div class="slide content-slide" id="slide-3">
<h2>Increased Complexity</h2>
<div style="display: flex; justify-content: center; align-items: center; height: 70vh;">
<img src="images/IMG_20250930_182227.jpg" alt="Game pieces detail" style="max-width: 90%; max-height: 100%; object-fit: contain; border-radius: 10px; box-shadow: 0 4px 15px rgba(0,255,255,0.3);">
</div>
</div>
<!-- Slide 4: Life hack -->
<div class="slide content-slide" id="slide-4">
<h2>Life Hack</h2>
<p style="font-size: 2.5em;">Old U-Bahn cards for Top Trumps</p>
</div>
<!-- Slide 5: Top Trumps image -->
<div class="slide image-slide" id="slide-5">
<h2>Top Trumps</h2>
<img src="images/top trumps.jpg" alt="Top Trumps cards">
</div>
<!-- Slide 6: Backstory -->
<div class="slide content-slide" id="slide-6">
<h2>Backstory</h2>
<p>In 1874 Mr. Ranmuchincinson found a carrot in his garden and he's dead now but he would probably have hacked games if he had watched this presentation.</p>
</div>
<!-- Slide 7: Royal Chess -->
<div class="slide image-slide" id="slide-7">
<h2>Meeting People Over Games</h2>
<img src="images/IMG_20250924_174907.jpg" alt="Royal Chess">
</div>
<!-- Slide 8: Quatro -->
<div class="slide content-slide" id="slide-8">
<h2>Quatro</h2>
<div style="display: flex; justify-content: center; align-items: center; height: 70vh;">
<img src="images/IMG_20250930_182505.jpg" alt="Complete game setup" style="max-width: 90%; max-height: 100%; object-fit: contain; border-radius: 10px; box-shadow: 0 4px 15px rgba(0,255,255,0.3);">
</div>
</div>
<!-- Slide 9: Octo -->
<div class="slide content-slide" id="slide-9">
<h2>Octo</h2>
<div style="display: flex; justify-content: center; align-items: center; height: 70vh;">
<img src="images/IMG_20250930_182236.jpg" alt="Game in progress" style="max-width: 90%; max-height: 100%; object-fit: contain; border-radius: 10px; box-shadow: 0 4px 15px rgba(0,255,255,0.3);">
</div>
</div>
<!-- Slide 10: Penta Game -->
<div class="slide content-slide" id="slide-10">
<h2>Penta Board Game</h2>
<p>Colour scheme investigation - inspiration</p>
<p style="font-size: 1.5em; margin-top: 40px;">Game mechanics and visual design exploration</p>
</div>
<!-- Slide 11: Hindu Gods Introduction -->
<div class="slide hindu-slide" id="slide-11">
<h2>Relationship to Hindu Gods</h2>
<p>Waiting for inspiration...</p>
<p>Idea to hack Quarto</p>
</div>
<!-- Slide 12: Brahma Saraswati -->
<div class="slide hindu-slide" id="slide-12">
<h2>Brahma & Saraswati</h2>
<img src="images/brahma sarasiwati.svg" alt="Brahma and Saraswati">
<p>Wisdom and Creation</p>
</div>
<!-- Slide 13: Hanuman -->
<div class="slide hindu-slide" id="slide-13">
<h2>Hanuman & Surya Chala Devi</h2>
<img src="images/hanuman and Survachala devi.svg" alt="Hanuman and Surya Chala Devi">
<p>Strength and Devotion</p>
</div>
<!-- Slide 14: Shiva & Parvati -->
<div class="slide hindu-slide" id="slide-14">
<h2>Shiva & Parvati</h2>
<img src="images/shiva and parvati.svg" alt="Shiva and Parvati">
<p>Destruction and Renewal</p>
</div>
<!-- Slide 15: Varuna & Gauri -->
<div class="slide hindu-slide" id="slide-15">
<h2>Varuna & Gauri</h2>
<img src="images/varuna and Gauri.svg" alt="Varuna and Gauri">
<p>Water and Purity</p>
</div>
<!-- Slide 16: Vishnu & Lakshmi -->
<div class="slide hindu-slide" id="slide-16">
<h2>Vishnu & Lakshmi</h2>
<img src="images/visnu laksami.svg" alt="Vishnu and Lakshmi">
<p>Preservation and Prosperity</p>
</div>
<!-- Slide 17: Harmany image -->
<div class="slide image-slide" id="slide-17">
<h2>Harmany</h2>
<img src="images/drawing-1.svg" alt="Harmany concept">
</div>
<!-- Slide 18: Physical Game Photo 1 -->
<div class="slide content-slide" id="slide-18">
<h2>Physical Game in Action</h2>
<div style="display: flex; justify-content: center; align-items: center; height: 70vh;">
<img src="images/IMG_20250930_182212.jpg" alt="Harmony game setup" style="max-width: 90%; max-height: 100%; object-fit: contain; border-radius: 10px; box-shadow: 0 4px 15px rgba(0,255,255,0.3);">
</div>
</div>
<!-- Slide 19: AI Research -->
<div class="slide content-slide" id="slide-19">
<h2>More AI Bollocks?</h2>
<p style="font-size: 1.8em; margin-bottom: 30px;">Want to see what AI agents can actually do?</p>
<p style="font-size: 1.4em; margin-bottom: 20px;"><strong>HY300 Linux Porting Project</strong></p>
<p style="font-size: 1.1em; line-height: 1.4;">🤖 AI agents reverse-engineered an Android projector and created a complete mainline Linux port</p>
<p style="font-size: 1.1em; line-height: 1.4;">⚡ From firmware analysis to kernel drivers to NixOS VM - all automated</p>
<p style="font-size: 1.1em; line-height: 1.4;">🔧 85 commits, 8 development phases, months of work in days</p>
<p style="font-size: 1.2em; margin-top: 30px; color: #0ff;"><strong>github.com/shift/sun50iw12p1-research</strong></p>
</div>
<!-- Slide 20: Play Harmony -->
<div class="slide content-slide" id="slide-20">
<h2>Ready to Play?</h2>
<p style="font-size: 2em; margin-bottom: 30px;">Let's play some Harmony!</p>
<div style="display: flex; align-items: center; justify-content: center; gap: 50px; margin-bottom: 20px;">
<!-- Local Game Button -->
<div style="text-align: center;">
<button onclick="window.location.href='harmony.html'" style="
font-size: 1.5em;
padding: 20px 40px;
background: linear-gradient(45deg, #ff6b35, #f7931e);
color: white;
border: none;
border-radius: 10px;
cursor: pointer;
box-shadow: 0 4px 15px rgba(247, 147, 30, 0.4);
transition: all 0.3s;
" onmouseover="this.style.transform='scale(1.05)'" onmouseout="this.style.transform='scale(1)'">
🎯 Play Now 🎯
</button>
<p style="font-size: 1em; margin-top: 10px; color: #888;">Local version</p>
</div>
<!-- QR Code for Mobile -->
<div style="text-align: center;">
<img src="images/harmony-qr-code.png" alt="QR Code for Harmony Game" style="width: 150px; height: 150px; border: 2px solid #fff; border-radius: 10px;">
<p style="font-size: 1em; margin-top: 10px; color: #888;">Scan for mobile</p>
<p style="font-size: 0.9em; color: #aaa;">code.c-base.org/shift/harmony</p>
</div>
</div>
<p style="font-size: 1.8em; color: #fff; margin-top: 20px; font-weight: bold;">Scan the QR code for the slides &amp; code.</p>
</div>
</div>
<!-- Slide counter -->
<div class="slide-counter">
<span id="current-slide">1</span> / <span id="total-slides">20</span>
</div>
<script>
// Slide navigation code
let currentSlideIndex = 0;
const totalSlides = 20;
let isTransitioning = false;
function showSlide(index) {
// Hide all slides
const slides = document.querySelectorAll('.slide');
slides.forEach(slide => slide.classList.remove('active'));
// Show current slide
const currentSlide = document.getElementById(`slide-${index + 1}`);
if (currentSlide) {
currentSlide.classList.add('active');
}
// Update counter
document.getElementById('current-slide').textContent = index + 1;
}
function nextSlide() {
if (isTransitioning || currentSlideIndex >= totalSlides - 1) return;
isTransitioning = true;
// Hide current slide
const currentSlide = document.querySelector('.slide.active');
if (currentSlide) {
currentSlide.classList.remove('active');
}
// Show game for 500ms between transitions
setTimeout(() => {
currentSlideIndex++;
showSlide(currentSlideIndex);
isTransitioning = false;
}, 500);
}
function previousSlide() {
if (isTransitioning || currentSlideIndex <= 0) return;
isTransitioning = true;
// Hide current slide
const currentSlide = document.querySelector('.slide.active');
if (currentSlide) {
currentSlide.classList.remove('active');
}
// Show game for 500ms between transitions
setTimeout(() => {
currentSlideIndex--;
showSlide(currentSlideIndex);
isTransitioning = false;
}, 500);
}
// Keyboard navigation
document.addEventListener('keydown', (e) => {
if (e.key === 'ArrowRight' || e.key === ' ') {
nextSlide();
} else if (e.key === 'ArrowLeft') {
previousSlide();
}
});
// Initialize
showSlide(0);
document.getElementById('total-slides').textContent = totalSlides;
// Real Harmony Game Logic - copied from harmony.html
class HarmonyGame {
constructor() {
this.board = Array(13).fill().map(() => Array(13).fill(null));
this.currentPlayer = 1;
this.gamePhase = 'playing';
this.gameStage = { 1: 'corner_harmony', 2: 'corner_harmony' };
this.selectedPieceType = null;
this.aiMoveDelay = 100; // Fast for presentation
this.elements = {
fire: '🔥', water: '💧', earth: '🌍', air: '💨', love: '❤️'
};
this.dominance = { fire: 'earth', earth: 'water', water: 'air', air: 'fire' };
this.inventory = {
1: { fire: 12, water: 12, earth: 12, air: 12, love: 1 },
2: { fire: 12, water: 12, earth: 12, air: 12, love: 1 }
};
this.heartPositions = { 1: null, 2: null };
this.weights = {
fiveInARow: 10000,
harmony: 10000,
fourInARow: 750,
threeInARow: 150,
twoInARow: 15,
harmonyProgress: 250,
blockHarmony: 200,
material: 5,
capture: 0,
positional: 3,
scarcityPenalty: 15,
captureRiskPenalty: 45,
noStonesAndNoHarmonyPenalty: 5000,
strategicMoveBonus: 50,
secondHarmonyBonus: 500,
heartMoveBonus: 30,
winningHeartPositionBonus: 2000,
secondHarmonyProgress: 800,
lockedPiecePenalty: 200
};
// Player types: both AI for presentation
this.playerTypes = {
1: 'ai',
2: 'ai'
};
this.completedHarmonies = { 1: new Set(), 2: new Set() };
this.harmoniesCount = { 1: 0, 2: 0 };
this.lockedPieces = new Set();
// Heart positions
this.cornerHeartPositions = [
{r: 1, c: 1}, {r: 1, c: 11}, {r: 11, c: 1}, {r: 11, c: 11}
];
this.middleHeartPositions = [
{r: 3, c: 3}, {r: 3, c: 9}, {r: 9, c: 3}, {r: 9, c: 9}
];
this.centerPosition = {r: 6, c: 6};
this.gameOver = false;
this.winner = null;
// Force presentation mode
this.presentationMode = true;
this.playablePositions = new Set();
this.heartOnlyPositions = new Set();
this.initializeBoardLayout();
this.initBoard();
this.updateDisplay();
this.startGame();
// Handle window resize
window.addEventListener('resize', () => this.resizeBoardForPresentation());
}
initializeBoardLayout() {
// Corner extended positions
this.playablePositions.add('0,0');
this.playablePositions.add('0,1');
this.playablePositions.add('0,11');
this.playablePositions.add('0,12');
this.playablePositions.add('12,0');
this.playablePositions.add('12,1');
this.playablePositions.add('12,11');
this.playablePositions.add('12,12');
// Main playable area (rows 1-11, cols 1-11)
for (let r = 1; r <= 11; r++) {
for (let c = 1; c <= 11; c++) {
this.playablePositions.add(`${r},${c}`);
}
}
// Add missing side positions for rows 1 and 11
this.playablePositions.add('1,0');
this.playablePositions.add('1,12');
this.playablePositions.add('11,0');
this.playablePositions.add('11,12');
// Heart-only positions
this.heartOnlyPositions.add('1,1');
this.heartOnlyPositions.add('1,11');
this.heartOnlyPositions.add('11,1');
this.heartOnlyPositions.add('11,11');
this.heartOnlyPositions.add('3,3');
this.heartOnlyPositions.add('3,9');
this.heartOnlyPositions.add('9,3');
this.heartOnlyPositions.add('9,9');
this.heartOnlyPositions.add('6,6');
}
initBoard() {
const board = document.getElementById('board');
board.innerHTML = '';
// Create grid lines
const gridLines = document.createElement('div');
gridLines.className = 'grid-lines';
// Horizontal lines
for (let i = 0; i <= 12; i++) {
const line = document.createElement('div');
line.className = 'grid-line horizontal';
line.style.top = `${40 + (i * 400 / 12)}px`;
gridLines.appendChild(line);
}
// Vertical lines
for (let i = 0; i <= 12; i++) {
const line = document.createElement('div');
line.className = 'grid-line vertical';
line.style.left = `${40 + (i * 400 / 12)}px`;
gridLines.appendChild(line);
}
board.appendChild(gridLines);
// Create intersections for 13x13 board
for (let r = 0; r < 13; r++) {
for (let c = 0; c < 13; c++) {
const intersection = document.createElement('div');
intersection.className = 'intersection';
intersection.dataset.row = r;
intersection.dataset.col = c;
// Position intersection on grid
const left = 40 + (c * 480 / 12);
const top = 40 + (r * 480 / 12);
intersection.style.left = `${left}px`;
intersection.style.top = `${top}px`;
// Check if this position is playable
const posKey = `${r},${c}`;
if (!this.playablePositions.has(posKey)) {
intersection.classList.add('unplayable');
} else if (this.heartOnlyPositions.has(posKey)) {
intersection.classList.add('heart-only');
}
board.appendChild(intersection);
}
}
// Resize board for presentation mode
setTimeout(() => this.resizeBoardForPresentation(), 10);
}
resizeBoardForPresentation() {
const board = document.querySelector('.board');
if (!board) return;
// Calculate size based on window dimensions
const containerWidth = window.innerWidth;
const containerHeight = window.innerHeight;
const size = Math.min(containerWidth, containerHeight) * 0.9;
// Set board size directly
board.style.width = size + 'px';
board.style.height = size + 'px';
// Scale intersections proportionally
const scaleFactor = size / 560; // 560 is original board size
const intersectionSize = Math.max(8, 20 * scaleFactor);
const fontSize = Math.max(6, 12 * scaleFactor);
const borderWidth = Math.max(1, 2 * scaleFactor);
const intersections = document.querySelectorAll('.intersection');
intersections.forEach(intersection => {
intersection.style.width = intersectionSize + 'px';
intersection.style.height = intersectionSize + 'px';
intersection.style.fontSize = fontSize + 'px';
intersection.style.borderWidth = borderWidth + 'px';
});
// Recalculate grid line positions
const gridSize = size - 80; // Account for margins
const horizontalLines = board.querySelectorAll('.grid-line.horizontal');
horizontalLines.forEach((line, i) => {
line.style.top = `${40 + (i * gridSize / 12)}px`;
});
const verticalLines = board.querySelectorAll('.grid-line.vertical');
verticalLines.forEach((line, i) => {
line.style.left = `${40 + (i * gridSize / 12)}px`;
});
// Recalculate intersection positions
intersections.forEach(intersection => {
const r = parseInt(intersection.dataset.row);
const c = parseInt(intersection.dataset.col);
const left = 40 + (c * gridSize / 12);
const top = 40 + (r * gridSize / 12);
intersection.style.left = left + 'px';
intersection.style.top = top + 'px';
});
}
isPlayable(r, c) {
if (r < 0 || r >= 13 || c < 0 || c >= 13) return false;
return this.playablePositions.has(`${r},${c}`);
}
startGame() {
// Start AI vs AI game automatically for presentation
if (this.playerTypes[this.currentPlayer] === 'ai') {
setTimeout(() => this.makeAIMove(), this.aiMoveDelay);
}
}
updateDisplay() {
// Update board display
for (let r = 0; r < 13; r++) {
for (let c = 0; c < 13; c++) {
const intersection = document.querySelector(`[data-row="${r}"][data-col="${c}"]`);
if (intersection && this.board[r][c]) {
const piece = this.board[r][c];
const player = parseInt(piece.slice(-1));
const elementSymbol = piece.slice(0, -1);
intersection.textContent = elementSymbol;
intersection.classList.add(`player-${player}`);
// Add harmony piece styling for locked pieces
if (this.lockedPieces.has(`${r},${c}`)) {
intersection.classList.add('harmony-piece');
}
} else if (intersection) {
intersection.textContent = '';
intersection.classList.remove('player-1', 'player-2', 'harmony-piece');
}
}
}
}
nextPlayer() {
this.currentPlayer = this.currentPlayer === 1 ? 2 : 1;
}
canPlaceHeart(player, r, c, silent = false) {
const posKey = `${r},${c}`;
if (!this.heartOnlyPositions.has(posKey)) {
return false;
}
// Enforce stage progression
const heartPos = {r, c};
const isCorner = this.cornerHeartPositions.some(p => p.r === r && p.c === c);
const isMiddle = this.middleHeartPositions.some(p => p.r === r && p.c === c);
const isCenter = this.centerPosition.r === r && this.centerPosition.c === c;
switch (this.gameStage[player]) {
case 'corner_harmony':
if (!isCorner) return false;
break;
case 'middle_harmony':
if (!isMiddle) return false;
break;
case 'center_harmony':
if (!isCenter) return false;
break;
}
return true;
}
async makeMove(r, c, elementType, player) {
if (elementType === 'love' && !this.canPlaceHeart(player, r, c)) {
return;
}
this.board[r][c] = this.elements[elementType] + player;
this.inventory[player][elementType]--;
if (elementType === 'love') {
this.heartPositions[player] = {r, c};
}
this.processElementalReactions(r, c, elementType, player);
await this.checkVictoryConditions();
if (!this.gameOver) {
this.nextPlayer();
if (this.playerTypes[this.currentPlayer] === 'ai') {
setTimeout(() => this.makeAIMove(), this.aiMoveDelay);
}
}
this.updateDisplay();
}
processElementalReactions(r, c, elementType, player) {
if (elementType === 'love') return;
const adjacentPositions = [
{r: r-1, c: c}, {r: r+1, c: c},
{r: r, c: c-1}, {r: r, c: c+1}
];
for (const pos of adjacentPositions) {
if (pos.r < 0 || pos.r >= 13 || pos.c < 0 || pos.c >= 13) continue;
const targetCell = this.board[pos.r][pos.c];
if (!targetCell) continue;
const targetPlayer = parseInt(targetCell.slice(-1));
if (targetPlayer === player) continue;
const targetElement = this.getElementTypeFromSymbol(targetCell.slice(0, -1));
if (targetElement === 'love') continue;
// Check if the piece is locked
if (this.lockedPieces.has(`${pos.r},${pos.c}`)) {
continue;
}
// Check if our element dominates the target
if (this.dominance[elementType] === targetElement) {
this.board[pos.r][pos.c] = null;
// Animate removal
const targetIntersection = document.querySelector(`[data-row="${pos.r}"][data-col="${pos.c}"]`);
if (targetIntersection) {
targetIntersection.classList.add('exploding');
setTimeout(() => {
targetIntersection.classList.remove('exploding');
}, 1000);
}
}
}
}
getElementTypeFromSymbol(symbol) {
for (const [type, emoji] of Object.entries(this.elements)) {
if (emoji === symbol) return type;
}
return null;
}
checkHarmonyAtPosition(heartPos, player) {
if (!heartPos) return false;
const completedCount = this.harmoniesCount[player];
if (completedCount === 0) {
return this.checkFirstHarmony(heartPos, player);
} else {
return this.checkSubsequentHarmony(heartPos, player);
}
}
checkFirstHarmony(heartPos, player) {
if (!heartPos) return false;
const adjacent = [
{r: heartPos.r - 1, c: heartPos.c},
{r: heartPos.r + 1, c: heartPos.c},
{r: heartPos.r, c: heartPos.c - 1},
{r: heartPos.r, c: heartPos.c + 1}
];
const elementsFound = new Set();
for (const pos of adjacent) {
const cell = this.board[pos.r]?.[pos.c];
if (cell && cell.endsWith(player.toString())) {
const elementSymbol = cell.slice(0, -1);
const elementType = this.getElementTypeFromSymbol(elementSymbol);
if (elementType && elementType !== 'love') {
elementsFound.add(elementType);
}
}
}
return elementsFound.size === 4;
}
checkSubsequentHarmony(heartPos, player) {
if (!heartPos) return false;
const adjacent = [
{r: heartPos.r - 1, c: heartPos.c},
{r: heartPos.r + 1, c: heartPos.c},
{r: heartPos.r, c: heartPos.c - 1},
{r: heartPos.r, c: heartPos.c + 1}
];
const directElements = [];
for (const pos of adjacent) {
const cell = this.board[pos.r]?.[pos.c];
if (cell && cell.endsWith(player.toString())) {
const elementSymbol = cell.slice(0, -1);
const elementType = this.getElementTypeFromSymbol(elementSymbol);
if (elementType && elementType !== 'love') {
directElements.push({pos, type: elementType});
}
}
}
if (directElements.length !== 1) return false;
const startElement = directElements[0];
const visited = new Set();
const elementsInChain = new Set();
const findConnectedElements = (pos, elementType) => {
const key = `${pos.r},${pos.c}`;
if (visited.has(key)) return;
visited.add(key);
elementsInChain.add(elementType);
const neighbors = [
{r: pos.r - 1, c: pos.c},
{r: pos.r + 1, c: pos.c},
{r: pos.r, c: pos.c - 1},
{r: pos.r, c: pos.c + 1}
];
for (const neighbor of neighbors) {
const cell = this.board[neighbor.r]?.[neighbor.c];
if (cell && cell.endsWith(player.toString())) {
const neighborSymbol = cell.slice(0, -1);
const neighborType = this.getElementTypeFromSymbol(neighborSymbol);
if (neighborType && neighborType !== 'love') {
findConnectedElements(neighbor, neighborType);
}
}
}
};
findConnectedElements(startElement.pos, startElement.type);
return elementsInChain.size === 4;
}
lockHarmonyPieces(heartPos) {
// Lock the heart and adjacent pieces
this.lockedPieces.add(`${heartPos.r},${heartPos.c}`);
const adjacent = [
{r: heartPos.r - 1, c: heartPos.c},
{r: heartPos.r + 1, c: heartPos.c},
{r: heartPos.r, c: heartPos.c - 1},
{r: heartPos.r, c: heartPos.c + 1}
];
for (const pos of adjacent) {
if (this.board[pos.r]?.[pos.c]) {
this.lockedPieces.add(`${pos.r},${pos.c}`);
}
}
}
async checkVictoryConditions() {
for (let player = 1; player <= 2; player++) {
const heartPos = this.heartPositions[player];
if (heartPos && this.checkHarmonyAtPosition(heartPos, player)) {
const harmonyId = `${heartPos.r},${heartPos.c}`;
if (!this.completedHarmonies[player].has(harmonyId)) {
this.completedHarmonies[player].add(harmonyId);
this.harmoniesCount[player]++;
this.lockHarmonyPieces(heartPos);
if (this.harmoniesCount[player] === 1) {
this.gameStage[player] = 'middle_harmony';
} else if (this.harmoniesCount[player] >= 2) {
this.gameStage[player] = 'center_harmony';
}
if (this.harmoniesCount[player] >= 3) {
await this.handleVictory(player);
return;
}
}
}
if (this.harmoniesCount[player] >= 2 && this.checkFiveInRow(player)) {
await this.handleVictory(player);
return;
}
}
// Check for stalemate (board full or no moves possible)
if (this.isBoardFull() || this.noMovesRemaining()) {
console.log('🔄 Game stalemate detected, restarting...');
setTimeout(() => {
this.resetGame();
if (this.playerTypes[this.currentPlayer] === 'ai') {
setTimeout(() => this.makeAIMove(), this.aiMoveDelay);
}
}, 3000);
}
}
checkFiveInRow(player) {
const directions = [
{r: 0, c: 1}, {r: 1, c: 0},
{r: 1, c: 1}, {r: 1, c: -1}
];
for (let r = 0; r < 13; r++) {
for (let c = 0; c < 13; c++) {
for (const dir of directions) {
if (this.checkLineFromPosition(r, c, dir, player, 5)) {
return true;
}
}
}
}
return false;
}
isBoardFull() {
for (let r = 0; r < 13; r++) {
for (let c = 0; c < 13; c++) {
if (this.isPlayable(r, c) && this.board[r][c] === null) {
return false;
}
}
}
return true;
}
noMovesRemaining() {
// Check if both players have no pieces left to play
for (let player = 1; player <= 2; player++) {
const inventory = this.inventory[player];
const totalPieces = inventory.fire + inventory.water + inventory.earth + inventory.air + inventory.love;
if (totalPieces > 0) {
return false;
}
}
return true;
}
checkLineFromPosition(startR, startC, direction, player, length) {
let firstElement = null;
for (let i = 0; i < length; i++) {
const r = startR + direction.r * i;
const c = startC + direction.c * i;
if (r < 0 || r >= 13 || c < 0 || c >= 13) return false;
const cell = this.board[r][c];
if (!cell || !cell.endsWith(player.toString())) return false;
const elementSymbol = cell.slice(0, -1);
const elementType = this.getElementTypeFromSymbol(elementSymbol);
if (elementType === 'love') return false;
if (firstElement === null) {
firstElement = elementType;
} else if (firstElement !== elementType) {
return false;
}
}
return true;
}
async makeAIMove() {
const currentAI = this.currentPlayer;
// Opening move override
if (this.gameStage[currentAI] === 'corner_harmony' && this.heartPositions[currentAI] === null && this.inventory[currentAI].love > 0) {
const availableCorners = this.cornerHeartPositions.filter(pos => this.board[pos.r][pos.c] === null);
if (availableCorners.length > 0) {
const bestCorner = availableCorners[Math.floor(Math.random() * availableCorners.length)];
await this.makeMove(bestCorner.r, bestCorner.c, 'love', currentAI);
return;
}
}
let bestMove = { score: -Infinity, r: -1, c: -1, element: null };
const availableElements = Object.entries(this.inventory[currentAI])
.filter(([_, count]) => count > 0)
.map(([element, _]) => element);
// Evaluate placing new pieces
for (let r = 0; r < 13; r++) {
for (let c = 0; c < 13; c++) {
if (this.board[r][c] === null && this.isPlayable(r, c)) {
for (const element of availableElements) {
if (element === 'love' && !this.canPlaceHeart(currentAI, r, c)) continue;
if (element !== 'love' && this.heartOnlyPositions.has(`${r},${c}`)) continue;
const score = Math.random() * 1000; // Simplified AI for presentation
if (score > bestMove.score) {
bestMove = { score, r, c, element };
}
}
}
}
}
if (bestMove.element) {
await this.makeMove(bestMove.r, bestMove.c, bestMove.element, currentAI);
} else {
this.nextPlayer();
this.updateDisplay();
}
}
async handleVictory(winner) {
this.gameOver = true;
this.winner = winner;
// Highlight winning pieces
for (let r = 0; r < 13; r++) {
for (let c = 0; c < 13; c++) {
const cell = this.board[r][c];
if (cell && cell.endsWith(winner.toString())) {
const intersection = document.querySelector(`[data-row="${r}"][data-col="${c}"]`);
if (intersection) {
intersection.classList.add('winning');
}
}
}
}
// Auto-restart for presentation
console.log(`🎯 Game Over! Player ${winner} won. Restarting in 4 seconds...`);
setTimeout(() => {
console.log('🔄 Restarting game...');
this.resetGame();
// Ensure AI starts playing again
if (this.playerTypes[this.currentPlayer] === 'ai') {
setTimeout(() => this.makeAIMove(), this.aiMoveDelay);
}
}, 4000);
}
resetGame() {
this.board = Array(13).fill().map(() => Array(13).fill(null));
this.currentPlayer = 1;
this.gamePhase = 'playing';
this.selectedPieceType = null;
this.completedHarmonies = { 1: new Set(), 2: new Set() };
this.harmoniesCount = { 1: 0, 2: 0 };
this.lockedPieces = new Set();
this.gameStage = { 1: 'corner_harmony', 2: 'corner_harmony' };
this.inventory = {
1: { fire: 12, water: 12, earth: 12, air: 12, love: 1 },
2: { fire: 12, water: 12, earth: 12, air: 12, love: 1 }
};
this.heartPositions = { 1: null, 2: null };
this.gameOver = false;
this.winner = null;
this.initBoard();
this.updateDisplay();
this.startGame();
}
}
// Initialize the real Harmony game when page loads
window.addEventListener('load', () => {
new HarmonyGame();
});
</script>
</body>
</html>