538 lines
18 KiB
JavaScript
538 lines
18 KiB
JavaScript
/**************************************
|
||
文件名:grid.js
|
||
功能:该模块用于处理游戏网格对象相关内容
|
||
版本:2.0(23.01.08)
|
||
**************************************/
|
||
|
||
/**************************************
|
||
对象名:Grid
|
||
参数:lang: 语言, mode: 游戏难度
|
||
**************************************/
|
||
function Grid(lang, mode){
|
||
//缓存标签
|
||
this.gameKey = `${mode}State`;
|
||
//存放tile对象数组
|
||
this.grid = null;
|
||
//方格大小
|
||
this.size = "54px";
|
||
//字体大小
|
||
this.fontSize = "25px";
|
||
//横向方格数
|
||
this.wSize = 9;
|
||
//纵向方格数
|
||
this.hSize = 9;
|
||
//游戏难度
|
||
this.mode = mode;
|
||
//地雷数量
|
||
this.mine = 10;
|
||
//当前状态
|
||
this.now = "Waiting";
|
||
//判断是否是第一次点击
|
||
this.first = true;
|
||
//实例化时间对象
|
||
this.time = new Time(0, 0, 0);
|
||
//时间计时器
|
||
this.t = null;
|
||
//非地雷方块数量
|
||
this.emptyNum = null;
|
||
//获取语言
|
||
this.lang = lang;
|
||
//获取HTML中grid元素
|
||
this.gridBlk = document.querySelector(".grid");
|
||
//排行榜对象实例化
|
||
this.leaderBoard = new LeaderBoard(mode);
|
||
}
|
||
|
||
/**************************************
|
||
方式名:setMode()
|
||
功能:按照难度初始化grid
|
||
**************************************/
|
||
Grid.prototype.setMode = function(){
|
||
switch(this.mode){
|
||
case "easy":
|
||
this.mine = 10;
|
||
this.wSize = 9;
|
||
this.hSize = 9;
|
||
break;
|
||
case "hard":
|
||
this.mine = 40;
|
||
this.wSize = 16;
|
||
this.hSize = 16;
|
||
break;
|
||
case "extra":
|
||
this.mine = 99;
|
||
this.wSize = 30;
|
||
this.hSize = 16;
|
||
break;
|
||
}
|
||
this.createGrid();
|
||
}
|
||
|
||
/**************************************
|
||
方式名:createGrid()
|
||
功能:创建HTML中grid网格,初步渲染游戏界面
|
||
**************************************/
|
||
Grid.prototype.createGrid = function(){
|
||
this.gridBlk.innerHTML = '';
|
||
if(this.mode == "extra") {
|
||
this.gridBlk.style.width = "1050px";
|
||
this.gridBlk.style.marginLeft = "calc((100vw - 1350px)/2)";
|
||
}else {
|
||
this.gridBlk.style.width = "630px";
|
||
this.gridBlk.style.marginLeft = "calc((100vw - 930px)/2)";
|
||
}
|
||
for (let i = 0; i < this.hSize; i++){
|
||
let newLine = document.createElement("div");
|
||
newLine.setAttribute("class", `line-${i} tileLine`);
|
||
if(this.mode == "extra") {
|
||
newLine.style.width = "1050px";
|
||
}else {
|
||
newLine.style.width = "630px";
|
||
}
|
||
|
||
for (let j = 0; j < this.wSize; j++){
|
||
let newTile = document.createElement("div");
|
||
newTile.setAttribute("class", `nonTriggered tile tile-${i}-${j}`);
|
||
switch(this.mode){
|
||
case "easy":
|
||
this.size = "54px";
|
||
this.fontSize = "25px";
|
||
break;
|
||
case "hard":
|
||
this.size = "30px";
|
||
this.fontSize = "14px";
|
||
break;
|
||
case "extra":
|
||
this.size = "30px";
|
||
this.fontSize = "14px";
|
||
break;
|
||
}
|
||
newLine.appendChild(newTile);
|
||
newTile.style.width = this.size;
|
||
newTile.style.height = this.size;
|
||
}
|
||
this.gridBlk.appendChild(newLine);
|
||
}
|
||
|
||
//读取缓存,如果缓存里有未完成的游戏,则恢复游戏
|
||
let recentGame = this.getGame();
|
||
if(recentGame){
|
||
this.recover(recentGame.grid, recentGame.time);
|
||
}
|
||
}
|
||
|
||
/**************************************
|
||
方式名:createGridObj()
|
||
功能:初始化Grid数组
|
||
**************************************/
|
||
Grid.prototype.createGridObj = function(){
|
||
for (let i = 0; i < this.hSize; i++){
|
||
this.grid.push([]);
|
||
for (let j = 0; j < this.wSize; j++){
|
||
this.grid[i].push(new Tile(i, j));
|
||
}
|
||
}
|
||
}
|
||
|
||
/**************************************
|
||
方式名:setValue()
|
||
功能:生成地雷,设置tile的value数值
|
||
**************************************/
|
||
Grid.prototype.setValue = function(){
|
||
//生成地雷算法
|
||
mineNum = this.mine;
|
||
for (let i = 0; i < this.hSize; i++){
|
||
for (let j = 0; j < this.wSize; j++){
|
||
//随机0~9的整数
|
||
let num = Math.floor(Math.random() * 10);
|
||
//数字为0,4时该方块为雷
|
||
if ((num == 0 || num == 4) && mineNum != 0){
|
||
this.grid[i][j].isMine = true;
|
||
let theTile = document.querySelector(`.tile-${i}-${j}`);
|
||
theTile.setAttribute("class", `nonTriggered tile tile-${i}-${j} mine`);
|
||
mineNum--;
|
||
}else if(mineNum == 0){
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
if(mineNum != 0) this.setValue();
|
||
|
||
//计算周围雷数算法
|
||
for (let i = 0; i < this.hSize; i++){
|
||
for (let j = 0; j < this.wSize; j++){
|
||
if(this.grid[i][j].isMine){
|
||
if (i != 0){
|
||
if (j != 0) this.grid[i-1][j-1].value++;
|
||
this.grid[i-1][j].value++;
|
||
if (j != this.wSize-1) this.grid[i-1][j+1].value++;
|
||
}
|
||
if (i != this.hSize-1){
|
||
if (j != 0) this.grid[i+1][j-1].value++;
|
||
this.grid[i+1][j].value++;
|
||
if (j != this.wSize-1) this.grid[i+1][j+1].value++;
|
||
}
|
||
if (j != 0) this.grid[i][j-1].value++;
|
||
if (j != this.wSize-1) this.grid[i][j+1].value++;
|
||
}
|
||
}
|
||
}
|
||
|
||
//设置每一个tile对象内和class中value值算法
|
||
for (let i = 0; i < this.hSize; i++){
|
||
for (let j = 0; j < this.wSize; j++){
|
||
let tileObj = this.grid[i][j];
|
||
let theTile = document.querySelector(`.tile-${i}-${j}`);
|
||
theTile.innerHTML = '';
|
||
|
||
//添加内置图像
|
||
let img = document.createElement("img");
|
||
img.setAttribute("src","./image/flag.png");
|
||
img.style.width = this.size;
|
||
img.style.height = this.size;
|
||
theTile.appendChild(img);
|
||
|
||
//添加value数字
|
||
if (tileObj.value != 0){
|
||
let valueCon = document.createElement("p");
|
||
valueCon.innerHTML = tileObj.value;
|
||
valueCon.style.width = this.size;
|
||
valueCon.style.height = this.size;
|
||
valueCon.style.fontSize = this.fontSize;
|
||
valueCon.style.lineHeight = this.size;
|
||
valueCon.style.bottom = `calc(${this.size}/2)`;
|
||
theTile.appendChild(valueCon);
|
||
}
|
||
|
||
if (!this.grid[i][j].isMine) theTile.setAttribute("class", `nonTriggered tile tile-${i}-${j} value-${tileObj.value}`);
|
||
else theTile.setAttribute("class", `nonTriggered tile tile-${i}-${j} mine value-${tileObj.value}`);
|
||
}
|
||
}
|
||
}
|
||
|
||
/**************************************
|
||
方式名:start()
|
||
功能:开始一局新游戏
|
||
**************************************/
|
||
Grid.prototype.start = function(){
|
||
//停止计时器
|
||
if (this.t){
|
||
clearInterval(this.t);
|
||
}
|
||
this.now = "Doing";
|
||
this.grid = [];
|
||
this.time = new Time(0, 0, 0);
|
||
this.emptyNum = this.hSize * this.wSize - this.mine;
|
||
this.createGridObj();
|
||
this.setValue();
|
||
this.timeStart();
|
||
}
|
||
|
||
/**************************************
|
||
方式名:click()
|
||
参数:i: 横坐标, j: 纵坐标
|
||
功能:左键单击tile判断
|
||
**************************************/
|
||
Grid.prototype.click = function(i, j){
|
||
let theTile = document.querySelector(`.tile-${i}-${j}`);
|
||
|
||
//若第一次触发的不是空白,游戏重置
|
||
if(this.first){
|
||
while(this.grid[i][j].value != 0 || this.grid[i][j].isMine){
|
||
this.start();
|
||
}
|
||
this.first = false;
|
||
}
|
||
|
||
if(this.grid[i][j].recent == "nonTriggered"){
|
||
this.grid[i][j].recent = "triggered";
|
||
let tileCon = theTile.getAttribute("class").split(" ");
|
||
tileCon.splice(tileCon.indexOf("nonTriggered"), 1);
|
||
tileCon.push("triggered");
|
||
tileCon = tileCon.join(" ");
|
||
theTile.setAttribute("class", tileCon);
|
||
|
||
if(this.grid[i][j].isMine){
|
||
let img = document.querySelector(`.tile-${i}-${j} img`);
|
||
img.setAttribute("src", "./image/bomb.png");
|
||
//停止计时器
|
||
clearInterval(this.t);
|
||
this.now = "Failed";
|
||
}else if(!this.grid[i][j].isMine){
|
||
this.emptyNum--;
|
||
}
|
||
|
||
if(this.grid[i][j].value == 0 && !this.grid[i][j].isMine) this.checkEmpty(i, j);
|
||
|
||
if(this.emptyNum == 0){
|
||
//停止计时器
|
||
clearInterval(this.t);
|
||
this.now = "Win";
|
||
}
|
||
|
||
this.maskBlk();
|
||
}
|
||
}
|
||
|
||
/**************************************
|
||
方式名:checkEmpty()
|
||
参数:i: 横坐标, j: 纵坐标
|
||
功能:检测单击方块周围是否为非雷方块
|
||
**************************************/
|
||
Grid.prototype.checkEmpty = function(i, j){
|
||
if (i != 0){
|
||
if (j != 0 && !this.grid[i-1][j-1].isMine && this.grid[i-1][j-1].recent == "nonTriggered") this.click(i-1, j-1);
|
||
if (!this.grid[i-1][j].isMine && this.grid[i-1][j].recent == "nonTriggered") this.click(i-1, j);
|
||
if (j != this.wSize-1 && !this.grid[i-1][j+1].isMine && this.grid[i-1][j+1].recent == "nonTriggered") this.click(i-1, j+1);
|
||
}
|
||
if (i != this.hSize-1){
|
||
if (j != 0 && !this.grid[i+1][j-1].isMine && this.grid[i+1][j-1].recent == "nonTriggered") this.click(i+1, j-1);
|
||
if (!this.grid[i+1][j].isMine && this.grid[i+1][j].recent == "nonTriggered") this.click(i+1, j);
|
||
if (j != this.wSize-1 && !this.grid[i+1][j+1].isMine && this.grid[i+1][j+1].recent == "nonTriggered") this.click(i+1, j+1);
|
||
}
|
||
if (j != 0 && !this.grid[i][j-1].isMine && this.grid[i][j-1].recent == "nonTriggered") this.click(i, j-1);
|
||
if (j != this.wSize-1 && !this.grid[i][j+1].isMine && this.grid[i][j+1].recent == "nonTriggered") this.click(i, j+1);
|
||
}
|
||
|
||
/**************************************
|
||
方式名:rightClick()
|
||
参数:i: 横坐标, j: 纵坐标
|
||
功能:右键单击tile判断
|
||
**************************************/
|
||
Grid.prototype.rightClick = function(i, j){
|
||
let theTile = document.querySelector(`.tile-${i}-${j}`);
|
||
|
||
if(this.grid[i][j].recent == "nonTriggered"){
|
||
this.grid[i][j].recent = "marked";
|
||
|
||
let tileCon = theTile.getAttribute("class").split(" ");
|
||
tileCon.splice(tileCon.indexOf("nonTriggered"), 1);
|
||
tileCon.push("marked");
|
||
tileCon = tileCon.join(" ");
|
||
theTile.setAttribute("class", tileCon);
|
||
}else if(this.grid[i][j].recent == "marked"){
|
||
this.grid[i][j].recent = "nonTriggered";
|
||
|
||
let tileCon = theTile.getAttribute("class").split(" ");
|
||
tileCon.splice(tileCon.indexOf("marked"), 1);
|
||
tileCon.push("nonTriggered");
|
||
tileCon = tileCon.join(" ");
|
||
theTile.setAttribute("class", tileCon);
|
||
}
|
||
}
|
||
|
||
/**************************************
|
||
方式名:timeStart()
|
||
功能:开始计时
|
||
**************************************/
|
||
Grid.prototype.timeStart = function(){
|
||
let timeCon = document.querySelector(".timeCon");
|
||
let self = this;
|
||
if (this.now = "Doing"){
|
||
this.t = setInterval(function(){
|
||
self.time.time.s++;
|
||
if (self.time.time.s == 60){
|
||
self.time.time.min++;
|
||
self.time.time.s = 0;
|
||
}
|
||
if (self.time.time.min == 60){
|
||
self.time.time.h++;
|
||
self.time.time.min = 0;
|
||
}
|
||
|
||
timeCon.innerHTML = self.time.getTime();
|
||
|
||
//每一秒保存一次游戏
|
||
self.setGame();
|
||
}, 1000);
|
||
}
|
||
}
|
||
|
||
/**************************************
|
||
方式名:restart()
|
||
功能:恢复初始状态
|
||
**************************************/
|
||
Grid.prototype.restart = function(){
|
||
//隐藏结束界面蒙版
|
||
document.querySelector(".mask").style.display = "none";
|
||
//恢复第一次点击
|
||
this.first = true;
|
||
//停止计时器
|
||
clearInterval(this.t);
|
||
//恢复所有tile的class
|
||
for (let i = 0; i < this.hSize; i++){
|
||
for (let j = 0; j < this.wSize; j++){
|
||
let theTile = document.querySelector(`.tile-${i}-${j}`);
|
||
theTile.setAttribute("class", `nonTriggered tile tile-${i}-${j}`);
|
||
}
|
||
}
|
||
//初始化时间
|
||
let timeCon = document.querySelector(".timeCon");
|
||
timeCon.innerHTML = "0h0min0s";
|
||
//初始化当前状态
|
||
this.now = "Waiting";
|
||
}
|
||
|
||
/**************************************
|
||
方式名:maskBlk()
|
||
功能:判断游戏是否结束,渲染结束界面蒙版
|
||
**************************************/
|
||
Grid.prototype.maskBlk = function(){
|
||
if(this.now == "Win"){
|
||
window.localStorage.removeItem(this.gameKey);
|
||
document.querySelector(".mask").style.display = "block";
|
||
switch (this.lang){
|
||
case "zh_cn":
|
||
document.querySelector(".content").innerHTML = "你赢了!";
|
||
document.querySelector(".enterName input").setAttribute("placeholder", "请输入您的昵称");
|
||
break;
|
||
case "en":
|
||
document.querySelector(".content").innerHTML = "You win!";
|
||
document.querySelector(".enterName input").setAttribute("placeholder", "Please enter your nickname");
|
||
break;
|
||
case "jp":
|
||
document.querySelector(".content").innerHTML = "ユーウィン!";
|
||
document.querySelector(".enterName input").setAttribute("placeholder", "ニックネームを入力してください");
|
||
break;
|
||
}
|
||
document.querySelector(".enterName").style.display = "flex";
|
||
}else if(this.now == "Failed"){
|
||
window.localStorage.removeItem(this.gameKey);
|
||
document.querySelector(".mask").style.display = "block";
|
||
switch (this.lang){
|
||
case "zh_cn":
|
||
document.querySelector(".content").innerHTML = "你输了!";
|
||
break;
|
||
case "en":
|
||
document.querySelector(".content").innerHTML = "You failed!";
|
||
break;
|
||
case "jp":
|
||
document.querySelector(".content").innerHTML = "残念!";
|
||
break;
|
||
}
|
||
document.querySelector(".enterName").style.display = "none";
|
||
}
|
||
}
|
||
|
||
/**************************************
|
||
方式名:rankConfirm()
|
||
功能:处理胜利界面确认按钮动作
|
||
**************************************/
|
||
Grid.prototype.rankConfirm = function(){
|
||
const ID = document.querySelector("input").value;
|
||
const time = this.time.getTime();
|
||
this.leaderBoard.setHigh(ID, time);
|
||
document.querySelector(".enterName").style.display = "none";
|
||
}
|
||
|
||
/**************************************
|
||
方式名:serialize()
|
||
功能:grid序列化
|
||
返回值:序列化后的grid以及游戏时间的对象
|
||
**************************************/
|
||
Grid.prototype.serialize = function(){
|
||
const grid = [];
|
||
for (let i = 0; i < this.hSize; i++){
|
||
grid.push([]);
|
||
for (let j = 0; j < this.wSize; j++){
|
||
grid[i].push(this.grid[i][j].serialize());
|
||
}
|
||
}
|
||
|
||
return {
|
||
grid: grid,
|
||
time: {
|
||
h: this.time.time.h,
|
||
min: this.time.time.min,
|
||
s: this.time.time.s
|
||
}
|
||
};
|
||
}
|
||
|
||
/**************************************
|
||
方式名:serialize()
|
||
参数:grid: 序列化的grid, time: 游戏时间
|
||
功能:游戏内容反序列化
|
||
**************************************/
|
||
Grid.prototype.recover = function(grid, time){
|
||
this.first = false;
|
||
this.now = "Doing";
|
||
this.time.time.h = time.h;
|
||
this.time.time.min = time.min;
|
||
this.time.time.s = time.s;
|
||
this.timeStart();
|
||
this.grid = [];
|
||
for (let i = 0; i < this.hSize; i++){
|
||
this.grid.push([]);
|
||
for (let j = 0; j < this.wSize; j++){
|
||
let sTile = grid[i][j];
|
||
let newTile = new Tile(i, j);
|
||
newTile.value = sTile.value;
|
||
newTile.isMine = sTile.isMine;
|
||
newTile.recent = sTile.recent;
|
||
this.grid[i].push(newTile);
|
||
}
|
||
}
|
||
this.recoverValue();
|
||
}
|
||
|
||
/**************************************
|
||
方式名:recoverValue()
|
||
功能:反序列化后重新渲染界面
|
||
**************************************/
|
||
Grid.prototype.recoverValue = function(){
|
||
this.emptyNum = this.hSize * this.wSize - this.mine;
|
||
for (let i = 0; i < this.hSize; i++){
|
||
for (let j = 0; j < this.wSize; j++){
|
||
let tileObj = this.grid[i][j];
|
||
let theTile = document.querySelector(`.tile-${i}-${j}`);
|
||
let tileCon = ["tile", `tile-${i}-${j}`];
|
||
if(tileObj.isMine) tileCon.push("mine");
|
||
tileCon.push(tileObj.recent);
|
||
if(tileObj.recent == "triggered") this.emptyNum--;
|
||
tileCon.push(`value-${tileObj.value}`);
|
||
tileCon = tileCon.join(" ");
|
||
theTile.setAttribute("class", tileCon);
|
||
|
||
//添加内置图像
|
||
let img = document.createElement("img");
|
||
img.setAttribute("src","./image/flag.png");
|
||
img.style.width = this.size;
|
||
img.style.height = this.size;
|
||
theTile.appendChild(img);
|
||
|
||
//添加value数字
|
||
if (tileObj.value != 0){
|
||
let valueCon = document.createElement("p");
|
||
valueCon.innerHTML = tileObj.value;
|
||
valueCon.style.width = this.size;
|
||
valueCon.style.height = this.size;
|
||
valueCon.style.fontSize = this.fontSize;
|
||
valueCon.style.lineHeight = this.size;
|
||
valueCon.style.bottom = `calc(${this.size}/2)`;
|
||
theTile.appendChild(valueCon);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/**************************************
|
||
方式名:setGame()
|
||
功能:游戏记录写入缓存
|
||
**************************************/
|
||
Grid.prototype.setGame = function(){
|
||
window.localStorage.setItem(
|
||
this.gameKey,
|
||
JSON.stringify(this.serialize())
|
||
);
|
||
}
|
||
|
||
/**************************************
|
||
方式名:getGame()
|
||
功能:从缓存中读取游戏记录
|
||
返回值:若缓存中存在游戏记录,返回游戏记录,否则返回null
|
||
**************************************/
|
||
Grid.prototype.getGame = function(){
|
||
const state = window.localStorage.getItem(this.gameKey);
|
||
return state ? JSON.parse(state) : null;
|
||
} |