Redstone-Sandbox / index.html
vip3's picture
Update index.html
8bc17d9 verified
raw
history blame
26.3 kB
<!DOCTYPE html>
<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>