利用JS实现AI自动玩贪吃蛇
作者:肥学
这篇文章主要为大家详细介绍了如何利用JS实现AI版自动玩贪吃蛇小游戏,文中的示例代码讲解详细,对我们学习JS游戏开发有一定帮助,需要的可以参考一下
演示
自动贪吃蛇
技术栈
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(); } } };
到此这篇关于利用JS实现AI自动玩贪吃蛇的文章就介绍到这了,更多相关JS自动玩贪吃蛇内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!