返回顶部

js 游戏编程:扫雷

全程无参考,自行设计编码,2小时速通:

<meta charset="UTF-8">
<style>
* {
	-webkit-touch-callout:none;
	-webkit-user-select:none;
	-khtml-user-select:none;
	-moz-user-select:none;
	-ms-user-select:none;
	user-select:none;
}
input , textarea{
	-webkit-user-select:auto;
    user-select: auto;
}
section {
    white-space: nowrap;
    min-width: 1024px;
}
.unit, .active, .bomb, .white, .flag {
    text-align: center;
    width: 2.8rem;
    height: 2.8rem;
    background-color: rgb(129, 169, 245);
    color: white;
    font-weight: bolder;
    line-height: 25px;
}
.active {
    background-color: rgb(61, 105, 187) !important;
}
.bomb {
    background-color: rgb(227, 40, 40) !important;
}
.white {
    background-color: rgb(73, 94, 133) !important;
}
.flag {
    background-color: rgb(247, 169, 86) !important;
}
body {
    overflow: auto;
}
span {
    animation: rot 2s linear infinite;
    font-size: 1.5rem;
    line-height: 27px;
    display: block;
    width: 2.1rem;
    height: 2.1rem;
}
@keyframes rot {
    0% {
        transform: rotate(0deg);
    }
    100% {
        transform: rotate(360deg);
    }
}
</style>
<div id="main"></div>
<script>
document.addEventListener('touchstart', e => 
  e.preventDefault());
