想画一个沙漠掘金游戏地图

想画一个沙漠掘金游戏地图

沙漠掘金

沙漠掘金是一个企业培训课程游戏,规则大致是:

  1. 玩家从大本营出发,到达矿山掘金后返回,如果规定的天数未回来,则失败,如果回来,则算成功,按照挖取到的金子排名。
  2. 玩家携带一定量的食物、水,具有一定的负重。
  3. 地图分为:村庄、大本营、沙漠、王陵、大山。玩家可以在村庄和大本营补给,大本营、王陵会有特殊事件,大山可以挖到金子,沙漠地区则会遭遇高温和沙尘暴。
  4. 不同的地区和日期会有不同的天气:晴天、高温、沙尘暴。不同的天气消耗的水和食物不同。
  5. 出发可以携带道具:指南针和帐篷。但是会占用负重。

这个游戏有多个版本,大致的游戏内容都一样。

生成一个地图

从网上能找到好多地图,奈何不会P图,还想地图能动态生成,就写一个吧。

效果:

在这里插入图片描述

这里使用js的canvas生成。

具体生成逻辑:把一个画布分成一定数量的小格子,然后随机出固定个数的地图原点,遍历所有小格子,一遍一遍往原点上堆积,形成固定数量的区域。

html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Canvas</title>
    <script src="./js/aa.js"></script>
    <style>
        canvas{
            position: absolute;
            top: 100;
            left: 100;
        }
        #msg{
            display: inline-block;
            width: 200px;
        }
    </style>
</head>
<body>
    <span>地图块数:</span><input type="number" id="nums"/><button onclick="generate()">开始生成</button>
    <div id="msg"></div>
    <div class="container">
        <canvas id="canvas" width="100" height="100" style="background-color: bisque;"></canvas>
    </div>
</body>

</html>

js

let windowWidth;
let windowHeight;
let canvas;
// 数量dom
let nd;
let nn;
let ss = 5;
// 消息dom
let md;
let ctx;
// 画布宽带
let width;
// 画布高度
let height;
// 地图块之间的间隔
let gap = 1;
// 地图块大小
let tw = 5;
// 地图区域数量
let nums;
// 行数
let rn;
// 列数
let cn;
// 地图左右边距
let pw;
// 地图上下边距
let ph;
// 地图
let maps;
async function init() {
    windowWidth = window.innerWidth;
    windowHeight = window.innerHeight;
    width = windowWidth - 100 * 2;
    height = windowHeight - 100 * 2;
    cn = Math.floor((width + gap)/(tw + gap));
    rn = Math.floor((height + gap)/(tw + gap));
    width = cn * (tw + gap) - gap;
    height = rn * (tw + gap) - gap;
    pw = (windowWidth - width)/2;
    ph = (windowHeight - height)/2;
    canvas = document.getElementById('canvas');
    ctx = canvas.getContext('2d');
    canvas.style.top = ph + 'px';
    canvas.style.left = pw + 'px';
    canvas.width = width;
    canvas.height = height;
    let start = Date.now();
    await createMap();
    let end = Date.now();
    console.log(`计算图形耗时:${(end - start - nn*ss)/1000}s`);
    render(maps);

}
/**
 * 随机一个数
 * @param {Number} min 下限
 * @param {Number} max 上限 
 * @returns 
 */
function random(min, max) {
    if (max == min) {
        return min;
    }
    return Math.floor(Math.random() * (max - min + 1)) + min;
}
/**
 * 随机颜色
 */
function getRandomHexColor() {  
    function getRandomHexDigit() {
      return Math.floor(Math.random() * 16).toString(16);  
    }  
    return `#${getRandomHexDigit()}${getRandomHexDigit()}${getRandomHexDigit()}${getRandomHexDigit()}${getRandomHexDigit()}${getRandomHexDigit()}`;  
}
/**
 * 创建地图
 * @returns 地图
 */
