H5 推箱子 送行者

开始做的时候还挺接地气的…😅要不是没有素材也不至于如此…

😀效果

🖇在线链接

https://linyisonger.github.io/H5.Examples

img

📕逻辑

代码逻辑并不是很难,写的时间不长,可能会有很多🐞,复制的时候注意安全…

定义宽度/高度这个看自己需要,我这边是因为没有素材所有…使用了中文文字,如果有素材的话可以根据素材进行变更。

// 宽度
let width = 20, height = 20;

定义地图枚举,我是这样想的所有可以挪动的,都是在空白格的基础上,不可挪动的一共就也没几种,所以使用枚举的组合,更便于判断。

// 地图枚举
let mapEnum = {
    blank: 1, // 空白格
    player: 2, // 玩家
    box: 4, // 箱子
    target: 8, // 目标
    wall: 16 // 墙
}

地图配置,这点儿可能会有疑惑,关于数字9呀、5呀、3呀命名枚举上没有写,这里是便是组合。

其中3:代表2玩家踩在1空白格上

其中5:代表4箱子放在1空白格上

其中9:代表8目标放在1空白格上

以此类推…不限于两两组合

let map = 
    [
        [16, 16, 16, 16, 16, 16],
        [16, 1, 1, 9, 1, 16],
        [16, 1, 5, 3, 1, 16],
        [16, 9, 16, 5, 1, 16],
        [16, 1, 5, 1, 9, 16],
        [16, 16, 16, 16, 16, 16],
    ]

初始化相关,关于玩家初始位置,方便后续操作,使用双重嵌套循环来捕获地图上的玩家位置。

以及操作按钮的点击事件,按钮的事件。

let player = { x: 0, y: 0 }

let sokobanDom = document.querySelector('.sokoban')
let sokobanMapDom = {}
let operateDom = document.querySelectorAll('.operate > .btn')



// 初始化
function init() {
    let ylength = map.length, xlength = 0
    for (let y = 0; y < map.length; y++) {
        const xmap = map[y];
        xlength = Math.max(xlength, xmap.length)
        for (let x = 0; x < xmap.length; x++) {
            if ((xmap[x] & mapEnum.player) != 0) {
                player = { x, y }
            }
        }
    }
    sokobanDom.setAttribute('style', `width:${xlength * width}px;height:${ylength * height}px`)
    for (let i = 0; i < operateDom.length; i++) {
        const btn = operateDom.item(i);
        btn.addEventListener('click', function () {
            onKeyDown(['ArrowUp', 'ArrowLeft', 'ArrowDown', 'ArrowRight'][i])
        })
    }
}

function onKeyDown(key) {
    let x = 0, y = 0;
    if (key == 'ArrowUp') y -= 1
    if (key == 'ArrowDown') y += 1
    if (key == 'ArrowLeft') x -= 1
    if (key == 'ArrowRight') x += 1
    try {
        if (x || y) walk(x, y)
    } catch (error) {
        // 报错就不处理,任性哈哈哈哈
    }
}

document.addEventListener("keydown", (ev) => onKeyDown(ev.key))

地图渲染,双层嵌套渲染,并不复杂的代码…

// 渲染
function randerMap() {
    for (let y = 0; y < map.length; y++) {
        const xmap = map[y];
        for (let x = 0; x < xmap.length; x++) {
            let sokobanItem = null;
            if (sokobanMapDom[`${x},${y}`]) {
                sokobanItem = sokobanMapDom[`${x},${y}`];
                sokobanItem.textContent = ''
            }
            else {
                sokobanItem = document.createElement('div')
                sokobanItem.classList.add('sokoban-item')
                sokobanItem.setAttribute(`style`, `left:${x * width}px;top:${y * height}px`)
                sokobanDom.appendChild(sokobanItem)
                sokobanMapDom[`${x},${y}`] = sokobanItem
            }
            if (xmap[x] == mapEnum.wall) {
                sokobanItem.textContent = '墙'
            }
            if (xmap[x] == (mapEnum.blank + mapEnum.target)) {
                sokobanItem.textContent = '口'
            }
            if (xmap[x] == (mapEnum.blank + mapEnum.box)) {
                sokobanItem.textContent = '棺'
            }
            if (xmap[x] == (mapEnum.blank + mapEnum.player)) {
                sokobanItem.textContent = '人'
            }
            if (xmap[x] == (mapEnum.blank + mapEnum.box + mapEnum.target)) {
                sokobanItem.textContent = '坟'
            }
            if (xmap[x] == (mapEnum.blank + mapEnum.player + mapEnum.target)) {
                sokobanItem.textContent = '囚'
            }
        }
    }
}

检测游戏是否结束,就判断了目标是否都包含盒子了,不知道这样写简洁不简洁,应该并不是很简洁吧。

// 检查游戏是否结束
function checkGameOver() {
    for (let y = 0; y < map.length; y++) {
        const xmap = map[y];
        for (let x = 0; x < xmap.length; x++) {
            if ((xmap[x] & mapEnum.target) != 0 && (xmap[x] & mapEnum.box) == 0) {
                return false;
            }
        }
    }
    return true;
}

代码主要逻辑走,其实就是根据走到方向以及下一个点,是否可以去移动,以及下下一个点是否可以移动,然后移动刷新地图。

// 走
function walk(x, y) {
    let tx = player.x + x, ty = player.y + y;
    // 假如这里是盒子的话
    if ((map[ty][tx] & mapEnum.box) != 0) {
        let bx = tx + x, by = ty + y;
        // 如果是盒子D叠盒子无法移动
        if ((map[by][bx] & mapEnum.box) != 0) {
            return;
        }
        // 假如盒子移动的方向包含地板,则允许移动
        if ((map[by][bx] & mapEnum.blank) != 0) {
            map[ty][tx] -= mapEnum.box;
            map[by][bx] += mapEnum.box;
            // 假如盒子移动的目标位置包含目标位置则检测游戏是否胜利
            if ((map[by][bx] & mapEnum.target) != 0 && checkGameOver()) {
                sokobanDom.innerHTML = `恭喜你安置了一切`
            }
        }
    }
    // 假如这里包含地板
    if ((map[ty][tx] & mapEnum.blank) != 0 && (map[ty][tx] & mapEnum.box) == 0) {
        map[player.y][player.x] -= mapEnum.player;
        map[ty][tx] += mapEnum.player
        player.x = tx;
        player.y = ty;
    }
    randerMap()
}
🐎源码

https://github.com/linyisonger/H5.Examples

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

林一怂儿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值