欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

兰顿蚂蚁html版

程序员文章站 2022-03-11 09:22:35
1024快乐!用html+js模拟兰顿蚂蚁(Langton‘s Ant)。地图和蚂蚁是分离的,地图自动扩充,地图能拖动缩放,,bug未知兰顿蚂蚁,是于1986年,由克里斯·兰顿提出来的,属于细胞自动机的一种。平面上的正方形格子被填上黑色或白色。在其中一格正方形内有一只“蚂蚁”。蚂蚁的头部朝向为:上下左右其中一方。蚂蚁的移动规则十分简单:若蚂蚁在黑格,右转90度,将该格改为白格,并向前移一格;若蚂蚁在白格,左转90度,将该格改为黑格,并向前移一格。规则虽然简单,蚂蚁的行为却十分复....

1024快乐!

 用html+js模拟兰顿蚂蚁(Langton‘s Ant)。地图和蚂蚁是分离的,地图自动扩充,地图能拖动缩放,可以添加干扰,,bug未知

兰顿蚂蚁,是于1986年,由克里斯·兰顿提出来的,属于细胞自动机的一种。

平面上的正方形格子被填上黑色或白色。在其中一格正方形内有一只“蚂蚁”。
蚂蚁的头部朝向为:上下左右其中一方。

蚂蚁的移动规则十分简单:
若蚂蚁在黑格,右转90度,将该格改为白格,并向前移一格;
若蚂蚁在白格,左转90度,将该格改为黑格,并向前移一格。

规则虽然简单,蚂蚁的行为却十分复杂。刚刚开始时留下的路线都会有接近对称,像是会重复,但不论起始状态如何,蚂蚁经过漫长的混乱活动后,会开辟出一条规则的“高速公路”。

<!-- ######################################################################### -->
<button onclick="nextSteps(1)">下一步</button>
<button onclick="nextSteps(10)">下10步</button>
<button onclick="nextSteps(100)">下100步</button>
<button onclick="nextSteps(1000)">下1000步</button>
<button onclick="nextSteps()">下N步</button>
<input type='number' min=1 max=10000 value=100 id="stepsCount">

<button onclick="randomDots()">干扰点</button>

<button onclick="nextStepsStart()">开始</button>
<button onclick="nextStepsStop()">停止</button>
步数:<span id='stepsTotal'>0</span>

