opencompute/index.html

758 lines
28 KiB
HTML
Raw Permalink Normal View History

2025-04-25 01:28:31 -04:00
<!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>