js实现一个拼图小游戏代码

代码语言:html

所属分类:游戏

代码描述:js实现一个拼图小游戏代码,start game 在这个游戏中,你通过改变拼图碎片的大小来移动它们。每个动作涉及两个相邻的碎片,并分为两个阶段进行。 首先,拖动拼图碎片的边缘以减小一个维度(高度或宽度),从而创建一个空位 然后,这个空位必须通过增加另一个维度(宽度或高度)来被邻近的碎片恰好填满 将鼠标光标悬停在拼图碎片的边缘上,可以找到一个合法的动作。红色和绿色虚线表示这个边缘是否可以合法移动。当线条为绿色时,按下鼠标左键,并在移动边缘到白色线之一时保持按住。然后释放按钮,你就完成了第一阶段。 大多

代码标签: js 拼图 游戏 代码

下面为部分代码预览,完整代码请点击下载或在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">&#x2261;</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 = "&#x2261";
  }
} //

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