兰顿蚂蚁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