打造AI贪吃蛇

演示

自动贪吃蛇
请添加图片描述

技术栈

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();
			}
		}
	};

点击直接资料领取

如果你在学习python或者Java哪怕是C遇到问题都可以来给我留言,因为在学习初期新手总会走很多弯路,这个时候如果没有有个人来帮一把的话很容易就放弃了。身边很多这样的例子许多人学着学着就转了专业换了方向,不仅是自身问题还是没有正确的学习。所以作为一个过来人我希望有问题给我留言,说不上是帮助就是顺手敲几行字的事情。

这里有python,Java学习资料还有有有趣好玩的编程项目,更有难寻的各种资源。反正看看也不亏。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
Snake-AI,这是一个用 C/C 语言编写的贪吃蛇游戏的人工智能。AI 的目的是让蛇尽可能的吃更多的食物,直到吃满整个地图。想参与这个项目,请查看todos。Demo使用方法编译与运行:$ make $ make run为了解详细使用方法, 请查看主函数main()算法介绍函数Snake.decideNext(): 计算蛇S1的下一个移动方向D计算从蛇S1的头部到达食物的最短路径P1。派一条与蛇S1完全一样的虚拟蛇S2沿路径P1吃掉食物。计算从蛇S2的头部到其尾部的最长路径P2。如果路径P2存在,将移动方向D设置为路径P1的第一个方向,否则进行步骤4。计算从蛇S1的头部到达其尾部的最长路径P3。如果P3存在,将移动方向D设置为路径P3的第一个方向,否则进行步骤5。将移动方向D设置为离食物最远的方向。函数Map.findMinPath(): 计算两个位置间的最短路径算法建立在BFS的基础上。为了使路径尽可能直,每次遍历邻接点时,在当前移动方向上的位置会被优先遍历。效果展示:(绿色区域为搜索算法扫描到的区域,红色区域为最后计算出的最短路径,每个位置上的数字表示了从起始位置开始到该位置的最短距离)函数Map.findMaxPath(): 计算两个位置间的最长路径算法建立在DFS与贪心算法的基础上。每次遍历邻接点时,离目标位置最远(使用曼哈顿距离估计)的位置将会被优先遍历到。另外,为了使路径尽可能直,如果两个位置到目标位置的距离相等,在当前移动方向上的位置将被优先遍历到。这个问题是一个NP完全问题,此算法得出的结果路径只是一个近似最长路径。效果展示:(绿色区域为搜索算法扫描到的区域,红色区域为最后计算出的最长路径,每个位置上的数字表示了从该位置开始到目标位置的估计距离) 标签:Snake
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

肥学

感谢支持

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

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

打赏作者

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

抵扣说明:

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

余额充值