Yeah, seems to be. The OP could take my prompt and modify it as necessary to make it closer.
The OP describes it as more like an Asteroids game where you’re controlling the bullet in flight.
Yeah, seems to be. The OP could take my prompt and modify it as necessary to make it closer.
The OP describes it as more like an Asteroids game where you’re controlling the bullet in flight.
Is this version any closer?
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Altair 8800 BASIC - TARGET (1977)</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=VT323&display=swap');
:root {
--term-green: #33ff33;
--term-green-glow: rgba(51, 255, 51, 0.4);
--term-bg: #050a05;
--term-dim: #1a5e1a;
}
.theme-amber {
--term-green: #ffb000;
--term-green-glow: rgba(255, 176, 0, 0.4);
--term-bg: #0a0700;
--term-dim: #5e4100;
}
.theme-bw {
--term-green: #e0e0e0;
--term-green-glow: rgba(224, 224, 224, 0.25);
--term-bg: #0a0a0a;
--term-dim: #555555;
}
body {
background-color: #020202;
color: var(--term-green);
font-family: 'VT323', monospace;
user-select: none;
overflow: hidden;
}
.crt-container {
position: relative;
background-color: var(--term-bg);
border: 16px solid #1c1d22;
border-radius: 20px;
box-shadow: inset 0 0 80px rgba(0, 0, 0, 0.8), 0 0 30px rgba(0, 0, 0, 0.9);
text-shadow: 0 0 6px var(--term-green-glow);
transition: color 0.3s ease, background-color 0.3s ease;
}
/* Scanline Overlay */
.crt-container::before {
content: " ";
display: block;
position: absolute;
top: 0; left: 0; bottom: 0; right: 0;
background: linear-gradient(rgba(18, 16, 16, 0) 50%, rgba(0, 0, 0, 0.35) 50%),
linear-gradient(90deg, rgba(255, 0, 0, 0.03), rgba(0, 255, 0, 0.01), rgba(0, 0, 255, 0.03));
z-index: 20;
background-size: 100% 4px, 6px 100%;
pointer-events: none;
border-radius: 8px;
}
/* Screen Flicker */
@keyframes flicker {
0% { opacity: 0.98; }
50% { opacity: 1; }
100% { opacity: 0.97; }
}
.crt-screen {
animation: flicker 0.15s infinite;
}
/* Standardized 80x24 ASCII display */
#terminal {
display: block;
white-space: pre;
font-family: 'VT323', monospace;
line-height: 1.05;
letter-spacing: 0px;
color: var(--term-green);
}
.btn-retro {
background-color: #111;
border: 1px solid var(--term-green);
color: var(--term-green);
padding: 4px 10px;
border-radius: 4px;
font-family: 'VT323', monospace;
transition: all 0.15s ease;
}
.btn-retro:hover {
background-color: var(--term-green);
color: #000;
box-shadow: 0 0 10px var(--term-green-glow);
}
/* Key binding help pill */
.key-badge {
background-color: rgba(255, 255, 255, 0.1);
border: 1px solid var(--term-dim);
padding: 1px 5px;
border-radius: 3px;
}
</style>
</head>
<body class="h-screen w-screen flex flex-col items-center justify-center p-2 sm:p-4">
<!-- CRT Monitor Frame -->
<div class="crt-container w-full max-w-5xl p-4 md:p-6 flex flex-col items-center justify-between">
<!-- Header Banner -->
<div class="w-full flex justify-between items-center border-b border-dashed pb-2 mb-2 text-xs sm:text-sm md:text-base opacity-90" style="border-color: var(--term-dim);">
<div>MITS ALTAIR 8800 / TELETYPE Model 33</div>
<div>ALTAIR BASIC v4.1 (8K)</div>
<div id="audio-indicator">SOUND: [ON]</div>
</div>
<!-- Terminal Screen Box -->
<div class="crt-screen w-full flex justify-center items-center my-1 overflow-x-auto">
<pre id="terminal" class="text-xs sm:text-sm md:text-base lg:text-lg select-none"></pre>
</div>
<!-- On-Screen Controls Footer -->
<div class="w-full border-t border-dashed pt-2 mt-2 flex flex-wrap justify-between items-center gap-2 text-xs sm:text-sm opacity-90" style="border-color: var(--term-dim);">
<div class="flex flex-wrap gap-2">
<span class="key-badge">W/A/S/D / Arrows</span>: Move
<span class="key-badge">B</span>: Step Budget
<span class="key-badge">T</span>: Target Quota
<span class="key-badge">X</span>: Symbol
</div>
<div class="flex flex-wrap gap-2">
<span class="key-badge">C</span>: Color Phosphor
<span class="key-badge">M</span>: Sound
<span class="key-badge">P</span>: Pause
<span class="key-badge">R</span>: Restart/Reset
</div>
</div>
</div>
<!-- Interactive Button Bar for Touch / Mouse Users -->
<div class="mt-3 flex flex-wrap justify-center gap-2 max-w-5xl w-full text-xs sm:text-sm">
<button class="btn-retro" onclick="game.handleInput('Up')">▲ [W]</button>
<button class="btn-retro" onclick="game.handleInput('Down')">▼ [S]</button>
<button class="btn-retro" onclick="game.handleInput('Left')">◄ [A]</button>
<button class="btn-retro" onclick="game.handleInput('Right')">► [D]</button>
<button class="btn-retro" onclick="game.toggleStepBudget()">Budget [B]</button>
<button class="btn-retro" onclick="game.toggleTargetQuota()">Quota [T]</button>
<button class="btn-retro" onclick="game.toggleSymbol()">Sprite [X]</button>
<button class="btn-retro" onclick="game.cycleTheme()">Color [C]</button>
<button class="btn-retro" onclick="audio.toggle()">Sound [M]</button>
<button class="btn-retro" onclick="game.startNewGame(game.mode)">Restart [R]</button>
</div>
<script>
/**
* Web Audio Synthesizer for Retro Switch/Teletype Effects
*/
class SoundSystem {
constructor() {
this.ctx = null;
this.enabled = true;
}
init() {
if (!this.ctx) {
const AudioContext = window.AudioContext || window.webkitAudioContext;
if (AudioContext) {
this.ctx = new AudioContext();
}
}
}
playStep() {
if (!this.enabled) return;
this.init();
if (!this.ctx) return;
try {
const osc = this.ctx.createOscillator();
const gain = this.ctx.createGain();
osc.type = 'square';
osc.frequency.setValueAtTime(180, this.ctx.currentTime);
osc.frequency.exponentialRampToValueAtTime(60, this.ctx.currentTime + 0.03);
gain.gain.setValueAtTime(0.08, this.ctx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.001, this.ctx.currentTime + 0.03);
osc.connect(gain);
gain.connect(this.ctx.destination);
osc.start();
osc.stop(this.ctx.currentTime + 0.03);
} catch(e) {}
}
playHit() {
if (!this.enabled) return;
this.init();
if (!this.ctx) return;
try {
const notes = [261.63, 329.63, 392.00, 523.25];
notes.forEach((freq, idx) => {
const osc = this.ctx.createOscillator();
const gain = this.ctx.createGain();
osc.type = 'triangle';
osc.frequency.setValueAtTime(freq, this.ctx.currentTime + idx * 0.05);
gain.gain.setValueAtTime(0.12, this.ctx.currentTime + idx * 0.05);
gain.gain.exponentialRampToValueAtTime(0.001, this.ctx.currentTime + idx * 0.05 + 0.12);
osc.connect(gain);
gain.connect(this.ctx.destination);
osc.start(this.ctx.currentTime + idx * 0.05);
osc.stop(this.ctx.currentTime + idx * 0.05 + 0.12);
});
} catch(e) {}
}
playGameOver(victory = true) {
if (!this.enabled) return;
this.init();
if (!this.ctx) return;
try {
const freqs = victory ? [300, 400, 500, 700] : [500, 400, 300, 200];
freqs.forEach((freq, idx) => {
const osc = this.ctx.createOscillator();
const gain = this.ctx.createGain();
osc.type = victory ? 'sine' : 'sawtooth';
osc.frequency.setValueAtTime(freq, this.ctx.currentTime + idx * 0.1);
gain.gain.setValueAtTime(0.15, this.ctx.currentTime + idx * 0.1);
gain.gain.exponentialRampToValueAtTime(0.001, this.ctx.currentTime + idx * 0.1 + 0.18);
osc.connect(gain);
gain.connect(this.ctx.destination);
osc.start(this.ctx.currentTime + idx * 0.1);
osc.stop(this.ctx.currentTime + idx * 0.1 + 0.18);
});
} catch(e) {}
}
toggle() {
this.enabled = !this.enabled;
const indicator = document.getElementById('audio-indicator');
if (indicator) {
indicator.textContent = `SOUND: [${this.enabled ? 'ON' : 'OFF'}]`;
}
}
}
const audio = new SoundSystem();
/**
* Game Engine and Grid Render Logic
*/
const COLS = 80;
const ROWS = 24;
const GAME_STATES = {
MENU: 'MENU',
PLAYING: 'PLAYING',
PAUSED: 'PAUSED',
GAMEOVER: 'GAMEOVER'
};
const MODES = {
STATIC: 1,
WANDERING: 2,
EVASIVE: 3,
MULTI: 4,
TIME_ATTACK: 5
};
class TargetGame {
constructor() {
this.state = GAME_STATES.MENU;
this.mode = MODES.STATIC;
// Player properties
this.px = 40;
this.py = 12;
this.playerSymbol = 'O';
// Target system
this.targets = []; // Array of {x, y, active, initialDist}
this.targetQuota = 10; // Default hits needed to complete round
this.targetsHit = 0;
// Move and step budget settings
this.stepsTaken = 0;
this.stepBudgetEnabled = false;
this.maxSteps = 100;
// Score metrics
this.score = 0;
this.totalOptimumSteps = 0;
// Time Attack properties
this.timeRemaining = 60;
this.timerId = null;
// Visual theme settings
this.themes = ['', 'theme-amber', 'theme-bw'];
this.themeIndex = 0;
// Explosion visual effect buffer
this.fxBuffer = []; // {x, y, char, frame}
// Animation loop tracking
this.lastWanderTime = 0;
this.highScores = {
1: 0, 2: 0, 3: 0, 4: 0, 5: 0
};
}
init() {
this.render();
setInterval(() => this.updateLoop(), 150);
}
startNewGame(selectedMode) {
this.mode = selectedMode;
this.state = GAME_STATES.PLAYING;
this.px = 40;
this.py = 12;
this.stepsTaken = 0;
this.targetsHit = 0;
this.score = 0;
this.totalOptimumSteps = 0;
this.fxBuffer = [];
if (this.timerId) clearInterval(this.timerId);
if (this.mode === MODES.TIME_ATTACK) {
this.timeRemaining = 60;
this.timerId = setInterval(() => {
if (this.state === GAME_STATES.PLAYING) {
this.timeRemaining--;
if (this.timeRemaining <= 0) {
this.timeRemaining = 0;
this.endGame(true, "TIME EXPIRED!");
}
}
}, 1000);
}
this.spawnTargets();
audio.playStep();
}
spawnTargets() {
this.targets = [];
const count = (this.mode === MODES.MULTI) ? 5 : 1;
for (let i = 0; i < count; i++) {
let tx, ty, dist;
do {
tx = Math.floor(Math.random() * (COLS - 4)) + 2;
ty = Math.floor(Math.random() * (ROWS - 6)) + 3;
dist = Math.abs(tx - this.px) + Math.abs(ty - this.py);
} while (dist < 5); // Ensure target doesn't spawn right next to player
this.targets.push({
x: tx,
y: ty,
active: true,
initialDist: dist
});
}
}
movePlayer(dx, dy) {
if (this.state !== GAME_STATES.PLAYING) return;
const newX = this.px + dx;
const newY = this.py + dy;
// Playfield boundaries (reserving header and footer lines)
if (newX >= 1 && newX <= COLS - 2 && newY >= 3 && newY <= ROWS - 3) {
this.px = newX;
this.py = newY;
this.stepsTaken++;
audio.playStep();
// Check Step Budget Violation
if (this.stepBudgetEnabled && this.stepsTaken >= this.maxSteps) {
this.endGame(false, "STEP BUDGET EXCEEDED!");
return;
}
// Evasive Target reaction logic
if (this.mode === MODES.EVASIVE) {
this.targets.forEach(t => {
if (t.active) {
const dist = Math.abs(t.x - this.px) + Math.abs(t.y - this.py);
if (dist < 4) {
// Move away from player
const escapeX = t.x + Math.sign(t.x - this.px);
const escapeY = t.y + Math.sign(t.y - this.py);
if (escapeX >= 2 && escapeX <= COLS - 3) t.x = escapeX;
if (escapeY >= 4 && escapeY <= ROWS - 4) t.y = escapeY;
}
}
});
}
this.checkCollisions();
}
}
checkCollisions() {
let hitAny = false;
this.targets.forEach(t => {
if (t.active && t.x === this.px && t.y === this.py) {
t.active = false;
hitAny = true;
// Add explosion effect
this.fxBuffer.push({ x: t.x, y: t.y, char: '*', frame: 0 });
this.fxBuffer.push({ x: t.x - 1, y: t.y, char: '+', frame: 0 });
this.fxBuffer.push({ x: t.x + 1, y: t.y, char: '+', frame: 0 });
this.fxBuffer.push({ x: t.x, y: t.y - 1, char: '+', frame: 0 });
this.fxBuffer.push({ x: t.x, y: t.y + 1, char: '+', frame: 0 });
this.targetsHit++;
this.score += 100 + Math.max(0, 50 - Math.abs(t.initialDist));
this.totalOptimumSteps += t.initialDist;
}
});
if (hitAny) {
audio.playHit();
// Check target quota completion
if (this.targetQuota !== 0 && this.targetsHit >= this.targetQuota) {
this.endGame(true, "TARGET QUOTA REACHED!");
return;
}
// Respawn targets if all active ones are hit
if (!this.targets.some(t => t.active)) {
this.spawnTargets();
}
}
}
updateLoop() {
// Handle explosion particle frames
if (this.fxBuffer.length > 0) {
this.fxBuffer.forEach(f => f.frame++);
this.fxBuffer = this.fxBuffer.filter(f => f.frame < 3);
}
// Handle Wandering Targets movement
if (this.state === GAME_STATES.PLAYING && this.mode === MODES.WANDERING) {
const now = Date.now();
if (now - this.lastWanderTime > 800) {
this.lastWanderTime = now;
this.targets.forEach(t => {
if (t.active && Math.random() < 0.6) {
const dirX = Math.floor(Math.random() * 3) - 1;
const dirY = Math.floor(Math.random() * 3) - 1;
const nx = t.x + dirX;
const ny = t.y + dirY;
if (nx >= 2 && nx <= COLS - 3) t.x = nx;
if (ny >= 4 && ny <= ROWS - 4) t.y = ny;
}
});
this.checkCollisions();
}
}
this.render();
}
endGame(victory, reasonMessage) {
this.state = GAME_STATES.GAMEOVER;
if (this.timerId) clearInterval(this.timerId);
// Update High Scores
if (this.score > this.highScores[this.mode]) {
this.highScores[this.mode] = this.score;
}
this.endReason = reasonMessage;
this.isVictory = victory;
audio.playGameOver(victory);
}
toggleStepBudget() {
if (!this.stepBudgetEnabled) {
this.stepBudgetEnabled = true;
this.maxSteps = 100;
} else if (this.maxSteps === 100) {
this.maxSteps = 200;
} else if (this.maxSteps === 200) {
this.maxSteps = 300;
} else {
this.stepBudgetEnabled = false;
}
audio.playStep();
}
toggleTargetQuota() {
if (this.targetQuota === 5) this.targetQuota = 10;
else if (this.targetQuota === 10) this.targetQuota = 20;
else if (this.targetQuota === 20) this.targetQuota = 0; // Infinite
else this.targetQuota = 5;
audio.playStep();
}
toggleSymbol() {
this.playerSymbol = (this.playerSymbol === 'O') ? 'X' : 'O';
audio.playStep();
}
cycleTheme() {
this.themeIndex = (this.themeIndex + 1) % this.themes.length;
document.body.className = "h-screen w-screen flex flex-col items-center justify-center p-2 sm:p-4 " + this.themes[this.themeIndex];
audio.playStep();
}
handleInput(command) {
if (command === 'Up') this.movePlayer(0, -1);
else if (command === 'Down') this.movePlayer(0, 1);
else if (command === 'Left') this.movePlayer(-1, 0);
else if (command === 'Right') this.movePlayer(1, 0);
}
render() {
const term = document.getElementById('terminal');
let buffer = [];
// Initialize empty 80x24 grid
for (let r = 0; r < ROWS; r++) {
buffer[r] = new Array(COLS).fill(' ');
}
if (this.state === GAME_STATES.MENU) {
this.renderMenu(buffer);
} else {
this.renderHUD(buffer);
this.renderPlayfield(buffer);
if (this.state === GAME_STATES.PAUSED) {
this.renderOverlayBox(buffer, ["--- GAME PAUSED ---", "", "PRESS [P] TO RESUME"]);
} else if (this.state === GAME_STATES.GAMEOVER) {
const eff = this.stepsTaken > 0 ? Math.min(100, Math.round((this.totalOptimumSteps / this.stepsTaken) * 100)) : 0;
this.renderOverlayBox(buffer, [
this.isVictory ? "=== MISSION ACCOMPLISHED ===" : "!!! GAME OVER !!!",
this.endReason || "",
"",
`FINAL SCORE: ${this.score}`,
`TARGETS HIT: ${this.targetsHit}`,
`STEPS TAKEN: ${this.stepsTaken}`,
`NAV EFFICIENCY: ${eff}%`,
"",
"PRESS [R] TO PLAY AGAIN OR [M] FOR MENU"
]);
}
}
// Render array lines to string
term.textContent = buffer.map(row => row.join('')).join('\n');
}
renderMenu(buffer) {
const lines = [
"================================================================================",
" ALTAIR 8800 BASIC - TARGET EMULATOR (1977) ",
"================================================================================",
"",
" SELECT GAME MODE:",
" [1] STATIC TARGET - Standard coordinate matching & pursuit",
" [2] WANDERING TARGET - Target moves randomly around grid",
" [3] EVASIVE TARGET - Target flees as player gets close",
" [4] MULTI-TARGET - Multiple targets active simultaneously",
" [5] TIME ATTACK - 60-second speed-matching challenge",
"",
" SETTINGS & CONTROLS:",
` [B] STEP BUDGET : ${this.stepBudgetEnabled ? this.maxSteps + ' STEPS' : 'DISABLED'}`,
` [T] TARGET QUOTA : ${this.targetQuota === 0 ? 'INFINITE' : this.targetQuota + ' TARGETS'}`,
` [X] PLAYER SYMBOL : [ ${this.playerSymbol} ]`,
` [C] COLOR PHOSPHOR : GREEN / AMBER / B&W`,
"",
" OBJECTIVE:",
" Navigate your player symbol to match coordinates with the target (*).",
" Minimize total step count to achieve maximum telemetry efficiency.",
"",
"================================================================================",
" PRESS NUMBER KEY [1-5] TO LOAD PROGRAM AND START RUN "
];
lines.forEach((line, r) => {
if (r < ROWS) {
for (let c = 0; c < line.length && c < COLS; c++) {
buffer[r][c] = line[c];
}
}
});
}
renderHUD(buffer) {
// Header Border
const line1 = "="+ "=".repeat(COLS - 2) + "=";
// Telemetry Data Bar
let nearestDist = "--";
const activeTargets = this.targets.filter(t => t.active);
if (activeTargets.length > 0) {
const nearest = activeTargets.reduce((min, t) => {
const d = Math.abs(t.x - this.px) + Math.abs(t.y - this.py);
return d < min ? d : min;
}, 999);
nearestDist = nearest.toString();
}
const modeNames = ["", "STATIC", "WANDER", "EVASIVE", "MULTI", "TIME-ATT"];
const budgetStr = this.stepBudgetEnabled ? `${this.stepsTaken}/${this.maxSteps}` : `${this.stepsTaken}`;
const quotaStr = this.targetQuota === 0 ? `${this.targetsHit}/INF` : `${this.targetsHit}/${this.targetQuota}`;
const timeStr = this.mode === MODES.TIME_ATTACK ? ` TIME:${this.timeRemaining}s` : '';
const line2 = ` MODE:${modeNames[this.mode]} | POS:(${this.px},${this.py}) | DIST:${nearestDist} | STEPS:${budgetStr} | TGT:${quotaStr}${timeStr} | SCORE:${this.score} `;
const line3 = "-".repeat(COLS);
for (let c = 0; c < COLS; c++) {
buffer[0][c] = line1[c] || ' ';
buffer[1][c] = line2[c] || ' ';
buffer[2][c] = line3[c] || ' ';
buffer[ROWS - 1][c] = line1[c] || ' ';
}
}
renderPlayfield(buffer) {
// Draw Playfield Box Bounds
for (let r = 3; r < ROWS - 1; r++) {
buffer[r][0] = '|';
buffer[r][COLS - 1] = '|';
}
// Render Targets
this.targets.forEach(t => {
if (t.active && t.x > 0 && t.x < COLS - 1 && t.y >= 3 && t.y < ROWS - 1) {
buffer[t.y][t.x] = '*';
}
});
// Render Explosion VFX
this.fxBuffer.forEach(f => {
if (f.x > 0 && f.x < COLS - 1 && f.y >= 3 && f.y < ROWS - 1) {
buffer[f.y][f.x] = f.char;
}
});
// Render Player
if (this.px > 0 && this.px < COLS - 1 && this.py >= 3 && this.py < ROWS - 1) {
buffer[this.py][this.px] = this.playerSymbol;
}
}
renderOverlayBox(buffer, textLines) {
const boxWidth = 50;
const boxHeight = textLines.length + 4;
const startR = Math.floor((ROWS - boxHeight) / 2);
const startC = Math.floor((COLS - boxWidth) / 2);
for (let r = 0; r < boxHeight; r++) {
const currentR = startR + r;
if (currentR < 0 || currentR >= ROWS) continue;
for (let c = 0; c < boxWidth; c++) {
const currentC = startC + c;
if (currentC < 0 || currentC >= COLS) continue;
if (r === 0 || r === boxHeight - 1) {
buffer[currentR][currentC] = '+';
} else if (c === 0 || c === boxWidth - 1) {
buffer[currentR][currentC] = '|';
} else {
buffer[currentR][currentC] = ' ';
}
}
}
// Print Overlay Text
textLines.forEach((line, idx) => {
const currentR = startR + 2 + idx;
const padding = Math.max(0, Math.floor((boxWidth - 2 - line.length) / 2));
const startTextC = startC + 1 + padding;
for (let i = 0; i < line.length; i++) {
if (startTextC + i < startC + boxWidth - 1) {
buffer[currentR][startTextC + i] = line[i];
}
}
});
}
}
// Initialize Game Instance
const game = new TargetGame();
// Keyboard Event Dispatcher
window.addEventListener('keydown', (e) => {
// Prevent default arrow key scrolling
if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'Space'].includes(e.code)) {
e.preventDefault();
}
if (game.state === GAME_STATES.MENU) {
if (['Digit1', 'Numpad1'].includes(e.code)) game.startNewGame(MODES.STATIC);
else if (['Digit2', 'Numpad2'].includes(e.code)) game.startNewGame(MODES.WANDERING);
else if (['Digit3', 'Numpad3'].includes(e.code)) game.startNewGame(MODES.EVASIVE);
else if (['Digit4', 'Numpad4'].includes(e.code)) game.startNewGame(MODES.MULTI);
else if (['Digit5', 'Numpad5'].includes(e.code)) game.startNewGame(MODES.TIME_ATTACK);
else if (e.code === 'KeyB') game.toggleStepBudget();
else if (e.code === 'KeyT') game.toggleTargetQuota();
else if (e.code === 'KeyX' || e.code === 'KeyS') game.toggleSymbol();
else if (e.code === 'KeyC') game.cycleTheme();
else if (e.code === 'KeyM') audio.toggle();
} else if (game.state === GAME_STATES.PLAYING) {
if (e.code === 'ArrowLeft' || e.code === 'KeyA') game.movePlayer(-1, 0);
else if (e.code === 'ArrowRight' || e.code === 'KeyD') game.movePlayer(1, 0);
else if (e.code === 'ArrowUp' || e.code === 'KeyW') game.movePlayer(0, -1);
else if (e.code === 'ArrowDown' || e.code === 'KeyS') game.movePlayer(0, 1);
else if (e.code === 'KeyP') game.state = GAME_STATES.PAUSED;
else if (e.code === 'KeyR') game.startNewGame(game.mode);
else if (e.code === 'KeyB') game.toggleStepBudget();
else if (e.code === 'KeyT') game.toggleTargetQuota();
else if (e.code === 'KeyX') game.toggleSymbol();
else if (e.code === 'KeyC') game.cycleTheme();
else if (e.code === 'KeyM') audio.toggle();
} else if (game.state === GAME_STATES.PAUSED) {
if (e.code === 'KeyP') game.state = GAME_STATES.PLAYING;
} else if (game.state === GAME_STATES.GAMEOVER) {
if (e.code === 'KeyR') game.startNewGame(game.mode);
else if (e.code === 'KeyM') game.state = GAME_STATES.MENU;
}
});
// Initialize display on window load
window.onload = () => {
game.init();
};
</script>
</body>
</html>
He said he still has it in the attic! Now, whether it works or not is a different question…
I’m impressed! ![]()
I’m sorry about vanishing for a few days. I was truly overwhelmed by how much effort so many of you put into my question, though in truth most of it went totally over my head. What I probably needed was a link to some hobbyist who had a web page of replicas you could simply play what looked like the game with a browser. I know there’s at least one for old Zork type games, so just maybe?
I was never into the hands-on side of things. My brother did all the tech stuff: he’d boot the computer, load the game, laugh at my frequent ineptitude at playing whatever it was.
After I posted that message, I finally realized what was going on with me. My Eldest Brother and I were part of a large family … and we were the two outsiders. All the other boys (and a couple of the girls!) were stereotypical jocks through and through. It was sports. sports, sports plus later on lots of booze and in some cases drugs, and in one bad case a career of crime to support it. With the sort of aftermath you might expect.
We two simply didn’t fit, and I think our overburdened parents just had no idea what to do with us. We were both excellent students – I think we fled to school – and we were polite and followed the rules so I guess they figured they could just concentrate on the needs of the noisier mob.
I actually got more involved with the computers after he’d moved on to CP/M and later some CP/M-DOS hybrid and so on. A friend of his had invented a game creation system that would turn out text games like Zork and its ilk. It was not at all demanding of true programming skills, what it took was creative writing to come up with your scenarios and puzzles and descriptions. Those I was good at, while Brother would loyally always expand the matrixe(?maybe?) so I could have more and more verb and objects as my games grew. We’d spend hours, me creating the game, him engineering it, and finally being my first player.
Wow. More detail than you need, sorry. Anyway, time passed, and that phase of things passed. I moved basically into writing stories that didn’t have to define down to verb-object phrases.
Brother was, no surprise, a software engineer. He had a bad heart attack, but seemed to recover well. He lost extra weight and started an exercise program under medical supervision and things seemed to be going well. Then it was October, which here in New England means falling leaves. And he got the idea that he was feeling great so why not rake up leaves and haul them away…
His backyard had a lot of trees, and was sort of secluded. The autopsy said he’d been dead for several hours before a neighbor happened to spot him lying there.
And it’s now October again, and every year I get sort of melancholy when the leaves start to fall.
Which is a long lead up to saying, I didn’t realize at first I wasn’t missing a computer game I was suddenly remembering.
Thank you all for your kind attempts to help, sorry for the waste of your time.
Sad story, but also lovely
FWIW I tried loading and running a bunch of game disks I found on the Internet under CP/M 2.2 in the SIMH emulator, and they worked fine. Zork and what not; none of them had the Target described in the OP, only the Basic Computer Game of that name. If you can find the disk, running the game was never going to be a problem. There are also various web-based simulators where everything runs in a web page.
Thanks for sharing that story. Sorry about your brother. Those are nice memories to have had.
There’s no waste of time here. We were all just curious too.
Have you seen the thread on nostalgia for text-adventure games?