想画一个沙漠掘金游戏地图
- 沙漠掘金
- 生成一个地图
- html
- js
沙漠掘金
沙漠掘金是一个企业培训课程游戏,规则大致是:
- 玩家从大本营出发,到达矿山掘金后返回,如果规定的天数未回来,则失败,如果回来,则算成功,按照挖取到的金子排名。
- 玩家携带一定量的食物、水,具有一定的负重。
- 地图分为:村庄、大本营、沙漠、王陵、大山。玩家可以在村庄和大本营补给,大本营、王陵会有特殊事件,大山可以挖到金子,沙漠地区则会遭遇高温和沙尘暴。
- 不同的地区和日期会有不同的天气:晴天、高温、沙尘暴。不同的天气消耗的水和食物不同。
- 出发可以携带道具:指南针和帐篷。但是会占用负重。
这个游戏有多个版本,大致的游戏内容都一样。
生成一个地图
从网上能找到好多地图,奈何不会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);});
});