Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- <!DOCTYPE html>
- <html lang="es">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>Ajedrez 2D - Vertical</title>
- <style>
- body {
- margin: 0;
- padding: 0;
- background-color: #3c3228;
- height: 100vh;
- overflow: hidden;
- font-family: Arial, sans-serif;
- }
- #gameContainer {
- position: relative;
- width: 100%;
- height: 100vh;
- }
- #gameCanvas {
- display: block;
- width: 100%;
- height: 100%;
- touch-action: none;
- }
- </style>
- </head>
- <body>
- <div id="gameContainer">
- <canvas id="gameCanvas"></canvas>
- </div><script>
- const PIECE_IMAGES = {
- "alfil": 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACPklEQVR4nJVTv0tbURT+zr2JL88Xq/kxaGqjD0I6BEpptyyC2k4lZBQk4GJLB6eIGUoNFpwDHVwC+QNe6Fg6ZA5ZhDRBOhQU8zD0BwTNIwjmxXe6vNi0qLUHLhzu+b7vnu8cLnBLEBGEELdBbua6JwkgHYvFlP8hC8Mw5MzMTDSdTl/u7u6ypmmrhmFIAJ67CEgpJQA8iMfj35aWlgYAVl0r8p+vu8BnqVTqsFarDRqNxmUul+sCeENEAHDjUIbkp5lMps/MbBiGs7m5yczMpVKJfT5f7rZOPEQEr9f7/vj42GHmi/X1dU4kEtzpdAbMPEgmk4cA6KbNeIQQEEK83tvb406nMzBNkxuNBluWNdjf3+doNPqJiJDP568UaESAANDc3NxYu93+uLGxsRgOhx0pJdm2jWKx+NM0zUUi+sLMAoDz90AIwNja2lpfVdXDUCiEQCDgaJrGuq6Tpmn9hYWFo+3tbc8ob7SDq1AUpZBKpV7FYjGP3+9Hs9mU1Wq1fnJykgTQ/8O3K8KTk5OPpZSZ2dnZy2AweO/09PRC13U1EAjg4OCAdV3/EYlE3pmm6et2u9Wtra0POzs7TAAEM0NRlHqhUHg0Pz8Py7JwdnaGSqXy2TTN1srKyovp6Wk5MTEBIkI2m0Wr1Xpo2/ZXcv044+Pjz6empl72er26ZVltAEcAagBsAHEACVVV7weDwSfn5+ffl5eX35bLZb52n0QEKSWEEMjn82KYX4sdyQV+/0IAYHdV7NbEyP2whl9EzdT/E9sjmgAAAABJRU5ErkJggg==',
- "caballo": 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACO0lEQVR4nJWSsUtbURjFz/3eDc8neZJQQQzUQluqHXRoIMoD8WGhHTo4CBX3bEX8AxwymEFHoaBLBye7Fzq0g+hgcbDQQrYspgFroY0RCXl5797TJRaR2NqzXTice86PT6G3NAALQHXf5gbf3yUiUEoBgNzGrwBgdHTU7+/vLwZBkAOwICILjuMAgPOvIKWUwsjISLZQKNh0Ov0rDMPO2NjYDwB3b9NGlFLIZDL38vn8+c7ODuM45trams3n8+fZbPYjgMcicmOIFhG4rvtqa2uLtVqtY4yx7Org4IC5XO77wMDAg55NSKqZmRkdBMGXQqHAlZUVY63l6ekpj46OLMlob2+Prut+2t3d1dcDnC6o5xsbG6zX6yaOY5I05XLZbm9v0xhDkvHS0hIBXILVAIBSqSQAMD4+/u7i4sKSTEiadrvN6elpHh8fXy5JqtWqTafTH67OEBHB4ODgo2Kx2Go2m4ZkUqlUODk5WRkaGvrWarVI0l4yCcOwASArIhAAQhKO4wSe53lnZ2c4OTlx5ufn3x8eHj6bnZ11PM+jMQbWWgWAExMTGQD3uy2gSCrf9+9MTU29nZub+xmGYRNASmv9Zn9/nySTTqfDKIpIMllfXyeAF1rrnvfwMJVKPQHwdHl5mSTbJOMul4RktLm5mQB4qbXG1QhVKpXU6upq1VoL3/e/Li4uol6vuyQhIkiSBFprp1ar/QGorv2uAGB4eNhrNBqv+/r6MtZaXvPZKIokiqIygM89N/yPfgP8iyghU/ecegAAAABJRU5ErkJggg==',
- "reina": 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACqElEQVR4nHVSsUsbYRx9v+++u5yEM2dIROJxOAQHOafDQTs4CEJrlkQOrKXQgAWh1MXFFvIfiAUrOHUolCLBTZAWaveiGW2DIZWACVaMopSGs3f3dWiuTVN84+O9H+/3eMD/kBzHkQAQEYGIAACO44T8rZAAsC4uBSDdxTEA/NYrIyMj+bGxsbcAtPn5+dLCwsI1AM227Y3R0dHn3XoGgABQMpl8yDm/Y5pmbnp6+r6qqvHDw8NvpVKp2dvbK9u2/cSyrAfRaHS0v7//8eTkJAdAkCQJAOJLS0tibm7uAIAFwLVtOwFgDcA7zjkAeAAymUzmdaFQELIsW4z9/pg5jiOZpvkqGo2WOecwDONnIpG4m0qlXqZSqY+KogwbhiGKxWKPqqo7g4ODHwzD6Ak7C5sd1nU9AJBdXFy8HB8fL05NTb3P5/Ofk8nk2vLysg/gXiwW+wFgpu3hBIAYYyIWi8VyuVy1Xq8r2WxWGhgY6CmXy346naaTkxNYliVWVlauJyYmYpubm5bneeUgCBgDIAqFAru8vLyqVCpfVldXtWq1qu7v71O1WuV7e3tSvV6Xdnd3pfX19Xiz2Wzc3Nx8be9D0J8oRF4kElnWdX11dnbWHxoakiKRCIQQaLVauLi48La3t3mj0dhwXfepEEIC4IfD8YQQrNVqvTg9Pd1xXVfSNM1XFAWqqkLX9YCIeK1WO9A07ZkQggEI0LUoxjn3GGNv+vr6ZkzTDM7PzxkA6Lruu64LIio2m83vbZ8XDimEEEIAwKNGo8FqtZrMGCNFUejs7Ew+OjpiRJTf2tqSAPihqTOBAABZlkuVSiWtKEqccx4VQpDv++7x8fEV5/xTh546S/zLEqH9ow5AbQt/ArgiIred8p8r3WBEFBARwqkKIRAEATrLC/ELqET6KyK0lIcAAAAASUVORK5CYII=',
- "rey": 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACHUlEQVR4nK1SPWhTURT+7rnPVF8QmwwPkoYEQqZ2EddAMmSThC4JHcXJxSGDSyEQujlWWkHQqRmSQcfsUuiUMYtEIrTkFcn0pBCNL/d+Lu9hf7QunuVwz7nfd75z+IBbQil1WxsAIH/CAZCNjY1cMpncd133KYA7Uf3WUACczc3NBAAnk8k8AhAopT7kcrl7ABLVatUBoP8qN5astcb6+nqhWCxOs9nsG61/Y0QkHnZlMgCsAXiWz+f3ATSSyeT7i4sLHh4eEsCTVCr1IpvN7gOoXMF1u10hqQqFwmA4HNL3ffb7fXqeF56entrd3d1VrVbjeDzmbDZju92m1roeKdGI5N2t1+vfSP4kuSRpRqMRS6USd3Z2GIYhSa5ILnzfp+u6r6J1HbRaLS0i8Dzv5fHxcfyRJFkulzkYDOKnJWna7fYPAA8jBaIAqG63q/b29qRUKn1sNBplkma1Wmnf9+G6LjzPg4iY+Xyue73ec6XUa5IagIkP6TiOAwCPO52Omc1m4WQysUEQ2PPzczudTldnZ2dma2vrM0lFUuIjOhGBtdYin89/Ojk5EWuthGEIYwwcx0EikZDlcqkWi8UXEWGz2RQA9jqB2t7e9g8ODt6GYdioVCoP0un0mjHGzufz70dHR34QBO9IqssWv2FPrTWMMfcBpAEkABDAAsBXpZQledWANywJiIjY2JkxwFqLaHf7L4K4frnHa/n/xS8fVwbw/zqGugAAAABJRU5ErkJggg==',
- "torre": 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACNklEQVR4nHVTPUhbURg933fvSwLB/vhoC4IRHKyBdirSFjo5xEHqljWDIHHrUjIJbSEUR/cOHczkVnBoJ8mSzUW01OUNDhliK1iK5PnevaeDRp5PPXCHD77vfPeccy9wHQIAInJnLSKjWnAHigAeklRrLQDcB/CApMnU90ak9nJIAfgwDJ9aa79NT08/EpEfALqVSqU9Pj4uIrIDYKdarX5SVUZR9Hk4HG6MthpjDADU2+02SXJ1dZULCwvc3d1lHMdcWVlhrVZjFEUcDAYslUrfM9KgJKVcLj+r1Wq/+/0+STpewGcOSfr19XWWSqWWql4zQlXVe+9nq9Xq5vz8/AsRoYioiMA5R2MMoyj6t729/V5EvpDUvJM2CII0SZLXS0tLvVar5eM4VlVFoVBI9/f3bbPZ3LTWNtI0tQDSfALm0v03a2trWRkkmRweHrJcLn/d2toyowA0zxAEgU/T1JC8ka+IIE1TU6/X/dWV8z1JkoiI/N3b20O328X5+TlEBIVCAQcHB4jjeGit5Wh5nsAHQcAwDOtTU1Po9XpMkgSXr0+dc5ybm3t1dHT0+Pj4eOC91yyBkoSIbCwuLr5bXl72qmpUL1SSVBHxk5OTz5vN5o73/q2IRKMUrIikxpgPjUbj48zMTHxychJkfRARkMTY2JhzzgWdTudnsVh8mZdQAYDT09OitTb7ia5wdnamqgqST4wxwahDAHBiYiLs9/uzN6Zuxx8R+XVjhTHm1s15OOdAEv8BPj4U5uV6iG8AAAAASUVORK5CYII='
- };</script><script>
- 'use strict';
- // =============================================================================
- // CONSTANTES Y CONFIGURACIÓN
- // =============================================================================
- let SCREEN_WIDTH = window.innerWidth;
- let SCREEN_HEIGHT = window.innerHeight;
- const BOARD_SIZE = 3;
- let CELL_SIZE = (SCREEN_HEIGHT * 0.35) / 3;
- let BOARD_SIZE_PX = CELL_SIZE * 3;
- let BOARD_OFFSET_X = (SCREEN_WIDTH - BOARD_SIZE_PX) / 2;
- let SEPARATION_HEIGHT = SCREEN_HEIGHT * 0.02;
- let SECTION_INFO_HEIGHT = SCREEN_HEIGHT * 0.08;
- let SECTION_OBJECTIVE_HEIGHT = SCREEN_HEIGHT * 0.35;
- let SECTION_GAME_HEIGHT = SECTION_INFO_HEIGHT + SECTION_OBJECTIVE_HEIGHT + SEPARATION_HEIGHT;
- let SECTION_INFO_TOP = SECTION_GAME_HEIGHT + SECTION_OBJECTIVE_HEIGHT + SEPARATION_HEIGHT;
- let OBJECTIVE_GRID_COORDS = [];
- for (let gy = 0; gy < BOARD_SIZE; gy++) {
- for (let gx = 0; gx < BOARD_SIZE; gx++) {
- const cx = BOARD_OFFSET_X + gx * CELL_SIZE + CELL_SIZE / 2;
- const cy = SECTION_INFO_HEIGHT + gy * CELL_SIZE + CELL_SIZE / 2;
- OBJECTIVE_GRID_COORDS.push([cx, cy]);
- }
- }
- let GAME_GRID_COORDS = [];
- for (let gy = 0; gy < BOARD_SIZE; gy++) {
- for (let gx = 0; gx < BOARD_SIZE; gx++) {
- const cx = BOARD_OFFSET_X + gx * CELL_SIZE + CELL_SIZE / 2;
- const cy = SECTION_GAME_HEIGHT + gy * CELL_SIZE + CELL_SIZE / 2;
- GAME_GRID_COORDS.push([cx, cy]);
- }
- }
- const COLOR_BG = [60, 50, 40];
- const COLOR_PANEL = [40, 30, 20];
- const COLOR_TEXT = [220, 210, 200];
- const COLOR_VALID_MOVE = [0, 255, 0];
- const COLOR_VICTORIA = [255, 215, 0];
- const COLOR_BOARD_LIGHT = [210, 180, 140];
- const COLOR_BOARD_DARK = [139, 69, 19];
- const TIPOS_PIEZAS = ['rey', 'reina', 'torre', 'alfil', 'caballo'];
- // =============================================================================
- // GESTIÓN DE ASSETS
- // =============================================================================
- const Assets = {
- pieceImages: {},
- loadedCount: 0,
- totalImages: 5,
- initialize: function() {
- this.loadPieceImage('rey', PIECE_IMAGES.rey);
- this.loadPieceImage('reina', PIECE_IMAGES.reina);
- this.loadPieceImage('torre', PIECE_IMAGES.torre);
- this.loadPieceImage('alfil', PIECE_IMAGES.alfil);
- this.loadPieceImage('caballo', PIECE_IMAGES.caballo);
- },
- loadPieceImage: function(tipo, url) {
- const img = new Image();
- img.src = url;
- img.onload = () => {
- this.loadedCount++;
- };
- img.onerror = () => {
- console.warn('No se pudo cargar ' + tipo);
- this.loadedCount++;
- };
- this.pieceImages[tipo] = img;
- },
- getPieceImage: function(tipo) {
- return this.pieceImages[tipo] || this.pieceImages['rey'];
- },
- isReady: function() {
- return this.loadedCount >= this.totalImages;
- }
- };
- function updateGridCoords() {
- SECTION_OBJECTIVE_HEIGHT = SCREEN_HEIGHT * 0.35;
- SEPARATION_HEIGHT = SCREEN_HEIGHT * 0.02;
- SECTION_INFO_HEIGHT = SCREEN_HEIGHT * 0.08;
- SECTION_GAME_HEIGHT = SECTION_INFO_HEIGHT + SECTION_OBJECTIVE_HEIGHT + SEPARATION_HEIGHT;
- SECTION_INFO_TOP = SECTION_GAME_HEIGHT + SECTION_OBJECTIVE_HEIGHT + SEPARATION_HEIGHT;
- OBJECTIVE_GRID_COORDS = [];
- for (let gy = 0; gy < BOARD_SIZE; gy++) {
- for (let gx = 0; gx < BOARD_SIZE; gx++) {
- const cx = BOARD_OFFSET_X + gx * CELL_SIZE + CELL_SIZE / 2;
- const cy = SECTION_INFO_HEIGHT + gy * CELL_SIZE + CELL_SIZE / 2;
- OBJECTIVE_GRID_COORDS.push([cx, cy]);
- }
- }
- GAME_GRID_COORDS = [];
- for (let gy = 0; gy < BOARD_SIZE; gy++) {
- for (let gx = 0; gx < BOARD_SIZE; gx++) {
- const cx = BOARD_OFFSET_X + gx * CELL_SIZE + CELL_SIZE / 2;
- const cy = SECTION_GAME_HEIGHT + gy * CELL_SIZE + CELL_SIZE / 2;
- GAME_GRID_COORDS.push([cx, cy]);
- }
- }
- }
- function recalculate() {
- SCREEN_WIDTH = window.innerWidth;
- SCREEN_HEIGHT = window.innerHeight;
- CELL_SIZE = (SCREEN_HEIGHT * 0.35) / 3;
- BOARD_SIZE_PX = CELL_SIZE * 3;
- BOARD_OFFSET_X = (SCREEN_WIDTH - BOARD_SIZE_PX) / 2;
- SEPARATION_HEIGHT = SCREEN_HEIGHT * 0.02;
- SECTION_INFO_HEIGHT = SCREEN_HEIGHT * 0.08;
- SECTION_OBJECTIVE_HEIGHT = SCREEN_HEIGHT * 0.35;
- SECTION_GAME_HEIGHT = SECTION_INFO_HEIGHT + SECTION_OBJECTIVE_HEIGHT + SEPARATION_HEIGHT;
- SECTION_INFO_TOP = SECTION_GAME_HEIGHT + SECTION_OBJECTIVE_HEIGHT + SEPARATION_HEIGHT;
- updateGridCoords();
- canvas.width = SCREEN_WIDTH;
- canvas.height = SCREEN_HEIGHT;
- }
- // =============================================================================
- // LÓGICA DEL JUEGO
- // =============================================================================
- class Piece {
- constructor(tipo, gx, gy) {
- this.tipo = tipo;
- this.pos = [gx, gy];
- }
- get x() {
- return this.pos[0];
- }
- get y() {
- return this.pos[1];
- }
- is_valid_move(gx, gy, pieces) {
- if (gx < 0 || gx >= BOARD_SIZE || gy < 0 || gy >= BOARD_SIZE) {
- return false;
- }
- for (const p of pieces) {
- if (p.pos[0] === gx && p.pos[1] === gy) {
- return false;
- }
- }
- return this._check_movement(gx, gy, pieces);
- }
- _check_movement(gx, gy, pieces) {
- const x = this.pos[0];
- const y = this.pos[1];
- const dx = gx - x;
- const dy = gy - y;
- if (this.tipo === 'rey') {
- return (Math.abs(dx) <= 1 && Math.abs(dy) <= 1) && (dx !== 0 || dy !== 0);
- } else if (this.tipo === 'reina') {
- if (dx === 0 || dy === 0 || Math.abs(dx) === Math.abs(dy)) {
- return this._is_path_clear(x, y, gx, gy, pieces);
- }
- return false;
- } else if (this.tipo === 'torre') {
- if (dx === 0 || dy === 0) {
- return this._is_path_clear(x, y, gx, gy, pieces);
- }
- return false;
- } else if (this.tipo === 'alfil') {
- if (Math.abs(dx) === Math.abs(dy)) {
- return this._is_path_clear(x, y, gx, gy, pieces);
- }
- return false;
- } else if (this.tipo === 'caballo') {
- return (Math.abs(dx) === 2 && Math.abs(dy) === 1) || (Math.abs(dx) === 1 && Math.abs(dy) === 2);
- }
- return false;
- }
- _is_path_clear(x1, y1, x2, y2, pieces) {
- const dx = (x2 === x1) ? 0 : ((x2 > x1) ? 1 : -1);
- const dy = (y2 === y1) ? 0 : ((y2 > y1) ? 1 : -1);
- let x = x1 + dx;
- let y = y1 + dy;
- while (x !== x2 || y !== y2) {
- for (const p of pieces) {
- if (p.pos[0] === x && p.pos[1] === y) {
- return false;
- }
- }
- x += dx;
- y += dy;
- }
- return true;
- }
- get_possible_moves(pieces) {
- const moves = [];
- const x = this.pos[0];
- const y = this.pos[1];
- if (this.tipo === 'rey') {
- for (let dx = -1; dx <= 1; dx++) {
- for (let dy = -1; dy <= 1; dy++) {
- if (dx === 0 && dy === 0) continue;
- const new_x = x + dx;
- const new_y = y + dy;
- if (this.is_valid_move(new_x, new_y, pieces)) {
- moves.push([new_x, new_y]);
- }
- }
- }
- } else if (this.tipo === 'reina') {
- moves.push(...this._get_linear_moves(x, y, [[1, 0], [-1, 0], [0, 1], [0, -1], [1, 1], [1, -1], [-1, 1], [-1, -1]], pieces));
- } else if (this.tipo === 'torre') {
- moves.push(...this._get_linear_moves(x, y, [[1, 0], [-1, 0], [0, 1], [0, -1]], pieces));
- } else if (this.tipo === 'alfil') {
- moves.push(...this._get_linear_moves(x, y, [[1, 1], [1, -1], [-1, 1], [-1, -1]], pieces));
- } else if (this.tipo === 'caballo') {
- for (const [dx, dy] of [[2, 1], [2, -1], [-2, 1], [-2, -1], [1, 2], [1, -2], [-1, 2], [-1, -2]]) {
- const new_x = x + dx;
- const new_y = y + dy;
- if (this.is_valid_move(new_x, new_y, pieces)) {
- moves.push([new_x, new_y]);
- }
- }
- }
- return moves;
- }
- _get_linear_moves(x, y, directions, pieces) {
- const moves = [];
- for (const [dx, dy] of directions) {
- let new_x = x + dx;
- let new_y = y + dy;
- while (new_x >= 0 && new_x < BOARD_SIZE && new_y >= 0 && new_y < BOARD_SIZE) {
- if (pieces.some(p => p.pos[0] === new_x && p.pos[1] === new_y)) {
- break;
- }
- if (this.is_valid_move(new_x, new_y, pieces)) {
- moves.push([new_x, new_y]);
- }
- new_x += dx;
- new_y += dy;
- }
- }
- return moves;
- }
- }
- class GameState {
- constructor() {
- this.pieces = [];
- this.selectedPiece = null;
- this.victory = false;
- this.targetPositions = {};
- this.moves = 0;
- recalculate();
- updateGridCoords();
- this.generatePositions();
- }
- generatePositions() {
- const maxIntentos = 100;
- let intentos = 0;
- while (intentos < maxIntentos) {
- intentos++;
- const positions = [];
- for (let y = 0; y < BOARD_SIZE; y++) {
- for (let x = 0; x < BOARD_SIZE; x++) {
- positions.push([x, y]);
- }
- }
- this._shuffleArray(positions);
- this.targetPositions = {};
- for (let i = 0; i < TIPOS_PIEZAS.length; i++) {
- this.targetPositions[TIPOS_PIEZAS[i]] = positions[i];
- }
- if (JSON.stringify(this.targetPositions['caballo']) === JSON.stringify([1, 1])) {
- for (const pos of positions) {
- if (JSON.stringify(pos) !== JSON.stringify([1, 1]) && !Object.values(this.targetPositions).some(p => JSON.stringify(p) === JSON.stringify(pos))) {
- this.targetPositions['caballo'] = pos;
- break;
- }
- }
- }
- const blancas = ['[0,0]', '[2,0]', '[1,1]', '[0,2]', '[2,2]'];
- this._shuffleArray(positions);
- this.pieces = [];
- for (const tipo of TIPOS_PIEZAS) {
- const pos = positions.pop();
- this.pieces.push(new Piece(tipo, pos[0], pos[1]));
- }
- const caballoInicial = this.pieces.find(p => p.tipo === 'caballo');
- if (JSON.stringify(caballoInicial.pos) === JSON.stringify([1, 1])) {
- for (const pos of positions) {
- if (JSON.stringify(pos) !== JSON.stringify([1, 1]) && !this.pieces.some(p => JSON.stringify(p.pos) === JSON.stringify(pos))) {
- caballoInicial.pos = pos;
- break;
- }
- }
- }
- const alfilObjetivo = this.targetPositions['alfil'];
- const alfilInicial = this.pieces.find(p => p.tipo === 'alfil');
- const alfilColorObj = blancas.includes(JSON.stringify(alfilObjetivo));
- const alfilColorIni = blancas.includes(JSON.stringify(alfilInicial.pos));
- if (alfilColorObj === alfilColorIni) {
- break;
- }
- }
- this.victory = false;
- this.selectedPiece = null;
- this.moves = 0;
- updateGridCoords();
- SolutionCalculator.calculateMinMoves(this.pieces, this.targetPositions).then(result => {
- this.minMoves = result;
- });
- }
- checkVictory() {
- for (const piece of this.pieces) {
- const targetPos = this.targetPositions[piece.tipo];
- if (!targetPos || JSON.stringify(piece.pos) !== JSON.stringify(targetPos)) {
- this.victory = false;
- return;
- }
- }
- this.victory = true;
- this.moves = 0;
- }
- getPieceAt(gx, gy) {
- for (const piece of this.pieces) {
- if (piece.pos[0] === gx && piece.pos[1] === gy) {
- return piece;
- }
- }
- return null;
- }
- _shuffleArray(array) {
- for (let i = array.length - 1; i > 0; i--) {
- const j = Math.floor(Math.random() * (i + 1));
- [array[i], array[j]] = [array[j], array[i]];
- }
- }
- }
- class SolutionCalculator {
- static async calculateMinMoves(pieces, targetPositions) {
- const PIECE_ORDER = ['rey', 'reina', 'torre', 'alfil', 'caballo'];
- const stateHash = (pieces) => {
- return JSON.stringify(pieces.map(p => ({
- tipo: p.tipo,
- x: p.pos[0],
- y: p.pos[1]
- })).sort((a, b) => PIECE_ORDER.indexOf(a.tipo) - PIECE_ORDER.indexOf(b.tipo)));
- };
- const isTarget = (pieces) => {
- return pieces.every(p => JSON.stringify(targetPositions[p.tipo]) === JSON.stringify(p.pos));
- };
- const getAllMoves = (pieces) => {
- const moves = [];
- for (const piece of pieces) {
- const pieceObj = new Piece(piece.tipo, piece.pos[0], piece.pos[1]);
- for (const newPos of pieceObj.get_possible_moves(pieces.map(p => new Piece(p.tipo, p.pos[0], p.pos[1])))) {
- moves.push({ tipo: piece.tipo, newPos });
- }
- }
- return moves;
- };
- const applyMove = (pieces, tipo, newPos) => {
- return pieces.map(p => p.tipo === tipo ? { tipo: p.tipo, pos: [newPos[0], newPos[1]] } : { tipo: p.tipo, pos: [p.pos[0], p.pos[1]] });
- };
- const initialHash = stateHash(pieces);
- if (isTarget(pieces)) return 0;
- const visited = new Set([initialHash]);
- const queue = [{ pieces: pieces.map(p => ({ tipo: p.tipo, pos: [p.pos[0], p.pos[1]] })), depth: 0 }];
- let statesVisited = 0;
- const MAX_STATES = 50000;
- const BATCH_SIZE = 1000;
- return new Promise((resolve) => {
- const processBatch = () => {
- let batchProcessed = 0;
- while (queue.length > 0 && batchProcessed < BATCH_SIZE) {
- const { pieces: current, depth } = queue.shift();
- statesVisited++;
- batchProcessed++;
- if (statesVisited > MAX_STATES) {
- resolve(null);
- return;
- }
- for (const { tipo, newPos } of getAllMoves(current)) {
- const newPieces = applyMove(current, tipo, newPos);
- const newHash = stateHash(newPieces);
- if (!visited.has(newHash)) {
- if (isTarget(newPieces)) {
- resolve(depth + 1);
- return;
- }
- visited.add(newHash);
- queue.push({ pieces: newPieces, depth: depth + 1 });
- }
- }
- }
- if (queue.length > 0) {
- setTimeout(processBatch, 0);
- } else {
- resolve(null);
- }
- };
- processBatch();
- });
- }
- }
- // =============================================================================
- // RENDERIZADO
- // =============================================================================
- class Renderer {
- constructor(canvas) {
- this.canvas = canvas;
- this.ctx = canvas.getContext('2d');
- }
- clear() {
- this.ctx.fillStyle = `rgb(${COLOR_BG[0]}, ${COLOR_BG[1]}, ${COLOR_BG[2]})`;
- this.ctx.fillRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
- }
- drawSeparators() {
- const separatorColor = `rgb(${COLOR_PANEL[0]}, ${COLOR_PANEL[1]}, ${COLOR_PANEL[2]})`;
- this.ctx.fillStyle = separatorColor;
- this.ctx.fillRect(0, SECTION_INFO_HEIGHT, SCREEN_WIDTH, SEPARATION_HEIGHT);
- this.ctx.fillRect(0, SECTION_GAME_HEIGHT, SCREEN_WIDTH, SEPARATION_HEIGHT);
- }
- drawGrid(coords, offsetBaseY) {
- for (let i = 0; i < coords.length; i++) {
- const gx = i % BOARD_SIZE;
- const gy = Math.floor(i / BOARD_SIZE);
- const x = BOARD_OFFSET_X + gx * CELL_SIZE;
- const y = offsetBaseY + gy * CELL_SIZE;
- const color = (gx + gy) % 2 === 0 ? COLOR_BOARD_LIGHT : COLOR_BOARD_DARK;
- this.ctx.fillStyle = `rgb(${color[0]}, ${color[1]}, ${color[2]})`;
- this.ctx.fillRect(x, y, CELL_SIZE, CELL_SIZE);
- this.ctx.strokeStyle = `rgb(${COLOR_PANEL[0]}, ${COLOR_PANEL[1]}, ${COLOR_PANEL[2]})`;
- this.ctx.lineWidth = 2;
- this.ctx.strokeRect(x, y, CELL_SIZE, CELL_SIZE);
- }
- }
- drawObjectiveBoard() {
- this.drawGrid(OBJECTIVE_GRID_COORDS, SECTION_INFO_HEIGHT);
- }
- drawGameBoard(targetPositions, pieces) {
- this.drawGrid(GAME_GRID_COORDS, SECTION_GAME_HEIGHT, targetPositions, pieces);
- }
- drawPieces(pieces, coords, targetPositions = null) {
- for (const piece of pieces) {
- if (targetPositions) {
- const targetPos = targetPositions[piece.tipo];
- if (targetPos && targetPos[0] === piece.pos[0] && targetPos[1] === piece.pos[1]) {
- this.drawCorrectPieceMarker(piece, coords);
- }
- }
- this._drawPiece(piece, coords);
- }
- }
- _drawPiece(piece, coords) {
- const idx = piece.y * BOARD_SIZE + piece.x;
- if (idx < coords.length) {
- const [cx, cy] = coords[idx];
- const img = Assets.getPieceImage(piece.tipo);
- const pieceSize = CELL_SIZE - 10;
- const drawX = cx - pieceSize / 2;
- const drawY = cy - pieceSize / 2;
- this.ctx.drawImage(img, drawX, drawY, pieceSize, pieceSize);
- }
- }
- drawValidMoves(selectedPiece, pieces, coords) {
- if (!selectedPiece) return;
- const moves = selectedPiece.get_possible_moves(pieces);
- this.ctx.fillStyle = `rgba(${COLOR_VALID_MOVE[0]}, ${COLOR_VALID_MOVE[1]}, ${COLOR_VALID_MOVE[2]}, 0.4)`;
- this.ctx.strokeStyle = `rgb(${COLOR_VALID_MOVE[0]}, ${COLOR_VALID_MOVE[1]}, ${COLOR_VALID_MOVE[2]})`;
- this.ctx.lineWidth = 3;
- for (const [gx, gy] of moves) {
- const idx = gy * BOARD_SIZE + gx;
- if (idx < coords.length) {
- const [cx, cy] = coords[idx];
- this.ctx.beginPath();
- this.ctx.arc(cx, cy, 20, 0, Math.PI * 2);
- this.ctx.fill();
- this.ctx.stroke();
- }
- }
- }
- drawSelectedPiece(piece, mousePos, coords) {
- if (!piece) return;
- const [mx, my] = mousePos;
- const img = Assets.getPieceImage(piece.tipo);
- const pieceSize = CELL_SIZE - 10;
- this.ctx.drawImage(img, mx - pieceSize / 2, my - pieceSize / 2, pieceSize, pieceSize);
- }
- drawCorrectPieceMarker(piece, coords) {
- const idx = piece.y * BOARD_SIZE + piece.x;
- if (idx < coords.length) {
- const [cx, cy] = coords[idx];
- const markerRadius = CELL_SIZE / 2.5;
- this.ctx.fillStyle = 'rgba(0, 255, 0, 0.4)';
- this.ctx.beginPath();
- this.ctx.arc(cx, cy, markerRadius, 0, Math.PI * 2);
- this.ctx.fill();
- this.ctx.strokeStyle = 'rgba(0, 255, 0, 0.8)';
- this.ctx.lineWidth = 3;
- this.ctx.stroke();
- }
- }
- drawObjectivePanel(gameState) {
- for (const [tipo, pos] of Object.entries(gameState.targetPositions)) {
- const [gx, gy] = pos;
- const idx = gy * BOARD_SIZE + gx;
- if (idx < OBJECTIVE_GRID_COORDS.length) {
- const [cx, cy] = OBJECTIVE_GRID_COORDS[idx];
- const img = Assets.getPieceImage(tipo);
- const pieceSize = CELL_SIZE - 10;
- const drawX = cx - pieceSize / 2;
- const drawY = cy - pieceSize / 2;
- this.ctx.drawImage(img, drawX, drawY, pieceSize, pieceSize);
- }
- }
- }
- drawGameInfo(gameState) {
- const infoY = SECTION_INFO_HEIGHT / 2 + 10;
- const ctx = this.ctx;
- const centerX = SCREEN_WIDTH / 2;
- ctx.fillStyle = `rgb(${COLOR_TEXT[0]}, ${COLOR_TEXT[1]}, ${COLOR_TEXT[2]})`;
- ctx.font = '16px Arial';
- ctx.textAlign = 'center';
- ctx.fillText('Ajedrez 2D | Arrastra fichas. Pulsa AQUI para Nuevo.', centerX, infoY);
- const movesText = `Movimientos: ${gameState.moves}`;
- const minMovesText = gameState.minMoves === null ? 'Calculando...' : `Mínimo: ${gameState.minMoves}`;
- ctx.font = '16px Arial';
- ctx.textAlign = 'center';
- ctx.fillText(movesText, centerX - 150, infoY + 25);
- ctx.fillText(minMovesText, centerX + 150, infoY + 25);
- }
- drawVictory() {
- const ctx = this.ctx;
- ctx.fillStyle = 'rgba(0, 0, 0, 0.6)';
- ctx.fillRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
- ctx.fillStyle = `rgb(${COLOR_VICTORIA[0]}, ${COLOR_VICTORIA[1]}, ${COLOR_VICTORIA[2]})`;
- ctx.font = 'bold 64px Arial';
- ctx.textAlign = 'center';
- ctx.textBaseline = 'middle';
- ctx.fillText('¡VICTORIA!', SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2);
- }
- drawLabel(text, centerX, centerY) {
- const ctx = this.ctx;
- const fontSize = Math.min(CELL_SIZE * 0.25, 30);
- ctx.fillStyle = `rgb(${COLOR_TEXT[0]}, ${COLOR_TEXT[1]}, ${COLOR_TEXT[2]})`;
- ctx.font = `bold ${fontSize}px Arial`;
- ctx.textAlign = 'center';
- ctx.textBaseline = 'middle';
- ctx.fillText(text, centerX, centerY);
- }
- }
- // =============================================================================
- // INPUT HANDLER
- // =============================================================================
- class InputHandler {
- constructor(canvas, renderer) {
- this.canvas = canvas;
- this.renderer = renderer;
- this.dragging = false;
- this.dragPiece = null;
- this.dragOffsetX = 0;
- this.dragOffsetY = 0;
- this.canvas.addEventListener('mousedown', (e) => this._handleMouseDown(e));
- this.canvas.addEventListener('mousemove', (e) => this._handleMouseMove(e));
- this.canvas.addEventListener('mouseup', (e) => this._handleMouseUp(e));
- this.canvas.addEventListener('mouseleave', (e) => this._handleMouseUp(e));
- this.canvas.addEventListener('touchstart', (e) => this._handleTouchStart(e), { passive: false });
- this.canvas.addEventListener('touchmove', (e) => this._handleTouchMove(e), { passive: false });
- this.canvas.addEventListener('touchend', (e) => this._handleTouchEnd(e));
- document.addEventListener('keydown', (e) => this._handleKeyDown(e));
- }
- _getMousePos(e) {
- const rect = this.canvas.getBoundingClientRect();
- return [e.clientX - rect.left, e.clientY - rect.top];
- }
- _getTouchPos(e) {
- const rect = this.canvas.getBoundingClientRect();
- if (!e.touches || e.touches.length === 0) {
- return null;
- }
- const touch = e.touches[0];
- return [touch.clientX - rect.left, touch.clientY - rect.top];
- }
- _getTouchEndPos(e) {
- const rect = this.canvas.getBoundingClientRect();
- if (!e.changedTouches || e.changedTouches.length === 0) {
- return null;
- }
- const touch = e.changedTouches[0];
- return [touch.clientX - rect.left, touch.clientY - rect.top];
- }
- _handleMouseDown(e) {
- if (e.button !== 0) return;
- const [mx, my] = this._getMousePos(e);
- this._processInput(mx, my);
- }
- _handleMouseMove(e) {
- if (!this.dragging) return;
- e.preventDefault();
- const [mx, my] = this._getMousePos(e);
- this.dragOffsetX = mx;
- this.dragOffsetY = my;
- }
- _handleMouseUp(e) {
- if (!this.dragging) return;
- const [mx, my] = this._getMousePos(e);
- this._processInput(mx, my, true);
- this.dragging = false;
- this.dragPiece = null;
- }
- _handleTouchStart(e) {
- e.preventDefault();
- const pos = this._getTouchPos(e);
- if (pos === null) return;
- const [mx, my] = pos;
- this._processInput(mx, my);
- }
- _handleTouchMove(e) {
- e.preventDefault();
- if (!this.dragging) return;
- const pos = this._getTouchPos(e);
- if (pos === null) return;
- const [mx, my] = pos;
- this.dragOffsetX = mx;
- this.dragOffsetY = my;
- }
- _handleTouchEnd(e) {
- if (!this.dragging) return;
- const pos = this._getTouchEndPos(e);
- if (pos === null) return;
- const [mx, my] = pos;
- this._processInput(mx, my, true);
- this.dragging = false;
- this.dragPiece = null;
- }
- _processInput(x, y, isRelease = false) {
- if (x === null || y === null) return;
- let gx, gy;
- let inGameBoard = false;
- if (y < SECTION_INFO_HEIGHT) {
- if (isRelease && this.dragging && this.dragPiece) {
- this.dragging = false;
- this.dragPiece = null;
- }
- gameState.generatePositions();
- return;
- }
- else if (y >= SECTION_INFO_HEIGHT && y < SECTION_GAME_HEIGHT) {
- gx = Math.floor((x - BOARD_OFFSET_X) / CELL_SIZE);
- gy = Math.floor((y - SECTION_INFO_HEIGHT) / CELL_SIZE);
- }
- else if (y >= SECTION_GAME_HEIGHT && y < SECTION_INFO_TOP) {
- gx = Math.floor((x - BOARD_OFFSET_X) / CELL_SIZE);
- gy = Math.floor((y - SECTION_GAME_HEIGHT) / CELL_SIZE);
- inGameBoard = true;
- }
- if (gx < 0 || gx >= BOARD_SIZE || gy < 0 || gy >= BOARD_SIZE) {
- if (isRelease && this.dragging && this.dragPiece) {
- this.dragging = false;
- this.dragPiece = null;
- }
- return;
- }
- if (isRelease && this.dragging && this.dragPiece) {
- if (this.dragPiece.is_valid_move(gx, gy, this.gameState.pieces)) {
- const idx = this.gameState.pieces.indexOf(this.dragPiece);
- if (idx > -1) {
- this.gameState.pieces.splice(idx, 1);
- }
- this.dragPiece.pos = [gx, gy];
- this.gameState.pieces.push(this.dragPiece);
- this.gameState.moves++;
- this.gameState.checkVictory();
- if (this.gameState.victory) {
- this._showVictory();
- }
- }
- this.dragging = false;
- this.dragPiece = null;
- return;
- }
- const piece = this.gameState.getPieceAt(gx, gy);
- if (inGameBoard && piece) {
- this.gameState.selectedPiece = piece;
- this.dragging = true;
- this.dragPiece = piece;
- this.dragOffsetX = x;
- this.dragOffsetY = y;
- } else {
- this.gameState.selectedPiece = null;
- }
- }
- _handleKeyDown(e) {}
- _showVictory() {}
- bindGameState(gameState) {
- this.gameState = gameState;
- }
- }
- // =============================================================================
- // BUCLE PRINCIPAL
- // =============================================================================
- const canvas = document.getElementById('gameCanvas');
- canvas.width = window.innerWidth;
- canvas.height = window.innerHeight;
- const renderer = new Renderer(canvas);
- const gameState = new GameState();
- const inputHandler = new InputHandler(canvas, renderer);
- inputHandler.bindGameState(gameState);
- Assets.initialize();
- recalculate();
- updateGridCoords();
- window.addEventListener('resize', recalculate);
- function mainLoop() {
- renderer.clear();
- renderer.drawSeparators();
- renderer.drawObjectiveBoard();
- renderer.drawGameBoard(gameState.targetPositions, gameState.pieces);
- if (inputHandler.dragging && gameState.selectedPiece) {
- renderer.drawValidMoves(gameState.selectedPiece, gameState.pieces, GAME_GRID_COORDS);
- }
- renderer.drawPieces(gameState.pieces, GAME_GRID_COORDS, gameState.targetPositions);
- if (gameState.selectedPiece && inputHandler.dragging) {
- renderer.drawSelectedPiece(gameState.selectedPiece, [inputHandler.dragOffsetX, inputHandler.dragOffsetY], GAME_GRID_COORDS);
- }
- renderer.drawObjectivePanel(gameState);
- renderer.drawGameInfo(gameState);
- requestAnimationFrame(mainLoop);
- }
- window.addEventListener('click', (e) => {
- if (gameState.victory && e.target === canvas) {
- gameState.generatePositions();
- }
- });
- function checkAssetsReady() {
- if (Assets.isReady()) {
- mainLoop();
- } else {
- setTimeout(checkAssetsReady, 100);
- }
- }
- checkAssetsReady();
- </script>
- </body>
- </html>
Add Comment
Please, Sign In to add comment