Files
TinyGolfSolver/tiny_solver.html

975 lines
22 KiB
HTML

<!DOCTYPE html>
<html lang="es">
<head>
<!--
Conversió de tiny_solver.tcl a HTML+JS amb Copilot
30/05/2026
-->
<meta charset="UTF-8">
<title>Solver TCL-like + UI Mejorada</title>
<style>
body { margin:0; padding:0; background:#111; color:#eee; font-family:monospace; }
#left { flex:1; overflow:auto; padding:10px; border-right:2px solid #333; }
#right { flex:1; overflow:hidden; }
#map { font-size:16px; line-height:16px; }
.row { height:16px; }
.tile { display:inline-block; width:16px; height:16px; text-align:center; }
.t-empty { background:#222; }
.t-wall { background:#888; }
.t-hole { background:#004; }
.t-water { background:#006; }
.t-sand { background:#663; }
.t-ball { background:#0a0; }
.t-rock { background:#a50; }
.t-door { background:#550; }
.t-switch{ background:#770; }
.t-special{ background:#a0a; }
button { margin:3px; padding:5px 10px; }
.hl {
color: yellow;
font-weight: bold;
text-shadow: 0 0 5px #ff0;
}
</style>
</head>
<body>
<div style="display:flex; height:100vh; width:100vw; overflow:hidden;">
<!-- ========================= -->
<!-- COLUMNA IZQUIERDA -->
<!-- ========================= -->
<div id="left">
<!-- ========================= -->
<!-- PRIMERA FILA: NIVELES -->
<!-- ========================= -->
<div>
<button onclick="prevLevel()">⬅ Nivel anterior</button>
<label for="levelSelect">Nivel:</label>
<select id="levelSelect"></select>
<button onclick="nextLevel()">Siguiente nivel ➡</button>
<button onclick="loadAndSolve()">Cargar y resolver</button>
</div>
<!-- ========================= -->
<!-- SEGUNDA FILA: ANIMACIÓN -->
<!-- ========================= -->
<div style="margin-top:10px;">
<button onclick="play()"></button>
<button onclick="pause()"></button>
<button onclick="stepBackward()"></button>
<button onclick="stepForward()"></button>
</div>
<!-- ========================= -->
<!-- MOVIMIENTOS Y PASO -->
<!-- ========================= -->
<div style="margin-top:10px;">
<div>Movimientos: <span id="moves"></span></div>
<div>Paso: <span id="stepInfo"></span></div>
</div>
<!-- ========================= -->
<!-- MAPA -->
<!-- ========================= -->
<div id="map" style="margin-top:10px;"></div>
</div>
<!-- ========================= -->
<!-- COLUMNA DERECHA -->
<!-- ========================= -->
<div id="right">
<iframe
src="https://www.lexaloffle.com/bbs/?pid=79680"
style="border:none; width:100%; height:100%; background:#000;">
</iframe>
</div>
</div>
<script>
/* ============================================================
=============== AQUÍ VA TODO EL SOLVER B1 =================
============================================================ */
/* ------------------ MAPAS (los 32 niveles) ------------------ */
const levels = {
"hole01": [
"------",
"- -",
"- -- -",
"- -- -",
"- -- -",
"- -- -",
"-B--H-",
"------"
],
"hole02": [
" ----- ",
"--- --",
"-H -",
"--- -",
" -- --",
" -B-- ",
" --- "
],
"hole03": [
" ---- ",
"-- ----",
"- -",
"- B -",
"- H -",
"- -",
"-- --",
" ------ "
],
"hole04": [
"---- ",
"-- ------",
"- -",
"- ---- -",
"- - -",
"-- -H - -",
" - - - -",
" - ---- -",
" - B-",
" --------"
],
"hole05": [
"------",
"-B--H-",
"- H-",
"- -- -",
"- -- -",
"- -",
"-B -",
"------"
],
"hole06": [
"..----",
".- H-",
"- H-",
"- ---",
"- -",
"--- -",
"- -",
"- ---",
"- -",
"-B B-",
"------"
],
"hole07": [
"-----..",
"-H ---",
"--- H-",
"..- ---",
"..- -..",
"..- -..",
"..- -..",
".-- -..",
".-BB-..",
".----.."
],
"hole08": [
"--------",
"-BH B-",
"- H-",
"- -",
"- -",
"-H -",
"-B HB-",
"--------"
],
"hole09": [
"-----.",
"- H-.",
"- H-.",
"- H-.",
"- ---.",
"- --..",
"- -..",
"- -.",
"-BBB -",
"------"
],
"hole10": [
"..---...",
"..-B-...",
"---B----",
"-H-B--W-",
"- - -- -",
"- - H- -",
"- - -- -",
"- -",
"------ -",
"-H -",
"--------"
],
"hole11": [
".---....",
".-H-....",
".- -----",
"--B -",
"-H -",
"-- B-",
".- ---",
".- BH-.",
".------."
],
"hole12": [
"..----..",
"..-H -..",
"--- ---",
"-B -",
"-B S -",
"--- ---",
"..-H -..",
"..----.."
],
"hole13": [
"----..----",
"-H-----H--",
"-H BBBB H-",
"-- WWWW --",
".- WWWW -.",
".- WWWW -.",
"-- WWWW --",
"-H BBBB H-",
"-H-----H--",
"----..----"
],
"hole14": [
"------..",
"-H --.",
"- --",
"- WBBW-",
"- WWBBW-",
"- WWWWW-",
"-HWW----",
"-HW--...",
"----...."
],
"hole15": [
"..------..",
"---H ---",
"-H-- -H-",
"-H-S -",
"- - S -",
"- S -",
"- S -",
"--- ---",
"..-BBBB-..",
"..------.."
],
"hole16": [
"---- ----",
"- H- -BW-",
"-WB- -H -",
"---- ----",
" ",
"---- ----",
"- W- -H -",
"-BH- -WB-",
"---- ----"
],
"hole17": [
"--------- ",
"- - ",
"- - ",
"- WBBW - ",
"- WWWW --",
"- WWWW H-",
"- --",
"- -- ",
"-H------ ",
"--- "
],
"hole18": [
" --- ",
" ---H-- ",
" ----B - ",
"-- - ",
"-H S -- ",
"-- S B- ",
" -B S --",
" -- S H-",
" - --",
" - B---- ",
" --H--- ",
" --- "
],
"hole19": [
"--------",
"-H H-",
"-H H-",
"-- BB --",
" -B B- ",
" -B B- ",
"-- BB --",
"-H H-",
"-H H-",
"--------"
],
"hole20": [
"------- ",
"- WSW - ",
"- S S--------",
"-S S B--WSBWH-",
"- W --S SW -",
"-HWSSW-- SWWS-",
"--------S SW -",
" -WS -",
" -------"
],
"hole21": [
"--------",
"- H-",
"-- -- --",
" - -- - ",
" -R R- ",
" - -- - ",
" -B-- - ",
"--------"
],
"hole22": [
"-----------",
"-HR R B-",
"-- - - - --",
"-- - - - --",
"-B R RH-",
"-----------"
],
"hole23": [
"---- ---",
"- ---- -",
"- R -",
"-- ---- -",
" - - -R-",
" -R- - -",
" - ---- -",
" -HR-B -",
"------- -",
" ---"
],
"hole24": [
"..---..",
"---H---",
"- -",
"- RRR -",
"--RBR--",
"- RRR -",
"- -",
"--- ---",
" --- "
],
"hole25": [
"---------",
"- RRBRW-",
"- -------",
"- -------",
"- HW-",
"---------"
],
"hole26": [
"----------",
"-H--BR--0-",
"-1-- -- -",
"- -",
"----------"
],
"hole27": [
"-------.",
"-H1 -.",
"---- --.",
"-0-- ---",
"- RB-0-",
"---BR -",
"..- ----",
".-- ----",
".- 1H-",
".-------"
],
"hole28": [
" --- ",
" -0------- ",
" -R- - - - ",
"------ - - ---",
"-H1H1 1H-",
"------1-1-1---",
" - - - - ",
" -B-B-B- ",
" ------- "
],
"hole29": [
"...----...",
"...-HH-...",
"----11----",
"-B S 0-",
"-B S 0-",
"--- ---",
"--- ---",
"-W S B-",
"-W S B-",
"---- ----",
"...-HH-...",
"...----..."
],
"hole30": [
"-------.",
"-H S W-.",
"--- ---.",
"-W S H-.",
"---1----",
".-0 R-",
".--- ---",
"..-R -",
"..--- --",
"...-BB-.",
"...----."
],
"hole31": [
"-----------...",
"-RRRR-BBBB-...",
"- ----- -...",
"- - ----",
"--- - ---HH-",
"-0000- 1HH-",
"--------------"
],
"hole32": [
" --- ",
"------W- ",
"-BRBBBB- ",
"------ - ",
" - ---",
" --- -H-",
" -H- - -",
" - - H-",
" -H ---",
" -- -- ",
" -H- ",
" --- "
]
};
/* ------------------ VARIABLES GLOBALES ------------------ */
let MAP_WIDTH = 0;
let MAP_HEIGHT = 0;
let STATES_SEEN = new Set();
let currentLevelName = "hole01";
let currentMap = levels[currentLevelName];
let solutionMoves = "";
let solutionStates = [];
let currentStep = 0;
let animTimer = null;
/* ------------------ CARGAR SELECTOR ------------------ */
(function initSelector() {
const sel = document.getElementById("levelSelect");
Object.keys(levels).forEach(name => {
const opt = document.createElement("option");
opt.value = name;
opt.textContent = name;
sel.appendChild(opt);
});
sel.value = currentLevelName;
drawRawMap(currentMap);
})();
/* ============================================================
=============== FUNCIONES DEL SOLVER TCL ==================
============================================================ */
// ================== CRC32 ==================
function crc32(str) {
let table = window.crcTable || (window.crcTable = (function() {
let c, table = [];
for (let n = 0; n < 256; n++) {
c = n;
for (let k = 0; k < 8; k++) {
c = ((c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1));
}
table[n] = c;
}
return table;
})());
let crc = 0 ^ (-1);
for (let i = 0; i < str.length; i++) {
crc = (crc >>> 8) ^ table[(crc ^ str.charCodeAt(i)) & 0xFF];
}
return (crc ^ (-1)) >>> 0;
}
// ================== CONVERSIÓN MAPA (convertMap) ==================
function convertMap(map) {
let state = {};
for (let j = 0; j < map.length; j++) {
for (let i = 0; i < map[j].length; i++) {
state[`${i},${j}`] = map[j][i];
}
}
return [{ moves: "", map: state }];
}
// ================== popState ==================
function popState(states) {
const st = states[0];
states.splice(0,1);
return [st.moves, st.map];
}
// ================== UTILIDADES DE TILES (clon TCL) ==================
function objectInTile(tile) {
switch(tile) {
case " ": case "H": case "W": case "S":
case "0": case "1": case "2":
return "";
case "B": case "C": case "I": case "T":
case "U": case "3": case "5":
return "B";
case "D": case "J": case "R": case "4": case "6":
return "R";
}
return "";
}
function leaveTile(from_tile) {
switch(from_tile) {
case "B":
case "R": return " ";
case "C":
case "D": return "I";
case "U": return "S";
case "J": return "H";
case "3":
case "4": return "0";
case "5":
case "6": return "2";
}
return from_tile;
}
function enterTile(from_tile, to_tile) {
let object = objectInTile(from_tile);
if (object === "B") {
switch(to_tile) {
case " ": return "B";
case "H": return "I";
case "I": return "C";
case "W": return "W";
case "S": return "T";
case "0": return "3";
case "2": return "5";
}
} else if (object === "R") {
switch(to_tile) {
case " ": return "R";
case "H": return "J";
case "I": return "D";
case "W": return "W";
case "0": return "4";
case "2": return "6";
}
}
return to_tile;
}
function movableTile(tile) {
return (tile==="B" || tile==="C" || tile==="U" || tile==="R" ||
tile==="D" || tile==="J" || tile==="3" || tile==="4" ||
tile==="5" || tile==="6");
}
function isWall(tile) {
return (tile==="-" || tile==="B" || tile==="R" || tile==="3" ||
tile==="4" || tile==="1" || tile==="5" || tile==="6" ||
tile==="U" || tile==="T" || tile==="J" || tile==="D" ||
tile==="C");
}
// ================== changeState (clon TCL) ==================
function changeState(move, state) {
let dmove, j0, j1, dj, i0, i1, di;
if (move === "U") {
dmove = [0,-1];
j0 = 0; j1 = MAP_HEIGHT; dj = 1;
i0 = 0; i1 = MAP_WIDTH; di = 1;
} else if (move === "D") {
dmove = [0,1];
j0 = MAP_HEIGHT-1; j1 = -1; dj = -1;
i0 = 0; i1 = MAP_WIDTH; di = 1;
} else if (move === "L") {
dmove = [-1,0];
j0 = 0; j1 = MAP_HEIGHT; dj = 1;
i0 = 0; i1 = MAP_WIDTH; di = 1;
} else if (move === "R") {
dmove = [1,0];
j0 = 0; j1 = MAP_HEIGHT; dj = 1;
i0 = MAP_WIDTH-1; i1 = -1; di = -1;
}
const premoves = state[0];
const mapObj = {...state[1]};
let arrmap = mapObj;
// T -> U
for (const pos in arrmap) {
if (arrmap[pos] === "T") arrmap[pos] = "U";
}
let nmoves = 1;
let total_moves = 0;
let iter = 0;
while (nmoves > 0 && iter < 9) {
nmoves = 0;
let j = j0;
while (j !== j1) {
let i = i0;
while (i !== i1) {
const x = i, y = j;
const key = `${x},${y}`;
let tile = arrmap[key] ?? " ";
if (movableTile(tile)) {
const test_x = x + dmove[0];
const test_y = y + dmove[1];
const tkey = `${test_x},${test_y}`;
let destTile = arrmap[tkey] ?? " ";
if (isWall(destTile)) {
if (tile === "U") arrmap[key] = "T";
} else {
arrmap[key] = leaveTile(tile);
arrmap[tkey] = enterTile(tile, destTile);
nmoves++;
// switches
let switches = 0;
for (const p in arrmap) {
if (arrmap[p] === "0") switches++;
}
if (switches === 0) {
for (const p in arrmap) {
if (arrmap[p] === "1") arrmap[p] = "2";
}
} else {
for (const p in arrmap) {
if (arrmap[p] === "2") arrmap[p] = "1";
}
}
}
}
i += di;
}
j += dj;
}
total_moves += nmoves;
iter++;
}
if (total_moves === 0) {
return [];
} else {
const result = [`${premoves}${move}`, arrmap];
const isNew = addLeaf(result);
if (!isNew) return [];
return result;
}
}
// ================== addLeaf (crc32 de filas) ==================
function addLeaf(state) {
const map = state[1];
let rows = [];
for (let j = 0; j < MAP_HEIGHT; j++) {
let row = "";
for (let i = 0; i < MAP_WIDTH; i++) {
row += (map[`${i},${j}`] ?? " ");
}
rows.push(row);
}
const hash = crc32(rows.join("|"));
const before = STATES_SEEN.size;
STATES_SEEN.add(hash);
return STATES_SEEN.size !== before; // true si es nuevo
}
// ================== checkHoles ==================
function checkHoles(state) {
const map = state[1];
for (const k in map) {
const tile = map[k];
if (tile === "H" || tile === "J") return 1;
}
return 0;
}
// ================== solveMap (clon TCL) ==================
function solveMap(map) {
let states = convertMap(map);
let nmoves = 0;
let found = 0;
let newstates = [];
let solution = "NOT FOUND";
const MAX_PAR = 21;
MAP_WIDTH = map[0].length;
MAP_HEIGHT = map.length;
const moves = ["U","D","L","R"];
STATES_SEEN = new Set();
while (found === 0 && nmoves < MAX_PAR && states.length > 0) {
const state = popState(states);
addLeaf(state);
for (const move of moves) {
const newstate = changeState(move, state);
if (newstate.length !== 0) {
newstates.push({ moves: newstate[0], map: newstate[1] });
nmoves = newstate[0].length;
if (checkHoles(newstate) === 0) {
found = 1;
solution = newstate[0];
break;
}
}
}
if (found === 0 && states.length === 0) {
states = newstates;
newstates = [];
}
}
return solution;
}
// ================== solveMap2 (replay para estados) ==================
function solveMap2(map, movesStr) {
let states = convertMap(map);
MAP_WIDTH = map[0].length;
MAP_HEIGHT = map.length;
STATES_SEEN = new Set();
let current = popState(states); // [moves,map]
let resultStates = [current[1]];
for (const mv of movesStr.split("")) {
const newstate = changeState(mv, current);
if (newstate.length === 0) break;
resultStates.push(newstate[1]);
current = newstate;
}
return resultStates;
}
// ================== DIBUJADO COLORES ==================
function tileClass(ch) {
if (ch === "-" || ch === ".") return "t-wall";
if (ch === " " ) return "t-empty";
if (ch === "H" || ch === "I" || ch === "C" || ch === "J" || ch === "D") return "t-hole";
if (ch === "W") return "t-water";
if (ch === "S" || ch === "T" || ch === "U") return "t-sand";
if (ch === "B" || ch === "3" || ch === "5") return "t-ball";
if (ch === "R" || ch === "4" || ch === "6") return "t-rock";
if (ch === "0" || ch === "1" || ch === "2") return "t-door";
return "t-special";
}
function drawStateColored(mapState) {
const mapDiv = document.getElementById("map");
mapDiv.innerHTML = "";
let coords = Object.keys(mapState).map(k => k.split(",").map(Number));
let maxY = Math.max(...coords.map(c => c[1]));
let maxX = Math.max(...coords.map(c => c[0]));
for (let y = 0; y <= maxY; y++) {
const rowDiv = document.createElement("div");
rowDiv.className = "row";
for (let x = 0; x <= maxX; x++) {
const ch = mapState[`${x},${y}`] ?? " ";
const span = document.createElement("span");
span.className = "tile " + tileClass(ch);
span.textContent = ch === " " ? " " : ch;
rowDiv.appendChild(span);
}
mapDiv.appendChild(rowDiv);
}
}
function drawRawMap(map) {
let state = {};
for (let j = 0; j < map.length; j++) {
for (let i = 0; i < map[j].length; i++) {
state[`${i},${j}`] = map[j][i];
}
}
drawStateColored(state);
}
function stepTo(idx) {
if (!solutionStates.length) return;
idx = Math.max(0, Math.min(idx, solutionStates.length - 1));
currentStep = idx;
drawStateColored(solutionStates[currentStep]);
// Resaltar movimiento correspondiente
renderMovesWithHighlight(currentStep);
document.getElementById("stepInfo").textContent =
`${currentStep} / ${solutionStates.length - 1}`;
}
function stepForward() { stepTo(currentStep + 1); }
function stepBackward() { stepTo(currentStep - 1); }
function play() {
if (!solutionStates.length) return;
if (animTimer) return;
animTimer = setInterval(() => {
if (currentStep >= solutionStates.length-1) {
pause();
} else {
stepForward();
}
}, 1000);
}
function pause() {
if (animTimer) {
clearInterval(animTimer);
animTimer = null;
}
}
/* ============================================================
=============== NUEVAS FUNCIONES PEDIDAS ==================
============================================================ */
/* ----------- Convertir UDLR → flechas ----------- */
function movesToArrows(m) {
return m
.replace(/U/g, "↑")
.replace(/D/g, "↓")
.replace(/L/g, "←")
.replace(/R/g, "→");
}
/* ----------- Cargar y resolver ----------- */
function loadAndSolve() {
pause();
currentLevelName = document.getElementById("levelSelect").value;
currentMap = levels[currentLevelName];
const sol = solveMap(currentMap);
solutionMoves = sol;
renderMovesWithHighlight(0);
if (sol === "NOT FOUND" || sol === "") {
solutionStates = [];
document.getElementById("stepInfo").textContent = "Sin solución";
return;
}
solutionStates = solveMap2(currentMap, sol);
currentStep = 0;
document.getElementById("stepInfo").textContent =
`0 / ${solutionStates.length - 1}`;
drawStateColored(solutionStates[0]);
}
/* ----------- Nivel anterior / siguiente ----------- */
function prevLevel() {
const keys = Object.keys(levels);
let idx = keys.indexOf(currentLevelName);
if (idx > 0) {
idx--;
document.getElementById("levelSelect").value = keys[idx];
loadAndSolve();
}
}
function nextLevel() {
const keys = Object.keys(levels);
let idx = keys.indexOf(currentLevelName);
if (idx < keys.length - 1) {
idx++;
document.getElementById("levelSelect").value = keys[idx];
loadAndSolve();
}
}
function renderMovesWithHighlight(step) {
if (!solutionMoves) {
document.getElementById("moves").innerHTML = "";
return;
}
let html = "";
for (let i = 0; i < solutionMoves.length; i++) {
let ch = solutionMoves[i];
let arrow =
ch === "U" ? "↑" :
ch === "D" ? "↓" :
ch === "L" ? "←" :
ch === "R" ? "→" : ch;
if (i === step) {
html += `<span class="hl">${arrow}</span>`;
} else {
html += arrow;
}
if (i < solutionMoves.length - 1) html += " ";
}
document.getElementById("moves").innerHTML = html;
}
/* ============================================================
=============== CONTROL POR TECLADO ========================
============================================================ */
document.addEventListener("keydown", function(e) {
console.log("KEYDOWN")
if (!solutionMoves || !solutionMoves.length) return;
if (!solutionStates.length) return;
// Mapa de teclas → movimientos
const keyToMove = {
"ArrowUp": "U",
"ArrowDown": "D",
"ArrowLeft": "L",
"ArrowRight": "R"
};
const mv = keyToMove[e.key];
if (!mv) return; // ignorar otras teclas
// Si ya estamos en el último paso, no avanzar
if (currentStep >= solutionMoves.length) return;
// Comprobar si la tecla coincide con el siguiente movimiento real
const expected = solutionMoves[currentStep];
if (mv === expected) {
// Avanzar un paso
stepTo(currentStep + 1);
} else {
// Si quieres, puedes mostrar un pequeño aviso visual
// pero por defecto simplemente ignoramos la tecla
}
});
</script>
</body>
</html>