JS自动玩贪吃蛇
肥学 人气:0演示
自动贪吃蛇
技术栈
bottom 属性规定元素的底部边缘。该属性定义了定位元素下外边距边界与其包含块下边界之间的偏移。
注释:如果 “position” 属性的值为 “static”,那么设置 “bottom” 属性不会产生任何效果。
对于 static 元素,为 auto;对于长度值,则为相应的绝对长度;对于百分比数值,为指定值;否则为 auto。
对于相对定义元素,如果 bottom 和 top 都是 auto,其计算值则都是 0;如果其中之一为auto,则取另一个值的相反数;如果二者都不是 auto,bottom 将取 top 值的相反数。
- 默认值: auto
- 继承性: no
- 版本: CSS2
- JavaScript 语法: object.style.bottom="50px"
user-select 属性规定是否能选取元素的文本。
在 web 浏览器中,如果您在文本上双击,文本会被选取或高亮显示。此属性用于阻止这种行为。
user-select: auto|none|text|all;
- auto 默认。如果浏览器允许,则可以选择文本。
- none 防止文本选取。
- text 文本可被用户选取。
- all 单击选取文本,而不是双击。
源码
样式设置
canvas { position: absolute; width: 100vh; height: 100vh; margin: auto; top: 0; bottom: 0; left: 0; right: 0; user-select: none; background: #000; cursor: pointer; }
构建食物对象
var food = { x: 0, y: 0, // add random food add: function add() { var emptyNodes = []; for (var x = 0; x < map.width; ++x) { for (var y = 0; y < map.height; ++y) { if (!map.collision(x, y)) emptyNodes.push({ x: x, y: y }); } } if (emptyNodes.length) { var p = emptyNodes[Math.floor(Math.random() * emptyNodes.length)]; this.x = p.x; this.y = p.y; } } };
构建贪吃蛇对象
var snake = { body: [], head: { x: 0, y: 0 }, removeTail: function removeTail() { var p = this.body.shift(); map.setSnake(p.x, p.y, 0); }, addHead: function addHead(x, y) { this.head.x = x; this.head.y = y; this.body.push({ x: x, y: y }); map.setSnake(x, y, 1); }, move: function move(dir) { var next = map.getNext(this.head.x, this.head.y, dir); this.addHead(next.x, next.y); if (next.x === food.x && next.y === food.y) { food.add(); } else this.removeTail(); }, // snake IA nextDirection: function nextDirection() { var x = this.head.x; var y = this.head.y; var pathNumber = map.tour(x, y); var distanceToFood = map.distance(pathNumber, map.tour(food.x, food.y)); var distanceToTail = map.distance(pathNumber, map.tour(snake.body[0].x, snake.body[0].y)); var cuttingAmountAvailable = distanceToTail - 4; var numEmptySquaresOnBoard = map.size - snake.body.length - 1; if (distanceToFood < distanceToTail) cuttingAmountAvailable -= 1; var cuttingAmountDesired = distanceToFood; if (cuttingAmountDesired < cuttingAmountAvailable) cuttingAmountAvailable = cuttingAmountDesired; if (cuttingAmountAvailable < 0) cuttingAmountAvailable = 0; var canGoRight = !map.collision(x + 1, y); var canGoLeft = !map.collision(x - 1, y); var canGoDown = !map.collision(x, y + 1); var canGoUp = !map.collision(x, y - 1); var bestDir = -1; var bestDist = -1; var dist = 0; if (canGoRight) { dist = map.distance(pathNumber, map.tour(x + 1, y)); if (dist <= cuttingAmountAvailable && dist > bestDist) { bestDir = map.Right; bestDist = dist; } } if (canGoLeft) { dist = map.distance(pathNumber, map.tour(x - 1, y)); if (dist <= cuttingAmountAvailable && dist > bestDist) { bestDir = map.Left; bestDist = dist; } } if (canGoDown) { dist = map.distance(pathNumber, map.tour(x, y + 1)); if (dist <= cuttingAmountAvailable && dist > bestDist) { bestDir = map.Down; bestDist = dist; } } if (canGoUp) { dist = map.distance(pathNumber, map.tour(x, y - 1)); if (dist <= cuttingAmountAvailable && dist > bestDist) { bestDir = map.Up; bestDist = dist; } } if (bestDist >= 0) return bestDir; if (canGoUp) return map.Up; if (canGoLeft) return map.Left; if (canGoDown) return map.Down; if (canGoRight) return map.Right; return map.Right; } };
构建自动贪吃
var map = { // init map init: function init(width, height) { var _this = this; this.width = width; this.height = height; this.size = width * height; this.scale = Math.min(canvasWidth, canvasHeight) / Math.max(this.width, this.height); // Hamiltonian Cycle // flags var _array2D = this.array2D(width, height, true); var _array2D2 = _slicedToArray(_array2D, 2); this.tour = _array2D2[0]; this.setTour = _array2D2[1]; var _array2D3 = this.array2D(width / 2, height / 2); var _array2D4 = _slicedToArray(_array2D3, 2); this.isVisited = _array2D4[0]; this.setVisited = _array2D4[1]; var _array2D5 = this.array2D(width / 2, height / 2); var _array2D6 = _slicedToArray(_array2D5, 2); this.canGoRight = _array2D6[0]; this.setGoRight = _array2D6[1]; var _array2D7 = this.array2D(width / 2, height / 2); var _array2D8 = _slicedToArray(_array2D7, 2); this.canGoDown = _array2D8[0]; this.setGoDown = _array2D8[1]; var _array2D9 = this.array2D(width, height); var _array2D10 = _slicedToArray(_array2D9, 2); this.isSnake = _array2D10[0]; this.setSnake = _array2D10[1]; this.canGoLeft = function (x, y) { if (x === 0) return false; return _this.canGoRight(x - 1, y); }; this.canGoUp = function (x, y) { if (y === 0) return false; return _this.canGoDown(x, y - 1); }; }, // directions Left: 1, Up: 2, Right: 3, Down: 4, // flat 2D array array2D: function array2D(width, height, protect) { var data = new Uint16Array(width * height); return [function (x, y) { return data[x + width * y]; }, protect ? function (x, y, value) { var i = x + width * y; if (!data[i]) data[i] = value; } : function (x, y, value) { data[x + width * y] = value; }]; }, // test snake collision collision: function collision(x, y) { if (x < 0 || x >= this.width) return true; if (y < 0 || y >= this.height) return true; return this.isSnake(x, y) !== 0; }, // path distance distance: function distance(a, b) { if (a < b) return b - a - 1;else return b - a - 1 + this.size; }, // Hamiltonian Cycle generate_r: function generate_r(fromx, fromy, x, y) { if (x < 0 || y < 0 || x >= this.width / 2 || y >= this.height / 2) return; if (this.isVisited(x, y)) return; this.setVisited(x, y, 1); if (fromx !== -1) { if (fromx < x) this.setGoRight(fromx, fromy, 1);else if (fromx > x) this.setGoRight(x, y, 1);else if (fromy < y) this.setGoDown(fromx, fromy, 1);else if (fromy > y) this.setGoDown(x, y, 1); } for (var i = 0; i < 2; i++) { var r = Math.floor(Math.random() * 4); switch (r) { case 0: this.generate_r(x, y, x - 1, y); break; case 1: this.generate_r(x, y, x + 1, y); break; case 2: this.generate_r(x, y, x, y - 1); break; case 3: this.generate_r(x, y, x, y + 1); break; } } this.generate_r(x, y, x - 1, y); this.generate_r(x, y, x + 1, y); this.generate_r(x, y, x, y + 1); this.generate_r(x, y, x, y - 1); }, // find next direction in cycle findNextDir: function findNextDir(x, y, dir) { if (dir === this.Right) { if (this.canGoUp(x, y)) return this.Up; if (this.canGoRight(x, y)) return this.Right; if (this.canGoDown(x, y)) return this.Down; return this.Left; } else if (dir === this.Down) { if (this.canGoRight(x, y)) return this.Right; if (this.canGoDown(x, y)) return this.Down; if (this.canGoLeft(x, y)) return this.Left; return this.Up; } else if (dir === this.Left) { if (this.canGoDown(x, y)) return this.Down; if (this.canGoLeft(x, y)) return this.Left; if (this.canGoUp(x, y)) return this.Up; return this.Right; } else if (dir === this.Up) { if (this.canGoLeft(x, y)) return this.Left; if (this.canGoUp(x, y)) return this.Up; if (this.canGoRight(x, y)) return this.Right; return this.Down; } return -1; //Unreachable }, // generate Hamiltonian Cycle generateTourNumber: function generateTourNumber() { var x = 0; var y = 0; var dir = this.canGoDown(x, y) ? this.Up : this.Left; var number = 0; do { var nextDir = this.findNextDir(x, y, dir); switch (dir) { case this.Right: this.setTour(x * 2, y * 2, number++); if (nextDir === dir || nextDir === this.Down || nextDir === this.Left) this.setTour(x * 2 + 1, y * 2, number++); if (nextDir === this.Down || nextDir === this.Left) this.setTour(x * 2 + 1, y * 2 + 1, number++); if (nextDir === this.Left) this.setTour(x * 2, y * 2 + 1, number++); break; case this.Down: this.setTour(x * 2 + 1, y * 2, number++); if (nextDir === dir || nextDir === this.Left || nextDir === this.Up) this.setTour(x * 2 + 1, y * 2 + 1, number++); if (nextDir === this.Left || nextDir === this.Up) this.setTour(x * 2, y * 2 + 1, number++); if (nextDir === this.Up) this.setTour(x * 2, y * 2, number++); break; case this.Left: this.setTour(x * 2 + 1, y * 2 + 1, number++); if (nextDir === dir || nextDir === this.Up || nextDir === this.Right) this.setTour(x * 2, y * 2 + 1, number++); if (nextDir === this.Up || nextDir === this.Right) this.setTour(x * 2, y * 2, number++); if (nextDir === this.Right) this.setTour(x * 2 + 1, y * 2, number++); break; case this.Up: this.setTour(x * 2, y * 2 + 1, number++); if (nextDir === dir || nextDir === this.Right || nextDir === this.Down) this.setTour(x * 2, y * 2, number++); if (nextDir === this.Right || nextDir === this.Down) this.setTour(x * 2 + 1, y * 2, number++); if (nextDir === this.Down) this.setTour(x * 2 + 1, y * 2 + 1, number++); break; } dir = nextDir; switch (nextDir) { case this.Right: ++x; break; case this.Left: --x; break; case this.Down: ++y; break; case this.Up: --y; break; } } while (number !== this.size); }, // get next node getNext: function getNext(x, y, dir) { switch (dir) { case this.Left: if (x) return { x: x - 1, y: y }; break; case this.Up: if (y) return { x: x, y: y - 1 }; break; case this.Right: return { x: x + 1, y: y }; break; case this.Down: return { x: x, y: y + 1 }; break; } return { x: x, y: y }; }, // draw map draw: function draw() { ctx.beginPath(); ctx.strokeStyle = "#fff"; ctx.lineCap = "round"; ctx.lineJoin = "round"; ctx.lineWidth = this.scale * 0.5; var _iteratorNormalCompletion = true; var _didIteratorError = false; var _iteratorError = undefined; try { for (var _iterator = snake.body[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { var b = _step.value; ctx.lineTo(this.scale * 0.5 + b.x * this.scale, this.scale * 0.5 + b.y * this.scale); } } catch (err) { _didIteratorError = true; _iteratorError = err; } finally { try { if (!_iteratorNormalCompletion && _iterator.return) { _iterator.return(); } } finally { if (_didIteratorError) { throw _iteratorError; } } } ctx.stroke(); if (snake.body.length < map.size - 1) { ctx.beginPath(); ctx.fillStyle = "#f80"; ctx.arc(this.scale * 0.5 + food.x * this.scale, this.scale * 0.5 + food.y * this.scale, 0.4 * this.scale, 0, 2 * Math.PI); ctx.fill(); } } };
加载全部内容