758 lines
28 KiB
HTML
758 lines
28 KiB
HTML
![]() |
<!DOCTYPE html>
|
||
|
<html lang="en">
|
||
|
<head>
|
||
|
<meta charset="UTF-8">
|
||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
|
<title>OpenCompute</title>
|
||
|
<style>
|
||
|
body {
|
||
|
margin: 0;
|
||
|
padding: 0;
|
||
|
background-color: #0c0c0c;
|
||
|
font-family: monospace;
|
||
|
overflow: hidden;
|
||
|
height: 100vh;
|
||
|
}
|
||
|
|
||
|
#terminal {
|
||
|
width: 100%;
|
||
|
height: 100%;
|
||
|
overflow-y: auto;
|
||
|
padding: 10px;
|
||
|
box-sizing: border-box;
|
||
|
color: #00ff00;
|
||
|
background-color: #121212;
|
||
|
font-size: 16px;
|
||
|
line-height: 1.3;
|
||
|
}
|
||
|
|
||
|
#input-line {
|
||
|
display: flex;
|
||
|
margin-top: 5px;
|
||
|
}
|
||
|
|
||
|
#prompt {
|
||
|
color: #00ff00;
|
||
|
margin-right: 5px;
|
||
|
}
|
||
|
|
||
|
#command-text {
|
||
|
white-space: pre;
|
||
|
}
|
||
|
|
||
|
#input {
|
||
|
background: transparent;
|
||
|
border: none;
|
||
|
outline: none;
|
||
|
color: #00ff00;
|
||
|
font-family: monospace;
|
||
|
font-size: 16px;
|
||
|
width: 100%;
|
||
|
}
|
||
|
|
||
|
.cursor {
|
||
|
display: inline-block;
|
||
|
width: 8px;
|
||
|
height: 16px;
|
||
|
background-color: #00ff00;
|
||
|
animation: blink 1s step-end infinite;
|
||
|
vertical-align: middle;
|
||
|
margin-left: 1px;
|
||
|
}
|
||
|
|
||
|
@keyframes blink {
|
||
|
0%, 100% { opacity: 1; }
|
||
|
50% { opacity: 0; }
|
||
|
}
|
||
|
|
||
|
pre {
|
||
|
margin: 0;
|
||
|
white-space: pre-wrap;
|
||
|
}
|
||
|
|
||
|
.hidden {
|
||
|
display: none;
|
||
|
}
|
||
|
|
||
|
.output {
|
||
|
margin-top: 5px;
|
||
|
white-space: pre-wrap;
|
||
|
}
|
||
|
|
||
|
#ascii-art-wide {
|
||
|
display: block;
|
||
|
}
|
||
|
|
||
|
#ascii-art-narrow {
|
||
|
display: none;
|
||
|
}
|
||
|
|
||
|
@media (max-width: 700px) {
|
||
|
#ascii-art-wide {
|
||
|
display: none;
|
||
|
}
|
||
|
|
||
|
#ascii-art-narrow {
|
||
|
display: block;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@media (max-width: 500px) {
|
||
|
#ascii-art-narrow {
|
||
|
font-size: 10px;
|
||
|
line-height: 1.1;
|
||
|
}
|
||
|
}
|
||
|
</style>
|
||
|
</head>
|
||
|
<body>
|
||
|
<div id="terminal">
|
||
|
<pre id="ascii-art-wide">
|
||
|
___ ___ _
|
||
|
/ _ \ _ __ ___ _ __ / __\___ _ __ ___ _ __ _ _| |_ ___
|
||
|
| | | | '_ \ / _ \ '_ / / / _ \| '_ ` _ \| '_ \| | | | __/ _ \
|
||
|
| |_| | |_) | __/ | / /__| (_) | | | | | | |_) | |_| | || __/
|
||
|
\___/| .__/ \___|_| \____/\___/|_| |_| |_| .__/ \__,_|\__\___|
|
||
|
|_| |_|
|
||
|
</pre>
|
||
|
<pre id="ascii-art-narrow">
|
||
|
___
|
||
|
/ _ \ _ __ ___ _ __
|
||
|
| | | | '_ \ / _ \ '_ \
|
||
|
| |_| | |_) | __/ | | |
|
||
|
\___/| .__/ \___|_| |_|
|
||
|
|_|
|
||
|
___ _
|
||
|
/ __\___ _ __ ___ _ __ _ _| |_ ___
|
||
|
/ / / _ \| '_ ` _ \| '_ \| | | | __/ _ \
|
||
|
/ /__| (_) | | | | | | |_) | |_| | || __/
|
||
|
\____/\___/|_| |_| |_| .__/ \__,_|\__\___|
|
||
|
|_|
|
||
|
</pre>
|
||
|
<div class="output">Welcome to MIT OpenCompute! </div>
|
||
|
<div class="output">Type 'help' to see available commands.</div>
|
||
|
<div id="output-container"></div>
|
||
|
<div id="input-line">
|
||
|
<span id="prompt">user@bash:~$</span>
|
||
|
<span id="command-text"></span><span class="cursor"></span>
|
||
|
</div>
|
||
|
<input type="text" id="mobile-input" style="opacity: 0; height: 0; position: absolute;" autocomplete="off">
|
||
|
</div>
|
||
|
|
||
|
<script>
|
||
|
const terminal = document.getElementById('terminal');
|
||
|
const outputContainer = document.getElementById('output-container');
|
||
|
const promptElement = document.getElementById('prompt');
|
||
|
const commandText = document.getElementById('command-text');
|
||
|
const cursor = document.querySelector('.cursor');
|
||
|
const mobileInput = document.getElementById('mobile-input');
|
||
|
|
||
|
let currentInput = '';
|
||
|
let commandHistory = [];
|
||
|
let historyIndex = -1;
|
||
|
let currentDirectory = '~';
|
||
|
|
||
|
// File system simulation
|
||
|
const fileSystem = {
|
||
|
'~': {
|
||
|
'about.txt': 'This is the official website of MIT OpenCompute. Created with help from Claude :P, more coming soon!',
|
||
|
'contact.txt': 'Hardware Subteam: ajzd [at] mit [dot] edu\nSoftware Subteam: ananthv [at] mit [dot] edu',
|
||
|
'join.txt': 'Fill out this form to join: https://forms.gle/qfDWwX4ChfbRaUBz8',
|
||
|
'projects': {
|
||
|
'openlaptop.txt': 'A completely open source RISC-V based laptop. More info coming soon!'
|
||
|
},
|
||
|
'secret': {
|
||
|
'minesweeper.txt': 'Type "play minesweeper" to start the game.\nAvoid the mines and clear the board!\n\nControls:\n- Enter coordinates in the format "row col" (e.g., "3 4")\n- Type "flag row col" to flag a potential mine\n- Type "unflag row col" to remove a flag\n- Type "quit" to exit game',
|
||
|
'minesweeper.js': '// Minesweeper game code is implemented in the main script'
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
// Update the prompt to show current directory
|
||
|
function updatePrompt() {
|
||
|
promptElement.textContent = `user@bash:${currentDirectory}$ `;
|
||
|
}
|
||
|
|
||
|
// Minesweeper game
|
||
|
let minesweeperState = {
|
||
|
board: [],
|
||
|
revealed: [],
|
||
|
flagged: [],
|
||
|
boardSize: 8,
|
||
|
mines: 10,
|
||
|
gameActive: false,
|
||
|
gameWon: false
|
||
|
};
|
||
|
|
||
|
function startMinesweeperGame() {
|
||
|
// Initialize the game state
|
||
|
minesweeperState = {
|
||
|
board: [],
|
||
|
revealed: [],
|
||
|
flagged: [],
|
||
|
boardSize: 8,
|
||
|
mines: 5,
|
||
|
gameActive: true,
|
||
|
gameWon: false
|
||
|
};
|
||
|
|
||
|
// Create empty board
|
||
|
for (let i = 0; i < minesweeperState.boardSize; i++) {
|
||
|
minesweeperState.board[i] = [];
|
||
|
minesweeperState.revealed[i] = [];
|
||
|
minesweeperState.flagged[i] = [];
|
||
|
for (let j = 0; j < minesweeperState.boardSize; j++) {
|
||
|
minesweeperState.board[i][j] = 0;
|
||
|
minesweeperState.revealed[i][j] = false;
|
||
|
minesweeperState.flagged[i][j] = false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Place mines randomly
|
||
|
let minesPlaced = 0;
|
||
|
while (minesPlaced < minesweeperState.mines) {
|
||
|
const row = Math.floor(Math.random() * minesweeperState.boardSize);
|
||
|
const col = Math.floor(Math.random() * minesweeperState.boardSize);
|
||
|
if (minesweeperState.board[row][col] !== -1) {
|
||
|
minesweeperState.board[row][col] = -1; // -1 represents a mine
|
||
|
minesPlaced++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Calculate numbers for cells adjacent to mines
|
||
|
for (let i = 0; i < minesweeperState.boardSize; i++) {
|
||
|
for (let j = 0; j < minesweeperState.boardSize; j++) {
|
||
|
if (minesweeperState.board[i][j] === -1) continue; // Skip mines
|
||
|
|
||
|
// Check all 8 neighboring cells
|
||
|
let mineCount = 0;
|
||
|
for (let di = -1; di <= 1; di++) {
|
||
|
for (let dj = -1; dj <= 1; dj++) {
|
||
|
if (di === 0 && dj === 0) continue; // Skip the cell itself
|
||
|
|
||
|
const ni = i + di;
|
||
|
const nj = j + dj;
|
||
|
|
||
|
// Check if neighbor is within bounds
|
||
|
if (ni >= 0 && ni < minesweeperState.boardSize &&
|
||
|
nj >= 0 && nj < minesweeperState.boardSize) {
|
||
|
// If neighbor is a mine, increment count
|
||
|
if (minesweeperState.board[ni][nj] === -1) {
|
||
|
mineCount++;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
minesweeperState.board[i][j] = mineCount;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
addOutput('=== MINESWEEPER ===');
|
||
|
addOutput(`Board Size: ${minesweeperState.boardSize}x${minesweeperState.boardSize} | Mines: ${minesweeperState.mines}`);
|
||
|
addOutput('Enter coordinates as "row col" (e.g., "3 4")');
|
||
|
addOutput('Flag a mine with "flag row col"');
|
||
|
addOutput('Unflag with "unflag row col"');
|
||
|
addOutput('Type "quit" to exit');
|
||
|
drawMinesweeperBoard();
|
||
|
}
|
||
|
|
||
|
function drawMinesweeperBoard() {
|
||
|
// Column headers
|
||
|
let boardOutput = ' ';
|
||
|
for (let j = 0; j < minesweeperState.boardSize; j++) {
|
||
|
boardOutput += `${j} `;
|
||
|
}
|
||
|
boardOutput += '\n';
|
||
|
|
||
|
// Top border
|
||
|
boardOutput += ' +';
|
||
|
for (let j = 0; j < minesweeperState.boardSize; j++) {
|
||
|
boardOutput += '--';
|
||
|
}
|
||
|
boardOutput += '+\n';
|
||
|
|
||
|
// Board with row headers
|
||
|
for (let i = 0; i < minesweeperState.boardSize; i++) {
|
||
|
boardOutput += ` ${i} |`;
|
||
|
for (let j = 0; j < minesweeperState.boardSize; j++) {
|
||
|
if (minesweeperState.flagged[i][j]) {
|
||
|
boardOutput += 'F ';
|
||
|
} else if (!minesweeperState.revealed[i][j]) {
|
||
|
boardOutput += '▓ ';
|
||
|
} else {
|
||
|
if (minesweeperState.board[i][j] === -1) {
|
||
|
boardOutput += '* '; // Mine
|
||
|
} else if (minesweeperState.board[i][j] === 0) {
|
||
|
boardOutput += ' '; // Empty cell
|
||
|
} else {
|
||
|
boardOutput += minesweeperState.board[i][j] + ' '; // Number
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
boardOutput += '|\n';
|
||
|
}
|
||
|
|
||
|
// Bottom border
|
||
|
boardOutput += ' +';
|
||
|
for (let j = 0; j < minesweeperState.boardSize; j++) {
|
||
|
boardOutput += '--';
|
||
|
}
|
||
|
boardOutput += '+';
|
||
|
|
||
|
addOutput(boardOutput);
|
||
|
|
||
|
// Display remaining mine count
|
||
|
const flaggedCount = minesweeperState.flagged.flat().filter(Boolean).length;
|
||
|
addOutput(`Mines remaining: ${Math.max(0, minesweeperState.mines - flaggedCount)}`);
|
||
|
}
|
||
|
|
||
|
function revealCell(row, col) {
|
||
|
// Check if coordinates are valid
|
||
|
if (row < 0 || row >= minesweeperState.boardSize || col < 0 || col >= minesweeperState.boardSize) {
|
||
|
addOutput('Invalid coordinates. Try again.');
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Can't reveal flagged cells
|
||
|
if (minesweeperState.flagged[row][col]) {
|
||
|
addOutput('Cannot reveal flagged cell. Unflag it first.');
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Cell already revealed
|
||
|
if (minesweeperState.revealed[row][col]) {
|
||
|
addOutput('Cell already revealed.');
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Reveal the cell
|
||
|
minesweeperState.revealed[row][col] = true;
|
||
|
|
||
|
// Check if it's a mine
|
||
|
if (minesweeperState.board[row][col] === -1) {
|
||
|
// Game over - reveal all mines
|
||
|
for (let i = 0; i < minesweeperState.boardSize; i++) {
|
||
|
for (let j = 0; j < minesweeperState.boardSize; j++) {
|
||
|
if (minesweeperState.board[i][j] === -1) {
|
||
|
minesweeperState.revealed[i][j] = true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
drawMinesweeperBoard();
|
||
|
addOutput('BOOM! You hit a mine. Game over!');
|
||
|
minesweeperState.gameActive = false;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// If cell is empty (0), reveal adjacent cells recursively
|
||
|
if (minesweeperState.board[row][col] === 0) {
|
||
|
revealAdjacentCells(row, col);
|
||
|
}
|
||
|
|
||
|
// Check win condition - all non-mine cells revealed
|
||
|
checkWinCondition();
|
||
|
|
||
|
// Redraw board
|
||
|
drawMinesweeperBoard();
|
||
|
}
|
||
|
|
||
|
function revealAdjacentCells(row, col) {
|
||
|
// Check all 8 adjacent cells
|
||
|
for (let di = -1; di <= 1; di++) {
|
||
|
for (let dj = -1; dj <= 1; dj++) {
|
||
|
if (di === 0 && dj === 0) continue; // Skip the cell itself
|
||
|
|
||
|
const ni = row + di;
|
||
|
const nj = col + dj;
|
||
|
|
||
|
// Check if neighbor is within bounds
|
||
|
if (ni >= 0 && ni < minesweeperState.boardSize &&
|
||
|
nj >= 0 && nj < minesweeperState.boardSize) {
|
||
|
// If not revealed and not flagged
|
||
|
if (!minesweeperState.revealed[ni][nj] && !minesweeperState.flagged[ni][nj]) {
|
||
|
minesweeperState.revealed[ni][nj] = true;
|
||
|
|
||
|
// If this is also an empty cell, continue recursion
|
||
|
if (minesweeperState.board[ni][nj] === 0) {
|
||
|
revealAdjacentCells(ni, nj);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function flagCell(row, col) {
|
||
|
// Check if coordinates are valid
|
||
|
if (row < 0 || row >= minesweeperState.boardSize || col < 0 || col >= minesweeperState.boardSize) {
|
||
|
addOutput('Invalid coordinates. Try again.');
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Can't flag revealed cells
|
||
|
if (minesweeperState.revealed[row][col]) {
|
||
|
addOutput('Cannot flag revealed cell.');
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Toggle flag
|
||
|
minesweeperState.flagged[row][col] = true;
|
||
|
|
||
|
// Check win condition
|
||
|
checkWinCondition();
|
||
|
|
||
|
// Redraw board
|
||
|
drawMinesweeperBoard();
|
||
|
}
|
||
|
|
||
|
function unflagCell(row, col) {
|
||
|
// Check if coordinates are valid
|
||
|
if (row < 0 || row >= minesweeperState.boardSize || col < 0 || col >= minesweeperState.boardSize) {
|
||
|
addOutput('Invalid coordinates. Try again.');
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Cell not flagged
|
||
|
if (!minesweeperState.flagged[row][col]) {
|
||
|
addOutput('Cell is not flagged.');
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Remove flag
|
||
|
minesweeperState.flagged[row][col] = false;
|
||
|
|
||
|
// Redraw board
|
||
|
drawMinesweeperBoard();
|
||
|
}
|
||
|
|
||
|
function checkWinCondition() {
|
||
|
// Win if all non-mine cells are revealed
|
||
|
for (let i = 0; i < minesweeperState.boardSize; i++) {
|
||
|
for (let j = 0; j < minesweeperState.boardSize; j++) {
|
||
|
// If cell is not a mine and not revealed, game not won yet
|
||
|
if (minesweeperState.board[i][j] !== -1 && !minesweeperState.revealed[i][j]) {
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Alternatively, win if all mines are correctly flagged
|
||
|
let correctlyFlagged = 0;
|
||
|
for (let i = 0; i < minesweeperState.boardSize; i++) {
|
||
|
for (let j = 0; j < minesweeperState.boardSize; j++) {
|
||
|
if (minesweeperState.board[i][j] === -1 && minesweeperState.flagged[i][j]) {
|
||
|
correctlyFlagged++;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (correctlyFlagged === minesweeperState.mines) {
|
||
|
// Game won
|
||
|
minesweeperState.gameActive = false;
|
||
|
minesweeperState.gameWon = true;
|
||
|
addOutput('Congratulations! You cleared all mines!');
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function processMinesweeperCommand(command) {
|
||
|
const parts = command.trim().split(' ');
|
||
|
|
||
|
if (command === 'quit') {
|
||
|
addOutput('Exiting Minesweeper game...');
|
||
|
minesweeperState.gameActive = false;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (parts[0] === 'flag' && parts.length === 3) {
|
||
|
const row = parseInt(parts[1]);
|
||
|
const col = parseInt(parts[2]);
|
||
|
if (isNaN(row) || isNaN(col)) {
|
||
|
addOutput('Invalid coordinates. Enter row and column as numbers.');
|
||
|
} else {
|
||
|
flagCell(row, col);
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (parts[0] === 'unflag' && parts.length === 3) {
|
||
|
const row = parseInt(parts[1]);
|
||
|
const col = parseInt(parts[2]);
|
||
|
if (isNaN(row) || isNaN(col)) {
|
||
|
addOutput('Invalid coordinates. Enter row and column as numbers.');
|
||
|
} else {
|
||
|
unflagCell(row, col);
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Default action is to reveal a cell
|
||
|
if (parts.length === 2) {
|
||
|
const row = parseInt(parts[0]);
|
||
|
const col = parseInt(parts[1]);
|
||
|
if (isNaN(row) || isNaN(col)) {
|
||
|
addOutput('Invalid coordinates. Enter row and column as numbers.');
|
||
|
} else {
|
||
|
revealCell(row, col);
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
addOutput('Invalid command. Enter coordinates as "row col", flag with "flag row col", or "quit" to exit.');
|
||
|
}
|
||
|
|
||
|
// Add a new output line to the terminal
|
||
|
function addOutput(text, isCommand = false) {
|
||
|
const outputLine = document.createElement('div');
|
||
|
outputLine.classList.add('output');
|
||
|
|
||
|
if (isCommand) {
|
||
|
outputLine.innerHTML = `<span id="prompt">${promptElement.textContent}</span>${text}`;
|
||
|
} else {
|
||
|
outputLine.textContent = text;
|
||
|
}
|
||
|
|
||
|
outputContainer.appendChild(outputLine);
|
||
|
terminal.scrollTop = terminal.scrollHeight;
|
||
|
}
|
||
|
|
||
|
// Get the current directory object from the file system
|
||
|
function getCurrentDirObject() {
|
||
|
const path = currentDirectory === '~' ? ['~'] : currentDirectory.split('/');
|
||
|
let currentObj = fileSystem;
|
||
|
|
||
|
for (const dir of path) {
|
||
|
if (dir === '') continue;
|
||
|
currentObj = currentObj[dir];
|
||
|
}
|
||
|
|
||
|
return currentObj;
|
||
|
}
|
||
|
|
||
|
// Process commands
|
||
|
function processCommand(command) {
|
||
|
if (!command) return;
|
||
|
|
||
|
addOutput(command, true);
|
||
|
commandHistory.push(command);
|
||
|
historyIndex = commandHistory.length;
|
||
|
|
||
|
const args = command.trim().split(' ');
|
||
|
const cmd = args[0].toLowerCase();
|
||
|
|
||
|
switch(cmd) {
|
||
|
case 'help':
|
||
|
addOutput('Available commands:');
|
||
|
addOutput(' help - Show this help message');
|
||
|
addOutput(' cat [file] - Display file content');
|
||
|
addOutput(' ls - List files and directories');
|
||
|
addOutput(' cd [directory] - Change directory');
|
||
|
addOutput(' pwd - Print working directory');
|
||
|
addOutput(' clear - Clear the terminal');
|
||
|
addOutput(' echo [text] - Display text');
|
||
|
addOutput(' secret - ???');
|
||
|
break;
|
||
|
|
||
|
case 'play':
|
||
|
if (args[1] === 'minesweeper') {
|
||
|
if (currentDirectory === '~/secret' || currentDirectory.includes('/secret')) {
|
||
|
startMinesweeperGame();
|
||
|
} else {
|
||
|
addOutput('bash: play minesweeper: command not found. Try exploring more directories...');
|
||
|
}
|
||
|
} else {
|
||
|
addOutput(`bash: play: command not found`);
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case 'cat':
|
||
|
if (args.length < 2) {
|
||
|
addOutput('Usage: cat [file]');
|
||
|
} else {
|
||
|
const fileName = args[1];
|
||
|
const currentDir = getCurrentDirObject();
|
||
|
|
||
|
if (currentDir[fileName] && typeof currentDir[fileName] === 'string') {
|
||
|
addOutput(currentDir[fileName]);
|
||
|
} else {
|
||
|
addOutput(`cat: ${fileName}: No such file or file is a directory`);
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case 'ls':
|
||
|
const currentDir = getCurrentDirObject();
|
||
|
const filesList = Object.keys(currentDir).join(' ');
|
||
|
addOutput(filesList);
|
||
|
break;
|
||
|
|
||
|
case 'cd':
|
||
|
if (args.length < 2) {
|
||
|
currentDirectory = '~';
|
||
|
} else {
|
||
|
const dirName = args[1];
|
||
|
const currentDir = getCurrentDirObject();
|
||
|
|
||
|
if (dirName === '..') {
|
||
|
if (currentDirectory !== '~') {
|
||
|
const pathParts = currentDirectory.split('/');
|
||
|
pathParts.pop();
|
||
|
currentDirectory = pathParts.join('/') || '~';
|
||
|
}
|
||
|
} else if (currentDir[dirName] && typeof currentDir[dirName] === 'object') {
|
||
|
if (currentDirectory === '~') {
|
||
|
currentDirectory = `~/${dirName}`;
|
||
|
} else {
|
||
|
currentDirectory = `${currentDirectory}/${dirName}`;
|
||
|
}
|
||
|
} else {
|
||
|
addOutput(`cd: ${dirName}: No such directory`);
|
||
|
}
|
||
|
}
|
||
|
updatePrompt();
|
||
|
break;
|
||
|
|
||
|
case 'pwd':
|
||
|
addOutput(currentDirectory);
|
||
|
break;
|
||
|
|
||
|
case 'clear':
|
||
|
outputContainer.innerHTML = '';
|
||
|
break;
|
||
|
|
||
|
case 'echo':
|
||
|
const text = args.slice(1).join(' ');
|
||
|
addOutput(text);
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
addOutput(`bash: ${cmd}: command not found`);
|
||
|
}
|
||
|
|
||
|
currentInput = '';
|
||
|
commandText.textContent = '';
|
||
|
}
|
||
|
|
||
|
// Handle key presses
|
||
|
document.addEventListener('keydown', function(e) {
|
||
|
// Do not process if the user is typing in a text input
|
||
|
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;
|
||
|
|
||
|
// Special handling for minesweeper game
|
||
|
if (minesweeperState.gameActive) {
|
||
|
switch(e.key) {
|
||
|
case 'Enter':
|
||
|
processMinesweeperCommand(currentInput);
|
||
|
currentInput = '';
|
||
|
commandText.textContent = '';
|
||
|
break;
|
||
|
|
||
|
case 'Backspace':
|
||
|
if (currentInput.length > 0) {
|
||
|
currentInput = currentInput.slice(0, -1);
|
||
|
commandText.textContent = currentInput;
|
||
|
}
|
||
|
e.preventDefault();
|
||
|
break;
|
||
|
|
||
|
case ' ':
|
||
|
currentInput += ' ';
|
||
|
commandText.textContent = currentInput;
|
||
|
e.preventDefault();
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
if (e.key.length === 1) {
|
||
|
currentInput += e.key;
|
||
|
commandText.textContent = currentInput;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Scroll to bottom for minesweeper game
|
||
|
terminal.scrollTop = terminal.scrollHeight;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Normal terminal handling
|
||
|
switch(e.key) {
|
||
|
case 'Enter':
|
||
|
processCommand(currentInput);
|
||
|
break;
|
||
|
|
||
|
case 'Backspace':
|
||
|
if (currentInput.length > 0) {
|
||
|
currentInput = currentInput.slice(0, -1);
|
||
|
commandText.textContent = currentInput;
|
||
|
}
|
||
|
e.preventDefault();
|
||
|
break;
|
||
|
|
||
|
case ' ':
|
||
|
currentInput += ' ';
|
||
|
commandText.textContent = currentInput;
|
||
|
e.preventDefault();
|
||
|
break;
|
||
|
|
||
|
case 'ArrowUp':
|
||
|
if (commandHistory.length > 0 && historyIndex > 0) {
|
||
|
historyIndex--;
|
||
|
currentInput = commandHistory[historyIndex];
|
||
|
commandText.textContent = currentInput;
|
||
|
}
|
||
|
e.preventDefault();
|
||
|
break;
|
||
|
|
||
|
case 'ArrowDown':
|
||
|
if (historyIndex < commandHistory.length - 1) {
|
||
|
historyIndex++;
|
||
|
currentInput = commandHistory[historyIndex];
|
||
|
commandText.textContent = currentInput;
|
||
|
} else {
|
||
|
historyIndex = commandHistory.length;
|
||
|
currentInput = '';
|
||
|
commandText.textContent = '';
|
||
|
}
|
||
|
e.preventDefault();
|
||
|
break;
|
||
|
|
||
|
case 'Tab':
|
||
|
// Simple tab completion
|
||
|
if (currentInput.startsWith('cat ') || currentInput.startsWith('cd ')) {
|
||
|
const parts = currentInput.split(' ');
|
||
|
const partialName = parts[1] || '';
|
||
|
const currentDir = getCurrentDirObject();
|
||
|
|
||
|
const matches = Object.keys(currentDir).filter(name =>
|
||
|
name.startsWith(partialName)
|
||
|
);
|
||
|
|
||
|
if (matches.length === 1) {
|
||
|
parts[1] = matches[0];
|
||
|
currentInput = parts.join(' ');
|
||
|
commandText.textContent = currentInput;
|
||
|
}
|
||
|
}
|
||
|
e.preventDefault();
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
if (e.key.length === 1) {
|
||
|
currentInput += e.key;
|
||
|
commandText.textContent = currentInput;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Scroll to bottom
|
||
|
terminal.scrollTop = terminal.scrollHeight;
|
||
|
});
|
||
|
|
||
|
// Click anywhere to focus
|
||
|
terminal.addEventListener('click', function() {
|
||
|
// Make cursor visible when clicking on terminal
|
||
|
cursor.style.display = 'inline-block';
|
||
|
});
|
||
|
|
||
|
// Initial setup
|
||
|
updatePrompt();
|
||
|
</script>
|
||
|
</body>
|
||
|
</html>
|