Guest User

single chess

a guest
Jun 25th, 2026
71
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 42.99 KB | Gaming | 0 0
  1. <!DOCTYPE html>
  2. <html lang="es">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>Ajedrez 2D - Vertical</title>
  7. <style>
  8. body {
  9. margin: 0;
  10. padding: 0;
  11. background-color: #3c3228;
  12. height: 100vh;
  13. overflow: hidden;
  14. font-family: Arial, sans-serif;
  15. }
  16.  
  17. #gameContainer {
  18. position: relative;
  19. width: 100%;
  20. height: 100vh;
  21. }
  22.  
  23. #gameCanvas {
  24. display: block;
  25. width: 100%;
  26. height: 100%;
  27. touch-action: none;
  28. }
  29. </style>
  30. </head>
  31. <body>
  32. <div id="gameContainer">
  33. <canvas id="gameCanvas"></canvas>
  34. </div><script>
  35. const PIECE_IMAGES = {
  36. "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==',
  37. "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==',
  38. "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=',
  39. "rey": 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACHUlEQVR4nK1SPWhTURT+7rnPVF8QmwwPkoYEQqZ2EddAMmSThC4JHcXJxSGDSyEQujlWWkHQqRmSQcfsUuiUMYtEIrTkFcn0pBCNL/d+Lu9hf7QunuVwz7nfd75z+IBbQil1WxsAIH/CAZCNjY1cMpncd133KYA7Uf3WUACczc3NBAAnk8k8AhAopT7kcrl7ABLVatUBoP8qN5astcb6+nqhWCxOs9nsG61/Y0QkHnZlMgCsAXiWz+f3ATSSyeT7i4sLHh4eEsCTVCr1IpvN7gOoXMF1u10hqQqFwmA4HNL3ffb7fXqeF56entrd3d1VrVbjeDzmbDZju92m1roeKdGI5N2t1+vfSP4kuSRpRqMRS6USd3Z2GIYhSa5ILnzfp+u6r6J1HbRaLS0i8Dzv5fHxcfyRJFkulzkYDOKnJWna7fYPAA8jBaIAqG63q/b29qRUKn1sNBplkma1Wmnf9+G6LjzPg4iY+Xyue73ec6XUa5IagIkP6TiOAwCPO52Omc1m4WQysUEQ2PPzczudTldnZ2dma2vrM0lFUuIjOhGBtdYin89/Ojk5EWuthGEIYwwcx0EikZDlcqkWi8UXEWGz2RQA9jqB2t7e9g8ODt6GYdioVCoP0un0mjHGzufz70dHR34QBO9IqssWv2FPrTWMMfcBpAEkABDAAsBXpZQledWANywJiIjY2JkxwFqLaHf7L4K4frnHa/n/xS8fVwbw/zqGugAAAABJRU5ErkJggg==',
  40. "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='
  41. };</script><script>
  42. 'use strict';
  43.  
  44. // =============================================================================
  45. // CONSTANTES Y CONFIGURACIÓN
  46. // =============================================================================
  47.  
  48. let SCREEN_WIDTH = window.innerWidth;
  49. let SCREEN_HEIGHT = window.innerHeight;
  50. const BOARD_SIZE = 3;
  51.  
  52. let CELL_SIZE = (SCREEN_HEIGHT * 0.35) / 3;
  53. let BOARD_SIZE_PX = CELL_SIZE * 3;
  54. let BOARD_OFFSET_X = (SCREEN_WIDTH - BOARD_SIZE_PX) / 2;
  55.  
  56. let SEPARATION_HEIGHT = SCREEN_HEIGHT * 0.02;
  57. let SECTION_INFO_HEIGHT = SCREEN_HEIGHT * 0.08;
  58. let SECTION_OBJECTIVE_HEIGHT = SCREEN_HEIGHT * 0.35;
  59. let SECTION_GAME_HEIGHT = SECTION_INFO_HEIGHT + SECTION_OBJECTIVE_HEIGHT + SEPARATION_HEIGHT;
  60. let SECTION_INFO_TOP = SECTION_GAME_HEIGHT + SECTION_OBJECTIVE_HEIGHT + SEPARATION_HEIGHT;
  61.  
  62. let OBJECTIVE_GRID_COORDS = [];
  63. for (let gy = 0; gy < BOARD_SIZE; gy++) {
  64. for (let gx = 0; gx < BOARD_SIZE; gx++) {
  65. const cx = BOARD_OFFSET_X + gx * CELL_SIZE + CELL_SIZE / 2;
  66. const cy = SECTION_INFO_HEIGHT + gy * CELL_SIZE + CELL_SIZE / 2;
  67. OBJECTIVE_GRID_COORDS.push([cx, cy]);
  68. }
  69. }
  70.  
  71. let GAME_GRID_COORDS = [];
  72. for (let gy = 0; gy < BOARD_SIZE; gy++) {
  73. for (let gx = 0; gx < BOARD_SIZE; gx++) {
  74. const cx = BOARD_OFFSET_X + gx * CELL_SIZE + CELL_SIZE / 2;
  75. const cy = SECTION_GAME_HEIGHT + gy * CELL_SIZE + CELL_SIZE / 2;
  76. GAME_GRID_COORDS.push([cx, cy]);
  77. }
  78. }
  79.  
  80. const COLOR_BG = [60, 50, 40];
  81. const COLOR_PANEL = [40, 30, 20];
  82. const COLOR_TEXT = [220, 210, 200];
  83. const COLOR_VALID_MOVE = [0, 255, 0];
  84. const COLOR_VICTORIA = [255, 215, 0];
  85. const COLOR_BOARD_LIGHT = [210, 180, 140];
  86. const COLOR_BOARD_DARK = [139, 69, 19];
  87.  
  88. const TIPOS_PIEZAS = ['rey', 'reina', 'torre', 'alfil', 'caballo'];
  89.  
  90. // =============================================================================
  91. // GESTIÓN DE ASSETS
  92. // =============================================================================
  93.  
  94. const Assets = {
  95. pieceImages: {},
  96. loadedCount: 0,
  97. totalImages: 5,
  98.  
  99. initialize: function() {
  100. this.loadPieceImage('rey', PIECE_IMAGES.rey);
  101. this.loadPieceImage('reina', PIECE_IMAGES.reina);
  102. this.loadPieceImage('torre', PIECE_IMAGES.torre);
  103. this.loadPieceImage('alfil', PIECE_IMAGES.alfil);
  104. this.loadPieceImage('caballo', PIECE_IMAGES.caballo);
  105. },
  106.  
  107. loadPieceImage: function(tipo, url) {
  108. const img = new Image();
  109. img.src = url;
  110. img.onload = () => {
  111. this.loadedCount++;
  112. };
  113. img.onerror = () => {
  114. console.warn('No se pudo cargar ' + tipo);
  115. this.loadedCount++;
  116. };
  117. this.pieceImages[tipo] = img;
  118. },
  119.  
  120. getPieceImage: function(tipo) {
  121. return this.pieceImages[tipo] || this.pieceImages['rey'];
  122. },
  123.  
  124. isReady: function() {
  125. return this.loadedCount >= this.totalImages;
  126. }
  127. };
  128.  
  129. function updateGridCoords() {
  130. SECTION_OBJECTIVE_HEIGHT = SCREEN_HEIGHT * 0.35;
  131. SEPARATION_HEIGHT = SCREEN_HEIGHT * 0.02;
  132. SECTION_INFO_HEIGHT = SCREEN_HEIGHT * 0.08;
  133. SECTION_GAME_HEIGHT = SECTION_INFO_HEIGHT + SECTION_OBJECTIVE_HEIGHT + SEPARATION_HEIGHT;
  134. SECTION_INFO_TOP = SECTION_GAME_HEIGHT + SECTION_OBJECTIVE_HEIGHT + SEPARATION_HEIGHT;
  135.  
  136. OBJECTIVE_GRID_COORDS = [];
  137. for (let gy = 0; gy < BOARD_SIZE; gy++) {
  138. for (let gx = 0; gx < BOARD_SIZE; gx++) {
  139. const cx = BOARD_OFFSET_X + gx * CELL_SIZE + CELL_SIZE / 2;
  140. const cy = SECTION_INFO_HEIGHT + gy * CELL_SIZE + CELL_SIZE / 2;
  141. OBJECTIVE_GRID_COORDS.push([cx, cy]);
  142. }
  143. }
  144.  
  145. GAME_GRID_COORDS = [];
  146. for (let gy = 0; gy < BOARD_SIZE; gy++) {
  147. for (let gx = 0; gx < BOARD_SIZE; gx++) {
  148. const cx = BOARD_OFFSET_X + gx * CELL_SIZE + CELL_SIZE / 2;
  149. const cy = SECTION_GAME_HEIGHT + gy * CELL_SIZE + CELL_SIZE / 2;
  150. GAME_GRID_COORDS.push([cx, cy]);
  151. }
  152. }
  153. }
  154.  
  155. function recalculate() {
  156. SCREEN_WIDTH = window.innerWidth;
  157. SCREEN_HEIGHT = window.innerHeight;
  158. CELL_SIZE = (SCREEN_HEIGHT * 0.35) / 3;
  159. BOARD_SIZE_PX = CELL_SIZE * 3;
  160. BOARD_OFFSET_X = (SCREEN_WIDTH - BOARD_SIZE_PX) / 2;
  161. SEPARATION_HEIGHT = SCREEN_HEIGHT * 0.02;
  162. SECTION_INFO_HEIGHT = SCREEN_HEIGHT * 0.08;
  163. SECTION_OBJECTIVE_HEIGHT = SCREEN_HEIGHT * 0.35;
  164. SECTION_GAME_HEIGHT = SECTION_INFO_HEIGHT + SECTION_OBJECTIVE_HEIGHT + SEPARATION_HEIGHT;
  165. SECTION_INFO_TOP = SECTION_GAME_HEIGHT + SECTION_OBJECTIVE_HEIGHT + SEPARATION_HEIGHT;
  166. updateGridCoords();
  167.  
  168. canvas.width = SCREEN_WIDTH;
  169. canvas.height = SCREEN_HEIGHT;
  170. }
  171.  
  172. // =============================================================================
  173. // LÓGICA DEL JUEGO
  174. // =============================================================================
  175.  
  176. class Piece {
  177. constructor(tipo, gx, gy) {
  178. this.tipo = tipo;
  179. this.pos = [gx, gy];
  180. }
  181.  
  182. get x() {
  183. return this.pos[0];
  184. }
  185.  
  186. get y() {
  187. return this.pos[1];
  188. }
  189.  
  190. is_valid_move(gx, gy, pieces) {
  191. if (gx < 0 || gx >= BOARD_SIZE || gy < 0 || gy >= BOARD_SIZE) {
  192. return false;
  193. }
  194.  
  195. for (const p of pieces) {
  196. if (p.pos[0] === gx && p.pos[1] === gy) {
  197. return false;
  198. }
  199. }
  200.  
  201. return this._check_movement(gx, gy, pieces);
  202. }
  203.  
  204. _check_movement(gx, gy, pieces) {
  205. const x = this.pos[0];
  206. const y = this.pos[1];
  207. const dx = gx - x;
  208. const dy = gy - y;
  209.  
  210. if (this.tipo === 'rey') {
  211. return (Math.abs(dx) <= 1 && Math.abs(dy) <= 1) && (dx !== 0 || dy !== 0);
  212. } else if (this.tipo === 'reina') {
  213. if (dx === 0 || dy === 0 || Math.abs(dx) === Math.abs(dy)) {
  214. return this._is_path_clear(x, y, gx, gy, pieces);
  215. }
  216. return false;
  217. } else if (this.tipo === 'torre') {
  218. if (dx === 0 || dy === 0) {
  219. return this._is_path_clear(x, y, gx, gy, pieces);
  220. }
  221. return false;
  222. } else if (this.tipo === 'alfil') {
  223. if (Math.abs(dx) === Math.abs(dy)) {
  224. return this._is_path_clear(x, y, gx, gy, pieces);
  225. }
  226. return false;
  227. } else if (this.tipo === 'caballo') {
  228. return (Math.abs(dx) === 2 && Math.abs(dy) === 1) || (Math.abs(dx) === 1 && Math.abs(dy) === 2);
  229. }
  230.  
  231. return false;
  232. }
  233.  
  234. _is_path_clear(x1, y1, x2, y2, pieces) {
  235. const dx = (x2 === x1) ? 0 : ((x2 > x1) ? 1 : -1);
  236. const dy = (y2 === y1) ? 0 : ((y2 > y1) ? 1 : -1);
  237.  
  238. let x = x1 + dx;
  239. let y = y1 + dy;
  240.  
  241. while (x !== x2 || y !== y2) {
  242. for (const p of pieces) {
  243. if (p.pos[0] === x && p.pos[1] === y) {
  244. return false;
  245. }
  246. }
  247. x += dx;
  248. y += dy;
  249. }
  250.  
  251. return true;
  252. }
  253.  
  254. get_possible_moves(pieces) {
  255. const moves = [];
  256. const x = this.pos[0];
  257. const y = this.pos[1];
  258.  
  259. if (this.tipo === 'rey') {
  260. for (let dx = -1; dx <= 1; dx++) {
  261. for (let dy = -1; dy <= 1; dy++) {
  262. if (dx === 0 && dy === 0) continue;
  263. const new_x = x + dx;
  264. const new_y = y + dy;
  265. if (this.is_valid_move(new_x, new_y, pieces)) {
  266. moves.push([new_x, new_y]);
  267. }
  268. }
  269. }
  270. } else if (this.tipo === 'reina') {
  271. 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));
  272. } else if (this.tipo === 'torre') {
  273. moves.push(...this._get_linear_moves(x, y, [[1, 0], [-1, 0], [0, 1], [0, -1]], pieces));
  274. } else if (this.tipo === 'alfil') {
  275. moves.push(...this._get_linear_moves(x, y, [[1, 1], [1, -1], [-1, 1], [-1, -1]], pieces));
  276. } else if (this.tipo === 'caballo') {
  277. for (const [dx, dy] of [[2, 1], [2, -1], [-2, 1], [-2, -1], [1, 2], [1, -2], [-1, 2], [-1, -2]]) {
  278. const new_x = x + dx;
  279. const new_y = y + dy;
  280. if (this.is_valid_move(new_x, new_y, pieces)) {
  281. moves.push([new_x, new_y]);
  282. }
  283. }
  284. }
  285.  
  286. return moves;
  287. }
  288.  
  289. _get_linear_moves(x, y, directions, pieces) {
  290. const moves = [];
  291. for (const [dx, dy] of directions) {
  292. let new_x = x + dx;
  293. let new_y = y + dy;
  294. while (new_x >= 0 && new_x < BOARD_SIZE && new_y >= 0 && new_y < BOARD_SIZE) {
  295. if (pieces.some(p => p.pos[0] === new_x && p.pos[1] === new_y)) {
  296. break;
  297. }
  298. if (this.is_valid_move(new_x, new_y, pieces)) {
  299. moves.push([new_x, new_y]);
  300. }
  301. new_x += dx;
  302. new_y += dy;
  303. }
  304. }
  305. return moves;
  306. }
  307. }
  308.  
  309. class GameState {
  310. constructor() {
  311. this.pieces = [];
  312. this.selectedPiece = null;
  313. this.victory = false;
  314. this.targetPositions = {};
  315. this.moves = 0;
  316. recalculate();
  317. updateGridCoords();
  318. this.generatePositions();
  319. }
  320.  
  321. generatePositions() {
  322. const maxIntentos = 100;
  323. let intentos = 0;
  324.  
  325. while (intentos < maxIntentos) {
  326. intentos++;
  327.  
  328. const positions = [];
  329. for (let y = 0; y < BOARD_SIZE; y++) {
  330. for (let x = 0; x < BOARD_SIZE; x++) {
  331. positions.push([x, y]);
  332. }
  333. }
  334. this._shuffleArray(positions);
  335.  
  336. this.targetPositions = {};
  337. for (let i = 0; i < TIPOS_PIEZAS.length; i++) {
  338. this.targetPositions[TIPOS_PIEZAS[i]] = positions[i];
  339. }
  340.  
  341. if (JSON.stringify(this.targetPositions['caballo']) === JSON.stringify([1, 1])) {
  342. for (const pos of positions) {
  343. if (JSON.stringify(pos) !== JSON.stringify([1, 1]) && !Object.values(this.targetPositions).some(p => JSON.stringify(p) === JSON.stringify(pos))) {
  344. this.targetPositions['caballo'] = pos;
  345. break;
  346. }
  347. }
  348. }
  349.  
  350. const blancas = ['[0,0]', '[2,0]', '[1,1]', '[0,2]', '[2,2]'];
  351. this._shuffleArray(positions);
  352.  
  353. this.pieces = [];
  354. for (const tipo of TIPOS_PIEZAS) {
  355. const pos = positions.pop();
  356. this.pieces.push(new Piece(tipo, pos[0], pos[1]));
  357. }
  358.  
  359. const caballoInicial = this.pieces.find(p => p.tipo === 'caballo');
  360. if (JSON.stringify(caballoInicial.pos) === JSON.stringify([1, 1])) {
  361. for (const pos of positions) {
  362. if (JSON.stringify(pos) !== JSON.stringify([1, 1]) && !this.pieces.some(p => JSON.stringify(p.pos) === JSON.stringify(pos))) {
  363. caballoInicial.pos = pos;
  364. break;
  365. }
  366. }
  367. }
  368.  
  369. const alfilObjetivo = this.targetPositions['alfil'];
  370. const alfilInicial = this.pieces.find(p => p.tipo === 'alfil');
  371.  
  372. const alfilColorObj = blancas.includes(JSON.stringify(alfilObjetivo));
  373. const alfilColorIni = blancas.includes(JSON.stringify(alfilInicial.pos));
  374.  
  375. if (alfilColorObj === alfilColorIni) {
  376. break;
  377. }
  378. }
  379.  
  380. this.victory = false;
  381. this.selectedPiece = null;
  382. this.moves = 0;
  383. updateGridCoords();
  384.  
  385. SolutionCalculator.calculateMinMoves(this.pieces, this.targetPositions).then(result => {
  386. this.minMoves = result;
  387. });
  388. }
  389.  
  390. checkVictory() {
  391. for (const piece of this.pieces) {
  392. const targetPos = this.targetPositions[piece.tipo];
  393. if (!targetPos || JSON.stringify(piece.pos) !== JSON.stringify(targetPos)) {
  394. this.victory = false;
  395. return;
  396. }
  397. }
  398. this.victory = true;
  399. this.moves = 0;
  400. }
  401.  
  402. getPieceAt(gx, gy) {
  403. for (const piece of this.pieces) {
  404. if (piece.pos[0] === gx && piece.pos[1] === gy) {
  405. return piece;
  406. }
  407. }
  408. return null;
  409. }
  410.  
  411. _shuffleArray(array) {
  412. for (let i = array.length - 1; i > 0; i--) {
  413. const j = Math.floor(Math.random() * (i + 1));
  414. [array[i], array[j]] = [array[j], array[i]];
  415. }
  416. }
  417. }
  418.  
  419. class SolutionCalculator {
  420. static async calculateMinMoves(pieces, targetPositions) {
  421. const PIECE_ORDER = ['rey', 'reina', 'torre', 'alfil', 'caballo'];
  422.  
  423. const stateHash = (pieces) => {
  424. return JSON.stringify(pieces.map(p => ({
  425. tipo: p.tipo,
  426. x: p.pos[0],
  427. y: p.pos[1]
  428. })).sort((a, b) => PIECE_ORDER.indexOf(a.tipo) - PIECE_ORDER.indexOf(b.tipo)));
  429. };
  430.  
  431. const isTarget = (pieces) => {
  432. return pieces.every(p => JSON.stringify(targetPositions[p.tipo]) === JSON.stringify(p.pos));
  433. };
  434.  
  435. const getAllMoves = (pieces) => {
  436. const moves = [];
  437. for (const piece of pieces) {
  438. const pieceObj = new Piece(piece.tipo, piece.pos[0], piece.pos[1]);
  439. for (const newPos of pieceObj.get_possible_moves(pieces.map(p => new Piece(p.tipo, p.pos[0], p.pos[1])))) {
  440. moves.push({ tipo: piece.tipo, newPos });
  441. }
  442. }
  443. return moves;
  444. };
  445.  
  446. const applyMove = (pieces, tipo, newPos) => {
  447. 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]] });
  448. };
  449.  
  450. const initialHash = stateHash(pieces);
  451. if (isTarget(pieces)) return 0;
  452.  
  453. const visited = new Set([initialHash]);
  454. const queue = [{ pieces: pieces.map(p => ({ tipo: p.tipo, pos: [p.pos[0], p.pos[1]] })), depth: 0 }];
  455.  
  456. let statesVisited = 0;
  457. const MAX_STATES = 50000;
  458. const BATCH_SIZE = 1000;
  459.  
  460. return new Promise((resolve) => {
  461. const processBatch = () => {
  462. let batchProcessed = 0;
  463.  
  464. while (queue.length > 0 && batchProcessed < BATCH_SIZE) {
  465. const { pieces: current, depth } = queue.shift();
  466. statesVisited++;
  467. batchProcessed++;
  468.  
  469. if (statesVisited > MAX_STATES) {
  470. resolve(null);
  471. return;
  472. }
  473.  
  474. for (const { tipo, newPos } of getAllMoves(current)) {
  475. const newPieces = applyMove(current, tipo, newPos);
  476. const newHash = stateHash(newPieces);
  477.  
  478. if (!visited.has(newHash)) {
  479. if (isTarget(newPieces)) {
  480. resolve(depth + 1);
  481. return;
  482. }
  483.  
  484. visited.add(newHash);
  485. queue.push({ pieces: newPieces, depth: depth + 1 });
  486. }
  487. }
  488. }
  489.  
  490. if (queue.length > 0) {
  491. setTimeout(processBatch, 0);
  492. } else {
  493. resolve(null);
  494. }
  495. };
  496.  
  497. processBatch();
  498. });
  499. }
  500. }
  501.  
  502. // =============================================================================
  503. // RENDERIZADO
  504. // =============================================================================
  505.  
  506. class Renderer {
  507. constructor(canvas) {
  508. this.canvas = canvas;
  509. this.ctx = canvas.getContext('2d');
  510. }
  511.  
  512. clear() {
  513. this.ctx.fillStyle = `rgb(${COLOR_BG[0]}, ${COLOR_BG[1]}, ${COLOR_BG[2]})`;
  514. this.ctx.fillRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
  515. }
  516.  
  517. drawSeparators() {
  518. const separatorColor = `rgb(${COLOR_PANEL[0]}, ${COLOR_PANEL[1]}, ${COLOR_PANEL[2]})`;
  519. this.ctx.fillStyle = separatorColor;
  520. this.ctx.fillRect(0, SECTION_INFO_HEIGHT, SCREEN_WIDTH, SEPARATION_HEIGHT);
  521. this.ctx.fillRect(0, SECTION_GAME_HEIGHT, SCREEN_WIDTH, SEPARATION_HEIGHT);
  522. }
  523.  
  524. drawGrid(coords, offsetBaseY) {
  525. for (let i = 0; i < coords.length; i++) {
  526. const gx = i % BOARD_SIZE;
  527. const gy = Math.floor(i / BOARD_SIZE);
  528.  
  529. const x = BOARD_OFFSET_X + gx * CELL_SIZE;
  530. const y = offsetBaseY + gy * CELL_SIZE;
  531.  
  532. const color = (gx + gy) % 2 === 0 ? COLOR_BOARD_LIGHT : COLOR_BOARD_DARK;
  533. this.ctx.fillStyle = `rgb(${color[0]}, ${color[1]}, ${color[2]})`;
  534. this.ctx.fillRect(x, y, CELL_SIZE, CELL_SIZE);
  535.  
  536. this.ctx.strokeStyle = `rgb(${COLOR_PANEL[0]}, ${COLOR_PANEL[1]}, ${COLOR_PANEL[2]})`;
  537. this.ctx.lineWidth = 2;
  538. this.ctx.strokeRect(x, y, CELL_SIZE, CELL_SIZE);
  539. }
  540. }
  541.  
  542. drawObjectiveBoard() {
  543. this.drawGrid(OBJECTIVE_GRID_COORDS, SECTION_INFO_HEIGHT);
  544. }
  545.  
  546. drawGameBoard(targetPositions, pieces) {
  547. this.drawGrid(GAME_GRID_COORDS, SECTION_GAME_HEIGHT, targetPositions, pieces);
  548. }
  549.  
  550. drawPieces(pieces, coords, targetPositions = null) {
  551. for (const piece of pieces) {
  552. if (targetPositions) {
  553. const targetPos = targetPositions[piece.tipo];
  554. if (targetPos && targetPos[0] === piece.pos[0] && targetPos[1] === piece.pos[1]) {
  555. this.drawCorrectPieceMarker(piece, coords);
  556. }
  557. }
  558.  
  559. this._drawPiece(piece, coords);
  560. }
  561. }
  562.  
  563. _drawPiece(piece, coords) {
  564. const idx = piece.y * BOARD_SIZE + piece.x;
  565. if (idx < coords.length) {
  566. const [cx, cy] = coords[idx];
  567. const img = Assets.getPieceImage(piece.tipo);
  568.  
  569. const pieceSize = CELL_SIZE - 10;
  570. const drawX = cx - pieceSize / 2;
  571. const drawY = cy - pieceSize / 2;
  572.  
  573. this.ctx.drawImage(img, drawX, drawY, pieceSize, pieceSize);
  574. }
  575. }
  576.  
  577. drawValidMoves(selectedPiece, pieces, coords) {
  578. if (!selectedPiece) return;
  579.  
  580. const moves = selectedPiece.get_possible_moves(pieces);
  581. this.ctx.fillStyle = `rgba(${COLOR_VALID_MOVE[0]}, ${COLOR_VALID_MOVE[1]}, ${COLOR_VALID_MOVE[2]}, 0.4)`;
  582. this.ctx.strokeStyle = `rgb(${COLOR_VALID_MOVE[0]}, ${COLOR_VALID_MOVE[1]}, ${COLOR_VALID_MOVE[2]})`;
  583. this.ctx.lineWidth = 3;
  584.  
  585. for (const [gx, gy] of moves) {
  586. const idx = gy * BOARD_SIZE + gx;
  587. if (idx < coords.length) {
  588. const [cx, cy] = coords[idx];
  589. this.ctx.beginPath();
  590. this.ctx.arc(cx, cy, 20, 0, Math.PI * 2);
  591. this.ctx.fill();
  592. this.ctx.stroke();
  593. }
  594. }
  595. }
  596.  
  597. drawSelectedPiece(piece, mousePos, coords) {
  598. if (!piece) return;
  599.  
  600. const [mx, my] = mousePos;
  601. const img = Assets.getPieceImage(piece.tipo);
  602.  
  603. const pieceSize = CELL_SIZE - 10;
  604. this.ctx.drawImage(img, mx - pieceSize / 2, my - pieceSize / 2, pieceSize, pieceSize);
  605. }
  606.  
  607. drawCorrectPieceMarker(piece, coords) {
  608. const idx = piece.y * BOARD_SIZE + piece.x;
  609. if (idx < coords.length) {
  610. const [cx, cy] = coords[idx];
  611. const markerRadius = CELL_SIZE / 2.5;
  612.  
  613. this.ctx.fillStyle = 'rgba(0, 255, 0, 0.4)';
  614. this.ctx.beginPath();
  615. this.ctx.arc(cx, cy, markerRadius, 0, Math.PI * 2);
  616. this.ctx.fill();
  617.  
  618. this.ctx.strokeStyle = 'rgba(0, 255, 0, 0.8)';
  619. this.ctx.lineWidth = 3;
  620. this.ctx.stroke();
  621. }
  622. }
  623.  
  624. drawObjectivePanel(gameState) {
  625. for (const [tipo, pos] of Object.entries(gameState.targetPositions)) {
  626. const [gx, gy] = pos;
  627. const idx = gy * BOARD_SIZE + gx;
  628. if (idx < OBJECTIVE_GRID_COORDS.length) {
  629. const [cx, cy] = OBJECTIVE_GRID_COORDS[idx];
  630. const img = Assets.getPieceImage(tipo);
  631.  
  632. const pieceSize = CELL_SIZE - 10;
  633. const drawX = cx - pieceSize / 2;
  634. const drawY = cy - pieceSize / 2;
  635.  
  636. this.ctx.drawImage(img, drawX, drawY, pieceSize, pieceSize);
  637. }
  638. }
  639. }
  640.  
  641. drawGameInfo(gameState) {
  642. const infoY = SECTION_INFO_HEIGHT / 2 + 10;
  643. const ctx = this.ctx;
  644. const centerX = SCREEN_WIDTH / 2;
  645.  
  646. ctx.fillStyle = `rgb(${COLOR_TEXT[0]}, ${COLOR_TEXT[1]}, ${COLOR_TEXT[2]})`;
  647. ctx.font = '16px Arial';
  648. ctx.textAlign = 'center';
  649. ctx.fillText('Ajedrez 2D | Arrastra fichas. Pulsa AQUI para Nuevo.', centerX, infoY);
  650.  
  651. const movesText = `Movimientos: ${gameState.moves}`;
  652. const minMovesText = gameState.minMoves === null ? 'Calculando...' : `Mínimo: ${gameState.minMoves}`;
  653.  
  654. ctx.font = '16px Arial';
  655. ctx.textAlign = 'center';
  656. ctx.fillText(movesText, centerX - 150, infoY + 25);
  657. ctx.fillText(minMovesText, centerX + 150, infoY + 25);
  658. }
  659.  
  660. drawVictory() {
  661. const ctx = this.ctx;
  662.  
  663. ctx.fillStyle = 'rgba(0, 0, 0, 0.6)';
  664. ctx.fillRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
  665.  
  666. ctx.fillStyle = `rgb(${COLOR_VICTORIA[0]}, ${COLOR_VICTORIA[1]}, ${COLOR_VICTORIA[2]})`;
  667. ctx.font = 'bold 64px Arial';
  668. ctx.textAlign = 'center';
  669. ctx.textBaseline = 'middle';
  670. ctx.fillText('¡VICTORIA!', SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2);
  671. }
  672.  
  673. drawLabel(text, centerX, centerY) {
  674. const ctx = this.ctx;
  675. const fontSize = Math.min(CELL_SIZE * 0.25, 30);
  676. ctx.fillStyle = `rgb(${COLOR_TEXT[0]}, ${COLOR_TEXT[1]}, ${COLOR_TEXT[2]})`;
  677. ctx.font = `bold ${fontSize}px Arial`;
  678. ctx.textAlign = 'center';
  679. ctx.textBaseline = 'middle';
  680. ctx.fillText(text, centerX, centerY);
  681. }
  682. }
  683.  
  684. // =============================================================================
  685. // INPUT HANDLER
  686. // =============================================================================
  687.  
  688. class InputHandler {
  689. constructor(canvas, renderer) {
  690. this.canvas = canvas;
  691. this.renderer = renderer;
  692. this.dragging = false;
  693. this.dragPiece = null;
  694. this.dragOffsetX = 0;
  695. this.dragOffsetY = 0;
  696.  
  697. this.canvas.addEventListener('mousedown', (e) => this._handleMouseDown(e));
  698. this.canvas.addEventListener('mousemove', (e) => this._handleMouseMove(e));
  699. this.canvas.addEventListener('mouseup', (e) => this._handleMouseUp(e));
  700. this.canvas.addEventListener('mouseleave', (e) => this._handleMouseUp(e));
  701.  
  702. this.canvas.addEventListener('touchstart', (e) => this._handleTouchStart(e), { passive: false });
  703. this.canvas.addEventListener('touchmove', (e) => this._handleTouchMove(e), { passive: false });
  704. this.canvas.addEventListener('touchend', (e) => this._handleTouchEnd(e));
  705.  
  706. document.addEventListener('keydown', (e) => this._handleKeyDown(e));
  707. }
  708.  
  709. _getMousePos(e) {
  710. const rect = this.canvas.getBoundingClientRect();
  711. return [e.clientX - rect.left, e.clientY - rect.top];
  712. }
  713.  
  714. _getTouchPos(e) {
  715. const rect = this.canvas.getBoundingClientRect();
  716. if (!e.touches || e.touches.length === 0) {
  717. return null;
  718. }
  719. const touch = e.touches[0];
  720. return [touch.clientX - rect.left, touch.clientY - rect.top];
  721. }
  722.  
  723. _getTouchEndPos(e) {
  724. const rect = this.canvas.getBoundingClientRect();
  725. if (!e.changedTouches || e.changedTouches.length === 0) {
  726. return null;
  727. }
  728. const touch = e.changedTouches[0];
  729. return [touch.clientX - rect.left, touch.clientY - rect.top];
  730. }
  731.  
  732. _handleMouseDown(e) {
  733. if (e.button !== 0) return;
  734.  
  735. const [mx, my] = this._getMousePos(e);
  736. this._processInput(mx, my);
  737. }
  738.  
  739. _handleMouseMove(e) {
  740. if (!this.dragging) return;
  741.  
  742. e.preventDefault();
  743.  
  744. const [mx, my] = this._getMousePos(e);
  745. this.dragOffsetX = mx;
  746. this.dragOffsetY = my;
  747. }
  748.  
  749. _handleMouseUp(e) {
  750. if (!this.dragging) return;
  751.  
  752. const [mx, my] = this._getMousePos(e);
  753. this._processInput(mx, my, true);
  754.  
  755. this.dragging = false;
  756. this.dragPiece = null;
  757. }
  758.  
  759. _handleTouchStart(e) {
  760. e.preventDefault();
  761.  
  762. const pos = this._getTouchPos(e);
  763. if (pos === null) return;
  764. const [mx, my] = pos;
  765. this._processInput(mx, my);
  766. }
  767.  
  768. _handleTouchMove(e) {
  769. e.preventDefault();
  770.  
  771. if (!this.dragging) return;
  772.  
  773. const pos = this._getTouchPos(e);
  774. if (pos === null) return;
  775. const [mx, my] = pos;
  776. this.dragOffsetX = mx;
  777. this.dragOffsetY = my;
  778. }
  779.  
  780. _handleTouchEnd(e) {
  781. if (!this.dragging) return;
  782.  
  783. const pos = this._getTouchEndPos(e);
  784. if (pos === null) return;
  785. const [mx, my] = pos;
  786. this._processInput(mx, my, true);
  787.  
  788. this.dragging = false;
  789. this.dragPiece = null;
  790. }
  791.  
  792. _processInput(x, y, isRelease = false) {
  793. if (x === null || y === null) return;
  794.  
  795. let gx, gy;
  796. let inGameBoard = false;
  797.  
  798. if (y < SECTION_INFO_HEIGHT) {
  799. if (isRelease && this.dragging && this.dragPiece) {
  800. this.dragging = false;
  801. this.dragPiece = null;
  802. }
  803. gameState.generatePositions();
  804. return;
  805. }
  806. else if (y >= SECTION_INFO_HEIGHT && y < SECTION_GAME_HEIGHT) {
  807. gx = Math.floor((x - BOARD_OFFSET_X) / CELL_SIZE);
  808. gy = Math.floor((y - SECTION_INFO_HEIGHT) / CELL_SIZE);
  809. }
  810. else if (y >= SECTION_GAME_HEIGHT && y < SECTION_INFO_TOP) {
  811. gx = Math.floor((x - BOARD_OFFSET_X) / CELL_SIZE);
  812. gy = Math.floor((y - SECTION_GAME_HEIGHT) / CELL_SIZE);
  813. inGameBoard = true;
  814. }
  815.  
  816. if (gx < 0 || gx >= BOARD_SIZE || gy < 0 || gy >= BOARD_SIZE) {
  817. if (isRelease && this.dragging && this.dragPiece) {
  818. this.dragging = false;
  819. this.dragPiece = null;
  820. }
  821. return;
  822. }
  823.  
  824. if (isRelease && this.dragging && this.dragPiece) {
  825. if (this.dragPiece.is_valid_move(gx, gy, this.gameState.pieces)) {
  826. const idx = this.gameState.pieces.indexOf(this.dragPiece);
  827. if (idx > -1) {
  828. this.gameState.pieces.splice(idx, 1);
  829. }
  830. this.dragPiece.pos = [gx, gy];
  831. this.gameState.pieces.push(this.dragPiece);
  832. this.gameState.moves++;
  833. this.gameState.checkVictory();
  834.  
  835. if (this.gameState.victory) {
  836. this._showVictory();
  837. }
  838. }
  839.  
  840. this.dragging = false;
  841. this.dragPiece = null;
  842. return;
  843. }
  844.  
  845. const piece = this.gameState.getPieceAt(gx, gy);
  846.  
  847. if (inGameBoard && piece) {
  848. this.gameState.selectedPiece = piece;
  849. this.dragging = true;
  850. this.dragPiece = piece;
  851. this.dragOffsetX = x;
  852. this.dragOffsetY = y;
  853. } else {
  854. this.gameState.selectedPiece = null;
  855. }
  856. }
  857.  
  858. _handleKeyDown(e) {}
  859.  
  860. _showVictory() {}
  861.  
  862. bindGameState(gameState) {
  863. this.gameState = gameState;
  864. }
  865. }
  866.  
  867. // =============================================================================
  868. // BUCLE PRINCIPAL
  869. // =============================================================================
  870.  
  871. const canvas = document.getElementById('gameCanvas');
  872. canvas.width = window.innerWidth;
  873. canvas.height = window.innerHeight;
  874.  
  875. const renderer = new Renderer(canvas);
  876. const gameState = new GameState();
  877. const inputHandler = new InputHandler(canvas, renderer);
  878.  
  879. inputHandler.bindGameState(gameState);
  880.  
  881. Assets.initialize();
  882. recalculate();
  883. updateGridCoords();
  884.  
  885. window.addEventListener('resize', recalculate);
  886.  
  887. function mainLoop() {
  888. renderer.clear();
  889. renderer.drawSeparators();
  890.  
  891. renderer.drawObjectiveBoard();
  892. renderer.drawGameBoard(gameState.targetPositions, gameState.pieces);
  893.  
  894. if (inputHandler.dragging && gameState.selectedPiece) {
  895. renderer.drawValidMoves(gameState.selectedPiece, gameState.pieces, GAME_GRID_COORDS);
  896. }
  897. renderer.drawPieces(gameState.pieces, GAME_GRID_COORDS, gameState.targetPositions);
  898.  
  899. if (gameState.selectedPiece && inputHandler.dragging) {
  900. renderer.drawSelectedPiece(gameState.selectedPiece, [inputHandler.dragOffsetX, inputHandler.dragOffsetY], GAME_GRID_COORDS);
  901. }
  902.  
  903. renderer.drawObjectivePanel(gameState);
  904. renderer.drawGameInfo(gameState);
  905.  
  906. requestAnimationFrame(mainLoop);
  907. }
  908.  
  909. window.addEventListener('click', (e) => {
  910. if (gameState.victory && e.target === canvas) {
  911. gameState.generatePositions();
  912. }
  913. });
  914.  
  915. function checkAssetsReady() {
  916. if (Assets.isReady()) {
  917. mainLoop();
  918. } else {
  919. setTimeout(checkAssetsReady, 100);
  920. }
  921. }
  922.  
  923. checkAssetsReady();
  924. </script>
  925. </body>
  926. </html>
Tags: Chess
Add Comment
Please, Sign In to add comment