Черви Патерсона - это своего рода клеточный автомат, который существует на бесконечной треугольной сетке и на каждом шагу поворачивается в каком-то направлении и перемещается на одну единицу. Их определяющие свойства заключаются в том, что они никогда не могут пересечь одно и то же место дважды, и, когда они сталкиваются с одним и тем же окружением, они принимают одно и то же решение. Червь всегда «видит» со своей точки зрения, его хвост расположен в 3, а его голова расположена в центре этого шестиугольника:
Например, рассмотрим червя с правилами:
- Если 0, 1, 2, 4 и 5 не заполнены, двигайтесь в направлении 2
- Если 0, 1, 4 и 5 не заполнены, а 2 заполнено, двигайтесь в направлении 0
- Если 0, 1 и 5 не заполнены, а 2 и 4 заполнены, двигайтесь в направлении 0
Это приводит к следующему пути (из Википедии):
Если червь оказывается в ситуации, когда все окружение заполнено, он прекращается.
вход
Список номеров. Девятое число указывает, какое решение червь должен принять в n-й новой ситуации, с которой он сталкивается, когда ему необходимо принять решение. Обратите внимание, что если все окружения, кроме одного, заполнены, он должен двигаться в единственном пустом направлении. Это не считается «решением» и не потребляет число. Для генерирования примера червя, показанного выше, ввод будет [2, 0, 0]
. На входе гарантированно создается червь, который завершает и не отслеживает свой путь, и вход никогда не будет слишком коротким.
Выход
Выведите список координат, указывающих, где находится голова червя, начиная с (1, 0)
. Мы будем рассматривать перемещение вверх и вправо как уменьшение только значения y, а перемещение вверх и влево - уменьшение значения x и уменьшение значения y. Например, выходной путь для примера ввода
(1, 0), (1, 1), (0, 0), (-1, -1), (0, -1), (0, 0), (0, 1), (-1, 0), (0, 0)
Контрольные примеры
Вы также можете использовать фрагмент javascript для запуска тестов.
[2,0,0]: (1,0),(1,1),(0,0),(-1,-1),(0,-1),(0,0),(0,1),(-1,0),(0,0)
[1,0,4,0,1,5]: (1,0),(2,1),(2,2),(1,2),(0,1),(0,0),(0,-1),(1,-1),(2,0),(2,1),(3,1),(4,2),(4,3),(3,3),(2,2),(1,1),(1,0),(2,0),(3,1),(3,0),(4,0),(5,1),(5,2),(4,2),(3,2),(2,1),(1,1),(0,0),(-1,0),(-2,-1),(-2,-2),(-1,-2),(0,-1),(1,0),(1,-1),(2,-1),(3,0),(4,1),(4,2),(5,3),(5,4),(4,4),(3,3),(3,4),(2,4),(1,3),(1,2),(1,1),(0,1),(-1,0),(-1,1),(-2,1),(-3,0),(-3,-1),(-2,-1),(-1,-1),(0,0)
[1,0,5,1]: (1,0),(2,1),(2,2),(1,2),(0,1),(0,0),(0,-1),(1,-1),(2,0),(2,1),(3,2),(3,3),(2,3),(1,2),(0,2),(-1,1),(-1,0),(0,0),(1,1),(1,2),(1,3),(0,3),(-1,2),(-1,1),(-2,0),(-2,-1),(-1,-1),(0,0)
[2,0,1,0]: (1,0),(1,1),(0,0),(-1,-1),(0,-1),(0,0),(-1,0),(-1,-1),(-1,-2),(0,-1),(1,0),(2,1),(1,1),(0,1),(0,0)
Следующая наскоро собранная (возможно, с ошибками) программа отобразит червей:
var canvas = document.querySelector("canvas");
var ctx = canvas.getContext("2d");
var input, memory;
var log = str => {
console.log(str);
document.querySelector("textarea").value += str + "\n";
};
var orientations = [
[1, 0],
[1, 1],
[0, 1],
[-1, 0],
[-1, -1],
[0, -1]
];
var arena = {
grid: [[[1, 0, 0]]],
maxX: 1,
maxY: 0,
expandGrid: function() {
var grid = this.grid;
var newWidth = grid[0].length + 2,
newHeight = grid.length + 2;
var createRow = () => {
var result = [];
for (let i = 0; i < newWidth; ++i) result.push([0, 0, 0]);
return result;
};
for (let row of grid) {
row.push([0, 0, 0]);
row.unshift([0, 0, 0]);
};
grid.push(createRow());
grid.unshift(createRow());
},
gridWidth: function() {
return this.grid[0].length
},
gridHeight: function() {
return this.grid.length
},
get: function(x, y) {
var colOffset = Math.floor(this.gridWidth() / 2),
rowOffset = Math.floor(this.gridHeight() / 2);
return this.grid[y + rowOffset][x + colOffset];
},
isAtEnd: function(x, y) {
var colOffset = Math.floor(this.gridWidth() / 2),
rowOffset = Math.floor(this.gridHeight() / 2);
return x === -colOffset || x + colOffset + 1 === this.gridWidth() ||
y === -rowOffset || y + rowOffset + 1 === this.gridHeight();
},
getEdge: function(x, y, o) {
if (o % 2 === 0) return this.get(x, y)[o / 2];
else {
let a, b;
[a, b] = orientations[(o + 3) % 6];
return this.get(x - a, y - b)[((o + 3) % 6) / 2];
}
},
setEdge: function(x, y, o) {
if (Math.abs(x) > this.maxX) this.maxX = Math.abs(x);
if (Math.abs(y) > this.maxY) this.maxY = Math.abs(y);
if (o % 2 === 0) {
if (this.get(x, y)[o / 2]) throw new Error("Path already taken");
this.get(x, y)[o / 2] = 1;
} else {
let a, b;
[a, b] = orientations[(o + 3) % 6];
if (this.get(x - a, y - b)[((o + 3) % 6) / 2]) throw new Error("Path already taken");
this.get(x - a, y - b)[((o + 3) % 6) / 2] = 1;
}
}
};
var drawGrid = function(area) {
var width = canvas.width,
height = canvas.height;
ctx.clearRect(0, 0, width, height);
var gridWidth = arena.gridWidth(),
gridHeight = arena.gridHeight();
var triangleLen = Math.floor(Math.min(
width / (arena.maxX * 2 + arena.maxY),
height / (arena.maxY * Math.sqrt(3)),
width / 4
));
var convert = (x, y) => [(x - y / 2) * triangleLen, triangleLen * y * Math.sqrt(3) / 2];
var drawCirc = function(x, y) {
var a, b;
ctx.beginPath();
[a, b] = convert(x, y);
ctx.arc(a, b, 5, 0, 2 * Math.PI);
ctx.fill();
};
ctx.fillStyle = "black";
for (let y = 0; triangleLen * y * Math.sqrt(3) / 2 < height; ++y) {
for (let x = Math.floor(y / 2); triangleLen * (x - y / 2) < width; ++x) {
drawCirc(x, y);
}
};
var drawLine = function(a, b, c, d) {
var x, y;
ctx.beginPath();
[x, y] = convert(a, b);
ctx.moveTo(x, y);
[x, y] = convert(a + c, b + d);
ctx.lineTo(x, y);
ctx.stroke();
};
var centerY = Math.round(height / (triangleLen * Math.sqrt(3)));
var centerX = Math.round(width / (2 * triangleLen) + centerY / 2);
ctx.fillStyle = "red";
drawCirc(centerX, centerY);
for (let row = -(gridHeight - 1) / 2; row < (gridHeight + 1) / 2; ++row) {
for (let col = -(gridWidth - 1) / 2; col < (gridWidth + 1) / 2; ++col) {
let lines = arena.get(col, row);
for (let j = 0; j < lines.length; ++j) {
if (lines[j]) {
let or = orientations[2 * j];
drawLine(col + centerX, row + centerY, or[0], or[1]);
}
}
}
}
};
var step = function(x, y, absoluteOrientation) {
log('(' + x + ',' + y + ')');
var surroundings = 0;
for (let i = 0; i < 6; ++i) {
if (arena.getEdge(x, y, (absoluteOrientation + i) % 6)) {
surroundings |= (1 << i);
}
}
if (surroundings == 63) {
console.log("Done!");
return;
}
var action;
if (memory[surroundings] !== undefined)
action = memory[surroundings];
else {
action = input.shift();
memory[surroundings] = action;
}
absoluteOrientation = (absoluteOrientation + action) % 6;
arena.setEdge(x, y, absoluteOrientation);
var or = orientations[absoluteOrientation];
x += or[0];
y += or[1];
if (arena.isAtEnd(x, y)) arena.expandGrid();
drawGrid(arena);
setTimeout(function() {
step(x, y, absoluteOrientation);
}, parseFloat(document.querySelector("input[type=number]").value));
};
var go = function() {
input = document.querySelector("input[type=text]").value.split(",").map(x => parseInt(x, 10));
arena.grid = [[[1, 0, 0]]];
arena.maxX = 1;
arena.maxY = 0;
memory = {};
for (let i = 0; i < 6; ++i) {
memory[(~(1 << i)) & 63] = i;
}
arena.expandGrid();
arena.expandGrid();
step(1, 0, 0);
};
document.querySelector("button").onclick = go;
canvas {
border: 1px solid black;
}
Input: <input type="text" placeholder="Comma separated input"><br />
Delay (ms): <input type="number" placeholder="delay" value="500"/><button>Go</button><br />
<canvas width="500" height="400"></canvas><br />
<textarea></textarea>
[1,0,4,2,0,1,5]
.)Ответы:
JavaScript (ES6),
261 250249 байтПринимает список ввода в обратном порядке. Возвращает список пар .[ х , у]
Попробуйте онлайн!
По сути это порт демонстрационного фрагмента.
источник
К (нгн / к) , 115 байт
(не считая части именования функций,
f:
)Попробуйте онлайн!
D,:-D:2\6 3
генерирует шесть основных направлений(1 0;1 1;0 1;-1 0;-1 -1;0 -1)
d::0
текущее направление, используемое в качестве индекса мод 6 вD
m::2/=6
генерирует начальную память червя32 16 8 4 2 1
. биты каждого числа кодируют окружение (0 = посещенный сегмент; 1 = не посещенный). изначальноm
содержит только однозначное окружение - то, в котором доступен единственный выход.X::(!6),x
правила червя мы собираемся0 1 2 3 4 5
соответствовать первоначальной однозначной обстановке вm
.{
...}/,1 0
применять до сходимости функцию,{
}
начиная с 1-элементного списка, содержащего1 0
. список будет содержать пары координат, которые посетил червь.D 6!d+!6
шесть основных направлений, начиная сd
и по часовой стрелкеh:*|x
последний аргумент, то есть положение головы червя(2*h:*|x)+/:D 6!d+!6
умножьте координаты головы на 2 и добавьте кардинальные направления. это наш способ представления отрезков между точками.+':x
добавить пары соседних посещенных точек - это дает нам представление сегментов между ними^(
...)?
... выяснить, какие из окружающих сегментов головы еще не посещеныp:2/
двоичное кодирование и присвоениеp
m::?m,p
Добавлять вm
и сохранить отчетливый, т.е. Append ,p
чтобыm
только тогда , когдаp
не происходит вm
$[
...;
...;
...]
если-то-ещеc:X m?p
найти индексp
вm
и использовать его в качестве индекса вX
. результаты индексации за пределами допустимого диапазона0N
("null")$[(~p)|^c:X m?p;x;
...]
еслиp
0 (нет пути выхода) илиc
есть0N
, то return,x
что приведет к сходимости и остановит циклx,,h+D 6!d+:c
еще добавить новую головуx
и повторитьисточник