first commit

This commit is contained in:
石皮幼鸟 2023-01-27 19:45:10 +08:00
commit d059bab473
24 changed files with 2065 additions and 0 deletions

21
README.md Normal file
View File

@ -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.

39
game.html Normal file
View File

@ -0,0 +1,39 @@
<!DOCTYPE html>
<html lang="zh_cn">
<head>
<meta charset="UTF-8" />
<title>game</title>
<link rel="stylesheet" href="./style/game.css" />
</head>
<body>
<main>
<div class="grid"></div>
</main>
<div class="right">
<div class="time">
<p class="timeTitle">Time</p>
<p class="timeCon">0h0min0s</p>
</div>
<button class="new">New Game</button>
<button class="back">Back</button>
</div>
<div class="mask">
<p class="content">You win!</p>
<div class="enterName">
<input type="text" placeholder="Please enter your name" />
<button class="confirm">Confirm</button>
</div>
<div class="maskBtn">
<button class="new">New Game</button>
<button class="back">Back</button>
</div>
</div>
<script src="./scripts/game/leaderBoard.js"></script>
<script src="./scripts/game/time.js"></script>
<script src="./scripts/game/tile.js"></script>
<script src="./scripts/game/grid.js"></script>
<script src="./scripts/game/listener.js"></script>
<script src="./scripts/game/language.js"></script>
<script src="./scripts/game/manager.js"></script>
</body>
</html>

BIN
icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
image/bomb.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

BIN
image/flag.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

12
index.html Normal file
View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="zh_cn">
<head>
<meta charset="UTF-8" />
<title>hello</title>
<link rel="stylesheet" href="./style/index.css" />
</head>
<body>
<div class="hello">石皮幼鸟制作</div>
<script src="./scripts/index.js"></script>
</body>
</html>

54
menu.html Normal file
View File

@ -0,0 +1,54 @@
<!DOCTYPE html>
<html lang="zh_cn">
<head>
<meta charset="UTF-8" />
<title>menu</title>
<link rel="stylesheet" href="./style/menu.css" />
</head>
<body>
<p class="menu">Minesweeper</p>
<div class="selection menuList">
<button class="start">Start Game</button>
<button class="tutorial">Tutorial</button>
<button class="leaderBoard">LeaderBoard</button>
<button class="exit">Exit</button>
</div>
<div class="difficulty">
<p class="diffCon">Difficulty</p>
<div class="selection">
<button class="easy">Easy</button>
<button class="hard">Hard</button>
</div>
<button class="extra">Extra</button>
<button class="back">Back</button>
</div>
<div class="tutorialBlk">
<p class="tutoCon">Tutorial</p>
<div class="tutor">
<P>Click the left mouse button to crack open the square</P>
<p>If a mine is triggered, the game is over</p>
<p>If a number appears, it means that the number of mines with the corresponding number is around</p>
<p>If blank, there are no mines around</p>
<p>Right mouse click on the square that marks what you suspect is a mine</p>
<p>Right click again to unmark</p>
<p>Knock out all the non-mine squares to win the final victory</p>
</div>
<button class="back">Back</button>
</div>
<div class="leaderBlk">
<p class="leaderCon">LeaderBoard</p>
<p class="leaderDif">Easy</p>
<ul class="leadList"></ul>
<button class="next">Hard</button>
<button class="back">Back</button>
</div>
<footer>
<ul class="language"></ul>
<div class="langSltBtn">Lang</div>
</footer>
<script src="./scripts/menu/language.js"></script>
<script src="./scripts/menu/listener.js"></script>
<script src="./scripts/menu/leaderBoard.js"></script>
<script src="./scripts/menu/menu.js"></script>
</body>
</html>

12
package-lock.json generated Normal file
View File

@ -0,0 +1,12 @@
{
"name": "minesweeper",
"version": "2.0.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "minesweeper",
"version": "2.0.0"
}
}
}

24
package.json Normal file
View File

@ -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
}
}

538
scripts/game/grid.js Normal file
View File

@ -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;
}

82
scripts/game/language.js Normal file
View File

@ -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 = "戻る";
}
}

113
scripts/game/leaderBoard.js Normal file
View File

@ -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;
}

77
scripts/game/listener.js Normal file
View File

@ -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;
}
}

36
scripts/game/manager.js Normal file
View File

@ -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);
}

36
scripts/game/tile.js Normal file
View File

@ -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对象
返回值包含valueisMinerecent的对象
**************************************/
Tile.prototype.serialize = function(){
return {
value: this.value,
isMine: this.isMine,
recent: this.recent
};
}

26
scripts/game/time.js Normal file
View File

@ -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`;
}

10
scripts/index.js Normal file
View File

@ -0,0 +1,10 @@
/**************************************
文件名index.js
功能该模块用于处理开始界面相关内容
版本2.0(23.01.08)
**************************************/
//3秒后进入主菜单
setTimeout(function (){
window.open('./menu.html?lang=en', "_self");
}, 3000);

118
scripts/menu/language.js Normal file
View File

@ -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 =
`<P>点击鼠标左键敲开方块</P>
<p>如果触发了地雷游戏结束</p>
<p>如果出现数字则意味着周围有对应数字的地雷数</p>
<p>如果是空白则周围无地雷</p>
<p>鼠标右击标记你怀疑是地雷的方块</p>
<p>再右击一次可以取消标记</p>
<p>敲开所有非地雷方块赢得最终胜利</p>`;
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 =
`<P>Click the left mouse button to crack open the square</P>
<p>If a mine is triggered, the game is over</p>
<p>If a number appears, it means that the number of mines with the corresponding number is around</p>
<p>If blank, there are no mines around</p>
<p>Right mouse click on the square that marks what you suspect is a mine</p>
<p>Right click again to unmark</p>
<p>Knock out all the non-mine squares to win the final victory</p>`;
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 =
`<P>マウスの左ボタンで四角を発動します</P>
<p>地雷が発生したらゲームオーバー</p>
<p>数字が表示された場合その数字に対応する地雷の数が周辺にあることを意味します</p>
<p>空白の場合地雷はありません</p>
<p>地雷と思われるマークをマウスの右ボタンでクリックする</p>
<p>もう一度右クリックするとマークが消えます</p>
<p>地雷のないマスをすべてトリガーにして最終的に勝利する</p>`;
document.querySelector(".leaderCon").innerHTML = "リーダーボード";
}

121
scripts/menu/leaderBoard.js Normal file
View File

@ -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 =
`<div>${i+1}</div><div>${rank[i].ID}</div><div>${rank[i].time}</div>`;
this.leadList.appendChild(li);
}
document.querySelector(".leaderDif").innerHTML = diffCon;
this.next.innerHTML = nextCon;
}

147
scripts/menu/listener.js Normal file
View File

@ -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;
}
}

40
scripts/menu/menu.js Normal file
View File

@ -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");
}

266
style/game.css Normal file
View File

@ -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);
}

20
style/index.css Normal file
View File

@ -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;
}

273
style/menu.css Normal file
View File

@ -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;
}