Spaces:
Sleeping
Sleeping
File size: 6,035 Bytes
4eb7f58 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 | """Custom web interface for the Minesweeper environment."""
MINESWEEPER_HTML = """<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Minesweeper</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #1a1a2e; color: #eee; min-height: 100vh; display: flex; flex-direction: column; align-items: center; padding: 20px; }
h1 { margin-bottom: 10px; font-size: 24px; }
.status-bar { display: flex; gap: 20px; margin-bottom: 15px; font-size: 14px; color: #aaa; }
.status-bar span { background: #16213e; padding: 6px 14px; border-radius: 6px; }
.status-bar .val { color: #e94560; font-weight: 600; }
#board { display: inline-grid; gap: 2px; background: #16213e; padding: 6px; border-radius: 8px; margin-bottom: 15px; }
.cell { width: 52px; height: 52px; display: flex; align-items: center; justify-content: center; font-size: 18px; font-weight: 700; border-radius: 4px; cursor: pointer; user-select: none; transition: background 0.1s; }
.cell.unrevealed { background: #0f3460; }
.cell.unrevealed:hover { background: #1a5276; }
.cell.revealed { background: #2c3e6d; cursor: default; }
.cell.flag { background: #e94560; }
.cell.mine { background: #c0392b; }
.cell.n1 { color: #3498db; } .cell.n2 { color: #2ecc71; } .cell.n3 { color: #e74c3c; }
.cell.n4 { color: #9b59b6; } .cell.n5 { color: #e67e22; } .cell.n6 { color: #1abc9c; }
.cell.n7 { color: #34495e; } .cell.n8 { color: #95a5a6; }
.controls { display: flex; gap: 10px; margin-bottom: 15px; align-items: center; }
.btn { padding: 10px 24px; border: none; border-radius: 6px; font-size: 14px; font-weight: 600; cursor: pointer; transition: opacity 0.15s; }
.btn:hover { opacity: 0.85; }
.btn-reset { background: #e94560; color: white; }
.btn-mode { background: #0f3460; color: white; min-width: 140px; }
.btn-mode.flagging { background: #e94560; }
#message { font-size: 18px; font-weight: 600; min-height: 28px; margin-bottom: 10px; }
.reward-display { font-size: 13px; color: #888; margin-bottom: 10px; }
.won { color: #2ecc71; }
.lost { color: #e74c3c; }
</style>
</head>
<body>
<h1>Minesweeper</h1>
<div id="message">Click Reset to start</div>
<div class="reward-display" id="reward"></div>
<div class="status-bar">
<span>Mines: <span class="val" id="mines">-</span></span>
<span>Flags: <span class="val" id="flags">-</span></span>
<span>Revealed: <span class="val" id="revealed">-</span></span>
</div>
<div id="board"></div>
<div class="controls">
<button class="btn btn-reset" id="reset-btn">Reset</button>
<button class="btn btn-mode" id="mode-btn">Mode: Reveal</button>
</div>
<script>
let mode = 'reveal';
let gameOver = false;
document.getElementById('reset-btn').addEventListener('click', doReset);
document.getElementById('mode-btn').addEventListener('click', () => {
mode = mode === 'reveal' ? 'flag' : 'reveal';
const btn = document.getElementById('mode-btn');
btn.textContent = 'Mode: ' + (mode === 'reveal' ? 'Reveal' : 'Flag');
btn.classList.toggle('flagging', mode === 'flag');
});
async function doReset() {
gameOver = false;
document.getElementById('message').textContent = 'Resetting...';
document.getElementById('message').className = '';
try {
const r = await fetch('/reset', { method: 'POST', headers: {'Content-Type':'application/json'} });
const data = await r.json();
renderBoard(data);
document.getElementById('message').textContent = 'Click a cell to play!';
} catch(e) { document.getElementById('message').textContent = 'Error: ' + e.message; }
}
async function doStep(row, col) {
if (gameOver) return;
try {
const r = await fetch('/step', {
method: 'POST',
headers: {'Content-Type':'application/json'},
body: JSON.stringify({ action: { row, col, action_type: mode } })
});
const data = await r.json();
renderBoard(data);
} catch(e) { document.getElementById('message').textContent = 'Error: ' + e.message; }
}
function renderBoard(data) {
const obs = data.observation || {};
const board = obs.board || [];
const rows = board.length;
const cols = rows > 0 ? board[0].length : 0;
document.getElementById('mines').textContent = obs.num_mines ?? '-';
document.getElementById('flags').textContent = obs.flags_placed ?? '-';
document.getElementById('revealed').textContent = obs.cells_revealed ?? '-';
if (data.reward !== null && data.reward !== undefined) {
document.getElementById('reward').textContent = 'Last reward: ' + data.reward;
}
const status = obs.game_status;
if (status === 'won') {
document.getElementById('message').textContent = 'You won!';
document.getElementById('message').className = 'won';
gameOver = true;
} else if (status === 'lost') {
document.getElementById('message').textContent = 'Game over - hit a mine!';
document.getElementById('message').className = 'lost';
gameOver = true;
}
const el = document.getElementById('board');
el.style.gridTemplateColumns = 'repeat(' + cols + ', 52px)';
el.innerHTML = '';
for (let r = 0; r < rows; r++) {
for (let c = 0; c < cols; c++) {
const v = board[r][c];
const cell = document.createElement('div');
cell.className = 'cell';
if (v === -1) {
cell.classList.add('unrevealed');
cell.addEventListener('click', () => doStep(r, c));
} else if (v === 'F') {
cell.classList.add('flag');
cell.textContent = '\\u{1f6a9}';
cell.addEventListener('click', () => doStep(r, c));
} else if (v === '*') {
cell.classList.add('mine');
cell.textContent = '\\u{1f4a3}';
} else {
cell.classList.add('revealed');
if (v > 0) { cell.textContent = v; cell.classList.add('n' + v); }
}
el.appendChild(cell);
}
}
}
// Auto-reset on load
doReset();
</script>
</body>
</html>"""
|