commit d059bab473d4a2fe1bfbf5af9b2c409294e76840 Author: shipiyouniao <2960474346@qq.com> Date: Fri Jan 27 19:45:10 2023 +0800 first commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..499e7a9 --- /dev/null +++ b/README.md @@ -0,0 +1,21 @@ +# Minesweeper made by SPYN + +An easy game which is similar to the Minesweeper made by Microsoft. + +The game is made for SPYN's practical engineering assignments. + +No commercial use, only for learning reference. + + + +## Getting Started + +This Game is built in JavaScript with HTML & CSS. + +To pack the code properly, you might need use NW.js(https://nwjs.io). + + + +## Additional Information + +The game supports 3 languages (ZH_CN\EN-WW\JA-JP), but the code comments are written in Chinese. \ No newline at end of file diff --git a/game.html b/game.html new file mode 100644 index 0000000..ba4c397 --- /dev/null +++ b/game.html @@ -0,0 +1,39 @@ + + + + + game + + + +
+
+
+
+
+

Time

+

0h0min0s

+
+ + +
+
+

You win!

+
+ + +
+
+ + +
+
+ + + + + + + + + \ No newline at end of file diff --git a/icon.ico b/icon.ico new file mode 100644 index 0000000..58e61e4 Binary files /dev/null and b/icon.ico differ diff --git a/image/bomb.png b/image/bomb.png new file mode 100644 index 0000000..476d368 Binary files /dev/null and b/image/bomb.png differ diff --git a/image/flag.png b/image/flag.png new file mode 100644 index 0000000..58e61e4 Binary files /dev/null and b/image/flag.png differ diff --git a/index.html b/index.html new file mode 100644 index 0000000..cad0e5c --- /dev/null +++ b/index.html @@ -0,0 +1,12 @@ + + + + + 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