<br>
<canvas id="out" width="800px" height="600px" style="border:2px solid #777;"></canvas>
<script>
	
	
	
	function CellMap(canvasId = 'out', initMapWidth = 50, initMapHeight = 50, mapWidthMax = 100000, mapHeightMax = 100000) {
		
		this.canvasId = canvasId;
		
		this.map = null;
		this.mapWidth = initMapWidth;
		this.mapHeight = initMapHeight;
		this.mapWidthMax = mapWidthMax;
		this.mapHeightMax = mapHeightMax;
		
		this.canvas = null;
		this.cctx = null;
		
		this.mapImageZoomMin = 0.5;
		this.mapImageZoomMax = 10;
		
		this.cellBaseWidth = 8;
		this.mapImageZoom = 1;
		this.mapImageOffsetX = 0;
		this.mapImageOffsetY = 0;
		this.mapImageWidth = null;
		this.mapImageHeight = null;
		
		// ------------------------------------
		this.init = function() {
			this.initMap();
			this.initCanvas();
			this.initVMap();
			
		}
		this.initMap = function() {
			this.map = Array(this.mapHeight);
			for (var i = 0, len = this.mapHeight; i < len; i++) {
				this.map[i] = Array(this.mapWidth);
			}
		}
		this.initCanvas = function() {
			this.canvas = document.getElementById(this.canvasId);
			this.canvasWidth = this.canvas.width;
			this.canvasHeight = this.canvas.height;
			//console.log(this.canvas, this.canvasWidth, this.canvasHeight);
			this.cctx = this.canvas.getContext("2d");
			this.cctx.strokeStyle = '#aaa';
			this.cctx.fillStyle = '#888';
			this.cctx.lineWidth = 1;
			this.cctx.translate(0.5,0.5);
		}
		// 支持缩放和拖动地图
		this.bindCanvasEvent = function() {
			var that = this;
			var down = false;
			var x1, x2, y1, y2;
			this.canvas.onmousedown = function(evt) { // 触发鼠标按下
				down = true;
				x1 = evt.offsetX;
				y1 = evt.offsetY;
			}
			this.canvas.onmouseup = function(evt) { // 触发鼠标抬起
				down = false;
				x2 = evt.offsetX;
				y2 = evt.offsetY;
				that.moveMapImageDisplayArea(x2 - x1, y2 - y1);
			}
			this.canvas.onmousemove = function(evt) { // 触发鼠标拖动
				if (!down) {
					return;
				}
				var x = evt.offsetX;
				var y = evt.offsetY;
				that.moveMapImageDisplayArea(x - x1, y - y1);
				x1 = x;
				y1 = y;
			}
			this.canvas.onmouseenter = function(evt) { // 触发鼠标进入
				down = false;
			}
			this.canvas.onmouseleave = function(evt) { // 触发鼠标离开
				down = false;
			}
			this.canvas.onmousewheel = function(evt) { // 触发鼠标滚轮
				var wheelDelta = evt.wheelDelta;
				that.zoomMapImageDisplayArea(wheelDelta, evt.offsetX, evt.offsetY);
			}
			// 手机端拖动
			this.canvas.ontouchstart = function(evt) { // 触发鼠标按下
				down = true;
				x1 = evt.touches[0].screenX;
				y1 = evt.touches[0].screenY;
				evt.preventDefault();
			}
			this.canvas.ontouchend = function(evt) { // 触发鼠标抬起
				down = false;
				x2 = evt.touches[0].screenX;
				y2 = evt.touches[0].screenY;
				that.moveMapImageDisplayArea(x2 - x1, y2 - y1);
				evt.preventDefault();
			}
			this.canvas.ontouchmove = function(evt) { // 触发鼠标拖动
				if (!down) {
					return;
				}
				var x = evt.touches[0].screenX;
				var y = evt.touches[0].screenY;
				that.moveMapImageDisplayArea(x - x1, y - y1);
				x1 = x;
				y1 = y;
				evt.preventDefault();
			}
		}
		
		// ------------------------------------
		// 移动地图
		this.moveMapImageDisplayArea = function(offsetX, offsetY) {
			//console.log(offsetX, offsetY);
			this.setMapImageDisplayArea(null, this.mapImageOffsetX - offsetX, this.mapImageOffsetY - offsetY);
			this.drawLines();
			this.drawCells();
		}
		// 缩放地图
		this.zoomMapImageDisplayArea = function(wheelDelta, pointerX, pointerY) {
			var newZoom = this.mapImageZoom;
			if (wheelDelta > 0) {
				newZoom += this.mapImageZoom * 0.1;
			} else {
				newZoom -= this.mapImageZoom * 0.1;
			}
			if (newZoom < this.mapImageZoomMin || newZoom > this.mapImageZoomMax) {
				return;
			}
			// 光标处点位置保持不变
			var xx = (this.mapImageOffsetX + pointerX) / this.mapImageZoom * newZoom - pointerX;
			var yy = (this.mapImageOffsetY + pointerY) / this.mapImageZoom * newZoom - pointerY;
			this.setMapImageDisplayArea(newZoom, xx, yy);
			this.drawLines();
			this.drawCells();
		}
		// 设置地图要显示的位置
		this.setMapImageDisplayArea = function(mapImageZoom = undefined, mapImageOffsetX = undefined, mapImageOffsetY = undefined) {
			
			if (mapImageZoom) {
				if (mapImageZoom < this.mapImageZoomMin) {
					mapImageZoom = this.mapImageZoomMin;
				}
				if (mapImageZoom > this.mapImageZoomMax) {
					mapImageZoom = this.mapImageZoomMax;
				}
				this.mapImageZoom = mapImageZoom;
			}
			if (mapImageOffsetX)
				this.mapImageOffsetX = mapImageOffsetX;
			if (mapImageOffsetY)
				this.mapImageOffsetY = mapImageOffsetY;
			
			// 地图图片大小
			this.mapImageWidth = this.mapWidth * this.cellBaseWidth * this.mapImageZoom;
			this.mapImageHeight = this.mapHeight * this.cellBaseWidth * this.mapImageZoom;
			
			// 展示到canvas上时,对应的cell或线的索引的位置
			var x1 = Math.floor(this.mapImageOffsetX / this.cellBaseWidth / this.mapImageZoom);
			var x2 = Math.ceil((this.mapImageOffsetX + this.canvasWidth) / this.cellBaseWidth / this.mapImageZoom);
			var y1 = Math.floor(this.mapImageOffsetY / this.cellBaseWidth / this.mapImageZoom);
			var y2 = Math.ceil((this.mapImageOffsetY + this.canvasHeight) / this.cellBaseWidth / this.mapImageZoom);
			
			//console.log(x1, x2, y1, y2);
			
			this.mapX1 = x1;
			this.mapX2 = x2;
			this.mapY1 = y1;
			this.mapY2 = y2;
			
			// cell的长宽
			this.cellImageBaseWidth = 1 * this.cellBaseWidth * this.mapImageZoom;
		}
		
		// 绘制网格
		this.drawLines = function() {
			var [x1, x2, y1, y2] = [this.mapX1, this.mapX2, this.mapY1, this.mapY2];
			// 边线位置
			var xFirstLinePos = x1 >= 0 ? 0 : (-this.mapImageOffsetX);
			var xLastLinePos = x2 < this.mapWidth ? this.canvasWidth : (this.mapImageWidth - this.mapImageOffsetX);
			var yFirstLinePos = y1 >= 0 ? 0 : (-this.mapImageOffsetY);
			var yLastLinePos = y2 < this.mapHeight ? this.canvasHeight : (this.mapImageHeight - this.mapImageOffsetY);
			//console.log('firstlast', xFirstLinePos, xLastLinePos, yFirstLinePos, yLastLinePos);
			
			//console.log('this.mapWidth', this.mapWidth);
			//console.log('this.mapHeight', this.mapHeight);
			
			var ctx = this.cctx;
			
			ctx.fillStyle = '#ffffff';
			ctx.fillRect(0, 0, this.canvasWidth, this.canvasHeight);
			
			ctx.beginPath();
			for (var i = x1; i <= x2; i++) {
				if (i < 0 || i > this.mapWidth) { // 超界线不绘制
					continue;
				}
				// 实际线位置,需要矫正偏移量
				var pos = i * this.cellBaseWidth * this.mapImageZoom;
				pos = pos - this.mapImageOffsetX;
				pos = Math.floor(pos);
				ctx.moveTo(pos, yFirstLinePos);
				ctx.lineTo(pos, yLastLinePos);
			}
			for (var i = y1; i <= y2; i++) {
				if (i < 0 || i > this.mapHeight) {
					continue;
				}
				var pos = i * this.cellBaseWidth * this.mapImageZoom;
				pos = pos - this.mapImageOffsetY;
				pos = Math.floor(pos);
				ctx.moveTo(xFirstLinePos, pos);
				ctx.lineTo(xLastLinePos, pos);
			}
			ctx.strokeStyle = '#aaa';
			ctx.stroke();
		}
		// 绘制格子状态
		this.drawCells = function() {
			var [x1, x2, y1, y2] = [this.mapX1, this.mapX2, this.mapY1, this.mapY2];
			
			var ctx = this.cctx;
			ctx.fillStyle = '#888';
			var pww = this.cellImageBaseWidth - 2;
			for (var i = x1; i <= x2; i++) {
				if (i < 0 || i >= this.mapWidth)
					continue;
				var px1 = i * this.cellBaseWidth * this.mapImageZoom - this.mapImageOffsetX;
				px1 = Math.floor(px1);
				for (var j = y1; j <= y2; j++) {
					if (j < 0 || j >= this.mapHeight)
						continue;
					if (this.map[j][i] === 1) {
						var py1 = j * this.cellBaseWidth * this.mapImageZoom - this.mapImageOffsetY;
						py1 = Math.floor(py1);
						ctx.fillRect(px1 + 1, py1 + 1, pww, pww);
					}
				}
			}
		}
		// 绘制格子状态
		this.drawCell = function(x, y, val = undefined) {
			//return;
			var [x1, x2, y1, y2] = [this.mapX1, this.mapX2, this.mapY1, this.mapY2];
			if (x >= x1 && x <= x2 && y >= y1 && y <= y2) {
				var pww = this.cellImageBaseWidth - 2;
				var px1 = x * this.cellBaseWidth * this.mapImageZoom - this.mapImageOffsetX;
				px1 = Math.floor(px1);
				var py1 = y * this.cellBaseWidth * this.mapImageZoom - this.mapImageOffsetY;
				py1 = Math.floor(py1);
				if (val === 1)
					this.cctx.fillStyle = '#888';
				else
					this.cctx.fillStyle = '#ffffff';
				this.cctx.fillRect(px1 + 1, py1 + 1, pww, pww);
			}
		}
		// 干扰点
		this.setRandomDots = function() {
			for (var i = 0, len = this.mapHeight; i < len; i++) {
				var j = Math.floor(Math.random() * 1000) % this.mapWidth;
				if (this.map[i][j] === 1)
					this.map[i][j] = 0;
				else
					this.map[i][j] = 1;
			}
			this.drawLines();
			this.drawCells();
		}
		
		// ------------------------------------
		// 扩充地图
		this.grownVMap = function(vmapX, vmapY) {
			if (vmapX > this.vmapX1 && vmapX < this.vmapX2 && vmapY > this.vmapY1 && vmapY < this.vmapY2) {
				return;
			}
			
			// 扩充方向
			var gx = 0;
			if (vmapX <= this.vmapX1) {
				gx = 1;
			} else if (vmapX >= this.vmapX2) {
				gx = 2;
			}
			var gy = 0;
			if (vmapY <= this.vmapY1) {
				gy = 1;
			} else if (vmapY >= this.vmapY2) {
				gy = 2;
			}
			// 扩充后地图大小
			var mapWidthNew = gx === 0 ? this.mapWidth : Math.floor(this.mapWidth * 1.3);
			var mapHeightNew = gy === 0 ? this.mapHeight : Math.floor(this.mapHeight * 1.3);
			if (mapWidthNew > mapWidthMax) {
				mapWidthNew = this.mapWidthMax;
			}
			if (mapHeightNew > mapHeightMax) {
				mapHeightNew = this.mapHeightMax;
			}
			//console.log('grownVMap ...', this.mapWidth, this.mapHeight, ' to ', mapWidthNew, mapHeightNew);
			//console.log('grownVMap ...gx gy', gx, gy);
			
			// 新旧对应的位置
			var tx = 0, tx2 = 0, ty = 0, ty2 = 2;
			if (gx === 1) {
				tx = mapWidthNew - this.mapWidth; // 前面的是扩充的
				tx2 = mapWidthNew;
			} else {
				tx = 0;
				tx2 = this.mapWidth; // 后面没了,或还有扩充的
			}
			if (gy == 1) {
				ty = mapHeightNew - this.mapHeight;
				ty2 = mapHeightNew;
			} else {
				ty = 0;
				ty2 = this.mapHeight;
			}
			// 新的地图
			var map2 = Array(mapHeightNew);
			for (var i = 0, len = mapHeightNew; i < len; i++) {
				if (i >= ty && i < ty2) { // 纵向范围内,从原来的计算
					if (gx === 1) {
						var arr1 = Array(mapWidthNew - this.mapWidth);
						var arr2 = this.map[i - ty]
						map2[i] = arr1.concat(arr2); // 前扩
					} else if (gx === 2) {
						var arr1 = Array(mapWidthNew - this.mapWidth);
						var arr2 = this.map[i - ty]
						map2[i] = arr2.concat(arr1); // 后扩
					} else {
						map2[i] = this.map[i - ty]; // 直接用原来的
					}
				} else {
					map2[i] = Array(mapWidthNew); // 纵向范围外
				}
			}
			
			// 地图更新
			this.mapWidth = mapWidthNew;
			this.mapHeight = mapHeightNew;
			this.map = map2;
			//console.log('grownVMap ', this.map);
			
			// 调整绘制偏移量
			if (gx === 1) {
				this.mapImageOffsetX = this.mapImageOffsetX + tx * this.cellBaseWidth * this.mapImageZoom;
			}
			if (gy === 1) {
				this.mapImageOffsetY = this.mapImageOffsetY + ty * this.cellBaseWidth * this.mapImageZoom;
			}
			
			// 绘制更新
			this.setMapImageDisplayArea();
			this.drawLines();
			this.drawCells();
			
			// 虚拟地图更新
			if (gx === 1) {
				this.vmapX0 = this.vmapX0 + tx;
			}
			if (gy === 1) {
				this.vmapY0 = this.vmapY0 + ty;
			}
			this.initVMap(this.vmapX0, this.vmapY0);
		}
		this.initVMap = function(vmapX0 = undefined, vmapY0 = undefined) {
			this.vmapX0 = vmapX0 ? vmapX0 : (this.mapWidth >> 1);
			this.vmapY0 = vmapY0 ? vmapY0 : (this.mapHeight >> 1);
			this.vmapX1 = -this.vmapX0;
			this.vmapX2 = this.mapWidth - this.vmapX0;
			this.vmapY1 = -this.vmapY0;
			this.vmapY2 = this.mapHeight - this.vmapY0;
			//console.log('initVMap',
			//		'vmapX0,vmapY0=', this.vmapX0, this.vmapY0,
			//		'vmapX1,vmapX2=', this.vmapX1, this.vmapX2,
			//		'vmapY1,vmapY2=', this.vmapY1, this.vmapY2);
		}
		this.mapx = function(vmapX) {
			return vmapX - this.vmapX0;
		}
		this.mapy = function(vmapY) {
			return vmapY - this.vmapY0;
		}
		// 虚拟地图的坐标点的值
		this.vmapPoint = function(vmapX, vmapY, val = undefined) {
			this.grownVMap(vmapX, vmapY);
			
			var mapx = vmapX + this.vmapX0;
			var mapy = vmapY + this.vmapY0;
			
			if (mapx < 0 || mapx >= this.mapWidth)
				return;
			if (mapy < 0 || mapy >= this.mapHeight)
				return;
			
			if (val === undefined) {
				return this.map[mapy][mapx];
			} else {
				//console.log('set cell', mapx, mapy, val);
				this.map[mapy][mapx] = val;
				this.drawCell(mapx, mapy, val);
			}
		}
	}
	
	function LangtonsAnt(canvasId = 'out', initMapWidth = 50, initMapHeight = 50, mapWidthMax = 100000, mapHeightMax = 100000) {
		CellMap.call(this, canvasId, initMapWidth, initMapHeight, mapWidthMax, mapHeightMax);
		
		this.stepsTotal = 0;
		
		this.antX = 0;
		this.antY = 0;
		this.antDirection = 1;
		
		this.super = {};
		this.super.init = this.init;
		
		this.init = function() {
			this.initMap(true);
			this.initCanvas();
			this.initVMap();
			//this.super.init.call(this);
			this.antX = 0;
			this.antY = 0;
			this.vmapPoint(this.antX, this.antY, 1);
		}
		
		this.nextStep = function() {
			var now = this.vmapPoint(this.antX, this.antY);
			if (now === undefined || now === 0) {
				this.antDirection += 1;
				if (this.antDirection === 5)
					this.antDirection = 1;
				this.vmapPoint(this.antX, this.antY, 1);
			} else {
				this.antDirection -= 1;
				if (this.antDirection === 0)
					this.antDirection = 4;
				this.vmapPoint(this.antX, this.antY, 0);
			}
			if (this.antDirection === 1) {
				this.antY --;
			} else if (this.antDirection === 2) {
				this.antX ++;
			} else if (this.antDirection === 3) {
				this.antY ++;
			} else if (this.antDirection === 4) {
				this.antX --;
			}
			
			this.stepsTotal++;
			//console.log('ant', this.antX, this.antY, now, this.stepsTotal);
			
		}
		this.nextSteps = function(cnt) {
			//console.log("nextSteps:", cnt);
			for (var i = 0; i < cnt; i++) {
				this.nextStep();
			}
		}
	}
	
	var ltant = new LangtonsAnt('out', 10, 10, 10000, 10000);
	ltant.mapImageZoom = 1;
	console.log(ltant);
	ltant.init();
	ltant.bindCanvasEvent();
	ltant.setMapImageDisplayArea();
	ltant.drawLines();
	ltant.drawCells();
	
	function nextSteps(cnt) {
		nextStepsStop();
		if (cnt === undefined) {
			cnt = document.getElementById("stepsCount").value;
			cnt = parseInt(cnt);
		}
		ltant.nextSteps(cnt);
		document.getElementById("stepsTotal").innerHTML = ltant.stepsTotal;
	}
	function nextStepsStart() {
		if (window.interval != null)
			return;
		window.interval = setInterval(function() {
			ltant.nextSteps(1);
			document.getElementById("stepsTotal").innerHTML = ltant.stepsTotal;
		}, 100);
	}
	function nextStepsStop() {
		if (window.interval == null)
			return;
		clearInterval(window.interval);
		window.interval = null;
	}
	function randomDots() {
		nextStepsStop();
		ltant.setRandomDots();
	}
</script>

<pre>
	
  兰顿蚂蚁,是于1986年,由克里斯·兰顿提出来的,属于细胞自动机的一种。

  平面上的正方形格子被填上黑色或白色。在其中一格正方形内有一只“蚂蚁”。
  蚂蚁的头部朝向为:上下左右其中一方。

  蚂蚁的移动规则十分简单:
  若蚂蚁在黑格,右转90度,将该格改为白格,并向前移一格;
  若蚂蚁在白格,左转90度,将该格改为黑格,并向前移一格。

  规则虽然简单,蚂蚁的行为却十分复杂。刚刚开始时留下的路线都会有接近对称,像是会重复,但不论起始状态如何,蚂蚁经过漫长的混乱活动后,会开辟出一条规则的“高速公路”。
  
</pre>
<!-- ######################################################################### -->

 

本文地址:https://blog.csdn.net/superzlc/article/details/109257190