async function createMap() {
    maps = [];
    nn = 0;
    console.log(`地图数量:${nums}`);
    let all = [];
    for (let i = 0; i < rn; i++) {
        for (let j = 0; j < cn; j++) {
            all.push({
                i: i,
                j: j
            });
        }
    }
    // 先随机出减去首尾的剩余节点
    let indexArr = new Array(all.length - 2).fill(0).map((item, index) => index + 1);
    for(let i = 0; i < nums; i++) {
        let index = random(0, indexArr.length - 1);
        let item = indexArr[index];
        indexArr.splice(index, 1);
        maps.push({
            i: all[item].i,
            j: all[item].j,
            links: [],
            start: false,
            end: false,
            color: getRandomHexColor(),
            type: 'shamo'
        });
    }
    // 标记区域类型,c村庄、皇陵、绿洲
    // 最靠近中间的两个点,标记为皇陵和绿洲
    // 远离中间的点,靠近大本营横坐标的一侧,随机出1/5,最小为1,也可以没有,的区域为村庄
    let center = { i: Math.floor(rn / 2), j: Math.floor(cn / 2) };
    let juliOrder = maps.filter(it => it.type == 'shamo');
    function getDir(p1, p2) {
        return Math.sqrt(Math.pow(Math.abs(p1.i - p2.i), 2) + Math.pow(Math.abs(p1.j - p2.j), 2));
    }
    juliOrder.sort((a, b) => {
        return getDir(a, center) - getDir(b, center);
    });
    // 
    if (juliOrder.length > 2) {
        juliOrder[0].type = 'huangling';
        juliOrder[1].type = 'lvzhou';
    }
    // 随机出村庄
    juliOrder = juliOrder.slice(2);
    if (juliOrder.length > 0) {
        if (juliOrder.length < nums/5) {
            juliOrder.forEach(it => it.type = 'cunzhuang');
        } else {
            let nn = Math.floor(nums/5);
            for(let i = 0; i < nn; i++) {
                let item = juliOrder[juliOrder.length - i - 1];
                item.type = 'cunzhuang';
            }
        }
    }
    // 尝试扩充
    let copyAll = [...all].filter(item => !maps.find(map => map.i === item.i && map.j === item.j));
    let ijMap = copyAll.reduce((x,y) => {
        x[y.i + '_' + y.j] = y;
        return x;
    }, {});
    let msg = '';
    while (Object.keys(ijMap).length) {
        msg = `剩余节点数量:${Object.keys(ijMap).length}`;
        console.log(msg);
        renderMsg(msg);
        nn++;
        await sleep(ss);
        for (let i = 0; i < maps.length; i++) {
            let map = maps[i];
            let ps = [{i:map.i,j:map.j}, ...map.links];
            // 找到一个边界点
            let ks = ps;
            let kss = [];
            for(let k = 0; k < ks.length; k++) {
                // 四个位置,上下左右
                let dirs = [
                    {i: 1, j: 0},
                    {i: -1, j: 0},
                    {i: 0, j: -1},
                    {i: 0, j: 1}
                ];
                kss.push(...dirs.map(dir => ({i: ks[k].i+dir.i, j: ks[k].j+dir.j})));
            }
            kss = kss.filter(p => p.i >= 0 && p.i < rn && p.j >= 0 && p.j < cn && ijMap[p.i + '_' + p.j]);
            if(!kss.length) {
                continue;
            }
            let dir = kss[random(0, kss.length - 1)];
            let key = dir.i + '_' + dir.j;
            let p = ijMap[key];
            if (p) {
                map.links.push(p);
                delete ijMap[key];
            }
        }
    }
    return Promise.resolve();
}
/**
 * 渲染出地图
 * @param {Object} maps 地图数据
 */
function render(maps) {
    console.log('渲染地图', maps);
    let colorMap = maps.reduce((x,y) => {
        x[y.i + '_' + y.j] = y.color;
        y.links.forEach(p => {
            x[p.i + '_' + p.j] = y.color;
        });
        return x;
    }, {});
    ctx.clearRect(0, 0, width, height);
    for (let i = 0; i < rn; i++) {
        for (let j = 0; j < cn; j++) {
            ctx.fillStyle = colorMap[i + '_' + j] || '#fff';
            ctx.fillRect(j * (tw + gap), i * (tw + gap), tw, tw);
        }
    }
}
/**
 * 生成地图
 */
function generate() {
    init();
}
function renderMsg(msg) {
    md.textContent = msg;
}
function sleep(time) {
    return new Promise(resolve => setTimeout(resolve, time));
}

document.addEventListener('DOMContentLoaded', function() {
    nd = document.getElementById('nums');
    md = document.getElementById('msg');
    nd.value = nums = random(15, 50);
    nd.addEventListener('change', function() {
        let value = this.value;
        if (this.value <= 0) {
            value = 1;
        } else if (this.value > 100) {
            value = 100;
        }
        nd.value = nums = parseInt(value);
    });
});

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值