korean-cpc-agents / korean_mud_game.html
ramsi-k's picture
add demo
fc190df
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>🏠 한국어 가족집 모험 🇰🇷</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@400;500;700&family=Orbitron:wght@400;700&display=swap');
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Courier New', monospace;
background: url('assets/images/bukchon-hanok-thumb-scaled.jpg') center/cover fixed;
background-color: #f5e9d9;
color: #3a3226;
min-height: 100vh;
overflow-x: hidden;
position: relative;
}
body::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: url('assets/images/repeat-background.jpg') repeat;
opacity: 0.15;
pointer-events: none;
z-index: -1;
}
/* Animated background elements */
.bg-particles {
position: fixed;
width: 100%;
height: 100%;
pointer-events: none;
z-index: -1;
overflow: hidden;
}
.particle {
position: absolute;
width: 2px;
height: 2px;
background: rgba(255, 255, 255, 0.3);
animation: float 6s ease-in-out infinite;
}
.flower-petal {
position: absolute;
width: 15px;
height: 15px;
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="%23ff9ff3" d="M12,2C13.1,2 14,2.9 14,4C14,5.1 13.1,6 12,6C10.9,6 10,5.1 10,4C10,2.9 10.9,2 12,2M15.5,8C16.3,8 17,8.7 17,9.5C17,10.3 16.3,11 15.5,11C14.7,11 14,10.3 14,9.5C14,8.7 14.7,8 15.5,8M8.5,8C9.3,8 10,8.7 10,9.5C10,10.3 9.3,11 8.5,11C7.7,11 7,10.3 7,9.5C7,8.7 7.7,8 8.5,8M12,11C13.1,11 14,11.9 14,13C14,14.1 13.1,15 12,15C10.9,15 10,14.1 10,13C10,11.9 10.9,11 12,11M19,17V19H5V17C5,14.8 8.1,13 12,13C15.9,13 19,14.8 19,17Z" /></svg>');
background-size: contain;
opacity: 0.7;
animation: petalFall linear infinite;
}
@keyframes float {
0%, 100% { transform: translateY(0px) rotate(0deg); opacity: 0.3; }
50% { transform: translateY(-20px) rotate(180deg); opacity: 0.8; }
}
@keyframes petalFall {
0% {
transform: translate(0, -10vh) rotate(0deg);
opacity: 0;
}
10% {
opacity: 0.7;
}
90% {
opacity: 0.7;
}
100% {
transform: translate(var(--random-x), 100vh) rotate(360deg);
opacity: 0;
}
}
/* Header */
.game-header {
background: linear-gradient(90deg, #8b6b4a, #6b4f32, #8b6b4a);
padding: 10px 0;
text-align: center;
border-bottom: 3px solid #3a3226;
}
.header-content h1 {
font-family: 'Gungsuh', 'Batang', serif;
font-size: 2.5em;
font-weight: 700;
color: #3a3226;
margin-bottom: 5px;
text-shadow: 2px 2px 4px rgba(0,0,0,0.1);
letter-spacing: 1px;
}
.progress-stats {
display: flex;
gap: 15px;
justify-content: center;
margin-top: 10px;
flex-wrap: wrap;
}
.progress-indicator {
background: rgba(107, 79, 50, 0.7);
border: 2px solid #3a3226;
border-radius: 20px;
padding: 5px 15px;
display: inline-flex;
align-items: center;
gap: 10px;
cursor: pointer;
transition: all 0.3s ease;
}
.progress-indicator:hover {
background: rgba(107, 79, 50, 0.9);
box-shadow: 0 0 10px rgba(107, 79, 50, 0.5);
}
.progress-count {
font-family: 'Courier New', monospace;
font-weight: bold;
color: #f5e9d9;
font-size: 1.2em;
}
.progress-label {
font-size: 0.9em;
color: #f5e9d9;
}
.header-content p {
color: rgba(255,255,255,0.9);
font-size: 1.1em;
}
/* Main game container */
.game-container {
display: grid;
grid-template-columns: 1fr 300px;
gap: 20px;
padding: 20px;
max-width: 1400px;
margin: 0 auto;
min-height: calc(100vh - 120px);
}
/* Chat area */
.chat-section {
background: rgba(245, 233, 217, 0.85);
border: 2px solid #8b6b4a;
border-radius: 0;
box-shadow: 0 4px 20px rgba(0,0,0,0.2),
inset 0 0 30px rgba(139, 107, 74, 0.3);
display: flex;
flex-direction: column;
overflow: hidden;
max-width: 800px;
margin: 0 auto;
position: relative;
}
.chat-section::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: url('assets/images/repeat-background.jpg') repeat;
opacity: 0.05;
pointer-events: none;
z-index: 0;
}
.chat-header {
background: linear-gradient(90deg, #8b6b4a, #6b4f32, #8b6b4a);
padding: 15px 20px;
color: #f5e9d9;
font-weight: 600;
border-bottom: 2px solid #3a3226;
}
.current-npc-info {
display: flex;
align-items: center;
gap: 15px;
}
.npc-avatar {
width: 50px;
height: 50px;
border-radius: 50%;
border: 3px solid white;
object-fit: cover;
}
.npc-details h3 {
font-size: 1.2em;
margin-bottom: 2px;
}
.npc-details p {
font-size: 0.9em;
opacity: 0.9;
}
#game-messages {
flex: 1;
padding: 20px;
overflow-y: auto;
max-height: 400px;
scrollbar-width: thin;
scrollbar-color: #4a5568 #2d3748;
}
#game-messages::-webkit-scrollbar {
width: 8px;
}
#game-messages::-webkit-scrollbar-track {
background: #2d3748;
border-radius: 4px;
}
#game-messages::-webkit-scrollbar-thumb {
background: #4a5568;
border-radius: 4px;
}
/* Message styling */
.game-text {
margin-bottom: 12px;
padding: 8px 12px;
line-height: 1.5;
border-left: 3px solid transparent;
opacity: 0;
animation: fadeIn 0.3s forwards;
}
@keyframes fadeIn {
to { opacity: 1; }
}
.npc-dialogue {
background: rgba(255,255,255,0.7);
color: #3a3226;
border-left: 3px solid #8b6b4a;
padding: 12px 16px;
margin: 10px 0;
position: relative;
transform: translateX(-20px);
animation: slideIn 0.5s forwards;
}
@keyframes slideIn {
to { transform: translateX(0); }
}
.npc-name {
color: #a52a2a;
font-weight: bold;
}
.command-echo {
background: rgba(255,255,255,0.3);
color: #3a3226;
font-style: italic;
border-radius: 15px 15px 5px 15px;
padding: 8px 12px;
text-align: right;
font-weight: 600;
}
.help-content {
background: rgba(245, 233, 217, 0.9);
border: 2px solid #8b6b4a;
border-radius: 0;
padding: 15px;
white-space: pre-line;
font-family: 'Courier New', monospace;
font-size: 0.9em;
color: #3a3226;
margin: 10px 0;
box-shadow: inset 0 0 10px rgba(139, 107, 74, 0.3);
}
.korean-text {
font-size: 1.1em;
line-height: 1.6;
}
.vocab-highlight {
background: linear-gradient(45deg, #ff6b6b, #feca57);
color: white;
padding: 2px 6px;
border-radius: 4px;
font-weight: 600;
}
/* Chat input */
.chat-input {
background: rgba(45, 55, 72, 0.9);
padding: 15px 20px;
border-top: 1px solid rgba(255,255,255,0.1);
}
.input-group {
display: flex;
gap: 10px;
}
.chat-input input {
flex: 1;
background: rgba(255,255,255,0.7);
border: 2px solid #8b6b4a;
border-radius: 0;
padding: 12px 20px;
color: #3a3226;
font-family: 'Courier New', monospace;
font-size: 1em;
transition: all 0.3s ease;
box-shadow: inset 0 0 10px rgba(139, 107, 74, 0.3);
}
.chat-input input:focus {
outline: none;
border-color: #3a3226;
box-shadow: inset 0 0 15px rgba(139, 107, 74, 0.5);
animation: caretBlink 1s step-end infinite;
}
@keyframes caretBlink {
50% { border-right: 2px solid #3a3226; }
}
.chat-input input::placeholder {
color: rgba(255,255,255,0.6);
}
.send-button {
background: linear-gradient(45deg, #8b6b4a, #6b4f32);
border: 2px outset #3a3226;
border-radius: 0;
padding: 12px 24px;
color: #f5e9d9;
font-family: 'Courier New', monospace;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 3px 3px 0 rgba(0,0,0,0.2);
text-shadow: none;
}
.send-button:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.6);
}
.send-button:disabled {
opacity: 0.6;
cursor: not-allowed;
transform: none;
}
/* Sidebar */
.game-sidebar {
display: flex;
flex-direction: column;
gap: 20px;
}
.panel {
background: rgba(15, 20, 25, 0.8);
backdrop-filter: blur(10px);
border-radius: 15px;
border: 1px solid rgba(255,255,255,0.1);
overflow: hidden;
box-shadow: 0 8px 32px rgba(0,0,0,0.3);
}
.panel-header {
background: linear-gradient(90deg, #8b6b4a, #6b4f32, #8b6b4a);
padding: 12px 16px;
font-weight: 600;
color: #f5e9d9;
border-bottom: 2px solid #3a3226;
}
.panel-content {
padding: 16px;
}
/* House map */
.house-map {
font-family: 'Courier New', monospace;
font-size: 0.8em;
line-height: 1.3;
text-align: center;
white-space: pre-line;
color: #a0aec0;
}
.current-room-highlight {
background: linear-gradient(45deg, #4ecdc4, #44a08d);
color: white;
padding: 2px 6px;
border-radius: 4px;
font-weight: bold;
}
/* Progress stats */
.stats {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
margin-bottom: 15px;
}
.stat-item {
background: rgba(255,255,255,0.05);
padding: 12px;
border-radius: 8px;
text-align: center;
border: 1px solid rgba(255,255,255,0.1);
}
.stat-value {
font-size: 1.4em;
font-weight: 700;
color: #4ecdc4;
display: block;
}
.stat-label {
font-size: 0.8em;
color: #a0aec0;
margin-top: 2px;
}
/* Quick actions */
.quick-actions-container {
display: flex;
justify-content: center;
gap: 10px;
padding: 10px;
background: rgba(107, 79, 50, 0.7);
border-top: 2px solid #3a3226;
border-bottom: 2px solid #3a3226;
}
.quick-actions-container .quick-btn {
flex: 1;
max-width: 120px;
padding: 8px 5px;
font-size: 0.9em;
font-family: 'Courier New', monospace;
border-radius: 0;
background: rgba(245, 233, 217, 0.9);
color: #3a3226;
border: 2px outset #8b6b4a;
font-weight: bold;
text-shadow: none;
box-shadow: none;
}
.quick-actions-container .quick-btn:hover {
background: rgba(245, 233, 217, 0.95);
animation: neonFlicker 0.5s infinite alternate;
}
.quick-btn {
background: #6b4f32;
border: 2px outset #8b6b4a;
border-radius: 0;
padding: 8px 12px;
color: #8e6f46;
font-family: 'Courier New', monospace;
font-size: 0.85em;
cursor: pointer;
position: relative;
text-shadow: 1px 1px 0 #3a3226;
box-shadow: 3px 3px 0 rgba(0,0,0,0.2);
}
.quick-btn:hover {
background: #8b6b4a;
box-shadow: 0 0 10px rgba(107, 79, 50, 0.7);
animation: neonFlicker 0.5s infinite alternate;
}
@keyframes neonFlicker {
0%, 19%, 21%, 23%, 25%, 54%, 56%, 100% {
box-shadow: 0 0 10px rgba(107, 79, 50, 0.7);
}
20%, 24%, 55% {
box-shadow: 0 0 15px rgba(107, 79, 50, 0.9);
}
}
/* Room navigation */
.room-nav {
display: grid;
grid-template-columns: 1fr;
gap: 6px;
}
.room-btn {
background: rgba(245, 233, 217, 0.2);
border: 2px solid #8b6b4a;
border-radius: 0;
padding: 8px 12px;
color: #ecebea;
font-size: 0.85em;
cursor: pointer;
transition: all 0.3s ease;
text-align: left;
margin-bottom: 5px;
}
.room-btn:hover {
background: rgba(139, 107, 74, 0.3);
border-color: #3a3226;
color: #3a3226;
}
.room-btn.current {
background: linear-gradient(90deg, #8b6b4a, #6b4f32);
border-color: #3a3226;
color: #f5e9d9;
font-weight: 600;
}
/* Loading indicator */
.loading {
color: #8b6b4a;
animation: pulse 1.5s ease-in-out infinite;
font-weight: 600;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
/* Success/Error messages */
.success { color: #6b4f32; font-weight: 600; }
.error { color: #a52a2a; font-weight: 600; background: rgba(165, 42, 42, 0.1); padding: 8px 12px; border-radius: 5px; }
/* Responsive design */
@media (max-width: 768px) {
.game-container {
grid-template-columns: 1fr;
padding: 10px;
}
.header-content h1 {
font-size: 1.8em;
}
.quick-actions {
grid-template-columns: 1fr;
}
.stats {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<div class="bg-particles" id="particles"></div>
<header class="game-header">
<div class="header-content">
<h1>🏠 한국어 가족집 모험</h1>
<p>Korean Family House Adventure - Learn Korean through immersive conversations!</p>
<div class="progress-stats">
<div class="progress-indicator" onclick="showVocabulary()">
<span class="progress-count" id="word-count">0</span>
<span class="progress-label">Korean Words Learned</span>
</div>
<div class="progress-indicator" onclick="showInventory()">
<span class="progress-count" id="inventory-count">0</span>
<span class="progress-label">Objects Examined</span>
</div>
</div>
</div>
</header>
<div class="game-container">
<div class="chat-section">
<div class="chat-header">
<div class="current-npc-info">
<img src="assets/images/Family portraits 2ChatGPT Image Aug 17, 2025, 10_48_14 PM.png" alt="NPC Avatar" class="npc-avatar" id="npc-avatar">
<div class="npc-details">
<h3 id="current-npc-name">Loading...</h3>
<p id="current-room-name">Getting ready...</p>
</div>
</div>
</div>
<div id="game-messages">
<div class="game-text loading">🎮 Starting your Korean adventure...</div>
</div>
<!-- Quick Actions moved here under chat -->
<div class="quick-actions-container">
<button class="quick-btn" onclick="sendQuickCommand('look')">👀 Look</button>
<button class="quick-btn" onclick="sendQuickCommand('help')">❓ Help</button>
<button class="quick-btn" onclick="showMapDialog()">🗺️ Map</button>
<button class="quick-btn" onclick="showExamineDialog()">🔍 Examine</button>
<button class="quick-btn" onclick="showChatDialog()">💬 Chat</button>
</div>
<div class="chat-input">
<div class="input-group">
<input type="text" id="command-input" placeholder="Type commands: help, look, examine [object], go [room], chat [message]..." autofocus>
<button class="send-button" id="submit-button">Send 💬</button>
</div>
</div>
</div>
<div class="game-sidebar">
<!-- Quick Actions removed from sidebar - now under chat -->
<!-- House map removed from sidebar -->
<div class="panel">
<div class="panel-header">
🚀 Quick Navigate
</div>
<div class="panel-content">
<div class="room-nav">
<button class="room-btn current" onclick="sendQuickCommand('go hall')">🏠 Hall (Central Hub)</button>
<button class="room-btn" onclick="sendQuickCommand('go kitchen')">🍳 Kitchen (Grandma)</button>
<button class="room-btn" onclick="sendQuickCommand('go garden')">🌸 Garden (Grandpa)</button>
<button class="room-btn" onclick="sendQuickCommand('go bedroom')">🎵 Bedroom (Sister)</button>
<button class="room-btn" onclick="sendQuickCommand('go study')">📚 Study (Brother)</button>
<button class="room-btn" onclick="sendQuickCommand('go classroom')">✏️ Classroom (Teacher)</button>
</div>
</div>
</div>
</div>
</div>
<script>
// Create animated background elements
function createParticles() {
const container = document.getElementById('particles');
// Create regular particles
for (let i = 0; i < 30; i++) {
const particle = document.createElement('div');
particle.className = 'particle';
particle.style.left = Math.random() * 100 + '%';
particle.style.top = Math.random() * 100 + '%';
particle.style.animationDelay = Math.random() * 6 + 's';
container.appendChild(particle);
}
// Create flower petals
for (let i = 0; i < 15; i++) {
const petal = document.createElement('div');
petal.className = 'flower-petal';
petal.style.left = Math.random() * 100 + '%';
petal.style.setProperty('--random-x', (Math.random() * 100 - 50) + 'px');
petal.style.animationDuration = (8 + Math.random() * 10) + 's';
petal.style.animationDelay = Math.random() * 5 + 's';
petal.style.width = (10 + Math.random() * 10) + 'px';
petal.style.height = petal.style.width;
container.appendChild(petal);
}
}
// API base URL
const API_BASE = '';
// DOM elements
const gameMessages = document.getElementById('game-messages');
const commandInput = document.getElementById('command-input');
const submitButton = document.getElementById('submit-button');
const currentNpcName = document.getElementById('current-npc-name');
const currentRoomName = document.getElementById('current-room-name');
const wordCountSpan = document.getElementById('word-count');
const npcAvatar = document.getElementById('npc-avatar');
// Check if required elements exist
if (!gameMessages || !commandInput || !submitButton || !wordCountSpan) {
console.error('Required DOM elements not found!');
}
// Game state
let gameState = {
current_room: '',
discovered_words: [],
isLoading: false,
conversationCount: 0
};
// Room and NPC mapping
const roomNames = {
'hall': 'Hall (대청)',
'kitchen': 'Kitchen (부엌)',
'garden': 'Garden (정원)',
'bedroom': 'Bedroom (침실)',
'study': 'Study (서재)',
'classroom': 'Classroom (교실)'
};
const npcNames = {
'ahjumma_gpt': 'Grandma Kim Soon-ja (김순자 할머니)',
'ahjussi_gpt': 'Grandpa Park Chul-min (박철민 할아버지)',
'unni_gpt': 'Sister Lee Min-ji (이민지 언니)',
'oppa_gpt': 'Brother Jung Jae-hyun (정재현 오빠)',
'seonsaengnim_gpt': 'Teacher Choi Soo-jin (최수진 선생님)'
};
const npcImages = {
'ahjumma_gpt': 'assets/images/Ahjumma ChatGPT Image Mar 26, 2025, 05_36_15 PM.png',
'ahjussi_gpt': 'assets/images/Ahjussi ChatGPT Image May 5, 2025, 04_37_07 PM.png',
'unni_gpt': 'assets/images/K-pop trainee ChatGPT Image May 5, 2025, 04_35_54 PM.png',
'oppa_gpt': 'assets/images/SunbaeOppa ChatGPT Image May 5, 2025, 04_07_13 PM.png',
'seonsaengnim_gpt': 'assets/images/SeonsaengnimChatGPT Image Aug 17, 2025, 10_41_39 PM.png'
};
// Initialize game
async function initGame() {
try {
createParticles();
addToGame('🎮 Welcome to the Korean Family House Adventure!', 'success korean-text');
addToGame('🏠 You\'re about to meet a wonderful Korean family who will teach you their language and culture.', 'game-text');
addToGame('');
await loadGameState();
await loadRoomInfo();
} catch (error) {
addToGame(`❌ Failed to start game: ${error.message}`, 'error');
console.error('Game initialization failed:', error);
}
}
// Load current game state
async function loadGameState() {
try {
const response = await fetch(`${API_BASE}/api/game/state`);
if (response.ok) {
gameState = await response.json();
updateUI();
}
} catch (error) {
console.error('Failed to load game state:', error);
}
}
// Load room information
async function loadRoomInfo() {
try {
const response = await fetch(`${API_BASE}/api/game/room-info`);
const data = await response.json();
if (data.success) {
addToGame(data.message, 'game-text korean-text');
if (data.data.npc_info && data.data.npc_info.name) {
updateNpcDisplay(data.data);
}
updateGameState(data.data);
} else {
addToGame(`❌ ${data.message}`, 'error');
}
} catch (error) {
addToGame(`❌ Failed to load room info: ${error.message}`, 'error');
}
}
// Update NPC display
function updateNpcDisplay(data) {
try {
if (data.current_room && data.room_mapping) {
const agentName = data.room_mapping[data.current_room];
if (agentName && npcNames[agentName]) {
if (currentNpcName) {
currentNpcName.textContent = npcNames[agentName];
}
if (currentRoomName) {
currentRoomName.textContent = roomNames[data.current_room] || data.current_room;
}
// Update avatar
if (npcAvatar && npcImages[agentName]) {
npcAvatar.src = npcImages[agentName];
npcAvatar.alt = npcNames[agentName];
}
}
}
} catch (error) {
console.error('Error updating NPC display:', error);
}
}
// Update UI with game state
function updateUI() {
// Update word count in header
if (wordCountSpan) {
wordCountSpan.textContent = gameState.discovered_words.length;
}
// Update inventory count in header
const inventoryCountSpan = document.getElementById('inventory-count');
if (inventoryCountSpan) {
inventoryCountSpan.textContent = (gameState.player_inventory || []).length;
}
// Note: conversation count was removed from design, so skipping that update
// Update room buttons
document.querySelectorAll('.room-btn').forEach(btn => {
btn.classList.remove('current');
});
// Highlight current room
const currentRoomBtn = document.querySelector(`[onclick*="${gameState.current_room}"]`);
if (currentRoomBtn) {
currentRoomBtn.classList.add('current');
}
}
// Update game state
function updateGameState(data) {
if (data.current_room) {
gameState.current_room = data.current_room;
}
if (data.discovered_words) {
gameState.discovered_words = data.discovered_words;
}
if (data.player_inventory) {
gameState.player_inventory = data.player_inventory;
}
updateUI();
}
// Add text to game messages with typing animation
function addToGame(text, className = 'game-text') {
try {
if (!gameMessages) {
console.error('gameMessages element not found');
return;
}
// Convert newlines to HTML breaks for proper formatting
const formattedText = text.replace(/\n/g, '<br>');
const div = document.createElement('div');
div.className = className;
gameMessages.appendChild(div);
let i = 0;
const typing = setInterval(() => {
if (i < formattedText.length) {
div.innerHTML = formattedText.substring(0, i+1);
i++;
if (gameMessages) {
gameMessages.scrollTop = gameMessages.scrollHeight;
}
} else {
clearInterval(typing);
}
}, 20);
} catch (error) {
console.error('Error adding text to game:', error);
}
}
// Send command to API
async function sendCommand(command) {
if (gameState.isLoading) return;
gameState.isLoading = true;
submitButton.disabled = true;
// Echo command
addToGame(`> ${command}`, 'command-echo');
try {
let response;
let data;
if (command.startsWith('go ') || command.startsWith('move ') || command.startsWith('이동 ')) {
// Movement command
response = await fetch(`${API_BASE}/api/game/move`, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({command: command})
});
data = await response.json();
} else if (command.startsWith('examine ')) {
// Examine object command
response = await fetch(`${API_BASE}/api/game/examine`, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({command: command})
});
data = await response.json();
} else if (command === 'look' || command === 'l') {
// Look around command
response = await fetch(`${API_BASE}/api/game/command`, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({command: command})
});
data = await response.json();
} else if (command.startsWith('take ')) {
// Take object command
response = await fetch(`${API_BASE}/api/game/take`, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({command: command})
});
data = await response.json();
} else if (command.startsWith('chat ') || command.startsWith('talk ') || command.startsWith('say ')) {
// Chat with NPC command
const chatMessage = command.replace(/^(chat|talk|say)\s+/, '');
response = await fetch(`${API_BASE}/api/game/talk`, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({message: chatMessage})
});
data = await response.json();
} else if (command === 'help' || command === '?' || command === 'h' || command === 'rooms' || command === 'map') {
// Help and info commands
response = await fetch(`${API_BASE}/api/game/command`, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({command: command})
});
data = await response.json();
} else {
// Invalid command - show help
addToGame('❌ Invalid command! Use one of these:', 'error');
addToGame('• look - Look around current room', 'help-content');
addToGame('• examine [object] - Examine an object', 'help-content');
addToGame('• go [room] - Move to another room', 'help-content');
addToGame('• chat [message] - Chat with family member in room', 'help-content');
addToGame('• help - Show full help menu', 'help-content');
return;
}
// Display response
if (data.success) {
if (command === 'help' || command === '?' || command === 'h' || command === 'rooms' || command === 'map') {
addToGame(data.message, 'help-content');
} else if (command === 'look' || command === 'l') {
// Format look command nicely
addToGame('👀 Looking around...', 'game-text');
addToGame(data.message, 'npc-dialogue korean-text');
} else if (command.startsWith('examine ')) {
// Format examine command nicely
addToGame('🔍 Examining...', 'game-text');
addToGame(data.message, 'npc-dialogue korean-text');
} else if (command.startsWith('chat ') || command.startsWith('talk ') || command.startsWith('say ')) {
// Format chat response nicely
addToGame(data.message, 'npc-dialogue korean-text');
} else if (command.startsWith('go ') || command.startsWith('move ')) {
// Format movement response
addToGame(data.message, 'game-text');
// Auto-look after moving (standard MUD behavior)
setTimeout(() => {
sendCommand('look');
}, 500);
} else {
addToGame(data.message, 'korean-text');
}
if (data.data) {
updateGameState(data.data);
updateNpcDisplay(data.data);
}
} else {
addToGame(`❌ ${data.message}`, 'error');
}
} catch (error) {
addToGame(`❌ Network error: ${error.message}`, 'error');
} finally {
gameState.isLoading = false;
submitButton.disabled = false;
}
}
// Send talk message to NPC
async function sendTalkMessage(message) {
if (gameState.isLoading) return;
gameState.isLoading = true;
submitButton.disabled = true;
addToGame(`💬 "${message}"`, 'command-echo');
addToGame('🤖 Korean family member is responding...', 'loading korean-text');
try {
const response = await fetch(`${API_BASE}/api/game/talk`, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({message: message})
});
const data = await response.json();
// Remove loading message
const lastElement = gameMessages.lastElementChild;
if (lastElement && lastElement.textContent.includes('responding')) {
gameMessages.removeChild(lastElement);
}
if (data.success) {
addToGame(data.message, 'npc-dialogue korean-text');
gameState.conversationCount++;
if (data.data) {
updateGameState(data.data);
updateNpcDisplay(data.data);
}
} else {
addToGame(`❌ ${data.message}`, 'error');
}
} catch (error) {
addToGame(`❌ Chat error: ${error.message}`, 'error');
} finally {
gameState.isLoading = false;
submitButton.disabled = false;
}
}
// Quick command buttons
function sendQuickCommand(command) {
sendCommand(command);
}
// Show talk dialog
function showTalkDialog() {
const message = prompt('💬 What would you like to say to the NPC?\\n(NPC에게 할 말을 입력하세요:)');
if (message && message.trim()) {
sendTalkMessage(message.trim());
}
}
// Show examine options
async function showExamineDialog() {
// Show available objects to examine in current room
try {
const response = await fetch(`${API_BASE}/api/game/room-info`);
const data = await response.json();
if (data.success && data.data.objects && data.data.objects.length > 0) {
let objectText = '🔍 Objects you can examine in this room:\n\n';
data.data.objects.forEach(obj => {
objectText += `• ${obj.name} - ${obj.description}\n`;
});
objectText += '\nType "examine [object name]" to interact with them!';
addToGame(objectText, 'help-content');
} else {
addToGame('🔍 There\'s nothing to examine in this room. Try moving to a different room!', 'help-content');
}
} catch (error) {
console.error('Error fetching room objects:', error);
addToGame('🔍 To examine objects, type "examine [object name]" or look around first to see what\'s available!', 'help-content');
}
}
// Show chat instructions
async function showChatDialog() {
try {
const response = await fetch(`${API_BASE}/api/game/room-info`);
const data = await response.json();
if (data.success && data.data.npc_info && data.data.npc_info.name) {
const npcName = data.data.npc_info.name;
const chatText = `💬 How to chat with ${npcName}:
📝 Method 1: Use "chat" command
Type: "chat Hello! How are you?"
📝 Method 2: Use "talk" command
Type: "talk Can you teach me Korean?"
📝 Method 3: Use "say" command
Type: "say I want to learn about Korean culture"
💡 Tips:
• Ask about objects you've examined
• Request Korean lessons
• Learn about Korean culture
• Practice conversation
Try asking ${npcName} about something!`;
addToGame(chatText, 'help-content');
} else {
addToGame('💬 No family member in this room! Move to a different room to chat with someone.', 'help-content');
}
} catch (error) {
console.error('Error fetching room info:', error);
addToGame('💬 To chat: "chat [your message]", "talk [your message]", or "say [your message]"', 'help-content');
}
}
// Show vocabulary learned
function showVocabulary() {
const words = gameState.discovered_words || [];
if (words.length === 0) {
addToGame('📚 You haven\'t learned any Korean words yet! Talk to family members to start learning.', 'help-content');
} else {
let vocabText = '📚 Korean Words You\'ve Learned:\n\n';
words.forEach((word, index) => {
vocabText += `${index + 1}. ${word}\n`;
});
vocabText += '\nKeep talking to family members to learn more! 화이팅! (Fighting!)';
addToGame(vocabText, 'help-content');
}
}
// Show inventory (examined objects)
function showInventory() {
const inventory = gameState.player_inventory || [];
if (inventory.length === 0) {
addToGame('🎒 Your inventory is empty! Examine objects around the house to collect them.', 'help-content');
} else {
let inventoryText = '🎒 Objects You\'ve Examined:\n\n';
inventory.forEach((item, index) => {
inventoryText += `${index + 1}. ${item}\n`;
});
inventoryText += '\nThese items represent your cultural discoveries! Keep exploring for more!';
addToGame(inventoryText, 'help-content');
}
}
// Show map dialog
function showMapDialog() {
addToGame('🗺️ Opening Korean house map...', 'game-text');
const mapText = `🏠 KOREAN FAMILY HOUSE MAP 🇰🇷
🌸 Garden (정원)
👴 Grandpa Park
|
🍳 Kitchen ---- 🏠 Hall ---- 📚 Study
👵 Grandma Kim 📖 Brother Jung
|
✏️ Classroom (교실)
👩‍🏫 Teacher Choi
|
🎵 Bedroom (침실)
👩 Sister Lee
🚪 Available Rooms:
• 🏠 Hall (대청) - Central hub, family gathering area
• 🍳 Kitchen (부엌) - Grandma Kim teaches honorifics
• 🌸 Garden (정원) - Grandpa Park shares traditional wisdom
• 🎵 Bedroom (침실) - Sister Lee teaches K-pop and slang
• 📚 Study (서재) - Brother Jung explains complex grammar
• ✏️ Classroom (교실) - Teacher Choi provides practical lessons
Use 'go [room]' to move around!`;
addToGame(mapText, 'help-content');
}
// Process user input
function processInput() {
const input = commandInput.value.trim();
if (!input || gameState.isLoading) return;
commandInput.value = '';
// All input goes through strict command parsing
sendCommand(input);
}
// Event listeners
submitButton.addEventListener('click', processInput);
commandInput.addEventListener('keyup', function(e) {
if (e.key === 'Enter') {
processInput();
}
});
// Auto-focus input
commandInput.focus();
// Start the game when DOM is ready
document.addEventListener('DOMContentLoaded', function() {
initGame();
});
// Fallback: start game immediately if DOM is already loaded
if (document.readyState === 'loading') {
// DOM still loading
} else {
// DOM already loaded
initGame();
}
</script>
</body>
</html>