Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Redstone Sandbox 2.0</title> | |
<style> | |
body { | |
margin: 0; | |
background: #1a1a1a; | |
font-family: 'Segoe UI', sans-serif; | |
overflow: hidden; | |
color: #fff; | |
} | |
#gameContainer { | |
position: relative; | |
width: 100vw; | |
height: 100vh; | |
background: radial-gradient(circle at center, #2a2a2a 0%, #1a1a1a 100%); | |
} | |
#grid { | |
position: absolute; | |
top: 50%; | |
left: 50%; | |
transform: translate(-50%, -50%); | |
background: rgba(0,0,0,0.3); | |
padding: 2px; | |
border-radius: 8px; | |
box-shadow: 0 0 50px rgba(0,0,0,0.5); | |
} | |
.grid-overlay { | |
position: absolute; | |
top: 0; | |
left: 0; | |
width: 100%; | |
height: 100%; | |
background: | |
linear-gradient(rgba(255,255,255,0.03) 1px, transparent 1px), | |
linear-gradient(90deg, rgba(255,255,255,0.03) 1px, transparent 1px); | |
background-size: 40px 40px; | |
pointer-events: none; | |
border-radius: 8px; | |
} | |
.cell { | |
width: 40px; | |
height: 40px; | |
border: 1px solid #2a2a2a; | |
position: absolute; | |
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); | |
border-radius: 6px; | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
font-size: 10px; | |
cursor: pointer; | |
box-shadow: inset 0 0 10px rgba(0,0,0,0.2); | |
} | |
.cell:hover { | |
box-shadow: inset 0 0 15px rgba(255,255,255,0.1); | |
} | |
.cell[data-power] { | |
transform: scale(1.05); | |
z-index: 1; | |
} | |
.toolbar { | |
position: fixed; | |
top: 20px; | |
left: 50%; | |
transform: translateX(-50%); | |
background: rgba(20,20,20,0.95); | |
padding: 15px; | |
border-radius: 15px; | |
display: flex; | |
gap: 12px; | |
box-shadow: 0 5px 25px rgba(0,0,0,0.3); | |
z-index: 100; | |
backdrop-filter: blur(10px); | |
border: 1px solid rgba(255,255,255,0.1); | |
} | |
.tool { | |
width: 50px; | |
height: 50px; | |
border: 2px solid #444; | |
border-radius: 10px; | |
cursor: pointer; | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
font-size: 22px; | |
background: rgba(255,255,255,0.05); | |
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); | |
position: relative; | |
} | |
.tool:hover { | |
transform: translateY(-2px); | |
background: rgba(255,255,255,0.1); | |
box-shadow: 0 0 20px rgba(255,85,85,0.3); | |
} | |
.tool.active { | |
border-color: #f55; | |
background: rgba(255,85,85,0.2); | |
transform: scale(1.1); | |
} | |
.tool-tooltip { | |
position: absolute; | |
bottom: -30px; | |
left: 50%; | |
transform: translateX(-50%); | |
background: rgba(0,0,0,0.8); | |
padding: 5px 10px; | |
border-radius: 4px; | |
font-size: 12px; | |
opacity: 0; | |
transition: 0.3s; | |
pointer-events: none; | |
white-space: nowrap; | |
} | |
.tool:hover .tool-tooltip { | |
opacity: 1; | |
bottom: -25px; | |
} | |
.button { | |
background: #f55; | |
border: none; | |
border-radius: 8px; | |
color: white; | |
cursor: pointer; | |
font-size: 16px; | |
padding: 10px 20px; | |
transition: 0.3s; | |
} | |
.button:hover { | |
transform: scale(1.1); | |
} | |
.button.pause { | |
background: #55f; | |
} | |
.button.pause.active { | |
background: #f55; | |
} | |
.save-btn, .load-btn { | |
background: #444; | |
margin: 0 5px; | |
} | |
.save-btn:hover, .load-btn:hover { | |
background: #555; | |
} | |
.wire { | |
background: #300; | |
transition: background-color 0.3s; | |
} | |
.wire.powered { | |
background: #f00; | |
} | |
.wire[data-power="1"] { background: #400; } | |
.wire[data-power="2"] { background: #500; } | |
.wire[data-power="3"] { background: #600; } | |
.wire[data-power="4"] { background: #700; } | |
.wire[data-power="5"] { background: #800; } | |
.wire[data-power="6"] { background: #900; } | |
.wire[data-power="7"] { background: #a00; } | |
.wire[data-power="8"] { background: #b00; } | |
.wire[data-power="9"] { background: #c00; } | |
.wire[data-power="10"] { background: #d00; } | |
.wire[data-power="11"] { background: #e00; } | |
.wire[data-power="12"] { background: #f00; } | |
.wire[data-power="13"] { background: #f11; } | |
.wire[data-power="14"] { background: #f22; } | |
.wire[data-power="15"] { background: #f33; } | |
.torch { | |
background: #320; | |
box-shadow: 0 0 10px #320; | |
} | |
.torch.powered { | |
background: #fa0; | |
box-shadow: 0 0 20px #fa0; | |
animation: torch-glow 1s infinite alternate; | |
} | |
.block { | |
background: #444; | |
box-shadow: inset 0 0 10px rgba(0,0,0,0.5); | |
} | |
.spreader { | |
background: #030; | |
transition: all 0.3s; | |
} | |
.spreader.powered { | |
background: #0f0; | |
box-shadow: 0 0 20px #0f0; | |
animation: spreader-pulse 1s infinite; | |
} | |
.lamp { | |
background: #330; | |
transition: all 0.3s; | |
} | |
.lamp.powered { | |
background: #ff0; | |
box-shadow: 0 0 30px #ff0; | |
animation: lamp-shine 2s infinite alternate; | |
} | |
.timer { | |
background: #303; | |
transition: all 0.3s; | |
} | |
.timer.powered { | |
background: #f0f; | |
box-shadow: 0 0 20px #f0f; | |
} | |
.power-level { | |
position: absolute; | |
top: 2px; | |
right: 2px; | |
font-size: 10px; | |
padding: 2px 4px; | |
background: rgba(0,0,0,0.5); | |
border-radius: 4px; | |
pointer-events: none; | |
} | |
.timer-value { | |
position: absolute; | |
bottom: 2px; | |
left: 2px; | |
font-size: 10px; | |
color: rgba(255,255,255,0.8); | |
} | |
@keyframes torch-glow { | |
from { box-shadow: 0 0 10px #fa0; } | |
to { box-shadow: 0 0 30px #fa0; } | |
} | |
@keyframes spreader-pulse { | |
0% { transform: scale(1); } | |
50% { transform: scale(1.1); } | |
100% { transform: scale(1); } | |
} | |
@keyframes lamp-shine { | |
from { box-shadow: 0 0 20px #ff0; } | |
to { box-shadow: 0 0 40px #ff0; } | |
} | |
@keyframes power-pulse { | |
0% { box-shadow: 0 0 10px currentColor; } | |
50% { box-shadow: 0 0 20px currentColor; } | |
100% { box-shadow: 0 0 10px currentColor; } | |
} | |
.powered { | |
animation: power-pulse 2s infinite; | |
} | |
</style> | |
</head> | |
<body> | |
<div id="gameContainer"> | |
<div class="toolbar"> | |
<div class="tool active" data-tool="wire" data-name="Wire">⚡ | |
<div class="tool-tooltip">Wire</div> | |
</div> | |
<div class="tool" data-tool="torch" data-name="Torch">🔥 | |
<div class="tool-tooltip">Torch</div> | |
</div> | |
<div class="tool" data-tool="block" data-name="Block">⬛ | |
<div class="tool-tooltip">Block</div> | |
</div> | |
<div class="tool" data-tool="spreader" data-name="Spreader">➕ | |
<div class="tool-tooltip">Spreader</div> | |
</div> | |
<div class="tool" data-tool="lamp" data-name="Lamp">💡 | |
<div class="tool-tooltip">Lamp</div> | |
</div> | |
<div class="tool" data-tool="timer" data-name="Timer">⏲️ | |
<div class="tool-tooltip">Timer</div> | |
</div> | |
<button class="button reset">Reset</button> | |
<button class="button pause">Pause</button> | |
<button class="button save-btn">Save</button> | |
<button class="button load-btn">Load</button> | |
</div> | |
<div id="grid"> | |
<div class="grid-overlay"></div> | |
</div> | |
</div> | |
<script> | |
class RedstoneSandbox { | |
constructor() { | |
this.gridSize = 20; | |
this.cellSize = 40; | |
this.grid = {}; | |
this.selectedTool = 'wire'; | |
this.timers = new Set(); | |
this.isPaused = false; | |
this.gameLoopInterval = null; | |
this.undoStack = []; | |
this.redoStack = []; | |
this.init(); | |
} | |
init() { | |
this.setupGrid(); | |
this.setupTools(); | |
this.setupButtons(); | |
this.bindEvents(); | |
this.addTooltips(); | |
this.setupSaveLoad(); | |
this.setupKeyboardShortcuts(); | |
this.startGameLoop(); | |
} | |
setupGrid() { | |
const grid = document.getElementById('grid'); | |
grid.style.width = `${this.gridSize * this.cellSize + 2}px`; | |
grid.style.height = `${this.gridSize * this.cellSize + 2}px`; | |
for(let y = 0; y < this.gridSize; y++) { | |
for(let x = 0; x < this.gridSize; x++) { | |
const cell = document.createElement('div'); | |
cell.className = 'cell'; | |
cell.style.left = `${x * this.cellSize}px`; | |
cell.style.top = `${y * this.cellSize}px`; | |
cell.dataset.x = x; | |
cell.dataset.y = y; | |
const powerLevel = document.createElement('div'); | |
powerLevel.className = 'power-level'; | |
const timerValue = document.createElement('div'); | |
timerValue.className = 'timer-value'; | |
cell.appendChild(powerLevel); | |
cell.appendChild(timerValue); | |
grid.appendChild(cell); | |
this.grid[`${x},${y}`] = { | |
element: cell, | |
powerDisplay: powerLevel, | |
timerDisplay: timerValue, | |
type: null, | |
powered: false, | |
powerLevel: 0, | |
timerInterval: 1, | |
lastUpdate: Date.now() | |
}; | |
} | |
} | |
} | |
addTooltips() { | |
document.querySelectorAll('.tool').forEach(tool => { | |
const tooltip = document.createElement('div'); | |
tooltip.className = 'tool-tooltip'; | |
tooltip.textContent = tool.dataset.name; | |
tool.appendChild(tooltip); | |
}); | |
} | |
setupSaveLoad() { | |
document.querySelector('.save-btn').addEventListener('click', () => { | |
const saveData = {}; | |
Object.entries(this.grid).forEach(([coord, cell]) => { | |
if(cell.type) { | |
saveData[coord] = { | |
type: cell.type, | |
powered: cell.powered, | |
powerLevel: cell.powerLevel, | |
timerInterval: cell.timerInterval | |
}; | |
} | |
}); | |
localStorage.setItem('redstoneSave', JSON.stringify(saveData)); | |
this.showNotification('Design saved successfully!'); | |
}); | |
document.querySelector('.load-btn').addEventListener('click', () => { | |
const saveData = JSON.parse(localStorage.getItem('redstoneSave') || '{}'); | |
this.reset(); | |
Object.entries(saveData).forEach(([coord, data]) => { | |
const [x,y] = coord.split(','); | |
const cell = this.grid[coord]; | |
cell.type = data.type; | |
cell.powered = data.powered; | |
cell.powerLevel = data.powerLevel; | |
cell.timerInterval = data.timerInterval; | |
cell.element.className = `cell ${data.type}`; | |
if(data.powered) cell.element.classList.add('powered'); | |
if(data.type === 'timer') { | |
this.timers.add(coord); | |
cell.timerDisplay.textContent = `${data.timerInterval}s`; | |
} | |
}); | |
this.updatePower(); | |
this.showNotification('Design loaded successfully!'); | |
}); | |
} | |
showNotification(message) { | |
const notification = document.createElement('div'); | |
notification.style.cssText = ` | |
position: fixed; | |
bottom: 20px; | |
left: 50%; | |
transform: translateX(-50%); | |
background: rgba(0,0,0,0.8); | |
color: white; | |
padding: 10px 20px; | |
border-radius: 5px; | |
z-index: 1000; | |
animation: fadeInOut 2s forwards; | |
`; | |
notification.textContent = message; | |
document.body.appendChild(notification); | |
setTimeout(() => notification.remove(), 2000); | |
} | |
setupKeyboardShortcuts() { | |
document.addEventListener('keydown', (e) => { | |
if(e.ctrlKey && e.key === 'z') { | |
this.undo(); | |
} else if(e.ctrlKey && e.key === 'y') { | |
this.redo(); | |
} else if(e.key === ' ') { | |
this.isPaused = !this.isPaused; | |
document.querySelector('.pause').classList.toggle('active'); | |
} | |
}); | |
} | |
saveState() { | |
const state = {}; | |
Object.entries(this.grid).forEach(([coord, cell]) => { | |
if(cell.type) { | |
state[coord] = { | |
type: cell.type, | |
powered: cell.powered, | |
powerLevel: cell.powerLevel, | |
timerInterval: cell.timerInterval | |
}; | |
} | |
}); | |
this.undoStack.push(state); | |
this.redoStack = []; | |
if(this.undoStack.length > 50) this.undoStack.shift(); | |
} | |
undo() { | |
if(this.undoStack.length === 0) return; | |
const currentState = {}; | |
Object.entries(this.grid).forEach(([coord, cell]) => { | |
if(cell.type) { | |
currentState[coord] = { | |
type: cell.type, | |
powered: cell.powered, | |
powerLevel: cell.powerLevel, | |
timerInterval: cell.timerInterval | |
}; | |
} | |
}); | |
this.redoStack.push(currentState); | |
const previousState = this.undoStack.pop(); | |
this.reset(); | |
this.loadState(previousState); | |
} | |
redo() { | |
if(this.redoStack.length === 0) return; | |
const state = this.redoStack.pop(); | |
this.reset(); | |
this.loadState(state); | |
this.undoStack.push(state); | |
} | |
loadState(state) { | |
Object.entries(state).forEach(([coord, data]) => { | |
const cell = this.grid[coord]; | |
cell.type = data.type; | |
cell.powered = data.powered; | |
cell.powerLevel = data.powerLevel; | |
cell.timerInterval = data.timerInterval; | |
cell.element.className = `cell ${data.type}`; | |
if(data.powered) cell.element.classList.add('powered'); | |
if(data.type === 'timer') { | |
this.timers.add(coord); | |
cell.timerDisplay.textContent = `${data.timerInterval}s`; | |
} | |
}); | |
this.updatePower(); | |
} | |
setupTools() { | |
const tools = document.querySelectorAll('.tool'); | |
tools.forEach(tool => { | |
tool.addEventListener('click', () => { | |
tools.forEach(t => t.classList.remove('active')); | |
tool.classList.add('active'); | |
this.selectedTool = tool.dataset.tool; | |
}); | |
}); | |
} | |
setupButtons() { | |
document.querySelector('.reset').addEventListener('click', () => { | |
this.reset(); | |
}); | |
const pauseBtn = document.querySelector('.pause'); | |
pauseBtn.addEventListener('click', () => { | |
this.isPaused = !this.isPaused; | |
pauseBtn.classList.toggle('active'); | |
pauseBtn.textContent = this.isPaused ? 'Resume' : 'Pause'; | |
}); | |
} | |
reset() { | |
Object.values(this.grid).forEach(cell => { | |
cell.element.className = 'cell'; | |
cell.type = null; | |
cell.powered = false; | |
cell.powerLevel = 0; | |
cell.powerDisplay.textContent = ''; | |
cell.timerDisplay.textContent = ''; | |
}); | |
this.timers.clear(); | |
} | |
bindEvents() { | |
const grid = document.getElementById('grid'); | |
grid.addEventListener('contextmenu', e => { | |
e.preventDefault(); | |
if(e.target.classList.contains('cell')) { | |
const x = parseInt(e.target.dataset.x); | |
const y = parseInt(e.target.dataset.y); | |
this.deleteComponent(x, y); | |
this.saveState(); | |
} | |
}); | |
grid.addEventListener('click', e => { | |
if(e.target.classList.contains('cell')) { | |
const x = parseInt(e.target.dataset.x); | |
const y = parseInt(e.target.dataset.y); | |
const cell = this.grid[`${x},${y}`]; | |
if(cell.type === 'torch') { | |
this.toggleTorch(cell); | |
} else if(cell.type === 'timer') { | |
this.adjustTimer(cell); | |
} else { | |
this.placeComponent(x, y); | |
} | |
this.saveState(); | |
} | |
}); | |
let isDragging = false; | |
grid.addEventListener('mousedown', () => isDragging = false); | |
grid.addEventListener('mousemove', () => isDragging = true); | |
grid.addEventListener('mouseup', e => { | |
if(!isDragging && e.target.classList.contains('cell')) { | |
const x = parseInt(e.target.dataset.x); | |
const y = parseInt(e.target.dataset.y); | |
this.placeComponent(x, y); | |
this.saveState(); | |
} | |
}); | |
} | |
deleteComponent(x, y) { | |
const cell = this.grid[`${x},${y}`]; | |
if(cell.type === 'timer') { | |
this.timers.delete(`${x},${y}`); | |
} | |
cell.element.className = 'cell'; | |
cell.type = null; | |
cell.powered = false; | |
cell.powerLevel = 0; | |
cell.powerDisplay.textContent = ''; | |
cell.timerDisplay.textContent = ''; | |
this.updatePower(); | |
} | |
toggleTorch(cell) { | |
cell.powered = !cell.powered; | |
cell.powerLevel = cell.powered ? 15 : 0; | |
cell.element.classList.toggle('powered'); | |
this.updatePower(); | |
} | |
adjustTimer(cell) { | |
cell.timerInterval = (cell.timerInterval % 15) + 1; | |
cell.timerDisplay.textContent = `${cell.timerInterval}s`; | |
} | |
placeComponent(x, y) { | |
const cell = this.grid[`${x},${y}`]; | |
if(cell.type === this.selectedTool) return; | |
cell.element.className = 'cell'; | |
cell.element.classList.add(this.selectedTool); | |
cell.type = this.selectedTool; | |
if(this.selectedTool === 'torch') { | |
cell.powered = true; | |
cell.powerLevel = 15; | |
cell.element.classList.add('powered'); | |
} else if(this.selectedTool === 'spreader') { | |
cell.powered = false; | |
cell.powerLevel = 0; | |
} | |
if(this.selectedTool === 'timer') { | |
cell.timerInterval = 1; | |
cell.timerDisplay.textContent = '1s'; | |
this.timers.add(`${x},${y}`); | |
} | |
this.updatePower(); | |
} | |
updatePower() { | |
if(this.isPaused) return; | |
Object.values(this.grid).forEach(cell => { | |
if(cell.type && !['torch', 'timer'].includes(cell.type)) { | |
cell.powered = false; | |
cell.powerLevel = 0; | |
cell.element.classList.remove('powered'); | |
} | |
}); | |
for(let i = 15; i > 0; i--) { | |
Object.entries(this.grid).forEach(([coord, cell]) => { | |
if(!cell.powered) return; | |
const [x, y] = coord.split(',').map(Number); | |
if(cell.type === 'wire') { | |
this.powerNearby(x, y, cell.powerLevel - 1); | |
} else if(['torch', 'timer'].includes(cell.type)) { | |
this.powerNearby(x, y, 15); | |
} else if(cell.type === 'spreader' && cell.powered) { | |
this.spreadPower(x, y); | |
} | |
}); | |
} | |
Object.values(this.grid).forEach(cell => { | |
if(cell.powerLevel > 0) { | |
cell.element.classList.add('powered'); | |
cell.element.dataset.power = cell.powerLevel; | |
cell.powerDisplay.textContent = cell.powerLevel; | |
} else { | |
cell.element.classList.remove('powered'); | |
delete cell.element.dataset.power; | |
cell.powerDisplay.textContent = ''; | |
} | |
}); | |
} | |
powerNearby(x, y, power) { | |
if(power <= 0) return; | |
[[x+1,y], [x-1,y], [x,y+1], [x,y-1]].forEach(([nx, ny]) => { | |
const neighbor = this.grid[`${nx},${ny}`]; | |
if(neighbor && neighbor.type && neighbor.type !== 'block' && neighbor.type !== 'timer' && neighbor.type !== 'torch' && power > neighbor.powerLevel) { | |
neighbor.powered = true; | |
neighbor.powerLevel = power; | |
} | |
}); | |
} | |
spreadPower(x, y) { | |
[[x+1,y], [x-1,y], [x,y+1], [x,y-1]].forEach(([nx, ny]) => { | |
const neighbor = this.grid[`${nx},${ny}`]; | |
if(neighbor && neighbor.type && neighbor.type !== 'block' && neighbor.type !== 'timer' && neighbor.type !== 'torch' && neighbor.powerLevel === 0) { | |
neighbor.powered = true; | |
neighbor.powerLevel = 15; | |
} | |
}); | |
} | |
startGameLoop() { | |
this.gameLoopInterval = setInterval(() => { | |
if(this.isPaused) return; | |
const now = Date.now(); | |
this.timers.forEach(coord => { | |
const cell = this.grid[coord]; | |
if(now - cell.lastUpdate >= cell.timerInterval * 1000) { | |
cell.powered = !cell.powered; | |
cell.powerLevel = cell.powered ? 15 : 0; | |
cell.lastUpdate = now; | |
this.updatePower(); | |
} | |
}); | |
}, 50); | |
} | |
} | |
new RedstoneSandbox(); | |
</script> | |
</body> | |
</html> |