Mario Annotation
by kthornbloom
HTML
<div id="toolbar">
<strong>Tiles:</strong>
<button class="tile-btn active" data-type="ground">Ground</button>
<button class="tile-btn" data-type="enemy1">Enemy 1</button>
<button class="tile-btn" data-type="enemy2">Enemy 2</button>
<button class="tile-btn" data-type="enemy3">Enemy 3</button>
<button class="tile-btn" data-type="powerup">Powerup</button>
<button class="tile-btn" data-type="goal">Level End</button>
<button class="tile-btn" data-type="tubeTop">Tube Top</button>
<button class="tile-btn" data-type="tubeBody">Tube Body</button>
<button class="tile-btn" data-type="empty">Empty</button>
<span style="margin-left: 16px;">
Cols:
<input type="number" id="colsInput" min="5" max="120" value="40" style="width: 60px;">
<button id="applyColsBtn">Set</button>
</span>
<button id="copyLevelBtn" style="margin-left: 16px;">Copy level as text</button>
<span id="copyStatus" style="margin-left: 8px; font-size: 0.9em;"></span>
</div>
<br>
<div id="levelGrid"></div>
CSS
body {
font-family: monospace;
}
#toolbar {
margin-bottom: 10px;
}
.tile-btn {
cursor: pointer;
margin-right: 4px;
padding: 4px 8px;
border-radius: 4px;
border: 1px solid #ccc;
background: #f5f5f5;
}
.tile-btn.active {
outline: 2px solid #333;
background: #e0e0e0;
}
#levelGrid {
display: inline-block;
background: #fafafa;
}
.row {
white-space: nowrap;
margin: 0;
padding: 0;
height: 30px;
}
/* Bigger, visually styled cells */
.cell {
display: inline-block;
width: 30px;
height: 30px;
box-sizing: border-box;
border: 1px solid #ddd;
vertical-align: mirgb(232, 232, 232);
}
.cell:hover {
outline: 2px solid #999;
outline-offset: -2px;
}
/* Tile types (display only) */
.cell-empty {
background: #ffffff;
}
.cell-ground {
background: #8b4513; /* brown */
}
.cell-enemy1 {
background: #e74c3c; /* red */
}
.cell-enemy2 {
background: #9b59b6; /* purple */
}
.cell-enemy3 {
background: #e67e22; /* orange */
}
.cell-powerup {
background: #f1c40f; /* yellow */
}
.cell-goal {
background: #3498db; /* blue */
}
/* Tubes */
.cell-tubeTop {
background: #27ae60;
border-bottom: 3px solid #145a32;
}
.cell-tubeBody {
background: #2ecc71;
}
JavaScript
// ----- CONFIG -----
const ROWS = 10; // fixed height
let COLS = 40; // adjustable by input
// Mapping from logical tile type -> export character & CSS class
const tileDefs = {
ground: { char: "▦", class: "cell-ground" },
enemy1: { char: "▢", class: "cell-enemy1" },
enemy2: { char: "▤", class: "cell-enemy2" },
enemy3: { char: "▥", class: "cell-enemy3" },
powerup: { char: "▣", class: "cell-powerup" },
goal: { char: "⚿", class: "cell-goal" },
tubeTop: { char: "╦", class: "cell-tubeTop" },
tubeBody: { char: "║", class: "cell-tubeBody" },
empty: { char: " ", class: "cell-empty" }
};
const allTileClasses = Object.values(tileDefs).map(def => def.class);
// ----- STATE -----
let grid = Array.from({ length: ROWS }, () =>
Array(COLS).fill("empty")
);
const levelGridEl = document.getElementById("levelGrid");
const tileButtons = document.querySelectorAll(".tile-btn");
const copyBtn = document.getElementById("copyLevelBtn");
const copyStatusEl = document.getElementById("copyStatus");
const colsInput = document.getElementById("colsInput");
const applyColsBtn = document.getElementById("applyColsBtn");
let activeType = "ground"; // default tool
let isMouseDown = false;
const cellEls = []; // [row][col] references if needed
// ----- HELPERS -----
function applyCellStyle(cell, type) {
cell.classList.remove(...allTileClasses);
cell.classList.add(tileDefs[type].class);
}
// Build / rebuild the grid DOM from the current grid state
function createGrid() {
levelGridEl.innerHTML = "";
cellEls.length = 0;
for (let r = 0; r < ROWS; r++) {
const rowDiv = document.createElement("div");
rowDiv.className = "row";
const rowCells = [];
for (let c = 0; c < COLS; c++) {
const cell = document.createElement("span");
cell.className = "cell";
cell.dataset.row = r;
cell.dataset.col = c;
const type = grid[r][c];
...