游戏介绍:《扫雷》是一款大众类的益智小游戏,于1992年发行。游戏目标是在最短的时间内根据点击格子出现的数字找出所有非雷格子,同时避免踩雷,踩到一个雷即全盘皆输。
游戏操作功能:
游戏的基本操作包括左键单击(Left Click)、右键单击(Right Click)、双击(Chording)三种。其中左键用于打开安全的格子,推进游戏进度;右键用于标记地雷,以辅助判断,或为接下来的双击做准备;双击在一个数字周围的地雷标记完时,相当于对数字周围未打开的方块均进行一次左键单击操作:
左键单击:在判断出不是雷的方块上按下左键,可以打开该方块。如果方块上出现数字,则该数字表示其周围3×3区域中的地雷数(一般为8个格子,对于边块为5个格子,对于角块为3个格子。所以扫雷中最大的数字为8);如果方块上为空(相当于0),则可以递归地打开与空相邻的方块;如果不幸触雷,则游戏结束。
右键单击:在判断为地雷的方块上按下右键,可以标记地雷(显示为小红旗)。重复一次或两次操作可取消标记(如果在游戏菜单中勾选了“标记(?)”,则需要两次操作来取消标雷)。
双击:同时按下左键和右键完成双击。当双击位置周围已标记雷数等于该位置数字时操作有效,相当于对该数字周围未打开的方块均进行一次左键单击操作。地雷未标记完全时使用双击无效。若数字周围有标错的地雷,则游戏结束,标错的地雷上会显示一个
废话不多说了,开搞~
开发准备:
首先我们要搭建TS的环境(此处省略N个字).......
配置文件 webpack.config.js
const HtmlWebpackPlugin = require("html-webpack-plugin")
const { CleanWebpackPlugin } = require("clean-webpack-plugin")
module.exports = {
entry:"./js/index.ts",
output:{
filename:"main.js"
},
resolve: {
extensions: ['.tsx', '.ts', '.js', '.json']
},
module:{
rules:[
{
test:/\.tsx?$/,
use:"ts-loader",
exclude:/node_modules/
}
]
},
devtool: process.env.NODE_ENV === "production"?false:"inline-source-map",
devServer:{
contentBase:"./dist",
compress:false,
stats:"errors-only",
port:1000,
host:"localhost"
},
plugins:[
new CleanWebpackPlugin({
cleanOnceBeforeBuildPatterns:["./dist"]
}),
new HtmlWebpackPlugin({
template: "./html/index.html"
})
]
}
`````package.json`````
{
"name": "ts-test",
"version": "1.0.0",
"description": "ts-test",
"main": "index.ts",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "cross-env NODE_ENV=development webpack-dev-server --config ./webpack.config.js",
"build": "cross-env NODE_ENV=production webpack --config ./webpack.config.js"
},
"keywords": [
"ts"
],
"author": "[email protected]",
"license": "MIT",
"devDependencies": {
"clean-webpack-plugin": "^3.0.0",
"cross-env": "^7.0.2",
"html-webpack-plugin": "^4.3.0",
"process-env": "^1.1.0",
"ts-lint": "^4.5.1",
"ts-loader": "^8.0.2",
"typescript": "^3.9.7",
"webpack": "^4.44.1",
"webpack-cli": "^3.3.12",
"webpack-dev-server": "^3.11.0"
},
"dependencies": {
"moment": "^2.27.0"
}
}
····html以及一些简单的样式美化····
Document
逻辑走起:
1、定义每个方格的属性
interface SquareInterface{
/** 是否是炸弹 */
isSquare:boolean,
/** 周围炸弹的数量 */
squareNum:number,
/** 是否展开 */
isOpen:boolean,
/** 当前下标 */
idx:number,
/** 是否被标记 */
mark:boolean
}
2、创建一个 MineClearance 类以及属性和默认值
export class MineClearance {
/** 总共炸弹数 */
thunder:number = 99
/** 一行为多少个方块 */
singleRow:number = 30
/** 总共方格数量 */
allSquareNum:number = this.singleRow * this.singleRow
/** 生成的所有方格 */
allSquare:Array = []
constructor(singleRow?:number,thunder?:number){
this.thunder = thunder || this.thunder
this.singleRow = singleRow || this.singleRow
this.allSquareNum = this.singleRow * this.singleRow
}
}
3、接下来就是扫雷的一些相关数据生成
/** 设置雷的数量 */
public setSquareNumAll(){
let inp = document.getElementById("inp") as HTMLInputElement
let inpSquare = document.getElementById("inpSquare") as HTMLInputElement
let thunder = Number(inp.value)
let singleRow = Number(inpSquare.value)
let allSquareNum = singleRow*singleRow
if(thunder>=allSquareNum){
this.innerBody("visible","雷的数量不能高于或等于网格数量")
}else {
this.thunder = thunder
this.singleRow = singleRow
this.allSquareNum = allSquareNum
this.initialize()
}
}
/** 生成数据 */
public setAllSquare(){
this.allSquare = []
/** 随机生成雷的下标 */
let randomSquareNumber:Array = []
for(let i=0;randomSquareNumber.length this.countSquareNum(item,index, this.allSquare))
}
4、根据生成的数据生成页面
/** 更新/生成dom
* @param { visibility: visible | hidden }
* @param { data:string = "" }
*/
public innerBody(visibility?:string,data?:string,){
let html:string = this.modelBox(visibility || "hidden",data)
html+= `雷的数量: 一行方格数: 重置 `
html+= ``
this.allSquare.forEach((item,index)=>{
html += `${item.isOpen?(item.isSquare?'':item.squareNum>0?item.squareNum:''):item.mark?"❗":""}`
})
html += ""
document.body.innerHTML = html
this. getItemEvent()
}
/** 给dom添加监听方法 */
public getItemEvent(){
let confirm = document.getElementById("confirm")
confirm?.addEventListener("click",this.setSquareNumAll.bind(this),false)
let closeMask = document.getElementById("closeMask")
closeMask?.addEventListener("click",this.closeModel.bind(this),false)
let bodyEmentAll = document.getElementsByClassName('item') as HTMLCollectionOf;
for(let i=0;i
5.操作方法及计算(核心模块)
// 场景1 点击打开为雷 游戏结束 (完成)
// 场景2 点击打开为数字 游戏继续
// 场景3 点击打开为空白 展开周围所有连接的空白及空白和空白周围的数字 游戏继续
// 场景4 点击的为已经打开 双击根据标记展开其他相邻方格,若为空白则以空白方格为单位再次扩展
// 场景5 标记错误双击已经打开的方格 游戏结束
// 场景6 找出所有雷 游戏结束 通关
// 场景7 找出打开所有非雷方格 游戏结束 通关
/** 计算方块周围炸弹数量 */
public countSquareNum(squartItem:SquareInterface,index:number,allSquareData:Array){
let squareNumb:number = 0
let aroundSquare:Array = this.getAroundIdx(index)
aroundSquare.map(item => {
if( item > -1 && this.allSquareNum > item && allSquareData[item].isSquare){
squareNumb++
}
})
squartItem.squareNum = squareNumb
return squartItem
}
/** 连续打开 */
openBox(data:SquareInterface){
this.allSquare[data.idx].isOpen = true
let aroundSquare:Array = this.getAroundIdx(data.idx).filter(it=> !this.allSquare[it].isOpen&&!this.allSquare[it].mark)
aroundSquare.map(item=>{
this.allSquare[item].isOpen = true
if(this.allSquare[item].squareNum<1)this.openBox(this.allSquare[item])
})
}
/** 左键单击 */
public leftOpenItem(ev){
let itemData:SquareInterface = ev.srcElement ? JSON.parse(ev.srcElement.dataset.item) : ev
/** 不打开标记方块 */
if(itemData.mark) return
if(itemData.isOpen){
// 如果点击的当前方格的雷的数量和周围标记的方格数量相等则打开周边其他方格
// 如果标记的不为雷 那么游戏失败
let aroundIdxs:Array = this.getAroundIdx(itemData.idx)
let marks:Array = aroundIdxs.filter(markidx => this.allSquare[markidx].mark)
if(marks.find(item=> !this.allSquare[item].isSquare)) {
this.allSquare.forEach(square => square.isOpen = true)
this.innerBody("visible","游戏结束")
}else if(marks.length === itemData.squareNum&&itemData.squareNum>0){
let notMarks:Array = aroundIdxs.filter(notMarkitem=> !this.allSquare[notMarkitem].mark)
notMarks.map(openItem=> {
if(this.allSquare[openItem].squareNum<1){
this.openBox(this.allSquare[openItem])
}
this.allSquare[openItem].isOpen = true
})
}
this.innerBody()
}else{
if(itemData.isSquare){
this.allSquare.forEach(item => item.isOpen = true)
}else if(itemData.squareNum == 0){
this.openBox(itemData)
if(this.thunder === this.allSquareNum - this.allSquare.filter(item=>item.isOpen).length) this.innerBody("visible","游戏通关")
}else{
this.allSquare[itemData.idx].isOpen = true
if(this.thunder === this.allSquareNum - this.allSquare.filter(item=>item.isOpen).length) this.innerBody("visible","游戏通关")
}
this.innerBody()
if(itemData.isSquare)this.innerBody("visible","游戏结束")
else if(this.thunder === this.allSquareNum - this.allSquare.filter(item=>item.isOpen).length) this.innerBody("visible","游戏通关")
}
}
/** 右键单击 */
public rigthMarkItem(ev){
ev.preventDefault()
let itemData:SquareInterface = JSON.parse(ev.srcElement.dataset.item)
if(itemData.isOpen) return
this.allSquare[itemData.idx].mark = !itemData.mark
this.innerBody()
if(this.thunder == this.allSquare.filter(item=>item.mark).length){
this.innerBody("visible","游戏通关")
}
}
6、当完成这些就只剩下最后的一步了,初始化扫雷
/** 初始化 */
public initialize(){
this.setAllSquare()
this.innerBody()
}
在你的index.ts中引用并使用
import { MineClearance } from "./mineClearance"
let clearance = new MineClearance(20,40)
clearance.initialize()
document.title = "简易版扫雷"
成品如下图:(⊙o⊙)… 最嘲讽的事情就是自己写的东西自己过不了关,尴尬了~~~
各位老铁是不是感觉这博客写的有点草率了?
好吧,感谢各位看到结尾,如此给博主面子我怎么能不对各位以诚相待呢???
本人入行不久,这个dome着实有点拉跨,还有很多可以优化和改进的地方(甚至可能会有bug 哈哈哈)。 如有大佬能够不吝赐教定不胜感激~~~