Minesweeper-2.0-test/scripts/game/grid.js

538 lines
18 KiB
JavaScript
Raw Normal View History

2023-01-27 11:45:10 +00:00
/**************************************
文件名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;
}