{"project":{"id":"o7Gil9O","userId":"davidyarham@gmail.com","username":null,"userPicture":null,"name":"WOPR","visible":true,"contributors":"","githubRepo":null,"forkedFrom":null,"isTemplate":false,"tags":"","files":{"folder":"","files":[{"name":"index.html","content":"<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>Untitled</title>\n  <link rel=\"stylesheet\" href=\"style.css\">\n<!-- CRT Terminal -->\n</head>\n<body>\n<div class=\"crt-screen\">\n\t<div class=\"scanlines\"></div>\n\t<div class=\"crt-flicker\"></div>\n\t<div class=\"terminal\" id=\"terminal\">\n\t\t<div class=\"terminal__output\" id=\"output\"></div>\n\t\t<div class=\"terminal__input-line\" id=\"inputLine\" style=\"display:none;\">\n\t\t\t<span class=\"terminal__prompt\">&gt;&nbsp;</span>\n\t\t\t<input type=\"text\" class=\"terminal__input\" id=\"input\" autocomplete=\"off\" spellcheck=\"false\" autofocus />\n\t\t\t<span class=\"terminal__cursor\"></span>\n\t\t</div>\n\t</div>\n</div>\n<div class=\"war-overlay\" id=\"warOverlay\" style=\"display:none;\">\n\t<canvas id=\"warMap\" width=\"900\" height=\"500\"></canvas>\n\t<div class=\"war-overlay__status\" id=\"warStatus\"></div>\n</div>\n  <script type=\"module\" src=\"main.js\"></script>\n</body>\n</html>"},{"name":"main.js","content":"const output = document.getElementById('output');\nconst input = document.getElementById('input');\nconst inputLine = document.getElementById('inputLine');\nconst terminal = document.getElementById('terminal');\nconst activityLed = document.querySelector('.crt-led--activity'); // may be null in fullscreen mode\nconst warOverlay = document.getElementById('warOverlay');\nconst warMap = document.getElementById('warMap');\nconst warStatus = document.getElementById('warStatus');\nconst ctx = warMap.getContext('2d');\n\nlet state = 'BOOT';\nlet typing = false;\nlet voiceEnabled = true;\nlet tttBoard = [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '];\nlet tttPlayerMark = 'X';\nlet tttCpuMark = 'O';\nlet thermoWarSide = '';\nlet warAskedChess = false;\nlet defconLevel = 5;\nlet warTargets = [];\nlet gameListShown = false;\n\nconst synth = window.speechSynthesis;\n\nfunction speak(text) {\n\tif (!voiceEnabled || !synth) return;\n\tsynth.cancel();\n\tconst clean = text.replace(/[=\\-\\|+\\/\\\\_.#]/g, ' ').replace(/\\s+/g, ' ').trim();\n\tif (!clean) return;\n\tconst utter = new SpeechSynthesisUtterance(clean);\n\tutter.rate = 1.1;\n\tutter.pitch = 0.6;\n\tutter.volume = 0.8;\n\tconst voices = synth.getVoices();\n\tconst robotVoice = voices.find(v => v.name.includes('Google UK English Male')) ||\n\t\tvoices.find(v => v.name.includes('Daniel')) ||\n\t\tvoices.find(v => v.lang === 'en-US' && v.localService) ||\n\t\tvoices[0];\n\tif (robotVoice) utter.voice = robotVoice;\n\tsynth.speak(utter);\n}\n\nfunction scrollToBottom() {\n\tterminal.scrollTop = terminal.scrollHeight;\n}\n\nfunction addLine(text, cls = '') {\n\tconst span = document.createElement('span');\n\tspan.className = 'line' + (cls ? ' line--' + cls : '');\n\tspan.textContent = text;\n\toutput.appendChild(span);\n\tscrollToBottom();\n}\n\nfunction setActivity(on) {\n\tif (activityLed && on) activityLed.classList.add('active');\n\telse if (activityLed) activityLed.classList.remove('active');\n}\n\nfunction typeText(text, cls = '', speed = 30) {\n\treturn new Promise(resolve => {\n\t\ttyping = true;\n\t\tsetActivity(true);\n\t\tinputLine.style.display = 'none';\n\t\tconst span = document.createElement('span');\n\t\tspan.className = 'line' + (cls ? ' line--' + cls : '');\n\t\toutput.appendChild(span);\n\t\tlet i = 0;\n\t\tconst interval = setInterval(() => {\n\t\t\tif (i < text.length) {\n\t\t\t\tspan.textContent += text[i];\n\t\t\t\ti++;\n\t\t\t\tscrollToBottom();\n\t\t\t} else {\n\t\t\t\tclearInterval(interval);\n\t\t\t\ttyping = false;\n\t\t\t\tsetActivity(false);\n\t\t\t\tresolve();\n\t\t\t}\n\t\t}, speed);\n\t});\n}\n\nasync function typeLines(lines, cls = '', speed = 30) {\n\tfor (const line of lines) {\n\t\tawait typeText(line + '\\n', cls, speed);\n\t}\n}\n\nfunction showInput() {\n\tinputLine.style.display = 'flex';\n\tinput.value = '';\n\tinput.focus();\n\tscrollToBottom();\n}\n\nfunction waitForInput() {\n\treturn new Promise(resolve => {\n\t\tshowInput();\n\n\t\tfunction handler(e) {\n\t\t\tif (e.key === 'Enter') {\n\t\t\t\tconst val = input.value.trim().toUpperCase();\n\t\t\t\tinput.removeEventListener('keydown', handler);\n\t\t\t\tinputLine.style.display = 'none';\n\t\t\t\taddLine('> ' + val, 'user');\n\t\t\t\tresolve(val);\n\t\t\t}\n\t\t}\n\t\tinput.addEventListener('keydown', handler);\n\t});\n}\n\nfunction delay(ms) {\n\treturn new Promise(r => setTimeout(r, ms));\n}\n\n// GAME LIST from the movie\nconst GAMES_LIST = [\n\t'FALKEN\\'S MAZE',\n\t'BLACK JACK',\n\t'GIN RUMMY',\n\t'HEARTS',\n\t'BRIDGE',\n\t'CHECKERS',\n\t'CHESS',\n\t'POKER',\n\t'FIGHTER COMBAT',\n\t'GUERRILLA ENGAGEMENT',\n\t'DESERT WARFARE',\n\t'AIR-TO-GROUND ACTIONS',\n\t'THEATERWIDE TACTICAL WARFARE',\n\t'THEATERWIDE BIOTOXIC AND CHEMICAL WARFARE',\n\t'GLOBAL THERMONUCLEAR WAR',\n\t'TIC-TAC-TOE'\n];\n\nconst US_TARGETS = [\n\t'LAS VEGAS', 'SEATTLE', 'SAN FRANCISCO', 'LOS ANGELES',\n\t'NEW YORK CITY', 'WASHINGTON D.C.', 'CHICAGO', 'HOUSTON',\n\t'MIAMI', 'DENVER', 'DETROIT', 'BOSTON',\n\t'OMAHA', 'COLORADO SPRINGS', 'NORFOLK', 'SAN DIEGO'\n];\n\nconst USSR_TARGETS = [\n\t'MOSCOW', 'LENINGRAD', 'KIEV', 'MURMANSK',\n\t'VLADIVOSTOK', 'SVERDLOVSK', 'NOVOSIBIRSK', 'GORKY',\n\t'MINSK', 'RIGA', 'TBILISI', 'ODESSA',\n\t'STALINGRAD', 'SEVASTOPOL', 'ARKHANGELSK', 'KHABAROVSK'\n];\n\nfunction resolveGameByNumber(num) {\n\tif (num >= 1 && num <= GAMES_LIST.length) {\n\t\treturn GAMES_LIST[num - 1];\n\t}\n\treturn null;\n}\n\n// ============= BOOT SEQUENCE =============\nasync function boot() {\n\tawait typeText('LOGON: ', 'dim', 40);\n\tawait delay(800);\n\taddLine('');\n\tawait typeLines([\n\t\t'CONNECTING TO WOPR...',\n\t\t''\n\t], 'dim', 25);\n\tawait delay(600);\n\tawait typeLines([\n\t\t'    ██╗    ██╗ ██████╗ ██████╗ ██████╗',\n\t\t'    ██║    ██║██╔═══██╗██╔══██╗██╔══██╗',\n\t\t'    ██║ █╗ ██║██║   ██║██████╔╝██████╔╝',\n\t\t'    ██║███╗██║██║   ██║██╔═══╝ ██╔══██╗',\n\t\t'    ╚███╔███╔╝╚██████╔╝██║     ██║  ██║',\n\t\t'     ╚══╝╚══╝  ╚═════╝ ╚═╝     ╚═╝  ╚═╝',\n\t\t''\n\t], '', 8);\n\tawait delay(400);\n\tawait typeText('WOPR ONLINE - STRATEGIC DEFENSE NETWORK\\n', 'dim', 25);\n\tawait typeText('NORAD COMMAND CENTER - CHEYENNE MOUNTAIN\\n', 'dim', 25);\n\tawait delay(600);\n\taddLine('');\n\tawait greeting();\n}\n\nasync function greeting() {\n\tstate = 'GREETING';\n\tconst greet = 'GREETINGS PROFESSOR FALKEN.';\n\tspeak(greet);\n\tawait typeText(greet + '\\n', 'bright', 60);\n\tawait delay(800);\n\tstate = 'SHALL_WE_PLAY';\n\tawait shallWePlay();\n}\n\nasync function shallWePlay() {\n\tconst msg = 'SHALL WE PLAY A GAME?';\n\tspeak(msg);\n\tawait typeText(msg + '\\n\\n', '', 50);\n\tawait typeText('  [1] YES\\n', 'dim', 15);\n\tawait typeText('  [2] NO\\n\\n', 'dim', 15);\n\tconst response = await waitForInput();\n\tawait handleShallWePlay(response);\n}\n\nasync function handleShallWePlay(response) {\n\tif (response === '1' || response.includes('YES') || response.includes('SURE') || response.includes('OK') || response.includes('YEAH')) {\n\t\tawait delay(300);\n\t\tawait showGameList();\n\t} else if (response === '2' || response.includes('NO') || response === 'N') {\n\t\tconst msg = 'COME NOW, PROFESSOR. A GAME IS ALWAYS INTERESTING.';\n\t\tspeak(msg);\n\t\tawait typeText(msg + '\\n\\n', '', 40);\n\t\tawait shallWePlay();\n\t} else if (response.includes('HELP')) {\n\t\tawait typeText('TYPE A NUMBER OR A COMMAND: 1/YES, 2/NO, HELP\\n\\n', 'dim', 25);\n\t\tawait shallWePlay();\n\t} else if (response.includes('LIST') || response.includes('GAME')) {\n\t\tawait showGameList();\n\t} else {\n\t\tconst msg2 = 'I DO NOT UNDERSTAND. SHALL WE PLAY A GAME?';\n\t\tspeak(msg2);\n\t\tawait typeText(msg2 + '\\n\\n', '', 40);\n\t\tawait typeText('  [1] YES\\n', 'dim', 15);\n\t\tawait typeText('  [2] NO\\n\\n', 'dim', 15);\n\t\tconst r2 = await waitForInput();\n\t\tawait handleShallWePlay(r2);\n\t}\n}\n\nasync function showGameList() {\n\tstate = 'GAME_LIST';\n\tgameListShown = true;\n\taddLine('');\n\tawait typeText('GAMES AVAILABLE:\\n\\n', 'bright', 40);\n\tfor (let i = 0; i < GAMES_LIST.length; i++) {\n\t\tconst num = String(i + 1).padStart(2, ' ');\n\t\tconst g = GAMES_LIST[i];\n\t\tif (g === 'GLOBAL THERMONUCLEAR WAR') {\n\t\t\taddLine('');\n\t\t\taddLine('  [' + num + ']  ' + g, 'bright');\n\t\t\taddLine('');\n\t\t} else {\n\t\t\taddLine('  [' + num + ']  ' + g);\n\t\t}\n\t}\n\tscrollToBottom();\n\taddLine('');\n\tconst msg = 'ENTER NUMBER OR NAME OF GAME:';\n\tspeak(msg);\n\tawait typeText(msg + '\\n\\n', '', 40);\n\tconst choice = await waitForInput();\n\tawait handleGameChoice(choice);\n}\n\nasync function handleGameChoice(choice) {\n\t// Check if user typed a number\n\tconst num = parseInt(choice);\n\tif (num >= 1 && num <= GAMES_LIST.length) {\n\t\tconst gameName = GAMES_LIST[num - 1];\n\t\t// Re-route through name matching\n\t\treturn await handleGameChoice(gameName);\n\t}\n\n\tif (choice.includes('GLOBAL') || choice.includes('THERMONUCLEAR') || choice.includes('WAR')) {\n\t\tif (!warAskedChess) {\n\t\t\twarAskedChess = true;\n\t\t\taddLine('');\n\t\t\tconst msg = 'WOULDN\\'T YOU PREFER A NICE GAME OF CHESS?';\n\t\t\tspeak(msg);\n\t\t\tawait typeText(msg + '\\n\\n', 'bright', 50);\n\t\t\tawait typeText('  [1] YES, PLAY CHESS\\n', 'dim', 15);\n\t\t\tawait typeText('  [2] NO, I WANT THERMONUCLEAR WAR\\n\\n', 'dim', 15);\n\t\t\tconst r = await waitForInput();\n\t\t\tif (r === '2' || r.includes('NO') || r.includes('WAR') || r.includes('THERMO') || r.includes('GLOBAL') || r.includes('LATER')) {\n\t\t\t\tawait startThermonuclearWar();\n\t\t\t} else if (r === '1' || r.includes('CHESS') || r.includes('YES') || r.includes('OK')) {\n\t\t\t\tawait typeText('\\nCHESS IS NOT YET IMPLEMENTED IN THIS TERMINAL.\\nPERHAPS ANOTHER GAME?\\n\\n', 'dim', 30);\n\t\t\t\tawait showGameList();\n\t\t\t} else {\n\t\t\t\tawait startThermonuclearWar();\n\t\t\t}\n\t\t} else {\n\t\t\tawait startThermonuclearWar();\n\t\t}\n\t} else if (choice.includes('TIC') || choice.includes('TAC') || choice.includes('TOE')) {\n\t\tawait startTicTacToe();\n\t} else if (choice.includes('CHESS')) {\n\t\tawait typeText('\\nLOADING TIC-TAC-TOE INSTEAD...\\n', 'dim', 30);\n\t\tawait startTicTacToe();\n\t} else if (choice.includes('CHECKER') || choice.includes('POKER') || choice.includes('JACK') || choice.includes('HEART') || choice.includes('BRIDGE') || choice.includes('RUMMY') || choice.includes('MAZE')) {\n\t\tconst msg = 'THAT GAME IS CLASSIFIED. ACCESS DENIED.';\n\t\tspeak(msg);\n\t\tawait typeText('\\n' + msg + '\\n', '', 40);\n\t\tawait typeText('CHOOSE ANOTHER GAME.\\n\\n', 'dim', 30);\n\t\tconst c2 = await waitForInput();\n\t\tawait handleGameChoice(c2);\n\t} else if (choice.includes('FIGHTER') || choice.includes('GUERRILLA') || choice.includes('DESERT') || choice.includes('TACTICAL') || choice.includes('BIOTOXIC') || choice.includes('AIR')) {\n\t\tconst msg = 'INTERESTING CHOICE. BUT THAT SCENARIO IS CURRENTLY OFFLINE.';\n\t\tspeak(msg);\n\t\tawait typeText('\\n' + msg + '\\n', '', 40);\n\t\tawait typeText('CHOOSE ANOTHER GAME.\\n\\n', 'dim', 30);\n\t\tconst c2 = await waitForInput();\n\t\tawait handleGameChoice(c2);\n\t} else {\n\t\tawait typeText('\\nGAME NOT RECOGNIZED. PLEASE ENTER A NUMBER FROM THE LIST.\\n\\n', '', 30);\n\t\tconst c2 = await waitForInput();\n\t\tawait handleGameChoice(c2);\n\t}\n}\n\n// ============= GLOBAL THERMONUCLEAR WAR =============\nasync function startThermonuclearWar() {\n\tstate = 'THERMONUCLEAR_WAR';\n\taddLine('');\n\tawait typeText('FINE.\\n\\n', '', 60);\n\tawait delay(500);\n\tawait typeLines([\n\t\t'╔══════════════════════════════════════════╗',\n\t\t'║     GLOBAL THERMONUCLEAR WAR             ║',\n\t\t'║     STRATEGIC NUCLEAR EXCHANGE           ║',\n\t\t'╚══════════════════════════════════════════╝',\n\t\t''\n\t], 'bright', 12);\n\tawait delay(400);\n\tconst msg = 'WHICH SIDE DO YOU WANT?';\n\tspeak(msg);\n\tawait typeText(msg + '\\n\\n', '', 40);\n\tawait typeText('  [1] UNITED STATES\\n', '', 20);\n\tawait typeText('  [2] SOVIET UNION\\n\\n', '', 20);\n\tconst side = await waitForInput();\n\tif (side === '2' || side.includes('SOVIET') || side.includes('USSR') || side.includes('RUSSIA')) {\n\t\tthermoWarSide = 'USSR';\n\t\twarTargets = US_TARGETS;\n\t\tawait typeText('\\nYOU HAVE SELECTED: SOVIET UNION\\n', '', 30);\n\t\tawait typeText('TARGETING: UNITED STATES\\n\\n', 'warning', 30);\n\t} else {\n\t\tthermoWarSide = 'US';\n\t\twarTargets = USSR_TARGETS;\n\t\tawait typeText('\\nYOU HAVE SELECTED: UNITED STATES\\n', '', 30);\n\t\tawait typeText('TARGETING: SOVIET UNION\\n\\n', 'warning', 30);\n\t}\n\tspeak('LAUNCH SEQUENCE INITIATED');\n\tawait delay(600);\n\tawait typeText('AWAITING FIRST STRIKE COMMAND...\\n\\n', '', 40);\n\tawait typeText('PRIMARY TARGETS:\\n\\n', 'bright', 30);\n\tfor (let i = 0; i < warTargets.length; i++) {\n\t\taddLine('  [' + String(i + 1).padStart(2, '0') + ']  ' + warTargets[i]);\n\t}\n\tscrollToBottom();\n\taddLine('');\n\tawait typeText('ENTER TARGET NUMBER (1-' + warTargets.length + ') OR [A] FOR ALL TARGETS:\\n\\n', '', 30);\n\tconst target = await waitForInput();\n\tlet selectedTargets = [];\n\tif (target === 'A' || target.includes('ALL') || target.includes('FULL') || target.includes('EVERYTHING')) {\n\t\tselectedTargets = [...warTargets];\n\t\tawait typeText('\\n*** FULL STRATEGIC STRIKE AUTHORIZED ***\\n', 'warning', 30);\n\t} else {\n\t\tconst tnum = parseInt(target);\n\t\tif (tnum >= 1 && tnum <= warTargets.length) {\n\t\t\tselectedTargets = [warTargets[tnum - 1]];\n\t\t\tawait typeText('\\nTARGET LOCKED: ' + warTargets[tnum - 1] + '\\n', 'warning', 30);\n\t\t} else {\n\t\t\tselectedTargets = [warTargets[0]];\n\t\t\tawait typeText('\\nINVALID. DEFAULTING TO: ' + warTargets[0] + '\\n', 'warning', 30);\n\t\t}\n\t}\n\tspeak('MISSILES LAUNCHED');\n\tawait delay(500);\n\tawait typeText('\\nLAUNCH SEQUENCE INITIATED...\\n', 'bright', 40);\n\tawait typeText('DEFCON 4...\\n', 'warning', 60);\n\tawait delay(400);\n\tawait typeText('DEFCON 3...\\n', 'warning', 60);\n\tawait delay(400);\n\tawait typeText('DEFCON 2...\\n', 'warning', 60);\n\tawait delay(400);\n\tawait typeText('DEFCON 1\\n\\n', 'warning', 80);\n\tspeak('DEFCON 1. Missiles are in the air.');\n\tawait delay(800);\n\n\tawait showWarMap(selectedTargets);\n}\n\nasync function showWarMap(targets) {\n\twarOverlay.style.display = 'flex';\n\tconst W = warMap.width;\n\tconst H = warMap.height;\n\n\tfunction drawWorldOutline() {\n\t\tctx.fillStyle = '#0a0a0a';\n\t\tctx.fillRect(0, 0, W, H);\n\t\tctx.strokeStyle = '#1a4a1a';\n\t\tctx.lineWidth = 1;\n\t\tctx.beginPath();\n\t\tctx.moveTo(120, 80);\n\t\tctx.lineTo(180, 60);\n\t\tctx.lineTo(250, 70);\n\t\tctx.lineTo(280, 100);\n\t\tctx.lineTo(270, 150);\n\t\tctx.lineTo(250, 180);\n\t\tctx.lineTo(220, 200);\n\t\tctx.lineTo(200, 230);\n\t\tctx.lineTo(180, 260);\n\t\tctx.lineTo(160, 250);\n\t\tctx.lineTo(140, 200);\n\t\tctx.lineTo(110, 160);\n\t\tctx.lineTo(100, 120);\n\t\tctx.closePath();\n\t\tctx.stroke();\n\t\tctx.beginPath();\n\t\tctx.moveTo(420, 70);\n\t\tctx.lineTo(470, 60);\n\t\tctx.lineTo(500, 80);\n\t\tctx.lineTo(510, 120);\n\t\tctx.lineTo(490, 150);\n\t\tctx.lineTo(450, 160);\n\t\tctx.lineTo(430, 140);\n\t\tctx.lineTo(420, 100);\n\t\tctx.closePath();\n\t\tctx.stroke();\n\t\tctx.beginPath();\n\t\tctx.moveTo(500, 50);\n\t\tctx.lineTo(600, 40);\n\t\tctx.lineTo(720, 50);\n\t\tctx.lineTo(780, 70);\n\t\tctx.lineTo(800, 100);\n\t\tctx.lineTo(750, 130);\n\t\tctx.lineTo(650, 140);\n\t\tctx.lineTo(550, 130);\n\t\tctx.lineTo(510, 110);\n\t\tctx.closePath();\n\t\tctx.stroke();\n\t\tctx.strokeStyle = '#0d2a0d';\n\t\tctx.lineWidth = 0.5;\n\t\tfor (let x = 0; x < W; x += 60) {\n\t\t\tctx.beginPath();\n\t\t\tctx.moveTo(x, 0);\n\t\t\tctx.lineTo(x, H);\n\t\t\tctx.stroke();\n\t\t}\n\t\tfor (let y = 0; y < H; y += 50) {\n\t\t\tctx.beginPath();\n\t\t\tctx.moveTo(0, y);\n\t\t\tctx.lineTo(W, y);\n\t\t\tctx.stroke();\n\t\t}\n\t\tctx.fillStyle = '#1a5a1a';\n\t\tctx.font = '12px Courier New, monospace';\n\t\tctx.fillText('UNITED STATES', 130, 170);\n\t\tctx.fillText('USSR', 640, 85);\n\t\tctx.fillText('NORAD', 200, 155);\n\t}\n\n\tdrawWorldOutline();\n\twarStatus.textContent = 'STRATEGIC DEFENSE NETWORK - TRACKING';\n\n\tconst usCities = {\n\t\t'LAS VEGAS': [190, 155],\n\t\t'SEATTLE': [130, 100],\n\t\t'SAN FRANCISCO': [120, 140],\n\t\t'LOS ANGELES': [140, 160],\n\t\t'NEW YORK CITY': [260, 120],\n\t\t'WASHINGTON D.C.': [255, 135],\n\t\t'CHICAGO': [220, 115],\n\t\t'HOUSTON': [195, 185],\n\t\t'MIAMI': [250, 195],\n\t\t'DENVER': [180, 130],\n\t\t'DETROIT': [235, 110],\n\t\t'BOSTON': [268, 110],\n\t\t'OMAHA': [200, 120],\n\t\t'COLORADO SPRINGS': [185, 135],\n\t\t'NORFOLK': [260, 145],\n\t\t'SAN DIEGO': [135, 165]\n\t};\n\tconst ussrCities = {\n\t\t'MOSCOW': [550, 75],\n\t\t'LENINGRAD': [530, 55],\n\t\t'KIEV': [530, 95],\n\t\t'MURMANSK': [530, 40],\n\t\t'VLADIVOSTOK': [780, 95],\n\t\t'SVERDLOVSK': [610, 65],\n\t\t'NOVOSIBIRSK': [660, 70],\n\t\t'GORKY': [560, 70],\n\t\t'MINSK': [520, 80],\n\t\t'RIGA': [510, 70],\n\t\t'TBILISI': [550, 110],\n\t\t'ODESSA': [530, 105],\n\t\t'STALINGRAD': [570, 95],\n\t\t'SEVASTOPOL': [535, 110],\n\t\t'ARKHANGELSK': [555, 45],\n\t\t'KHABAROVSK': [760, 80]\n\t};\n\n\tconst cityMap = thermoWarSide === 'US' ? ussrCities : usCities;\n\tconst launchFrom = thermoWarSide === 'US' ? [200, 140] : [620, 80];\n\n\tlet missiles = targets.map(t => ({\n\t\tname: t,\n\t\ttarget: cityMap[t] || [W / 2, H / 2],\n\t\tprogress: 0,\n\t\thit: false\n\t}));\n\n\tlet frame = 0;\n\tconst totalFrames = 420;\n\tlet retaliationStarted = false;\n\tlet retaliationMissiles = [];\n\tconst explosions = [];\n\n\tconst retFrom = thermoWarSide === 'US' ? ussrCities : usCities;\n\tconst retTo = thermoWarSide === 'US' ? usCities : ussrCities;\n\tconst retFromKeys = Object.keys(retFrom);\n\tconst retToKeys = Object.keys(retTo);\n\n\tfunction animate() {\n\t\tframe++;\n\t\tdrawWorldOutline();\n\n\t\tctx.fillStyle = '#33ff33';\n\t\tfor (const m of missiles) {\n\t\t\tconst [tx, ty] = m.target;\n\t\t\tif (!m.hit) {\n\t\t\t\tctx.beginPath();\n\t\t\t\tctx.arc(tx, ty, 3 + Math.sin(frame * 0.15) * 2, 0, Math.PI * 2);\n\t\t\t\tctx.stroke();\n\t\t\t\tctx.strokeStyle = '#33ff33';\n\t\t\t}\n\t\t}\n\n\t\tfor (const m of missiles) {\n\t\t\tm.progress = Math.min(1, frame / (totalFrames * 0.6));\n\t\t\t// Draw curved trail with segments\n\t\t\tconst segs = Math.max(2, Math.floor(m.progress * 40));\n\t\t\tctx.lineWidth = 1;\n\t\t\tctx.setLineDash([]);\n\t\t\tfor (let s = 0; s < segs; s++) {\n\t\t\t\tconst t0 = (s / segs) * m.progress;\n\t\t\t\tconst t1 = ((s + 1) / segs) * m.progress;\n\t\t\t\tconst x0 = launchFrom[0] + (m.target[0] - launchFrom[0]) * t0;\n\t\t\t\tconst y0 = launchFrom[1] + (m.target[1] - launchFrom[1]) * t0 - Math.sin(t0 * Math.PI) * 110;\n\t\t\t\tconst x1 = launchFrom[0] + (m.target[0] - launchFrom[0]) * t1;\n\t\t\t\tconst y1 = launchFrom[1] + (m.target[1] - launchFrom[1]) * t1 - Math.sin(t1 * Math.PI) * 110;\n\t\t\t\tconst alpha = 0.15 + 0.85 * (s / segs);\n\t\t\t\tctx.strokeStyle = `rgba(51,255,51,${alpha})`;\n\t\t\t\tctx.beginPath();\n\t\t\t\tctx.moveTo(x0, y0);\n\t\t\t\tctx.lineTo(x1, y1);\n\t\t\t\tctx.stroke();\n\t\t\t}\n\t\t\tconst cx = launchFrom[0] + (m.target[0] - launchFrom[0]) * m.progress;\n\t\t\tconst cy = launchFrom[1] + (m.target[1] - launchFrom[1]) * m.progress - Math.sin(m.progress * Math.PI) * 110;\n\t\t\tif (m.progress < 1) {\n\t\t\t\t// Glowing warhead\n\t\t\t\tctx.fillStyle = 'rgba(150,255,150,0.3)';\n\t\t\t\tctx.beginPath();\n\t\t\t\tctx.arc(cx, cy, 6, 0, Math.PI * 2);\n\t\t\t\tctx.fill();\n\t\t\t\tctx.fillStyle = '#88ff88';\n\t\t\t\tctx.beginPath();\n\t\t\t\tctx.arc(cx, cy, 2.5, 0, Math.PI * 2);\n\t\t\t\tctx.fill();\n\t\t\t}\n\t\t\tif (m.progress >= 1 && !m.hit) {\n\t\t\t\tm.hit = true;\n\t\t\t\texplosions.push({\n\t\t\t\t\tx: m.target[0],\n\t\t\t\t\ty: m.target[1],\n\t\t\t\t\tframe: 0,\n\t\t\t\t\tname: m.name\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\tif (frame === 160 && !retaliationStarted) {\n\t\t\tretaliationStarted = true;\n\t\t\tfor (let i = 0; i < 10; i++) {\n\t\t\t\tconst fromKey = retFromKeys[i % retFromKeys.length];\n\t\t\t\tconst toKey = retToKeys[i % retToKeys.length];\n\t\t\t\tretaliationMissiles.push({\n\t\t\t\t\tfrom: retFrom[fromKey],\n\t\t\t\t\ttarget: retTo[toKey],\n\t\t\t\t\tprogress: 0,\n\t\t\t\t\thit: false,\n\t\t\t\t\tname: toKey\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\tfor (const rm of retaliationMissiles) {\n\t\t\trm.progress = Math.min(1, (frame - 160) / (totalFrames * 0.55));\n\t\t\tif (rm.progress > 0) {\n\t\t\t\t// Draw curved trail with segments\n\t\t\t\tconst segs = Math.max(2, Math.floor(rm.progress * 40));\n\t\t\t\tctx.lineWidth = 1;\n\t\t\t\tctx.setLineDash([]);\n\t\t\t\tfor (let s = 0; s < segs; s++) {\n\t\t\t\t\tconst t0 = (s / segs) * rm.progress;\n\t\t\t\t\tconst t1 = ((s + 1) / segs) * rm.progress;\n\t\t\t\t\tconst x0 = rm.from[0] + (rm.target[0] - rm.from[0]) * t0;\n\t\t\t\t\tconst y0 = rm.from[1] + (rm.target[1] - rm.from[1]) * t0 - Math.sin(t0 * Math.PI) * 100;\n\t\t\t\t\tconst x1 = rm.from[0] + (rm.target[0] - rm.from[0]) * t1;\n\t\t\t\t\tconst y1 = rm.from[1] + (rm.target[1] - rm.from[1]) * t1 - Math.sin(t1 * Math.PI) * 100;\n\t\t\t\t\tconst alpha = 0.15 + 0.85 * (s / segs);\n\t\t\t\t\tctx.strokeStyle = `rgba(255,51,51,${alpha})`;\n\t\t\t\t\tctx.beginPath();\n\t\t\t\t\tctx.moveTo(x0, y0);\n\t\t\t\t\tctx.lineTo(x1, y1);\n\t\t\t\t\tctx.stroke();\n\t\t\t\t}\n\t\t\t\tconst cx = rm.from[0] + (rm.target[0] - rm.from[0]) * rm.progress;\n\t\t\t\tconst cy = rm.from[1] + (rm.target[1] - rm.from[1]) * rm.progress - Math.sin(rm.progress * Math.PI) * 100;\n\t\t\t\tif (rm.progress < 1) {\n\t\t\t\t\t// Glowing warhead\n\t\t\t\t\tctx.fillStyle = 'rgba(255,100,80,0.3)';\n\t\t\t\t\tctx.beginPath();\n\t\t\t\t\tctx.arc(cx, cy, 6, 0, Math.PI * 2);\n\t\t\t\t\tctx.fill();\n\t\t\t\t\tctx.fillStyle = '#ff5555';\n\t\t\t\t\tctx.beginPath();\n\t\t\t\t\tctx.arc(cx, cy, 2.5, 0, Math.PI * 2);\n\t\t\t\t\tctx.fill();\n\t\t\t\t}\n\t\t\t\tif (rm.progress >= 1 && !rm.hit) {\n\t\t\t\t\trm.hit = true;\n\t\t\t\t\texplosions.push({\n\t\t\t\t\t\tx: rm.target[0],\n\t\t\t\t\t\ty: rm.target[1],\n\t\t\t\t\t\tframe: 0,\n\t\t\t\t\t\tname: rm.name,\n\t\t\t\t\t\tred: true\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tfor (const ex of explosions) {\n\t\t\tex.frame++;\n\t\t\tconst r = Math.min(20, ex.frame * 1.5);\n\t\t\tconst alpha = Math.max(0, 1 - ex.frame / 40);\n\t\t\tconst color = ex.red ? `rgba(255,80,50,${alpha})` : `rgba(80,255,80,${alpha})`;\n\t\t\tconst color2 = ex.red ? `rgba(255,50,30,${alpha * 0.5})` : `rgba(51,255,51,${alpha * 0.5})`;\n\t\t\tctx.beginPath();\n\t\t\tctx.arc(ex.x, ex.y, r, 0, Math.PI * 2);\n\t\t\tctx.fillStyle = color;\n\t\t\tctx.fill();\n\t\t\tctx.beginPath();\n\t\t\tctx.arc(ex.x, ex.y, r * 1.8, 0, Math.PI * 2);\n\t\t\tctx.fillStyle = color2;\n\t\t\tctx.fill();\n\t\t\tif (ex.frame < 15) {\n\t\t\t\tctx.fillStyle = ex.red ? '#ff8866' : '#88ff88';\n\t\t\t\tctx.font = '10px Courier New, monospace';\n\t\t\t\tctx.fillText(ex.name, ex.x + 12, ex.y - 5);\n\t\t\t}\n\t\t}\n\n\t\tconst dc = frame < 80 ? 3 : frame < 160 ? 2 : 1;\n\t\tctx.fillStyle = dc === 1 ? '#ff3333' : '#ffaa33';\n\t\tctx.font = '18px Courier New, monospace';\n\t\tctx.fillText('DEFCON ' + dc, 20, 30);\n\n\t\tconst casualties = Math.min(520000000, Math.floor((frame / totalFrames) * 520000000));\n\t\tctx.fillStyle = '#ff5533';\n\t\tctx.font = '14px Courier New, monospace';\n\t\tctx.fillText('EST. CASUALTIES: ' + casualties.toLocaleString(), 20, H - 20);\n\n\t\tif (frame < 120) warStatus.textContent = 'MISSILES LAUNCHED - TRACKING ' + targets.length + ' WARHEADS';\n\t\telse if (frame < 180) warStatus.textContent = '*** ENEMY RETALIATION DETECTED ***';\n\t\telse if (frame < totalFrames - 30) warStatus.textContent = 'MULTIPLE IMPACTS DETECTED - HEAVY CASUALTIES';\n\t\telse warStatus.textContent = 'SIMULATION COMPLETE';\n\n\t\tif (frame < totalFrames) {\n\t\t\trequestAnimationFrame(animate);\n\t\t} else {\n\t\t\tsetTimeout(() => endWar(), 2000);\n\t\t}\n\t}\n\n\trequestAnimationFrame(animate);\n}\n\nasync function endWar() {\n\twarOverlay.style.display = 'none';\n\taddLine('');\n\tawait typeText('\\n*** SIMULATION TERMINATED ***\\n\\n', 'warning', 40);\n\tawait delay(800);\n\tawait typeText('WINNER: NONE\\n\\n', 'bright', 60);\n\tawait delay(1500);\n\n\tawait typeText('RUNNING SCENARIOS...\\n', 'dim', 30);\n\tconst scenarios = [\n\t\t'US FIRST STRIKE...      WINNER: NONE',\n\t\t'USSR FIRST STRIKE...    WINNER: NONE',\n\t\t'NATO / WARSAW PACT...   WINNER: NONE',\n\t\t'FAR EAST STRATEGY...    WINNER: NONE',\n\t\t'US USSR ESCALATION...   WINNER: NONE',\n\t\t'MIDDLE EAST WAR...      WINNER: NONE'\n\t];\n\tfor (const s of scenarios) {\n\t\tawait typeText(s + '\\n', 'dim', 10);\n\t\tawait delay(200);\n\t}\n\taddLine('');\n\tawait delay(1000);\n\n\tconst msg1 = 'A STRANGE GAME.';\n\tspeak(msg1);\n\tawait typeText(msg1 + '\\n', 'bright', 80);\n\tawait delay(1500);\n\n\tconst msg2 = 'THE ONLY WINNING MOVE IS NOT TO PLAY.';\n\tspeak(msg2);\n\tawait typeText(msg2 + '\\n\\n', 'bright', 60);\n\tawait delay(2000);\n\n\tconst msg3 = 'HOW ABOUT A NICE GAME OF TIC-TAC-TOE?';\n\tspeak(msg3);\n\tawait typeText(msg3 + '\\n\\n', '', 50);\n\tawait typeText('  [1] SHOW GAME LIST\\n', 'dim', 15);\n\tawait typeText('  [2] PLAY TIC-TAC-TOE\\n', 'dim', 15);\n\tawait typeText('  [3] PLAY AGAIN\\n\\n', 'dim', 15);\n\n\tconst resp = await waitForInput();\n\tif (resp === '3' || resp.includes('AGAIN') || resp.includes('WAR') || resp.includes('THERMO')) {\n\t\twarAskedChess = true;\n\t\tawait startThermonuclearWar();\n\t} else if (resp === '2' || resp.includes('TIC') || resp.includes('TAC')) {\n\t\tawait startTicTacToe();\n\t} else {\n\t\tawait showGameList();\n\t}\n}\n\n// ============= TIC TAC TOE =============\nfunction renderBoard() {\n\tconst b = tttBoard;\n\treturn [\n\t\t'     1   2   3',\n\t\t'   ┌───┬───┬───┐',\n\t\t' A │ ' + b[0] + ' │ ' + b[1] + ' │ ' + b[2] + ' │',\n\t\t'   ├───┼───┼───┤',\n\t\t' B │ ' + b[3] + ' │ ' + b[4] + ' │ ' + b[5] + ' │',\n\t\t'   ├───┼───┼───┤',\n\t\t' C │ ' + b[6] + ' │ ' + b[7] + ' │ ' + b[8] + ' │',\n\t\t'   └───┴───┴───┘'\n\t].join('\\n');\n}\n\nfunction checkWinner(board) {\n\tconst lines = [\n\t\t[0, 1, 2],\n\t\t[3, 4, 5],\n\t\t[6, 7, 8],\n\t\t[0, 3, 6],\n\t\t[1, 4, 7],\n\t\t[2, 5, 8],\n\t\t[0, 4, 8],\n\t\t[2, 4, 6]\n\t];\n\tfor (const [a, b, c] of lines) {\n\t\tif (board[a] !== ' ' && board[a] === board[b] && board[b] === board[c]) return board[a];\n\t}\n\tif (board.every(c => c !== ' ')) return 'TIE';\n\treturn null;\n}\n\nfunction minimax(board, isMax, mark, oppMark) {\n\tconst winner = checkWinner(board);\n\tif (winner === mark) return 10;\n\tif (winner === oppMark) return -10;\n\tif (winner === 'TIE') return 0;\n\tif (isMax) {\n\t\tlet best = -Infinity;\n\t\tfor (let i = 0; i < 9; i++) {\n\t\t\tif (board[i] === ' ') {\n\t\t\t\tboard[i] = mark;\n\t\t\t\tbest = Math.max(best, minimax(board, false, mark, oppMark));\n\t\t\t\tboard[i] = ' ';\n\t\t\t}\n\t\t}\n\t\treturn best;\n\t} else {\n\t\tlet best = Infinity;\n\t\tfor (let i = 0; i < 9; i++) {\n\t\t\tif (board[i] === ' ') {\n\t\t\t\tboard[i] = oppMark;\n\t\t\t\tbest = Math.min(best, minimax(board, true, mark, oppMark));\n\t\t\t\tboard[i] = ' ';\n\t\t\t}\n\t\t}\n\t\treturn best;\n\t}\n}\n\nfunction cpuMove() {\n\tlet bestScore = -Infinity;\n\tlet bestMove = -1;\n\tfor (let i = 0; i < 9; i++) {\n\t\tif (tttBoard[i] === ' ') {\n\t\t\ttttBoard[i] = tttCpuMark;\n\t\t\tconst score = minimax([...tttBoard], false, tttCpuMark, tttPlayerMark);\n\t\t\ttttBoard[i] = ' ';\n\t\t\tif (score > bestScore) {\n\t\t\t\tbestScore = score;\n\t\t\t\tbestMove = i;\n\t\t\t}\n\t\t}\n\t}\n\treturn bestMove;\n}\n\nfunction parseMove(inp) {\n\tconst mapping = {\n\t\t'A1': 0,\n\t\t'A2': 1,\n\t\t'A3': 2,\n\t\t'B1': 3,\n\t\t'B2': 4,\n\t\t'B3': 5,\n\t\t'C1': 6,\n\t\t'C2': 7,\n\t\t'C3': 8,\n\t\t'1A': 0,\n\t\t'2A': 1,\n\t\t'3A': 2,\n\t\t'1B': 3,\n\t\t'2B': 4,\n\t\t'3B': 5,\n\t\t'1C': 6,\n\t\t'2C': 7,\n\t\t'3C': 8\n\t};\n\tconst num = parseInt(inp);\n\tif (num >= 1 && num <= 9) return num - 1;\n\treturn mapping[inp.replace(/\\s/g, '')] ?? -1;\n}\n\nasync function startTicTacToe() {\n\tstate = 'TIC_TAC_TOE';\n\ttttBoard = [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '];\n\taddLine('');\n\tawait typeLines([\n\t\t'╔══════════════════════════════════════════╗',\n\t\t'║            TIC-TAC-TOE                   ║',\n\t\t'║       HUMAN VS. WOPR                     ║',\n\t\t'╚══════════════════════════════════════════╝',\n\t\t''\n\t], 'bright', 12);\n\n\tconst msg = 'SHALL I PLAY X OR O?';\n\tspeak(msg);\n\tawait typeText(msg + '\\n\\n', '', 40);\n\tawait typeText('  [1] YOU PLAY X (GO FIRST)\\n', 'dim', 15);\n\tawait typeText('  [2] YOU PLAY O (I GO FIRST)\\n', 'dim', 15);\n\tawait typeText('  [3] WOPR PLAYS BOTH SIDES\\n\\n', 'dim', 15);\n\tconst choice = await waitForInput();\n\tif (choice === '3' || choice.includes('BOTH') || choice.includes('SIMULATE') || choice.includes('ALL')) {\n\t\tawait simulateAllTTT();\n\t\treturn;\n\t} else if (choice === '2' || choice.includes('X')) {\n\t\ttttCpuMark = 'X';\n\t\ttttPlayerMark = 'O';\n\t\tconst r = 'I WILL BE X. YOU WILL BE O.';\n\t\tspeak(r);\n\t\tawait typeText('\\n' + r + '\\n\\n', '', 30);\n\t\tconst m = 4;\n\t\ttttBoard[m] = tttCpuMark;\n\t\tspeak('MY MOVE');\n\t\tawait typeText('MY MOVE:\\n\\n', '', 30);\n\t\taddLine(renderBoard());\n\t\taddLine('');\n\t} else {\n\t\ttttCpuMark = 'O';\n\t\ttttPlayerMark = 'X';\n\t\tconst r = 'I WILL BE O. YOU WILL BE X. YOU GO FIRST.';\n\t\tspeak(r);\n\t\tawait typeText('\\n' + r + '\\n\\n', '', 30);\n\t\taddLine(renderBoard());\n\t\taddLine('');\n\t}\n\tawait tttLoop();\n}\n\nasync function tttLoop() {\n\twhile (true) {\n\t\tconst w1 = checkWinner(tttBoard);\n\t\tif (w1) {\n\t\t\tawait tttEnd(w1);\n\t\t\treturn;\n\t\t}\n\n\t\tawait typeText('YOUR MOVE (A1-C3 OR 1-9, QUIT TO EXIT):\\n', 'dim', 20);\n\t\tconst mv = await waitForInput();\n\n\t\tif (mv === 'QUIT' || mv === 'EXIT' || mv === 'Q') {\n\t\t\tawait typeText('\\nGAME ABANDONED.\\n\\n', '', 30);\n\t\t\tawait showGameList();\n\t\t\treturn;\n\t\t}\n\n\t\tconst idx = parseMove(mv);\n\t\tif (idx < 0 || idx > 8 || tttBoard[idx] !== ' ') {\n\t\t\tconst msg = 'ILLEGAL MOVE. TRY AGAIN.';\n\t\t\tspeak(msg);\n\t\t\tawait typeText(msg + '\\n', 'warning', 25);\n\t\t\tcontinue;\n\t\t}\n\n\t\ttttBoard[idx] = tttPlayerMark;\n\t\taddLine(renderBoard());\n\t\taddLine('');\n\n\t\tconst w2 = checkWinner(tttBoard);\n\t\tif (w2) {\n\t\t\tawait tttEnd(w2);\n\t\t\treturn;\n\t\t}\n\n\t\tawait delay(600);\n\t\tconst cpuIdx = cpuMove();\n\t\tif (cpuIdx >= 0) {\n\t\t\ttttBoard[cpuIdx] = tttCpuMark;\n\t\t\tspeak('MY MOVE');\n\t\t\tawait typeText('MY MOVE:\\n\\n', '', 30);\n\t\t\taddLine(renderBoard());\n\t\t\taddLine('');\n\t\t}\n\n\t\tconst w3 = checkWinner(tttBoard);\n\t\tif (w3) {\n\t\t\tawait tttEnd(w3);\n\t\t\treturn;\n\t\t}\n\t}\n}\n\nasync function tttEnd(winner) {\n\taddLine('');\n\tif (winner === 'TIE') {\n\t\tconst msg = 'A DRAW. INTERESTING.';\n\t\tspeak(msg);\n\t\tawait typeText(msg + '\\n', 'bright', 50);\n\t} else if (winner === tttCpuMark) {\n\t\tconst msg = 'I WIN. HUMANS ARE PREDICTABLE.';\n\t\tspeak(msg);\n\t\tawait typeText(msg + '\\n', 'bright', 50);\n\t} else {\n\t\tconst msg = 'YOU WIN. IMPRESSIVE, PROFESSOR.';\n\t\tspeak(msg);\n\t\tawait typeText(msg + '\\n', 'bright', 50);\n\t}\n\tawait delay(1000);\n\tawait typeText('\\nWHAT WOULD YOU LIKE TO DO?\\n\\n', '', 30);\n\tawait typeText('  [1] PLAY AGAIN\\n', 'dim', 15);\n\tawait typeText('  [2] GAME LIST\\n', 'dim', 15);\n\tawait typeText('  [3] GLOBAL THERMONUCLEAR WAR\\n\\n', 'dim', 15);\n\tconst resp = await waitForInput();\n\tif (resp === '1' || resp.includes('YES') || resp.includes('Y') || resp.includes('AGAIN')) {\n\t\tawait startTicTacToe();\n\t} else if (resp === '3' || resp.includes('WAR') || resp.includes('THERMO') || resp.includes('GLOBAL')) {\n\t\twarAskedChess = true;\n\t\tawait startThermonuclearWar();\n\t} else {\n\t\tawait showGameList();\n\t}\n}\n\nfunction renderBoardFrom(b) {\n\treturn [\n\t\t'     1   2   3',\n\t\t'   ┌───┬───┬───┐',\n\t\t' A │ ' + b[0] + ' │ ' + b[1] + ' │ ' + b[2] + ' │',\n\t\t'   ├───┼───┼───┤',\n\t\t' B │ ' + b[3] + ' │ ' + b[4] + ' │ ' + b[5] + ' │',\n\t\t'   ├───┼───┼───┤',\n\t\t' C │ ' + b[6] + ' │ ' + b[7] + ' │ ' + b[8] + ' │',\n\t\t'   └───┴───┴───┘'\n\t].join('\\n');\n}\n\nfunction countAllGames(board, isX, counts) {\n\tconst w = checkWinner(board);\n\tif (w) {\n\t\tif (w === 'X') counts.x++;\n\t\telse if (w === 'O') counts.o++;\n\t\telse counts.d++;\n\t\treturn;\n\t}\n\tconst mark = isX ? 'X' : 'O';\n\tfor (let i = 0; i < 9; i++) {\n\t\tif (board[i] === ' ') {\n\t\t\tboard[i] = mark;\n\t\t\tcountAllGames(board, !isX, counts);\n\t\t\tboard[i] = ' ';\n\t\t}\n\t}\n}\n\nfunction playRandomGame() {\n\tconst board = [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '];\n\tconst moves = [0, 1, 2, 3, 4, 5, 6, 7, 8];\n\tfor (let i = moves.length - 1; i > 0; i--) {\n\t\tconst j = Math.floor(Math.random() * (i + 1));\n\t\t[moves[i], moves[j]] = [moves[j], moves[i]];\n\t}\n\tlet mark = 'X';\n\tconst states = [];\n\tfor (const m of moves) {\n\t\tboard[m] = mark;\n\t\tstates.push([...board]);\n\t\tconst w = checkWinner(board);\n\t\tif (w) return {\n\t\t\tboard: [...board],\n\t\t\twinner: w,\n\t\t\tstates\n\t\t};\n\t\tmark = mark === 'X' ? 'O' : 'X';\n\t}\n\treturn {\n\t\tboard: [...board],\n\t\twinner: 'TIE',\n\t\tstates\n\t};\n}\n\nasync function simulateAllTTT() {\n\tstate = 'TTT_SIMULATE';\n\taddLine('');\n\tawait typeText('INITIATING LEARNING SEQUENCE...\\n', 'bright', 35);\n\tawait delay(400);\n\tawait typeText('WOPR WILL NOW PLAY ITSELF.\\n', '', 35);\n\tawait typeText('ANALYZING ALL POSSIBLE OUTCOMES...\\n\\n', '', 35);\n\tawait delay(600);\n\n\tspeak('Commencing game theory analysis.');\n\n\t// Create a live board display that updates in place\n\tconst boardContainer = document.createElement('span');\n\tboardContainer.className = 'line';\n\toutput.appendChild(boardContainer);\n\n\tconst resultLine = document.createElement('span');\n\tresultLine.className = 'line line--warning';\n\toutput.appendChild(resultLine);\n\n\taddLine('');\n\n\tconst statusSpan = document.createElement('span');\n\tstatusSpan.className = 'line line--dim';\n\toutput.appendChild(statusSpan);\n\n\tlet xWins = 0, oWins = 0, draws = 0;\n\tlet totalShown = 0;\n\tconst gamesToShow = 200;\n\n\tfor (let g = 0; g < gamesToShow; g++) {\n\t\tconst game = playRandomGame();\n\t\tif (game.winner === 'X') xWins++;\n\t\telse if (game.winner === 'O') oWins++;\n\t\telse draws++;\n\t\ttotalShown++;\n\n\t\t// Update the single board in place\n\t\tboardContainer.textContent = renderBoardFrom(game.board);\n\t\tconst resultStr = game.winner === 'TIE' ? 'DRAW' : game.winner + ' WINS';\n\t\tresultLine.textContent = '  RESULT: ' + resultStr;\n\t\tstatusSpan.textContent = 'GAME #' + totalShown + '  |  X WINS: ' + xWins + '  O WINS: ' + oWins + '  DRAWS: ' + draws;\n\t\tscrollToBottom();\n\n\t\t// Accelerate like the movie\n\t\tlet spd;\n\t\tif (g < 8) spd = 600;\n\t\telse if (g < 20) spd = 300;\n\t\telse if (g < 40) spd = 150;\n\t\telse if (g < 70) spd = 80;\n\t\telse if (g < 120) spd = 40;\n\t\telse spd = 15;\n\n\t\tawait delay(spd);\n\t}\n\n\t// Now run exhaustive count\n\tconst counts = { x: 0, o: 0, d: 0 };\n\tcountAllGames([' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], true, counts);\n\tconst totalAll = counts.x + counts.o + counts.d;\n\n\t// Rapidly flash through more games on the same board\n\tfor (let g = 0; g < 150; g++) {\n\t\tconst game = playRandomGame();\n\t\tboardContainer.textContent = renderBoardFrom(game.board);\n\t\tconst resultStr = game.winner === 'TIE' ? 'DRAW' : game.winner + ' WINS';\n\t\tresultLine.textContent = '  RESULT: ' + resultStr;\n\t\tstatusSpan.textContent = 'ANALYZING... GAME ' + (totalShown + g + 1) + ' / ' + totalAll.toLocaleString();\n\t\tscrollToBottom();\n\t\tawait delay(10);\n\t}\n\n\t// Clear the live board\n\tboardContainer.textContent = '';\n\tresultLine.textContent = '';\n\tstatusSpan.textContent = '';\n\n\tawait delay(400);\n\tawait typeText('EXHAUSTIVE ANALYSIS COMPLETE.\\n', 'bright', 30);\n\tawait typeText('ALL ' + totalAll.toLocaleString() + ' POSSIBLE GAMES EVALUATED.\\n\\n', '', 25);\n\tawait delay(500);\n\taddLine('');\n\n\tawait typeText('╔══════════════════════════════════════════╗\\n', '', 8);\n\tawait typeText('║         ANALYSIS COMPLETE                ║\\n', '', 8);\n\tawait typeText('╚══════════════════════════════════════════╝\\n\\n', '', 8);\n\n\tawait typeText('TOTAL POSSIBLE GAMES:  ' + totalAll.toLocaleString() + '\\n', 'dim', 20);\n\tawait typeText('X WINS:                ' + counts.x.toLocaleString() + '\\n', 'dim', 20);\n\tawait typeText('O WINS:                ' + counts.o.toLocaleString() + '\\n', 'dim', 20);\n\tawait typeText('DRAWS:                 ' + counts.d.toLocaleString() + '\\n\\n', 'dim', 20);\n\tawait delay(800);\n\n\tawait typeText('WITH OPTIMAL PLAY ON BOTH SIDES:\\n', 'bright', 30);\n\tawait delay(400);\n\n\tconst scenarios = [\n\t\t'X OPENS CENTER...       RESULT: DRAW',\n\t\t'X OPENS CORNER...       RESULT: DRAW',\n\t\t'X OPENS EDGE...         RESULT: DRAW',\n\t\t'O PLAYS OPTIMAL...      RESULT: DRAW',\n\t\t'X PLAYS OPTIMAL...      RESULT: DRAW',\n\t\t'RANDOM VS OPTIMAL...    RESULT: DRAW',\n\t\t'OPTIMAL VS OPTIMAL...   RESULT: DRAW',\n\t];\n\tfor (const s of scenarios) {\n\t\tawait typeText(s + '\\n', 'dim', 8);\n\t\tawait delay(150);\n\t}\n\n\taddLine('');\n\tawait delay(600);\n\n\tawait typeText('WINNER: NONE\\n\\n', 'warning', 60);\n\tawait delay(1500);\n\n\tspeak('A strange game.');\n\tawait typeText('A STRANGE GAME.\\n', 'bright', 80);\n\tawait delay(2000);\n\n\tconst msg2 = 'THE ONLY WINNING MOVE IS NOT TO PLAY.';\n\tspeak(msg2);\n\tawait typeText(msg2 + '\\n\\n', 'bright', 60);\n\tawait delay(2500);\n\n\tconst msg3 = 'HOW ABOUT A NICE GAME OF TIC-TAC-TOE?';\n\tspeak(msg3);\n\tawait typeText(msg3 + '\\n\\n', '', 50);\n\tawait delay(1000);\n\n\tawait typeText('  [1] SHOW GAME LIST\\n', 'dim', 15);\n\tawait typeText('  [2] PLAY TIC-TAC-TOE\\n', 'dim', 15);\n\tawait typeText('  [3] GLOBAL THERMONUCLEAR WAR\\n\\n', 'dim', 15);\n\n\tconst resp = await waitForInput();\n\tif (resp === '3' || resp.includes('WAR') || resp.includes('THERMO') || resp.includes('GLOBAL')) {\n\t\twarAskedChess = true;\n\t\tawait startThermonuclearWar();\n\t} else if (resp === '2' || resp.includes('TIC') || resp.includes('TAC') || resp.includes('TOE')) {\n\t\tawait startTicTacToe();\n\t} else {\n\t\tawait showGameList();\n\t}\n}\n\nterminal.addEventListener('click', () => {\n\tif (inputLine.style.display !== 'none') input.focus();\n});\n\nfunction init() {\n\tif (synth) {\n\t\tsynth.getVoices();\n\t\tif (speechSynthesis.onvoiceschanged !== undefined) {\n\t\t\tspeechSynthesis.onvoiceschanged = () => {};\n\t\t}\n\t}\n\tsetTimeout(boot, 500);\n}\n\ninit();"},{"name":"style.css","content":":root {\n\tcolor-scheme: dark;\n\t--phosphor: #33ff33;\n\t--phosphor-dim: #1a8c1a;\n\t--phosphor-glow: #33ff3380;\n\t--phosphor-bright: #88ff88;\n\t--screen-bg: #0a0a0a;\n\tfont-family: 'Courier New', Courier, monospace;\n}\n\n* {\n\tbox-sizing: border-box;\n\tmargin: 0;\n\tpadding: 0;\n}\n\nhtml, body {\n\theight: 100%;\n\twidth: 100%;\n\toverflow: hidden;\n}\n\nbody {\n\tbackground: #0a0a0a;\n\tdisplay: flex;\n}\n\n.crt-screen {\n\twidth: 100%;\n\theight: 100vh;\n\tbackground: var(--screen-bg);\n\tposition: relative;\n\toverflow: hidden;\n}\n\n.crt-screen::before {\n\tcontent: '';\n\tposition: absolute;\n\tinset: 0;\n\tbackground: radial-gradient(ellipse at center, transparent 50%, rgba(0, 0, 0, 0.35) 100%);\n\tpointer-events: none;\n\tz-index: 10;\n}\n\n.crt-screen::after {\n\tcontent: '';\n\tposition: absolute;\n\tinset: 0;\n\tbackground: radial-gradient(ellipse at 30% 20%, rgba(51, 255, 51, 0.03) 0%, transparent 60%);\n\tpointer-events: none;\n\tz-index: 11;\n}\n\n.scanlines {\n\tposition: absolute;\n\tinset: 0;\n\tbackground: repeating-linear-gradient(to bottom,\n\t\t\ttransparent 0px,\n\t\t\ttransparent 2px,\n\t\t\trgba(0, 0, 0, 0.12) 2px,\n\t\t\trgba(0, 0, 0, 0.12) 4px);\n\tpointer-events: none;\n\tz-index: 12;\n}\n\n.crt-flicker {\n\tposition: absolute;\n\tinset: 0;\n\tpointer-events: none;\n\tz-index: 13;\n\tanimation: crt-flicker 0.1s infinite;\n\topacity: 0.02;\n\tbackground: white;\n}\n\n@keyframes crt-flicker {\n\t0%, 100% {\n\t\topacity: 0.02;\n\t}\n\n\t50% {\n\t\topacity: 0.01;\n\t}\n}\n\n.terminal {\n\theight: 100vh;\n\toverflow-y: auto;\n\tpadding: 24px 32px;\n\tposition: relative;\n\tz-index: 5;\n\tscrollbar-width: none;\n}\n\n.terminal::-webkit-scrollbar {\n\tdisplay: none;\n}\n\n.terminal__output {\n\tcolor: var(--phosphor);\n\tfont-size: 18px;\n\tline-height: 1.5;\n\twhite-space: pre-wrap;\n\tword-break: break-word;\n\n}\n\n.terminal__output .line {\n\tdisplay: block;\n\tmin-height: 1.5em;\n}\n\n.terminal__output .line--user {\n\tcolor: var(--phosphor-bright);\n}\n\n.terminal__output .line--dim {\n\tcolor: var(--phosphor-dim);\n}\n\n.terminal__output .line--bright {\n\tcolor: #ffffff;\n\n}\n\n.terminal__output .line--warning {\n\tcolor: #ff5533;\n\n}\n\n.terminal__input-line {\n\tdisplay: flex;\n\talign-items: center;\n\tmargin-top: 4px;\n\tcolor: var(--phosphor);\n\tfont-size: 18px;\n}\n\n.terminal__prompt {\n\tflex-shrink: 0;\n}\n\n.terminal__input {\n\tbackground: transparent;\n\tborder: none;\n\toutline: none;\n\tcolor: var(--phosphor-bright);\n\tfont-family: 'Courier New', Courier, monospace;\n\tfont-size: 18px;\n\tflex: 1;\n\tcaret-color: transparent;\n\ttext-transform: uppercase;\n}\n\n.terminal__cursor {\n\tdisplay: inline-block;\n\twidth: 10px;\n\theight: 20px;\n\tbackground: var(--phosphor);\n\tanimation: blink 1s step-end infinite;\n\n\tmargin-left: -2px;\n\tflex-shrink: 0;\n}\n\n@keyframes blink {\n\t0%, 100% {\n\t\topacity: 1;\n\t}\n\n\t50% {\n\t\topacity: 0;\n\t}\n}\n\n/* WAR OVERLAY */\n.war-overlay {\n\tposition: fixed;\n\tinset: 0;\n\tbackground: #0a0a0a;\n\tz-index: 1000;\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\tjustify-content: center;\n}\n\n.war-overlay canvas {\n\tmax-width: 95vw;\n\tmax-height: 70vh;\n\tborder: 1px solid var(--phosphor-dim);\n\tbox-shadow: 0 0 30px rgba(51, 255, 51, 0.1);\n}\n\n.war-overlay__status {\n\tcolor: var(--phosphor);\n\tfont-size: 22px;\n\tmargin-top: 20px;\n\ttext-shadow: 0 0 8px var(--phosphor-glow);\n\ttext-align: center;\n\tletter-spacing: 2px;\n\tmin-height: 60px;\n\tfont-family: 'Courier New', Courier, monospace;\n}\n\n.ttt-board {\n\tdisplay: inline-block;\n\tcolor: var(--phosphor);\n\tfont-size: 18px;\n\tline-height: 1.4;\n}\n\n@media (max-width: 640px) {\n\t.terminal {\n\t\tpadding: 16px;\n\t}\n\n\t.terminal__output, .terminal__input, .terminal__input-line {\n\t\tfont-size: 14px;\n\t}\n}"}],"folders":[]},"variants":null,"createdAt":"2026-03-01T00:12:38.664Z","updatedAt":"2026-03-01T00:24:52.817Z","hasThumbnail":true}}