// 数学工具
const random = () => Math.random();
const floor = (v) => Math.floor(v);
// main 区域
let main = document.getElementById('main');
///// 地板 mtx - 地板单位元素 box
// 1 - 创建 地板 mtx 的容器
let mtx;
let rn = 10;
let cn = 10;
let crtMtx = (rnn, cnn) => {
    // 创建行容器
    if(rnn !== undefined) rn = rnn;
    if(cnn !== undefined) cn = cnn;
    mtx = new Array(rn);
    for(var i = 0; i < rn; i++){
        // 创建当前行 每一列 box 的容器
        mtx[i] = new Array(cn + 1);
        // 每一行的第一列 为 当前行每一列 box 的父容器(行) sec
        mtx[i][0] = document.createElement('section');
        // 将每一行对应的 sec 容器 渲染到 main 容器上
        main.appendChild(mtx[i][0]);
    }
}
// 2 - 创建 地板元素 box(包括 这个地砖 的相关数据)
let geneBoxAndData = (x, y) => {
    var box = document.createElement('button');
    box.className = 'unit';
    box.innerHTML = '&nbsp;';
    // 记录 box 的坐标
    box.setAttribute("x", x);
    box.setAttribute("y", y);
    // var val = 0; // 是雷:-1,不是雷-周围雷的个数:0-8,被打开 +8
    return {"e":box, "v":0};
}
// 3 - 整个地板,生成每个地砖,并渲染到页面
let geneMtxData = () => {
    for(var r = 0; r < mtx.length; r++){
        var colSec = mtx[r][0];
        for(var c = 1; c < mtx[0].length; c++){
            // 生成数据
            var data = geneBoxAndData(r, c);
            // 加入dom和数据
            colSec.appendChild(data.e);
            mtx[r][c] = data;
        }
    }
}
//// 1 - 整个地板,地雷生成(在mtx上生成num个雷)
//// 2 - 必须管理每个地雷 --- 地雷列表
////  2.1 - 列表的元素 是地雷的坐标(r, c)
let bombs = [];
//    2.2 - 第一次点击之后的位置 不能是地雷
let first = [5, 5];
let geneBombs = (num) => {
    if(num > (rn * cn) >> 2) {
        alert('雷的数量不能超过当前地砖的25%');
        initGlobal();
        return;
    }
    var rdmRow = floor(random() * (mtx.length - 1));
    var rdmCol = floor(random() * (mtx[0].length - 1)) + 1;
    for(var i = 0; i < num; i++){
        while(mtx[rdmRow][rdmCol].v === -1 || 
            (rdmRow < first[0] + 2 && rdmRow > first[0] - 2 && 
             rdmCol < first[1] + 2 && rdmCol > first[1] - 2)){
            rdmRow = floor(random() * (mtx.length - 1));
            rdmCol = floor(random() * (mtx[0].length - 1)) + 1;
        } mtx[rdmRow][rdmCol].v = -1;
        // 管理这个雷的位置
        bombs.push([rdmRow, rdmCol]);
    }
}
//// 确定 整个地板-每个地砖 的地雷数据
// 1 - 确定 一个雷对周围地砖的影响
let oneBombData = (r, c) => {
    for(var i = -1; i < 2; i++){
        var row = r + i;
        // 行存在
        if(row < 0 || row >= mtx.length) continue;
        for(var j = -1; j < 2; j++){
            var col = c + j;
            // 列存在
            if(col < 1 || col >= mtx[0].length) continue;
            // 当前行列位置 不是雷
            if(mtx[row][col].v != -1) mtx[row][col].v++;
        }
    }
}
// 2 - 确定 所有地雷对整个地砖的影响
let allBombsData = () => {
    for(var i = 0; i < bombs.length; i++)
        oneBombData(bombs[i][0], bombs[i][1]);
}
let renderOne = (r, c) => {
    var data = mtx[r][c];
    if(data.v < 1) data.e.innerHTML = data.v == -1 ? '<span>原</span>' : '&nbsp;';
    else data.e.innerHTML = data.v;
    if(data.v == -1) {
        if(!flags.has(`${r},${c}`)) data.e.className = "bomb";
    }
    else if(data.v == 0) data.e.className = "white";
    else data.e.className = "active";
}
//// 渲染一个地砖 并访问
let renderVisitOne = (r, c)=>{
    // 渲染
    renderOne(r, c);
    // 访问
    vst.set(`${r},${c}`, "");
    vstnum++;
}
let renderAll = () => {
    for(var r = 0; r < mtx.length; r++){
        for(var c = 1; c < mtx[0].length; c++)
            renderOne(r, c);
    }
}
// 求单次点击最大扩大范围
//  - 防止重复遍历,记录已访问位置
let vst = new Map();
let dfs = (r, c) => {
    renderVisitOne(r, c);
    if(mtx[r][c].v == -1) {
        isFail = true;
        return;
    }
    for(var i = -1; i < 2; i++){
        var row = r + i;
        // 行存在
        if(row < 0 || row >= mtx.length) continue;
        for(var j = -1; j < 2; j++){
            var col = c + j;
            // 列存在
            if(col < 1 || col >= mtx[0].length) continue;
            // 当前行列位置 是0
            if(!vst.has(`${row},${col}`)){
                if(mtx[row][col].v == 0) dfs(row, col);
                else if(mtx[r][c].v == 0 && mtx[row][col].v > 0 ) renderVisitOne(row, col);
            }
        }
    }
}
// main
// 长按逻辑 - 是否插旗了
let flags = new Map();
// 长按逻辑 - 时间点之差
let downTime;
let ltOrC = (ev, flt = nextLongTouch, fc = nextClick, t = 130) => {
    if(ev.timeStamp - downTime > t) flt(ev);
    else fc(ev);
}
let getDownNow = (ev) => downTime = ev.timeStamp;
// LongTouchEv
let addLTEv = (e, flt, fc, t) => {
    e.addEventListener('mousedown', getDownNow);
    e.addEventListener('touchstart', getDownNow);
    downTime = new Date().getTime(); // 初始化时间戳
    e.addEventListener('mouseup', ltOrC);
    e.addEventListener('touchend', ltOrC);
}
let removeLTEv = (e) => {
    e.removeEventListener('mousedown', getDownNow);
    e.removeEventListener('touchstart', getDownNow);
    e.removeEventListener('mouseup', ltOrC);
    e.removeEventListener('touchend', ltOrC);
}
// 第二次及以后的点击事件
let nextClick = (ev) => {
    var btn = ev.target;
    if(!btn.hasAttribute("x")) return;
    var x = floor(btn.getAttribute("x"));
    var y = floor(btn.getAttribute("y"));
    // 点击逻辑 - dfs
    if(flags.has(`${x},${y}`)) return;
    // 没有插旗,就去dfs回溯访问所有能访问/无雷的地砖
    dfs(x, y); // vst = new Map();
    if(isFail) {
        var remd = cntRemindNum();
        main.removeEventListener('mousedown', firstClick);
        main.removeEventListener('touchstart', firstClick);
        removeLTEv(main);
        ((remd) => {
            alert('你已经失败了!\n' + 
                '   剩余雷数:' + (remindBombs - remd) + " 个没有扫出来!\n" + 
                '    5 秒后重启游戏!');
            renderAll();
            return new Promise(resolve => {
                setTimeout(resolve, 5000);
            })
        })(remd).then(() => initGlobal());
    }
    if(vstnum == sum){
        if(cntRemindNum() == targetBombs){
            alert('恭喜您已通关!');
            initGlobal();
        }
    }
}
let nextLongTouch = (ev) => {
    var btn = ev.target;
    if(!btn.hasAttribute("x")) return;
    var x = floor(btn.getAttribute("x"));
    var y = floor(btn.getAttribute("y"));
    // 长按逻辑 - 插旗 / 拔旗
    var key = `${x},${y}`;
    if(flags.has(key)) {
        flags.delete(key);
        mtx[x][y].e.className = 'unit';
        vstnum--;
    } else {
        if(vst.has(key)) return;
        flags.set(key, "");
        mtx[x][y].e.className = 'flag';
        vstnum++;
    }
    if(vstnum == sum){
        alert('恭喜您已通关!');
        initGlobal();
    }
}
// 第一次的点击事件
let firstClick = (ev) => {
    // 事件委托
    var btn = ev.target;
    first[0] = floor(btn.getAttribute("x"));
    first[1] = floor(btn.getAttribute("y"));
    // 生成地雷
    geneBombs(targetBombs);
    allBombsData();
    dfs(first[0], first[1]);
    // renderBombsData();
    main.removeEventListener('mousedown', firstClick);
    main.removeEventListener('touchstart', firstClick);
    // 添加之后的每次点击事件
    addLTEv(main, nextLongTouch, nextClick, 1000);
}
let initBombs = () => {
    // 添加 第一次点击的事件监听
    main.addEventListener('mousedown', firstClick);
    main.addEventListener('touchstart', firstClick);
}
// 游戏相关数据
let isFail = false;
let targetBombs = 60;
let remindBombs = targetBombs;
// 当前访问了多少个
//  - 虽然记录了访问数组,但是还要维护访问个数
let vstnum = -1;
// 总数
let sum = 0;
let initGlobal = () => {
    main.removeEventListener('mousedown', firstClick);
    main.removeEventListener('touchstart', firstClick);
    removeLTEv(main);
    main.innerHTML = '';
    mtx = null;
    first = [];
    bombs = [];
    vst = new Map();
    flags = new Map();
    isFail = false;
    targetBombs = 60;
    remindBombs = targetBombs;
    vstnum = -1;
    sum = 0;
    // 开始执行主函数
    _main_func();
}
// 计算剩余雷数
let cntRemindNum = () => {
    var remd = 0;
    flags.forEach((v, k, m) => {
        var strs = k.split(",");
        if(mtx[floor(strs[0])][floor(strs[1])].v == -1) remd++;
    });
    return remd;
}
let _main_func = (rn, cn) => {
    crtMtx(rn, cn);
    sum = rn * cn;
    geneMtxData();
    initBombs();
}
_main_func(15, 20);
</script>
posted @ 2023-09-30 02:01  你好,一多  阅读(5)  评论(0编辑  收藏  举报