js实现一个拼图小游戏代码
代码语言:html
所属分类:游戏
代码描述:js实现一个拼图小游戏代码,start game 在这个游戏中,你通过改变拼图碎片的大小来移动它们。每个动作涉及两个相邻的碎片,并分为两个阶段进行。 首先,拖动拼图碎片的边缘以减小一个维度(高度或宽度),从而创建一个空位 然后,这个空位必须通过增加另一个维度(宽度或高度)来被邻近的碎片恰好填满 将鼠标光标悬停在拼图碎片的边缘上,可以找到一个合法的动作。红色和绿色虚线表示这个边缘是否可以合法移动。当线条为绿色时,按下鼠标左键,并在移动边缘到白色线之一时保持按住。然后释放按钮,你就完成了第一阶段。 大多
下面为部分代码预览,完整代码请点击下载或在bfwstudio webide中打开
<!DOCTYPE html> <html lang="en" > <head> <meta charset="UTF-8"> <style> body { font-family: Arial, Helvetica, "Liberation Sans", FreeSans, sans-serif; background-color: #000; margin: 0; padding: 0; border-width: 0; overflow: hidden; cursor: pointer; position: relative; } .explic { background-color: white; position: absolute; z-index: 10; left: 50vw; top: 50vh; transform: translate(-50%, -50%); padding: 5px; border: 10px solid #ccc; border-radius: 15px; min-width: 75%; /* max-width: 75%; */ display: none; } .explic.disp { display: block; } .explic .buttons { text-align: center; } .explic button { margin-left: 10px; margin-right: 10px; } .highligh { font-style: italic; } input { caret-color: auto; } #menu { font-size: 80%; margin: 0; padding: 5px; position: absolute; left: 5px; top: 5px; border-radius: 10px; background-color: rgba(255, 255, 128, 0.9); color: black; z-index: 10; } #menu.hidden #showhide { display: none; } #controls { margin-top: 0px; margin-bottom: 0px; } #menu.hidden #controls { color: #00c; font-weight: bold; font-size: 200%; } #menu button { margin-right: 5px; margin-left: 5px; border-radius: 5px; } #menu .center { text-align: center; } #splash { color: white; z-index: 15; position: absolute; left: 50%; top: 50vh; transform: translate(-50%, -50%); border-radius: 20px; border: 10px solid white; font-size: 400%; padding: 0.25ex; } #splash.hidden { display: none; } </style> </head> <body translate="no"> <div id="menu"> <p id="controls">≡</p> <div id="showhide"> <hr> <p><label for="numbered">numbered:</label> <input type="checkbox" id="numbered"></p> <p><label for="level">level:</label> <select id="level"> <option value="1" selected>1.5 x 1.5</option> <option value="2">2.5 x 1.5</option> <option value="3">2.5 x 2.5</option> <option value="4">3.5 x 2.5</option> <option value="5">3.5 x 3.5</option> </select></p> <p class="center"><button type="button" id="instructions">instructions</button></p> <p class="center"><button type="button" id="start">start game</button></p> </div> <!-- showhide --> </div> <!-- menu --> <div id="splash">YOU WIN!</div> <div class="explic" id="explic1"> <p>这个游戏的目标是通过重新排列拼图碎片来重现初始配置。</p> <p>初始配置在游戏表面右侧和底部边缘有一排1 x 1的方块,其余部分为2 x 2的方块。</p> <p>初学者应从非编号版本开始。当你轻松解决时,尝试编号版本。会更难。</p> <p class="buttons"><button type="button" class="next">next</button><button type="button" class="close">close</button></p> </div> <div class="explic" id="explic2"> <p>在这个游戏中,你通过改变拼图碎片的大小来移动它们。每个动作涉及两个相邻的碎片,并分为两个阶段进行。 <p> <ul> <li>首先,拖动拼图碎片的边缘以减小一个维度(高度或宽度),从而创建一个空位</li> <li>然后,这个空位必须通过增加<span class="highligh">另一个维度</span>(宽度或高度)来被邻近的碎片恰好填满</li> </ul> <p>将鼠标光标悬停在拼图碎片的边缘上,可以找到一个合法的动作。红色和绿色虚线表示这个边缘是否可以合法移动。当线条为绿色时,按下鼠标左键,并在移动边缘到白色线之一时保持按住。然后释放按钮,你就完成了第一阶段。</p> <p>大多数情况下,动作的第二部分只有一种可能性。它会自动发生。</p> <p>在某些情况下,系统会要求你选择哪个方块来填补空位。只需点击其中一个即可。</p> <p class="buttons"><button type="button" class="next">next</button><button type="button" class="close">close</button></p> </div> <div class="explic" id="explic3"> <p>游戏开始时,会显示原始位置。点击拼图一次以打乱碎片。</p> <p>然后……尽情享受吧!</p> <p class="buttons"><button type="button" class="close">close</button></p> </div> <script > "use strict"; let canv, ctx; // canvas and context let maxx, maxy; // canvas dimensions let grid; let mousePosition; let rectangles; let explics; const bgColor = "#000"; let ui, uiv; // for animation let messages; let splash; let firstRun = true; // styles for lines const LINE_ATTR_HOVER_NO = { color: "#f00", lineDash: [10, 10], lineWidth: 3 }; const LINE_ATTR_HOVER_YES = { color: "#0f0", lineDash: [10, 10], lineWidth: 3 }; const LINE_ATTR_CHOICE = { color: "#0f0", lineDash: [5, 5], lineWidth: 2 }; const LINE_ATTR_TARGET = { lineWidth: 1.5, color: "#fff" }; let tolMouse = 10; // tolerance on mouse position // shortcuts for Math. const mrandom = Math.random; const mfloor = Math.floor; const mround = Math.round; const mceil = Math.ceil; const mabs = Math.abs; const mmin = Math.min; const mmax = Math.max; const mPI = Math.PI; const mPIS2 = Math.PI / 2; const mPIS3 = Math.PI / 3; const m2PI = Math.PI * 2; const m2PIS3 = (Math.PI * 2) / 3; const msin = Math.sin; const mcos = Math.cos; const matan2 = Math.atan2; const mhypot = Math.hypot; const msqrt = Math.sqrt; const rac3 = msqrt(3); const rac3s2 = rac3 / 2; //------------------------------------------------------------------------ function isMiniature() { return location.pathname.includes("/fullcpgrid/"); } function alea(mini, maxi) { // random number in given range if (typeof maxi == "undefined") return mini * mrandom(); // range 0..mini return mini + mrandom() * (maxi - mini); // range mini..maxi } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - function intAlea(mini, maxi) { // random integer in given range (mini..maxi - 1 or 0..mini - 1) // if (typeof maxi == "undefined") return mfloor(mini * mrandom()); // range 0..mini - 1 return mini + mfloor(mrandom() * (maxi - mini)); // range mini .. maxi - 1 } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - function lerp(v0, v1, alpha) { return v0 * (1 - alpha) + v1 * alpha; } //------------------------------------------------------------------------ function setMenu(open) { const menu = document.getElementById("menu"); const ctrl = document.querySelector("#controls"); splash.classList.add("hidden"); if (open) { menu.classList.remove("hidden"); ctrl.innerHTML = "close controls"; } else { fclose(); // hide instructions too menu.classList.add("hidden"); ctrl.innerHTML = "≡"; } } // function toggleMenu() { const menu = document.getElementById("menu"); setMenu(menu.classList.contains("hidden")); } // toggleMenu // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - function prepareUI() { document.querySelector("#controls").addEventListener("click", toggleMenu); ui = {}; // User Interface HTML elements uiv = {}; // User Interface values of controls ["numbered", "level", "instructions", "start"].forEach( (ctrlName) => (ui[ctrlName] = document.getElementById(ctrlName)) ); readUI(); ui.instructions.addEventListener("click", startExplic); ui.start.addEventListener("click", startGame); } function readUI() { uiv.numbered = ui.numbered.checked; uiv.level = parseInt(ui.level.value, 10); } function startExplic() { fclose(); // in case already in progress explics[0].page.classList.add("disp"); } function startGame() { readUI(); messages.push({ message: "reset" }); } //------------------------------------------------------------------------ //------------------------------------------------------------------------ class Rectangle { static #radius = 7; constructor(kx0, ky0, nx, ny, grid) { this.kx0 = kx0; this.kx1 = kx0 + nx - 1; this.ky0 = ky0; this.ky1 = ky0 + ny - 1; this.hue = intAlea(360); this.sat = 30 + mround(70 * (1 - alea(1) * alea(1))); for (let ky = ky0; ky <= this.ky1; ++ky) { grid[ky] = grid[ky] || []; for (let kx = kx0; kx <= this.kx1; ++kx) { grid[ky][kx] = { rect: this }; } } this.grid = grid; this.num = rectangles.length + 1; } setCoords() { this.ux0 = this.grid.offsx + this.kx0 * this.grid.len; // u for "unmargined" this.uy0 = this.grid.offsy + this.ky0 * this.grid.len; this.ux1 = this.grid.offsx + (this.kx1 + 1) * this.grid.len; this.uy1 = this.grid.offsy + (this.ky1 + 1) * this.grid.len; this.x0 = this.ux0 + 1; this.y0 = this.uy0 + 1; this.x1 = this.ux1 - 1; this.y1 = this.uy1 - 1; } draw(partial) { /* partial is optional if used, the rectangle will be drawn with one edge in a position different from the normal one */ let edge, pos; let radius = Rectangle.#radius; let { x0, x1, y0, y1 } = this; if (partial) { ({ edge, pos } = partial); switch (edge) { case 0: y0 = mmin(y1 - this.grid.len + 2, mmax(y0, pos)); break; case 1: x1 = mmax(x0 + this.grid.len - 2, mmin(x1, pos)); break; case 2: y1 = mmax(y0 + this.grid.len - 2, mmin(y1, pos)); break; case 3: x0 = mmin(x1 - this.grid.len + 2, mmax(x0, pos)); break; } } if (2 * radius > x1 - x0) radius = (x1 - x0) / 2; if (2 * radius > y1 - y0) radius = (y1 - y0) / 2; let gr; let dx = (x1 - (x0 + y1 - y0)) / 2; gr = ctx.createLinearGradient(x0, y1, x1 - dx, y0 - dx); gr.addColorStop(0, `hsl(${this.hue} ${this.sat}% 25%)`); gr.addColorStop(0.4, `hsl(${this.hue} ${this.sat}% 45%)`); gr.addColorStop(0.5, `hsl(${this.hue} ${this.sat}% 50%)`); gr.addColorStop(0.6, `hsl(${this.hue} ${this.sat}% 55%)`); gr.addColorStop(1, `hsl(${this.hue} ${this.sat}% 75%)`); ctx.beginPath(); ctx.moveTo((x0 + x1) / 2, y0); ctx.arcTo(x1, y0, x1, y1, radius); ctx.arcTo(x1, y1, x0, y1, radius); ctx.arcTo(x0, y1, x0, y0, radius); ctx.arcTo(x0, y0, x1, y0, radius); ctx.closePath(); ctx.fillStyle = gr; ctx.fill(); if (uiv.numbered) { ctx.fillStyle = "#fff"; ctx.fillText(this.num, (x0 + x1) / 2, (y0 + y1) / 2); ctx.lineWidth = 2; ctx.strokeStyle = "#000"; ctx.strokeText(this.num, (x0 + x1) / 2, (y0 + y1) / 2); } } // draw drawEdge(edge, attrib) { const radius = Rectangle.#radius; switch (edge) { case 0: drawLine( this.x0 + radius, this.uy0, this.x1 - radius, this.uy0, attrib ); break; case 1: drawLine( this.ux1, this.y0 + radius, this.ux1, this.y1 - radius, attrib ); break; case 2: drawLine( this.x0 + radius, this.uy1, this.x1 - radius, this.uy1, attrib ); break; case 3: drawLine( this.ux0, this.y0 + radius, this.ux0, this.y1 - radius, attrib ); break; } } // drawEdge posTarget(edge, target) { let lTarget0 = target * this.grid.len; let lTarget1 = (target + 1) * this.grid.len; switch (edge) { case 0: return this.grid.offsy + lTarget0; case 1: return this.grid.offsx + lTarget1; case 2: return this.grid.offsy + lTarget1; case 3: return this.grid.offsx + lTarget0; } } drawTarget(edge, target, attrib) { let pos = this.posTarget(edge, target); switch (edge) { case 0: case 2: drawLine(this.x0, pos, this.x1, pos, attrib); break; case 1: case 3: drawLine(pos, this.y0, pos, this.y1, attrib); break; } } // drawEdge } // class Rectangle //------------------------------------------------------------------------ function drawLine(x0, y0, x1, y1, attrib) { let color = "#fff"; if (attrib && attrib.color !== undefined) color = attrib.color; ctx.strokeStyle = color; if (attrib && attrib.lineWidth !== undefined) ctx.lineWidth = attrib.lineWidth; else ctx.lineWidth = 2; if (attrib && attrib.lineDash !== undefined) ctx.setLineDash(attrib.lineDash); ctx.strokeStyle = color; ctx.beginPath(); ctx.moveTo(x0, y0); ctx.lineTo(x1, y1); ctx.stroke(); ctx.setLineDash([]); } //------------------------------------------------------------------------ function findReducibles() { let kRect, rect, neigh; let possib = []; let sides = [0, 1, 2, 3]; rectangles.forEach((rect, kRect) => { // shuffle sides let k1 = intAlea(4); [sides[3], sides[k1]] = [sides[k1], sides[3]]; k1 = intAlea(3); [sides[2], sides[k1]] = [sides[k1], sides[2]]; k1 = intAlea(2); [sides[1], sides[k1]] = [sides[k1], sides[1]]; for (let kSide = 0; kSide < 4; ++kSide) { switch (sides[kSide]) { case 0: if (rect.kx0 == 0) break; neigh = grid[rect.ky0][rect.kx0 - 1].rect; if (neigh.ky0 != rect.ky0) break; // not same top edge : reject if (neigh.ky1 == rect.ky1) break; // same opposite edge : reject if (neigh.ky1 < rect.ky1) possib.push([ { rect: rect, edge: 0, target: neigh.ky1 + 1 }, { rect: neigh, edge: 1, target: rect.kx1 } ]); else possib.push([ { rect: neigh, edge: 0, target: rect.ky1 + 1 }, { rect: rect, edge: 3, target: neigh.kx0 } ]); // possib.push({ rect, edge: sides[kSide], neigh }); break; case 1: if (rect.ky0 == 0) break; neigh = grid[rect.ky0 - 1][rect.kx1].rect; if (neigh.kx1 != rect.kx1) break; // not same right edge : reject if (neigh.kx0 == rect.kx0) break; // same opposite edge : reject if (neigh.kx0 > rect.kx0) possib.push([ { rect: rect, edge: 1, target: neigh.kx0 - 1 }, { rect: neigh, edge: 2, target: rect.ky1 } ]); else possib.push([ { rect: neigh, edge: 1, target: rect.kx0 - 1 }, { rect: rect, edge: 0, target: neigh.ky0 } ]); // possib.push({ rect, edge: sides[kSide], neigh }); break; case 2: if (rect.kx1 == grid.nbx - 1) break; neigh = grid[rect.ky1][rect.kx1 + 1].rect; if (neigh.ky1 != rect.ky1) break; // not same bottom edge : reject if (neigh.ky0 == rect.ky0) break; // same opposite edge : reject if (neigh.ky0 > rect.ky0) possib.push([ { rect: rect, edge: 2, target: neigh.ky0 - 1 }, { rect: neigh, edge: 3, target: rect.kx0 } ]); else possib.push([ { rect: neigh, edge: 2, target: rect.ky0 - 1 }, { rect: rect, edge: 1, target: neigh.kx1 } ]); // possib.push({ rect, edge: sides[kSide], neigh }); break; case 3: if (rect.ky1 == grid.nby - 1) break; neigh = grid[rect.ky1 + 1][rect.kx0].rect; if (neigh.kx0 != rect.kx0) break; // not same right edge : reject if (neigh.kx1 == rect.kx1) break; // same opposite edge : reject if (neigh.kx1 < rect.kx1) possib.push([ { rect: rect, edge: 3, target: neigh.kx1 + 1 }, { rect: neigh, edge: 0, target: rect.ky0 } ]); else possib.push([ { rect: neigh, edge: 3, target: rect.kx1 + 1 }, { rect: rect, edge: 2, target: neigh.ky1 } ]); // possib.push({ rect, edge: sides[kSide], neigh }); break; } // switch kSide } // for kSide }); // rectangles.forEach return possib; } // findReducibles //------------------------------------------------------------------------ function findEdges(p) { // the edge or edges close to the mouse position given in p let tEdges = []; rectangles.forEach((rect) => { if (p.x > rect.x0 && p.x < rect.x1) { if (mabs(p.y - rect.uy0) < tolMouse) tEdges.push({ rect, edge: 0 }); if (mabs(p.y - rect.uy1) < tolMouse) tEdges.push({ rect, edge: 2 }); } if (p.y > rect.y0 && p.y < rect.y1) { if (mabs(p.x - rect.ux0) < tolMouse) tEdges.push({ rect, edge: 3 }); if (mabs(p.x - rect.ux1) < tolMouse) tEdges.push({ rect, edge: 1 }); } }); // rectangles.forEach if (tEdges.length > 1) { let nbh = tEdges.reduce( (s, ed) => s + ([0, 2].includes(ed.edge) ? 1 : 0), 0 ); if (nbh >= tEdges.length) { tEdges = tEdges.filter((ed) => [0, 2].includes(ed.edge)); } else { tEdges = tEdges.filter((ed) => [1, 3].includes(ed.edge)); } } return tEdges; } // findEdges //------------------------------------------------------------------------ function shuffleOnce() { .........完整代码请登录后点击上方下载按钮下载查看
网友评论0