XiMNXY5$z1jQWg_nu7_
+
+
+
+ hello
+
+
+
+ 石皮幼鸟制作
+
+
+
\ No newline at end of file
diff --git a/menu.html b/menu.html
new file mode 100644
index 0000000..348d23f
--- /dev/null
+++ b/menu.html
@@ -0,0 +1,54 @@
+
+
+
+
+ menu
+
+
+
+
+
+
+
Difficulty
+
+
+
+
+
+
+
+
+
Tutorial
+
+
Click the left mouse button to crack open the square
+
If a mine is triggered, the game is over
+
If a number appears, it means that the number of mines with the corresponding number is around
+
If blank, there are no mines around
+
Right mouse click on the square that marks what you suspect is a mine
+
Right click again to unmark
+
Knock out all the non-mine squares to win the final victory
+
+
+
+
+
LeaderBoard
+
Easy
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..730e3ac
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,12 @@
+{
+ "name": "minesweeper",
+ "version": "2.0.0",
+ "lockfileVersion": 2,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "minesweeper",
+ "version": "2.0.0"
+ }
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..d1ff60c
--- /dev/null
+++ b/package.json
@@ -0,0 +1,24 @@
+{
+ "main": "index.html",
+ "name": "minesweeper",
+ "description": "A web game which is similar to the Minesweeper made by Microsoft.",
+ "version": "2.0.0",
+ "keywords": ["admin"],
+ "window": {
+ "title": "Minesweeper",
+ "icon": "icon.ico",
+ "toolbar": false,
+ "frame": true,
+ "width": 1280,
+ "height": 760,
+ "position": "center",
+ "min_width": 900,
+ "min_height": 600,
+ "resizable":true
+ },
+ "webkit": {
+ "plugin": true,
+ "java":false,
+ "page-cache":true
+ }
+ }
\ No newline at end of file
diff --git a/scripts/game/grid.js b/scripts/game/grid.js
new file mode 100644
index 0000000..0eb992b
--- /dev/null
+++ b/scripts/game/grid.js
@@ -0,0 +1,538 @@
+/**************************************
+ 文件名: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;
+}
\ No newline at end of file
diff --git a/scripts/game/language.js b/scripts/game/language.js
new file mode 100644
index 0000000..439315d
--- /dev/null
+++ b/scripts/game/language.js
@@ -0,0 +1,82 @@
+/**************************************
+ 文件名:language.js
+ 功能:该模块用于处理主菜单语言相关内容
+ 版本:2.0(23.01.08)
+**************************************/
+
+/**************************************
+ 对象名:Language
+ 参数:lang: 传入语言(zh_cn为中文,en为英文,jp为日文)
+**************************************/
+function Language(lang){
+ this.lang = lang;
+}
+
+/**************************************
+ 方式名:langRender()
+ 功能:根据语种渲染对应的语言
+**************************************/
+Language.prototype.langRender = function(){
+ switch (this.lang){
+ case "zh_cn":
+ this.chineseRender();
+ break;
+ case "en":
+ this.englishRender();
+ break;
+ case "jp":
+ this.japaneseRender();
+ break;
+ }
+}
+
+/**************************************
+ 方式名:chineseRender()
+ 功能:渲染中文
+**************************************/
+Language.prototype.chineseRender = function(){
+ document.querySelector(".timeTitle").innerHTML = "游戏时长";
+ document.querySelector(".confirm").innerHTML = "确认";
+ let newBtn = document.querySelectorAll(".new");
+ for (let i = 0; i < newBtn.length; i++){
+ newBtn[i].innerHTML = "重开一局";
+ }
+ let back = document.querySelectorAll(".back");
+ for (let i = 0; i < back.length; i++){
+ back[i].innerHTML = "返回";
+ }
+}
+
+/**************************************
+ 方式名:englishRender()
+ 功能:渲染英文
+**************************************/
+Language.prototype.englishRender = function(){
+ document.querySelector(".timeTitle").innerHTML = "Time";
+ document.querySelector(".confirm").innerHTML = "Confirm";
+ let newBtn = document.querySelectorAll(".new");
+ for (let i = 0; i < newBtn.length; i++){
+ newBtn[i].innerHTML = "New Game";
+ }
+ let back = document.querySelectorAll(".back");
+ for (let i = 0; i < back.length; i++){
+ back[i].innerHTML = "Back";
+ }
+}
+
+/**************************************
+ 方式名:japaneseRender()
+ 功能:渲染日语
+**************************************/
+Language.prototype.japaneseRender = function(){
+ document.querySelector(".timeTitle").innerHTML = "プレイ時間";
+ document.querySelector(".confirm").innerHTML = "確認する";
+ let newBtn = document.querySelectorAll(".new");
+ for (let i = 0; i < newBtn.length; i++){
+ newBtn[i].innerHTML = "ニューゲーム";
+ }
+ let back = document.querySelectorAll(".back");
+ for (let i = 0; i < back.length; i++){
+ back[i].innerHTML = "戻る";
+ }
+}
\ No newline at end of file
diff --git a/scripts/game/leaderBoard.js b/scripts/game/leaderBoard.js
new file mode 100644
index 0000000..248408a
--- /dev/null
+++ b/scripts/game/leaderBoard.js
@@ -0,0 +1,113 @@
+/**************************************
+ 文件名:leaderBoard.js
+ 功能:该模块用于处理排行榜相关内容
+ 版本:2.0(23.01.08)
+**************************************/
+
+/**************************************
+ 对象名:LeaderBoard
+ 参数:mode: 游戏难度
+**************************************/
+function LeaderBoard(mode){
+ //获取难度
+ this.mode = mode
+ //设置排行榜缓存标签
+ this.HighRankKey = 'MinesweeperRank';
+ //从缓存中获取排行榜
+ this.highRank = this.getRank();
+
+}
+
+/**************************************
+ 方式名:setHigh()
+ 参数:ID: 玩家昵称, time: 游戏时长字符串
+ 功能:设置最高分
+**************************************/
+LeaderBoard.prototype.setHigh = function(ID, time){
+ let newHigh = {
+ ID: ID,
+ time: time
+ }
+ switch(this.mode){
+ case "easy":
+ this.rankCompare(this.highRank.easyRank, newHigh);
+ break;
+ case "hard":
+ this.rankCompare(this.highRank.hardRank, newHigh);
+ break;
+ case "extra":
+ this.rankCompare(this.highRank.extraRank, newHigh);
+ break;
+ }
+}
+
+/**************************************
+ 方式名:rankCompare()
+ 参数:rank: 对应难度排行榜数组, newHigh: 通关成绩对象
+ 功能:对比现有排行榜内容和传入成绩,如果破纪录则加入排行榜
+**************************************/
+LeaderBoard.prototype.rankCompare = function(rank, newHigh){
+ let nH = newHigh.time.substring(0, newHigh.time.indexOf("h"));
+ let nMin = newHigh.time.substring(newHigh.time.indexOf("h")+1, newHigh.time.indexOf("min"));
+ let nS = newHigh.time.substring(newHigh.time.indexOf("min")+3, newHigh.time.indexOf("s"));
+ let flag = false;
+ if(rank.length == 0){
+ rank.splice(0, 0, newHigh);
+ flag = true;
+ }else {
+ for (let i = 0; i < rank.length; i++){
+ let obj = rank[i];
+ let h = parseInt(obj.time.substring(0, obj.time.indexOf("h")));
+ let min = parseInt(obj.time.substring(obj.time.indexOf("h")+1, obj.time.indexOf("min")));
+ let s = parseInt(obj.time.substring(obj.time.indexOf("min")+3, obj.time.indexOf("s")));
+ if(nH < h){
+ flag = true;
+ }else if(nH == h && nMin < min){
+ flag = true;
+ }else if(nH == h && nMin == min && nS < s){
+ flag = true;
+ }
+ if(flag){
+ rank.splice(i, 0, newHigh);
+ if(rank.length == 11){
+ rank.pop();
+ }
+ break;
+ }
+ }
+ if(rank.length < 10 && !flag){
+ rank.splice(rank.length, 0, newHigh);
+ flag = true
+ }
+ }
+ if(flag){
+ this.setRank();
+ }
+}
+
+/**************************************
+ 方式名:setRank()
+ 功能:将排行榜写入缓存
+**************************************/
+LeaderBoard.prototype.setRank = function(){
+ window.localStorage.setItem(
+ this.HighRankKey,
+ JSON.stringify(this.highRank)
+ )
+}
+
+/**************************************
+ 方式名:getRank()
+ 功能:从缓存中获取排行榜
+ 返回值:返回排行榜对象
+**************************************/
+LeaderBoard.prototype.getRank = function(){
+ let newRank = {
+ easyRank: [],
+ hardRank: [],
+ extraRank: []
+ }
+ const rank = window.localStorage.getItem(this.HighRankKey);
+ //如果缓存中没有排行榜,返回空的排行榜对象
+ return rank ? JSON.parse(rank) : newRank;
+}
\ No newline at end of file
diff --git a/scripts/game/listener.js b/scripts/game/listener.js
new file mode 100644
index 0000000..ba2f806
--- /dev/null
+++ b/scripts/game/listener.js
@@ -0,0 +1,77 @@
+/**************************************
+ 文件名:listener.js
+ 功能:该模块用于处理事件监听相关内容
+ 版本:2.0(23.01.08)
+**************************************/
+
+/**************************************
+ 对象名:Listener
+ 参数:lang: 传入语言, grid: 传入grid对象
+**************************************/
+function Listener(lang, grid){
+ //在设置监听的匿名函数中,“this”不是指代的Listener对象,必须单独获取
+ let self = this;
+ //获取语言
+ this.lang = lang;
+ //获取grid对象
+ this.grid = grid;
+ //获取所有返回按钮
+ this.back = document.querySelectorAll(".back");
+ //获取所有新游戏按钮
+ this.newBtn = document.querySelectorAll(".new");
+ //监听输入框旁的确认按钮
+ this.confirm = document.querySelector(".confirm");
+
+ //监听返回按钮点击事件
+ for (let i = 0; i < this.back.length; i++){
+ this.back[i].addEventListener("click", function(){
+ window.open(`./menu.html?lang=${self.lang}`, "_self");
+ });
+ }
+
+ //监听新游戏按钮点击事件
+ for (let i = 0; i < this.newBtn.length; i++){
+ this.newBtn[i].addEventListener("click", function(){
+ self.grid.restart();
+ })
+ }
+
+ //监听确认按钮点击事件
+ this.confirm.addEventListener("click", function(){
+ self.grid.rankConfirm();
+ })
+
+ //监听tile点击事件
+ for (let i = 0; i < this.grid.hSize; i++){
+ for (let j = 0; j < this.grid.wSize; j++){
+ let theTile = document.querySelector(`.tile-${i}-${j}`);
+
+ theTile.addEventListener("click", function (){
+ if(self.grid.now == "Waiting"){
+ self.grid.start();
+ }
+
+ self.grid.click(i, j);
+ });
+
+ theTile.addEventListener("contextmenu", function (){
+ self.grid.rightClick(i, j);
+ });
+ }
+ }
+}
+
+//屏蔽右键菜单
+document.oncontextmenu = function (event){
+ if(window.event){
+ event = window.event;
+ }try{
+ var the = event.srcElement;
+ if (!((the.tagName == "INPUT" && the.type.toLowerCase() == "text") || the.tagName == "TEXTAREA")){
+ return false;
+ }
+ return true;
+ }catch (e){
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/scripts/game/manager.js b/scripts/game/manager.js
new file mode 100644
index 0000000..dacb86d
--- /dev/null
+++ b/scripts/game/manager.js
@@ -0,0 +1,36 @@
+/**************************************
+ 文件名:manager.js
+ 功能:该模块用于处理游戏界面相关内容
+ 版本:2.0(23.01.08)
+**************************************/
+
+//新建Manager对象
+new Manager();
+
+/**************************************
+ 对象名:Manager
+**************************************/
+function Manager(){
+ //获取URL中“?”及后续部分
+ let url = location.search;
+ if (url.indexOf("?") != -1){
+ //截取1到url.length的部分
+ url = url.substring(1);
+ //若传入了多个参数,以&分隔,此处将参数分解为数组内多个元素
+ url = url.split('&');
+ }
+ //参数第0项是语言参数lang,第一项是难度参数mode,以等号分开,取等号后内容
+ this.lang = url[0].split('=')[1];
+ this.mode = url[1].split('=')[1];
+
+ //Language对象实例化
+ this.language = new Language(this.lang);
+ this.language.langRender();
+
+ //Grid对象实例化
+ this.grid = new Grid(this.lang, this.mode);
+ this.grid.setMode();
+
+ //Listener对象实例化
+ this.listener = new Listener(this.lang, this.grid);
+}
\ No newline at end of file
diff --git a/scripts/game/tile.js b/scripts/game/tile.js
new file mode 100644
index 0000000..b4fe819
--- /dev/null
+++ b/scripts/game/tile.js
@@ -0,0 +1,36 @@
+/**************************************
+ 文件名:tile.js
+ 功能:该模块用于处理单个小方格相关内容
+ 版本:2.0(23.01.08)
+**************************************/
+
+/**************************************
+ 对象名:Tile
+ 参数:x: 横坐标, y: 纵坐标
+**************************************/
+function Tile(x, y){
+ //方块的位置
+ this.position = {
+ positionX: x,
+ positionY: y
+ }
+ //value为数字0~8时,指代周围有对应数字的雷数
+ this.value = 0;
+ //为true时是雷
+ this.isMine = false;
+ //方块目前状态,“nonTriggered”是未触发,“triggered”是已触发,“marked”是被标记
+ this.recent = "nonTriggered";
+}
+
+/**************************************
+ 方式名:serialize()
+ 功能:序列化当前Tile对象
+ 返回值:包含value、isMine、recent的对象
+**************************************/
+Tile.prototype.serialize = function(){
+ return {
+ value: this.value,
+ isMine: this.isMine,
+ recent: this.recent
+ };
+}
\ No newline at end of file
diff --git a/scripts/game/time.js b/scripts/game/time.js
new file mode 100644
index 0000000..9e3aaf6
--- /dev/null
+++ b/scripts/game/time.js
@@ -0,0 +1,26 @@
+/**************************************
+ 文件名:time.js
+ 功能:该模块用于处理游戏时间相关内容
+ 版本:2.0(23.01.08)
+**************************************/
+
+/**************************************
+ 对象名:Time
+ 参数:h: 小时, min: 分钟, s: 秒
+**************************************/
+function Time(h, min, s){
+ this.time = {
+ h: h,
+ min: min,
+ s: s
+ }
+}
+
+/**************************************
+ 方式名:getTime()
+ 功能:获得当前游戏时间
+ 返回值:当前游戏时间字符串
+**************************************/
+Time.prototype.getTime = function(){
+ return `${this.time.h}h${this.time.min}min${this.time.s}s`;
+}
\ No newline at end of file
diff --git a/scripts/index.js b/scripts/index.js
new file mode 100644
index 0000000..9487cb9
--- /dev/null
+++ b/scripts/index.js
@@ -0,0 +1,10 @@
+/**************************************
+ 文件名:index.js
+ 功能:该模块用于处理开始界面相关内容
+ 版本:2.0(23.01.08)
+**************************************/
+
+//3秒后进入主菜单
+setTimeout(function (){
+ window.open('./menu.html?lang=en', "_self");
+}, 3000);
\ No newline at end of file
diff --git a/scripts/menu/language.js b/scripts/menu/language.js
new file mode 100644
index 0000000..2fa4e00
--- /dev/null
+++ b/scripts/menu/language.js
@@ -0,0 +1,118 @@
+/**************************************
+ 文件名:language.js
+ 功能:该模块用于处理主菜单语言相关内容
+ 版本:2.0(23.01.08)
+**************************************/
+
+/**************************************
+ 对象名:Language
+ 参数:lang: 传入语言(zh_cn为中文,en为英文,jp为日文)
+**************************************/
+function Language(lang){
+ this.lang = lang;
+}
+
+/**************************************
+ 方式名:langRender()
+ 功能:根据语种渲染对应的语言
+**************************************/
+Language.prototype.langRender = function(){
+ switch (this.lang){
+ case "zh_cn":
+ this.chineseRender();
+ break;
+ case "en":
+ this.englishRender();
+ break;
+ case "jp":
+ this.japaneseRender();
+ break;
+ }
+}
+
+/**************************************
+ 方式名:chineseRender()
+ 功能:渲染中文
+**************************************/
+Language.prototype.chineseRender = function(){
+ document.querySelector(".menu").innerHTML = "扫雷";
+ document.querySelector(".start").innerHTML = "开始游戏";
+ document.querySelector(".tutorial").innerHTML = "新手教程";
+ document.querySelector(".leaderBoard").innerHTML = "排行榜";
+ document.querySelector(".exit").innerHTML = "退出游戏";
+ document.querySelector(".diffCon").innerHTML = "难度选择";
+ document.querySelector(".easy").innerHTML = "简单";
+ document.querySelector(".hard").innerHTML = "困难";
+ let back = document.querySelectorAll(".back");
+ for (let i = 0; i < back.length; i++){
+ back[i].innerHTML = "返回";
+ }
+ document.querySelector(".tutoCon").innerHTML = "新手教程";
+ document.querySelector(".tutor").innerHTML =
+ `点击鼠标左键敲开方块
+ 如果触发了地雷,游戏结束
+ 如果出现数字,则意味着周围有对应数字的地雷数
+ 如果是空白,则周围无地雷
+ 鼠标右击标记你怀疑是地雷的方块
+ 再右击一次可以取消标记
+ 敲开所有非地雷方块,赢得最终胜利
`;
+ document.querySelector(".leaderCon").innerHTML = "排行榜";
+}
+
+/**************************************
+ 方式名:englishRender()
+ 功能:渲染英文
+**************************************/
+Language.prototype.englishRender = function(){
+ document.querySelector(".menu").innerHTML = "Minesweeper";
+ document.querySelector(".start").innerHTML = "Start Game";
+ document.querySelector(".tutorial").innerHTML = "Tutorial";
+ document.querySelector(".leaderBoard").innerHTML = "Leaderboard";
+ document.querySelector(".exit").innerHTML = "Exit";
+ document.querySelector(".diffCon").innerHTML = "Difficulty";
+ document.querySelector(".easy").innerHTML = "Easy";
+ document.querySelector(".hard").innerHTML = "Hard";
+ let back = document.querySelectorAll(".back");
+ for (let i = 0; i < back.length; i++){
+ back[i].innerHTML = "Back";
+ }
+ document.querySelector(".tutoCon").innerHTML = "Tutorial";
+ document.querySelector(".tutor").innerHTML =
+ `Click the left mouse button to crack open the square
+ If a mine is triggered, the game is over
+ If a number appears, it means that the number of mines with the corresponding number is around
+ If blank, there are no mines around
+ Right mouse click on the square that marks what you suspect is a mine
+ Right click again to unmark
+ Knock out all the non-mine squares to win the final victory
`;
+ document.querySelector(".leaderCon").innerHTML = "Leaderboard";
+}
+
+/**************************************
+ 方式名:japaneseRender()
+ 功能:渲染日语
+**************************************/
+Language.prototype.japaneseRender = function(){
+ document.querySelector(".menu").innerHTML = "マインスイーパー";
+ document.querySelector(".start").innerHTML = "スタート";
+ document.querySelector(".tutorial").innerHTML = "私は初心者";
+ document.querySelector(".leaderBoard").innerHTML = "リーダーボード";
+ document.querySelector(".exit").innerHTML = "ゲーム終了";
+ document.querySelector(".diffCon").innerHTML = "難易度";
+ document.querySelector(".easy").innerHTML = "簡単";
+ document.querySelector(".hard").innerHTML = "難しい";
+ let back = document.querySelectorAll(".back");
+ for (let i = 0; i < back.length; i++){
+ back[i].innerHTML = "戻る";
+ }
+ document.querySelector(".tutoCon").innerHTML = "チュートリアル";
+ document.querySelector(".tutor").innerHTML =
+ `マウスの左ボタンで四角を発動します
+ 地雷が発生したらゲームオーバー
+ 数字が表示された場合、その数字に対応する地雷の数が周辺にあることを意味します
+ 空白の場合、地雷はありません
+ 地雷と思われるマークをマウスの右ボタンでクリックする
+ もう一度右クリックすると、マークが消えます
+ 地雷のないマスをすべてトリガーにして最終的に勝利する
`;
+ document.querySelector(".leaderCon").innerHTML = "リーダーボード";
+}
\ No newline at end of file
diff --git a/scripts/menu/leaderBoard.js b/scripts/menu/leaderBoard.js
new file mode 100644
index 0000000..73a1def
--- /dev/null
+++ b/scripts/menu/leaderBoard.js
@@ -0,0 +1,121 @@
+/**************************************
+ 文件名:leaderBoard.js
+ 功能:该模块用于处理主菜单排行榜相关内容
+ 版本:2.0(23.01.08)
+**************************************/
+
+/**************************************
+ 对象名:LeaderBoard
+ 参数:lang: 传入语言
+**************************************/
+function LeaderBoard(lang){
+ //设置排行榜缓存标签
+ this.HighRankKey = 'MinesweeperRank';
+ //从缓存中获取排行榜
+ this.highRank = this.getRank();
+ //获取HTML中排行榜
+ this.leadList = document.querySelector(".leadList");
+ //获取排行榜下一页按钮
+ this.next = document.querySelector(".next");
+ //获取语言
+ this.lang = lang;
+
+ //在设置监听的匿名函数中,“this”不是指代的leaderBoard对象,必须单独获取
+ let self = this;
+ //下一页按钮事件监听
+ this.next.addEventListener("click", function(){
+ let nextDiff = self.next.innerHTML;
+ if(nextDiff == "Easy" || nextDiff == "简单" || nextDiff == "簡単"){
+ self.rankRender("easy");
+ }else if(nextDiff == "Hard" || nextDiff == "困难" || nextDiff == "難しい"){
+ self.rankRender("hard");
+ }else if(nextDiff == "Extra"){
+ self.rankRender("extra");
+ }
+ });
+}
+
+/**************************************
+ 方式名:getRank()
+ 功能:从缓存中获取排行榜
+ 返回值:返回排行榜对象
+**************************************/
+LeaderBoard.prototype.getRank = function(){
+ let newRank = {
+ easyRank: [],
+ hardRank: [],
+ extraRank: []
+ }
+ const rank = window.localStorage.getItem(this.HighRankKey);
+ //如果缓存中没有排行榜,返回空的排行榜对象
+ return rank ? JSON.parse(rank) : newRank;
+}
+
+/**************************************
+ 方式名:rankRender(difficulty)
+ 参数:difficulty: 游戏难度(easy为简单,hard为困难,extra为ex难度)
+ 功能:渲染排行榜
+**************************************/
+LeaderBoard.prototype.rankRender = function(difficulty){
+ let rank = null;
+ let diffCon = '';
+ let nextCon = '';
+ switch(difficulty){
+ case "easy":
+ rank = this.highRank.easyRank;
+ switch(this.lang){
+ case "zh_cn":
+ diffCon = "简单";
+ nextCon = "困难";
+ break;
+ case "en":
+ diffCon = "Easy";
+ nextCon = "Hard";
+ break;
+ case "jp":
+ diffCon = "簡単";
+ nextCon = "難しい";
+ break;
+ }
+ break;
+ case "hard":
+ rank = this.highRank.hardRank;
+ switch(this.lang){
+ case "zh_cn":
+ diffCon = "困难";
+ break;
+ case "en":
+ diffCon = "Hard";
+ break;
+ case "jp":
+ diffCon = "難しい";
+ break;
+ }
+ nextCon = "Extra";
+ break;
+ case "extra":
+ rank = this.highRank.extraRank;
+ switch(this.lang){
+ case "zh_cn":
+ nextCon = "简单";
+ break;
+ case "en":
+ nextCon = "Easy";
+ break;
+ case "jp":
+ nextCon = "簡単";
+ break;
+ }
+ diffCon = "Extra";
+ break;
+ }
+ this.leadList.innerHTML = '';
+ for (let i = 0; i < rank.length; i++){
+ let li = document.createElement("li");
+ li.innerHTML =
+ `${i+1}
${rank[i].ID}
${rank[i].time}
`;
+ this.leadList.appendChild(li);
+ }
+ document.querySelector(".leaderDif").innerHTML = diffCon;
+ this.next.innerHTML = nextCon;
+}
\ No newline at end of file
diff --git a/scripts/menu/listener.js b/scripts/menu/listener.js
new file mode 100644
index 0000000..8fede83
--- /dev/null
+++ b/scripts/menu/listener.js
@@ -0,0 +1,147 @@
+/**************************************
+ 文件名:listener.js
+ 功能:该模块用于处理事件监听相关内容
+ 版本:2.0(23.01.08)
+**************************************/
+
+/**************************************
+ 对象名:Listener
+ 参数:lang: 传入语言
+**************************************/
+function Listener(lang) {
+ //获取HTML中的Language按钮
+ this.langSltBtn = document.querySelector(".langSltBtn");
+ //获取语言按钮列表
+ this.langSlt = document.querySelector("footer .language");
+ //获取所有的语言按钮
+ this.langs = document.querySelectorAll(".lang");
+ //获取难度菜单
+ this.difficulty = document.querySelector(".difficulty");
+ //获取主菜单列表按钮
+ this.selection = document.querySelectorAll(".menuList button");
+ //获取难度列表按钮
+ this.diffBtn = document.querySelectorAll(".difficulty button");
+ //获取新手教程界面
+ this.tutorial = document.querySelector(".tutorialBlk");
+ //获取新手教程界面返回按钮
+ this.tutoBack = document.querySelector(".tutorialBlk .back");
+ //获取排行榜界面
+ this.leaderBlk = document.querySelector(".leaderBlk");
+ //获取排行榜界面返回按钮
+ this.leadBack = document.querySelector(".leaderBlk .back");
+ //获取语言
+ this.lang = lang;
+
+ //在设置监听的匿名函数中,“this”不是指代的Listener对象,必须单独获取
+ let self = this;
+ //语言按钮点击事件
+ let flag = false;
+ this.langSltBtn.addEventListener("click", function (){
+ flag = !flag;
+ self.langSlt.style.display = (flag) ? "flex" : "none";
+ });
+
+ //主菜单列表按钮点击事件
+ for (let i = 0; i < this.selection.length; i++){
+ this.selection[i].addEventListener("click", function (){
+ self.btnClick(i);
+ })
+ }
+
+ //难度列表按钮点击事件
+ for (let i = 0; i < this.diffBtn.length; i++){
+ this.diffBtn[i].addEventListener("click", function (){
+ self.diffBtnClick(i);
+ })
+ }
+
+ //新手教程界面返回按钮点击事件
+ this.tutoBack.addEventListener("click", function (){
+ self.tutorial.style.display = "none";
+ });
+
+ //排行榜界面返回按钮点击事件
+ this.leadBack.addEventListener("click", function (){
+ self.leaderBlk.style.display = "none";
+ });
+
+ //切换语言
+ for (let i = 0; i < this.langs.length; i++) {
+ this.langs[i].addEventListener("click", function (){
+ let lang = self.langs[i].innerHTML;
+ let langCode = '';
+ switch(lang){
+ case "中文":
+ langCode = "zh_cn";
+ break;
+ case "English":
+ langCode = "en";
+ break;
+ case "日本語":
+ langCode = "jp";
+ break;
+ }
+ window.open(`./menu.html?lang=${langCode}`,"_self");
+ });
+ }
+}
+
+/**************************************
+ 方式名:btnClick()
+ 参数:i: 第i个按钮
+ 功能:处理主菜单按钮事件
+**************************************/
+Listener.prototype.btnClick = function(i){
+ switch (i){
+ case 0:
+ this.difficulty.style.display = "block";
+ break;
+ case 1:
+ this.tutorial.style.display = "block";
+ break;
+ case 2:
+ this.leaderBlk.style.display = "block";
+ break;
+ case 3:
+ window.location.href = "about:blank";
+ window.close();
+ break;
+ }
+}
+
+/**************************************
+ 方式名:diffBtnClick()
+ 参数:i: 第i个按钮
+ 功能:处理难度选单按钮事件
+**************************************/
+Listener.prototype.diffBtnClick = function(i){
+ switch (i){
+ case 0:
+ window.open(`./game.html?lang=${this.lang}&mode=easy`, "_self");
+ break;
+ case 1:
+ window.open(`./game.html?lang=${this.lang}&mode=hard`, "_self");
+ break;
+ case 2:
+ window.open(`./game.html?lang=${this.lang}&mode=extra`, "_self");
+ break;
+ case 3:
+ this.difficulty.style.display = "none";
+ break;
+ }
+}
+
+//屏蔽右键菜单
+document.oncontextmenu = function(event){
+ if(window.event){
+ event = window.event;
+ }try{
+ var the = event.srcElement;
+ if (!((the.tagName == "INPUT" && the.type.toLowerCase() == "text") || the.tagName == "TEXTAREA")){
+ return false;
+ }
+ return true;
+ }catch (e){
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/scripts/menu/menu.js b/scripts/menu/menu.js
new file mode 100644
index 0000000..e9e2c75
--- /dev/null
+++ b/scripts/menu/menu.js
@@ -0,0 +1,40 @@
+/**************************************
+ 文件名:menu.js
+ 功能:该模块用于主菜单相关内容
+ 版本:2.0(23.01.08)
+**************************************/
+
+new Menu();
+
+/**************************************
+ 对象名:Menu
+**************************************/
+function Menu(){
+ //获取URL中“?”及后续部分
+ let url = location.search;
+ if (url.indexOf("?") != -1){
+ //截取1到url.length的部分
+ url = url.substring(1);
+ //若传入了多个参数,以&分隔,此处将参数分解为数组内多个元素
+ url = url.split('&');
+ }
+
+ //url第0项是语言
+ this.lang = url[0].split('=')[1];
+
+ //获取HTML语言列表
+ this.langSlt = document.querySelector("footer .language");
+ this.languages = ["中文", "English", "日本語"];
+ //插入语言功能
+ for (let lang of this.languages){
+ const langLi = document.createElement("li");
+ langLi.setAttribute("class", `lang ${lang}`);
+ langLi.innerHTML = lang;
+ this.langSlt.appendChild(langLi);
+ }
+ this.listener = new Listener(this.lang);
+ this.language = new Language(this.lang);
+ this.language.langRender();
+ this.leaderBoard = new LeaderBoard(this.lang);
+ this.leaderBoard.rankRender("easy");
+}
\ No newline at end of file
diff --git a/style/game.css b/style/game.css
new file mode 100644
index 0000000..758e6d2
--- /dev/null
+++ b/style/game.css
@@ -0,0 +1,266 @@
+@charset "UTF-8";
+ul, li{
+ margin: 0;
+ padding: 0;
+ list-style: none;
+}
+
+body {
+ display: flex;
+ margin: 0;
+ padding: 0;
+ font-family: "雅黑", "Clear Sans", "Helvetica Neue", Arial, sans-serif;
+ background-color: #eeca98;
+}
+
+.right {
+ position: fixed;
+ width: 300px;
+ height: 100vh;
+ right: 0;
+ border-left: 2px solid black;
+ background-color: rgb(135, 133, 132, 0.4);
+}
+
+main {
+ height: 100vh;
+ width: calc(100vw - 300px);
+ margin: 0;
+ padding: 0;
+}
+
+.grid {
+ /* 简单9*9模式下方块宽度54px*9+间隔18px*8 = 630px */
+ /* 困难16*16模式下方块宽度30px*16+间隔10px*15 = 630px */
+ /* Extra30*16模式下高度同困难,宽度由js改为单个方块宽度30px*30+间隔10px*15 = 1050px */
+ width: 630px;
+ height: 630px;
+ display: flex;
+ justify-content: space-between;
+ flex-direction: column;
+ margin: calc((100vh - 630px)/2);
+ margin-left: calc((100vw - 930px)/2);
+ position: relative;
+}
+
+.grid .tileLine {
+ display: flex;
+ justify-content: space-between;
+ width: 630px;
+ height: auto;
+}
+
+.grid .tileLine .tile {
+ display: flex;
+ width: 54px;
+ height: 54px;
+ border-radius: 8px;
+ background-color: #8f7a67;
+}
+
+.grid .tileLine .nonTriggered:active {
+ background: #6f5c4b;
+ color: black;
+}
+
+.grid .tileLine .marked:active {
+ background: #6f5c4b;
+ color: black;
+}
+
+.grid .tileLine .triggered {
+ background-color: #bca794;
+}
+
+.grid .tileLine .tile p{
+ display: none;
+ position: relative;
+ text-align: center;
+ font-family: Arial-Black;
+ font-weight: 700;
+ width: 54px;
+ height: 54px;
+ line-height: 54px;
+ font-size: 25px;
+ bottom: 27px;
+}
+
+.grid .tileLine .triggered.value-1 p{
+ display: block;
+ color: aqua;
+}
+
+.grid .tileLine .triggered.value-2 p{
+ display: block;
+ color: #bee952;
+}
+
+.grid .tileLine .triggered.value-3 p{
+ display: block;
+ color: chartreuse;
+}
+
+.grid .tileLine .triggered.value-4 p{
+ display: block;
+ color: rgb(233, 24, 233);
+}
+
+.grid .tileLine .triggered.value-5 p{
+ display: block;
+ color: red;
+}
+
+.grid .tileLine .triggered.value-6 p{
+ display: block;
+ color: purple;
+}
+
+.grid .tileLine .triggered.value-7 p{
+ display: block;
+ color: purple;
+}
+
+.grid .tileLine .triggered.value-8 p{
+ display: block;
+ color: purple;
+}
+
+.grid .tileLine .tile img{
+ display: none;
+ width: 54px;
+ height: 54px;
+}
+
+.grid .tileLine .triggered.mine p{
+ display: none;
+}
+
+.grid .tileLine .triggered.mine img{
+ display: block;
+}
+
+.grid .tileLine .marked img{
+ display: block;
+}
+
+button {
+ width: 120px;
+ height: 50px;
+ background: #8f7a67;
+ border-radius: 4px;
+ font-size: 14px;
+ color: #ffffff;
+ text-align: center;
+ font-family: "雅黑", Arial-Black;
+ font-weight: 700;
+ line-height: 44px;
+ cursor: pointer;
+}
+
+button:hover {
+ background: #6f5c4b;
+}
+
+button:active {
+ background: #6f5c4b;
+ color: black;
+}
+
+.right .back {
+ margin-left: 90px;
+ position: fixed;
+ bottom: 100px;
+}
+
+.right .new {
+ margin-left: 90px;
+ position: fixed;
+ bottom: 200px;
+}
+
+.time {
+ margin-top: 60px;
+ display: flex;
+ flex-direction: column;
+ justify-content: space-evenly;
+ align-items: center;
+ height: 200px;
+ font-family: "雅黑", Arial-Black;
+ font-weight: 700;
+}
+
+.time .timeTitle {
+ font-size: 40px;
+ display: block;
+ text-align: center;
+ line-height: 40px;
+ height: 40px;
+}
+
+.time .timeCon {
+ display: block;
+ font-size: 25px;
+ text-align: center;
+ line-height: 50px;
+ height: 50px;
+ width: 160px;
+ border: 1px solid black;
+ background-color: #ebe84d;
+ border-radius: 5px;
+}
+
+.mask {
+ display: none;
+ position: fixed;
+ height: 100vh;
+ width: 100vw;
+ background-color: rgb(118, 115, 115, 0.6);
+ z-index: 100;
+}
+
+.mask .content {
+ text-align: center;
+ display: block;
+ font-size: 98px;
+ line-height: 98px;
+ height: 98px;
+ top: 300px;
+ font-family: Arial-Black;
+ font-weight: 700;
+}
+
+.mask .enterName {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ width: 600px;
+ margin-top: 100px;
+ margin-left: calc((100vw - 600px)/2);
+}
+
+.mask .enterName input {
+ width: 400px;
+ height: 50px;
+ padding-left: 20px;
+ font-size: 18px;
+ border: 2px solid black;
+ border-radius: 8px;
+ background-color: #bca794;
+ color: white;
+ font-weight: 700;
+ line-height: 50px;
+}
+
+.mask .enterName input:focus {
+ outline: none;
+}
+
+.mask .maskBtn {
+ position: absolute;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ width: 260px;
+ bottom: 200px;
+ margin-left: calc((100vw - 260px)/2);
+}
\ No newline at end of file
diff --git a/style/index.css b/style/index.css
new file mode 100644
index 0000000..29c7178
--- /dev/null
+++ b/style/index.css
@@ -0,0 +1,20 @@
+@charset "UTF-8";
+.hello {
+ position: absolute;
+ font-size: 98px;
+ display: block;
+ margin: -59px -300px;
+ text-align: center;
+ line-height: 98px;
+ height: 98px;
+ width: 600px;
+ top: 50%;
+ left: 50%;
+}
+
+body {
+ margin: 0;
+ padding: 0;
+ font-family: "雅黑", "Clear Sans", "Helvetica Neue", Arial, sans-serif;
+ background-color: #eeca98;
+}
\ No newline at end of file
diff --git a/style/menu.css b/style/menu.css
new file mode 100644
index 0000000..f39069f
--- /dev/null
+++ b/style/menu.css
@@ -0,0 +1,273 @@
+@charset "UTF-8";
+ul, li{
+ margin: 0;
+ padding: 0;
+ list-style: none;
+}
+
+.menu {
+ position: relative;
+ font-size: 98px;
+ display: block;
+ margin-top: 5%;
+ text-align: center;
+ line-height: 98px;
+ height: 98px;
+}
+
+body {
+ margin: 0;
+ padding: 0;
+ font-family: "雅黑", "Clear Sans", "Helvetica Neue", Arial, sans-serif;
+ background-color: #eeca98;
+}
+
+.selection {
+ display: flex;
+ justify-content: space-evenly;
+ align-items: center;
+ flex-direction: column;
+}
+
+button {
+ margin-top: 20px;
+ width: 120px;
+ height: 50px;
+ background: #8f7a67;
+ border-radius: 4px;
+ font-size: 14px;
+ color: #ffffff;
+ text-align: center;
+ font-family: "雅黑", Arial-Black;
+ font-weight: 700;
+ line-height: 44px;
+ cursor: pointer;
+}
+
+button:hover {
+ background: #6f5c4b;
+}
+
+button:active {
+ background: #6f5c4b;
+ color: black;
+}
+
+footer {
+ display: block;
+ position: fixed;
+ left: 60px;
+ bottom: 60px;
+}
+
+footer .langSltBtn {
+ width: 60px;
+ height: 60px;
+ border-radius: 4px;
+ border: 2px solid black;
+ font-size: 14px;
+ background: #8f7a67;
+ color: #ffffff;
+ text-align: center;
+ font-family: "雅黑", Arial-Black;
+ font-weight: 700;
+ cursor: pointer;
+ line-height: 60px;
+}
+
+footer .langSltBtn:hover {
+ background: #6f5c4b;
+}
+
+footer .langSltBtn:active {
+ background: #6f5c4b;
+ color: black;
+}
+
+footer .language {
+ width: 100px;
+ height: auto;
+ border-radius: 4px;
+ border: 2px solid black;
+ font-size: 14px;
+ background: #8f7a67;
+ color: #ffffff;
+ text-align: center;
+ font-family: "雅黑", Arial-Black;
+ font-weight: 700;
+ cursor: pointer;
+ line-height: 60px;
+ display: none;
+ flex-direction: column;
+ margin-bottom: 30px;
+ align-items: center;
+}
+
+footer .language .lang {
+ width: 100px;
+ height: 40px;
+ border: 2px solid white;
+ border-top: none;
+ border-left: none;
+ border-right: none;
+ display: block;
+ line-height: 40px;
+}
+
+footer .language .lang:hover {
+ background: #6f5c4b;
+}
+
+footer .language .lang:active {
+ background: #6f5c4b;
+ color: black;
+}
+
+footer .language .lang:last-child {
+ border-bottom: none;
+}
+
+.difficulty {
+ display: none;
+ z-index: 20;
+ position: fixed;
+ width: 320px;
+ height: 380px;
+ top: 50%;
+ left: 50%;
+ margin: -190px -160px;
+ border: 4px solid black;
+ border-radius: 10px;
+ background-color: #ed9e3e;
+}
+
+.difficulty .diffCon {
+ font-size: 60px;
+ display: block;
+ margin: 0;
+ margin-top: 10%;
+ text-align: center;
+ line-height: 60px;
+ height: 60px;
+}
+
+.difficulty .selection {
+ margin-top: 30px;
+}
+
+.back {
+ width: 60px;
+ height: 60px;
+ line-height: 60px;
+ position: absolute;
+ bottom: 20px;
+ left: 20px;
+}
+
+.tutorialBlk {
+ display: none;
+ z-index: 20;
+ position: fixed;
+ width: 600px;
+ height: 500px;
+ top: 50%;
+ left: 50%;
+ margin: -250px -300px;
+ border: 4px solid black;
+ border-radius: 10px;
+ background-color: #ed9e3e;
+}
+
+.tutorialBlk .tutoCon {
+ font-size: 60px;
+ display: block;
+ margin: 0;
+ margin-top: 20px;
+ text-align: center;
+ line-height: 60px;
+ height: 60px;
+}
+
+.tutorialBlk .tutor {
+ display: flex;
+ flex-direction: column;
+ margin-top: 20px;
+ margin-left: 20px;
+ margin-right: 20px;
+ justify-content: space-evenly;
+ align-items: flex-start;
+ height: 320px;
+}
+
+.tutorialBlk .tutor p {
+ font-size: 20px;
+ margin: 0;
+}
+
+.extra {
+ width: 60px;
+ height: 60px;
+ line-height: 60px;
+ position: absolute;
+ bottom: 20px;
+ right: 20px;
+}
+
+.leaderBlk {
+ display: none;
+ z-index: 20;
+ position: fixed;
+ width: 600px;
+ height: 500px;
+ top: 50%;
+ left: 50%;
+ margin: -250px -300px;
+ border: 4px solid black;
+ border-radius: 10px;
+ background-color: #ed9e3e;
+}
+
+.leaderBlk .leaderCon {
+ font-size: 60px;
+ display: block;
+ margin: 0;
+ margin-top: 20px;
+ text-align: center;
+ line-height: 60px;
+ height: 60px;
+}
+
+.leaderBlk .leaderDif {
+ font-size: 40px;
+ display: block;
+ margin: 0;
+ margin-top: 10px;
+ text-align: center;
+ line-height: 60px;
+ height: 40px;
+}
+
+.leaderBlk .leadList {
+ display: block;
+ margin-top: 40px;
+ margin-left: 30px;
+ margin-right: 30px;
+ height: 350px;
+}
+
+.leaderBlk .leadList li {
+ display: flex;
+ font-size: 20px;
+ justify-content: space-between;
+ align-items: center;
+ margin: 20 0;
+}
+
+.leaderBlk .next {
+ width: 60px;
+ height: 60px;
+ line-height: 60px;
+ position: absolute;
+ bottom: 20px;
+ right: 20px;
+}
\ No newline at end of file