Генератор Рогалик Дом


Я решил сделать рогалик для удовольствия. Вот первая часть, генератор, здания (в ссылке внизу поста, каждый квадрат представляет собой здание):

var mm=millis()
//var mm2=0

var rooms=[]
var S=63 //What's a better name for S?
S+=S%2 //forces S to be even

for(var i=0;i<S;i++){
    rooms[i]=[]
    for(var j=0;j<S;j++){
        rooms[i][j]=Infinity
    }
}

var pick=function(array){

    var queue=[]
    //var mm3=millis()
    for(var i=1;i<S-1;i++){
        for(var j=1;j<S-1;j++){
            if(rooms[i][j]==Infinity){
                var t = min(min(rooms[i-1][j],rooms[i][j-1]),
                            min(rooms[i+1][j],rooms[i][j+1]))
                if(t<Infinity){
                    queue.push([i,j,pow(S,2)])
                }
            }
        }
    }
    //mm2+=millis()-mm3
    return queue[~~(Math.random()*queue.length)]
}

var createRoom=function(pos){
    rooms[pos[0]][pos[1]]=pos[2]
}

var drawRooms=function(){
    pushMatrix()
    //scale(4.0)
    colorMode(HSB)
    background(255, 0, 255)
    noStroke()
    for(var i=0;i<S;i++){
        for(var j=0;j<S;j++){
            if(rooms[i][j]!==Infinity){
                fill(rooms[i][j]*10,255,255)
                rect(i*width/S,j*height/S,ceil(width/S),ceil(height/S))
            }
        }
    }
    popMatrix()
}

var redo=function(x,y,t){
    if(rooms[x][y]!==Infinity&&rooms[x][y]>t){
        rooms[x][y]=t
        t++
        redo(x+1,y,t)
        redo(x,y+1,t)
        redo(x-1,y,t)
        redo(x,y-1,t)
    }
}

createRoom([S/2,S/2,1])
for(var i=0;i<pow(S,2)/10;i++){
    createRoom(pick())
}
redo(S/2,S/2,0)

drawRooms()
fill(0)
var benchmark=millis()-mm
text('Took '+benchmark+' milliseconds to run.',10,10)

Я бы сделал это в processing.py но у меня были некоторые проблемы с матрицами. Если кто-нибудь знает, как использовать их в ванильный питон, не стесняйтесь, чтобы дать мне объяснение.

И наконец, вот ссылка я уже упоминал, если вы хотите взглянуть на него:

("Радуга" представляет расстояние от центра "комната" (Красная площадь) в цветной номер).

https://www.khanacademy.org/computer-programming/roguelike-room-generator/6424540231008256



159
3
задан 31 марта 2018 в 03:03 Источник Поделиться
Комментарии
2 ответа

Общий отказ от ответственности: я пытался найти как можно больше вещей в вашем коде, что вы можете исправить, но я не могу найти все. Аналогично, просто потому, что причин может быть много ниже, не означает, что код-это бесполезно.

До рассмотрения

Я буду рассматривать код как написал на своей KhanAcademy, так как я не могу сделать вот работающий код. Я предполагаю, это запустить через какой-то компилятор. Вы в настоящее время используете неопределенную функцию millis и использовать min и pow без Math префикс, который делает эти неопределенные в обычного JavaScript, а также.

Использовать линтер

У вас полностью отключен Линтер в коде. Я рекомендую против этого, за исключением случаев, где вы должны указать зависимость в собственном коде и не жалейте времени, чтобы переписать ее в том же стиле код, который вы используете.

Линта заставить вас писать последовательный код. Их правила были обсуждаться снова и снова, и их значения по умолчанию, как правило, выполнимо. Если вам не нравится определенное правило, вы можете отключить его, или изменить правила. Если у вас есть хорошая причина, чтобы сделать что-то определенным образом, и это трипс правило, вы можете отключить правило для этого одну строку или один блок.

Своей KhanAcademy, что у JSHint. Вы можете найти документацию здесь. В вашем случае вы постоянно не используете точку с запятой. Я лично предпочитаю всегда использовать их, потому что они мешают опечатки вызывает странные ошибки, но вы можете сказать, у JSHint, чтобы отстали от тебя об этом // jshint asi: true. Большинство странное поведение было поймано по другим правилам.

Читабельность

Именовании переменных

Самая большая проблема с вашим кодом заключается в именовании переменных и функций. Переменные и имена функций являются основным способом понимания того, что кусок кода делает. Комментарии второй более подробно остановиться на некоторых ожидания у вас есть.


  • mm используется в начале. Из контекста я понимаю, что это время начала свой тест, так назовите его как-то так startTime.

  • Одной переменной букву фамилии, как правило, плохая идея. S используется для определения размеров сетки. Давайте просто называть это так: dimensions.

  • В ваш выбор функции, вы используете queue. Очередь-это структура, где-то в конце и получить вещи с самого начала. Вместо этого вы выбираете случайный элемент. Лучшее название будет pool или collection или pickableRooms.

  • Переменные с числом в них обычно указывают, что вы должны переименовать их. dir2 не является исключением. Учитывая, что он находится в createPath функция, я предполагаю, что это направление. После прочтения контексте, я думаю, что это на самом деле что-то вроде oppositeDirection.

  • pos2 используется только ниже этого. Так как это будет указывать на позицию в заданном направлении, назовем это adjecentPosition.

  • t в redo функция ставит меня в тупик. Я думаю, что он должен быть на расстоянии от центра, так назовем это distance.

  • Имя функции redo непонятно. Вы должны заменить его с тем, что функция на самом деле делает, что, кажется, пересчитать расстояния на созданные номера.

Неиспользуемые переменные

У вас есть неиспользуемые переменные в списке аргументов ваших pick функция. Я удивлен, что Линтер не поймать. В любом случае, если вы не используете аргумент к методу в теле метода, вы не должны включать его в подпись.

Если вы бы использовали этот аргумент arrayвы должны переименовать его, что переменная на самом деле содержится, а не его тип. Вы могли бы использовать roomMatrix или distanceMatrix например.

Странно побитовое не

Вы используете двойной побитовый оператор Not в коде. После попытки его, я предполагаю, что это какой-то операции, чтобы получить целую часть поплавка.

Поскольку вы никогда не передавать его отрицательные числа1, считая расчете индекса, просто использовать Math.floor для удобочитаемости.

Вмятие

Где-то в коде у вас есть следующие строки:

var t = min(min(rooms[i-1][j],rooms[i][j-1]),
min(rooms[i+1][j],rooms[i][j+1]))

Я думаю, что есть две вещи неправильно с этих двух строк:


  • Вы вручную совмещая две линии. Не делай этого, потому что когда вы начинаете использовать системы управления версиями он будет кусать вас. Линии, которые не изменились, не должны быть изменены, потому что они вызывают конфликты слияния и нанести линии, чтобы показать в коммиты, которые есть только пробел изменений.

  • Если поставить открывающую фигурную скобку в определенный отступ, необходимо поставить закрывающую фигурную скобку на той же вмятины. Это касается [, { и (.

Следующий код лучше читается, и подчеркивает, что вы верите, что вы можете использовать только два аргумента в Math.min(..) функции, которые не по делу.

var t = min(
min(rooms[i-1][j],rooms[i][j-1]),
min(rooms[i+1][j],rooms[i][j+1])
)

Поэтому мы можем изменить его на следующий код, который дает понять, что вы хотите на самом деле получить минимальное из всех соседних комнат.

var t = min(
rooms[i-1][j],
rooms[i][j-1],
rooms[i+1][j],
rooms[i][j+1]
)

Структурирование кода

Функции

Вы выложили часть кода в функции. Заметным исключением является код инициализации. Рассмотреть вопрос о переходе его на собственный функция.

Синтаксис функции

Синтаксис var a = function (arguments) - это немного странно. Он прекрасно показывает, что функция на самом деле является переменной, которую можно назвать, но это, кажется, не очень полезно в данном случае. Рассмотрим только использование function a (arguments).

Я заметил, что своей KhanAcademy считает, что это неправильный синтаксис. Я невежественный, почему это происходит.

Аннотация от базовой структуры данных

Ваши функции взаимодействуют с базовой структурой данных в некотором роде или другой. В настоящее время вы не проверяя на наличие ошибок. Как только вы начинаете делать это, вы, вероятно, заметите, что вы не дублируя код.

Вы, наверное, хотите скрыть реализацию получения смежных комнат в отдельные функции, такие как northи добавить обработку ошибок там для обработки случаев, когда вы пытаетесь получить номер за пределами границ.

Рабочие программы

Перекрывающиеся квадраты

Потому что, как вы вычисляете ширину, вы добавляете ошибки на ширину ваших площадей, в результате чего некоторые из них будут отключены на один пиксель.

Off by one

Любой простой способ борьбы с этим-расчет эффективной ширины и высоты, и использовать его везде:

var effectiveWidth = floor(width / dimensions)
var effectiveHeight = floor(height / dimensions)

и

for (var i = 0; i < dimensions; i++) {
for (var j = 0; j < dimensions; j++) {
if (rooms[i][j] !== Infinity) {
fill(rooms[i][j] * 255 / dimensions, 255, 255)
rect(i * effectiveWidth, j * effectiveHeight, effectiveWidth, effectiveHeight)
}
}
}

Размеры постоянн

У вас есть константа, которая определяет, на какой сетке мы играем. Кроме того, что вы 'автозамена' это. Не производить ошибочное поведение при неправильных ввода, но вместо того, чтобы просто выдавать ошибку.

var dimensions = 6

if (dimensions % 2 !== 0) {
throw new Error('Dimensions must be even')
}

Вы не одна... дважды

При установке dimensions в 4, Вы можете иметь максимум 2 на 2 номера. Когда вы установите его на 6, Вы можете иметь максимум 4 на 4 номера. Вы заметите, что таким образом тоже не в центре.

Off by... two?

Что вы на самом деле хотите, чтобы иметь размеры, чтобы быть нечетным, так что вы можете на самом деле получить среднего. Тогда вам придется перебрать все возможные номера. Тогда вы просто должны проверить, если вы пытаетесь найти номер вне сетки, и вернуться Infinity в этом случае.

Результат



//The "rainbow" part represents the distance from the center "room" (the reddest square) to the colored room.

//jshint asi: true

var startTime = millis()

var rooms = []
var paths = []
var dimensions = 5
var numberOfRooms = Math.floor(pow(dimensions, 2))
var padding = 20

var startX = Math.floor(dimensions / 2)
var startY = Math.floor(dimensions / 2)

var squareWidth = floor((width - padding * 2) / dimensions)
var squareHeight = floor((height - padding * 2) / dimensions)

if (dimensions % 2 !== 1) {
throw new Error('dimensions should be odd')
}

var initialize = function() {
for (var i = 0; i < dimensions; i++) {
rooms[i] = []
paths[i] = []
for (var j = 0; j < dimensions; j++) {
rooms[i][j] = Infinity
paths[i][j] = [false, false, false, false] // up, right, down, left
}
}
}

var north = function(pos) {
if (pos[1] === 0) {
return Infinity
}

return rooms[pos[0]][pos[1] - 1]
}

var west = function(pos) {
if (pos[0] === 0) {
return Infinity
}

return rooms[pos[0] - 1][pos[1]]
}

var south = function(pos) {
if (pos[1] === dimensions - 1) {
return Infinity
}

return rooms[pos[0]][pos[1] + 1]
}

var east = function(pos) {
if (pos[0] === dimensions - 1) {
return Infinity
}

return rooms[pos[0] + 1][pos[1]]
}

var pickRandomRoom = function() {
var pool = []

for (var i = 0; i < dimensions; i++) {
for (var j = 0; j < dimensions; j++) {
if (rooms[i][j] === Infinity) {
var pos = [i, j]

var t = min(
north(pos),
west(pos),
south(pos),
east(pos)
)

if (t < Infinity) {
pool.push([i, j, pow(dimensions, 2)])
}
}
}
}

return pool[Math.floor(Math.random() * pool.length)]
}

var createRoom = function(pos) {
rooms[pos[0]][pos[1]] = pos[2]
}

var createPath = function(pos, dir) {
var oppositeDirection = (dir + 2) % 4
var adjacentPosition = [pos[0], pos[1]]
paths[pos[0]][pos[1]][dir] = true
switch (dir) { //there's gotta be some better way to do this
case 0:
adjacentPosition[0]--
break
case 1:
adjacentPosition[1]--
break
case 2:
adjacentPosition[0]++
break
case 3:
adjacentPosition[1]++
break
}
paths[adjacentPosition[0]][adjacentPosition[1]][oppositeDirection] = true
}

var drawRooms = function() {
pushMatrix()
//scale(4.0)
colorMode(HSB)
background(255, 0, 255)

//noStroke()
for (var i = 0; i < dimensions; i++) {
for (var j = 0; j < dimensions; j++) {
if (rooms[i][j] !== Infinity) {
fill(rooms[i][j] * 255 / dimensions, 255, 255)
rect(
padding + i * squareWidth,
padding + j * squareHeight,
squareWidth,
squareHeight
)
}
}
}
popMatrix()
}

var recalculateDistances = function(x, y, distance) {
if (
x >= 0 &&
x < dimensions &&
y >= 0 &&
y < dimensions &&
rooms[x][y] !== Infinity &&
rooms[x][y] > distance
) {
rooms[x][y] = distance
distance += 1
recalculateDistances(x, y + 1, distance)
recalculateDistances(x + 1, y, distance)
recalculateDistances(x, y - 1, distance)
recalculateDistances(x - 1, y, distance)
}
}

initialize()
createRoom([startX, startY, 1])
for (var i = 0; i < numberOfRooms - 1; i++) {
createRoom(pickRandomRoom())
}
recalculateDistances(startX, startY, 0)

drawRooms()
fill(0)
var benchmark = millis() - startTime
text('Took ' + benchmark + ' milliseconds to run.', 10, 10)



Или как работать на своей KhanAcademy

Сноски


  • 1 Math.floor возвращает наибольшее целое число, меньшее, чем аргумент, передаваемый на него, так Math.floor(-1.5) === -2. Побитовое не трюк возвращает что-то другое: ~~-1.5 === -1. Добавить комментарий к такого рода трюков, так что ваше будущее я не должен выяснить, что такая странная выходка не делает.

3
ответ дан 1 апреля 2018 в 05:04 Источник Поделиться

Использование объектов

Оуч ваш код-это идти по этой дороге многих проблем. Вам нужно организовать код в ясный объект.

А не полный обзор, это пример предложение. Увидеть пример кода .

Некоторая информация относительно образца.

Код имеет параметры вместе тоже. Использует холст прямо, а не библиотека.

displayWidth / roomSize устанавливает, что у тебя как S

Есть два объекта.

Room держите все, что нужно для комнаты. Где он находится, если он был добавить (открыть). Номера, которые находятся на краю (выбор номера) являются кандидатами вызову и номер будет иметь флаг, чтобы указать, является кандидатом.

Dungeon создает и хранит все номера. Он имеет 3 массивов, один для всех комнат, в одной из открытых комнат, и один для номера кандидата.

При добавлении номера он переехал в он открытый массив. Все номера рядом с ним, что не открыты и еще не включили в список кандидатов попадает в качестве кандидатов и добавляется в массив кандидата.

После номера добавляется случайный номер удаляется из массива кандидата и открыл. Свершилось то, чего нет больше кандидатов на складе или открытой комнаты была достичь.

Затем она рисует номера.

Этот код много раз быстрее, чем при использовании утомительных поисков итерации как он не тратит время перебора несвязанных номеров. Также дист расч производится по мере добавления номера.

Кстати, для положительных чисел этаже их с логической или ноль (см. код).
~~ две операции, так что половине скорости одного оператора.



const displayWidth = 512;
const displayHeight = 512;
const roomSize = 8; // Room size in pixels number of rooms will fit canvae
const distColScale = 20;
const dungeonSize = displayWidth / roomSize | 0;
const roomCount = dungeonSize * dungeonSize | 0;
const maxOpenRooms = roomCount / 3;
const directions = [{x : 0, y : -1}, {x : 1, y : 0}, {x : 0, y : 1}, {x : -1, y : 0}];
const canvas = document.createElement("canvas");
canvas.width = displayWidth;
canvas.height = displayHeight;
document.body.appendChild(canvas);
const ctx = canvas.getContext("2d");

function Room(index, dungeon) {
this.row = index / dungeon.size | 0;
this.col = index % dungeon.size;
this.x = this.col * roomSize;
this.y = this.row * roomSize;
this.color = "red";
this.dist = 0;
this.open = false;
this.candidate = false;
}
Room.prototype = { // the functions for a room
draw() { // draws a room
ctx.fillStyle = "black";
ctx.fillRect(this.x, this.y, roomSize, roomSize);
ctx.fillStyle = this.color;
ctx.fillRect(this.x+1, this.y+1, roomSize-2, roomSize-2);
},
distColor(dist) {
this.color = "hsl(" + ((dist * 255 / distColScale) % 360) + ",100%,50%)";
this.dist = dist;
},
}

function Dungeon(size) {
this.size = size;
this.rooms = []; // to hold all rooms
this.open = []; // holds open rooms
this.candidates = []; // to hold rooms that can be added
for (let i = 0; i < roomCount; i++) { this.addRoom(new Room(i, this)) }
var room = this.roomAt(size / 2 | 0, size / 2 | 0);
while (room !== undefined && this.open.length < maxOpenRooms) {
this.openRoom(room);
room = this.randomCandidate()
}
this.drawOpenRooms();
}

Dungeon.prototype = {
addRoom(room) {this.rooms.push(room) },
drawOpenRooms() {
ctx.clearRect(0, 0, displayWidth, displayHeight);
for (const room of this.open) { room.draw() }
},
roomAt(x, y) { // get the room at x,y
if (x >= 0 && x < this.size && y >= 0 && y < this.size) {
return this.rooms[x + y * this.size];
}
},
randomCandidate() { // returns a random candidate room
if (this.candidates.length === 0) { return } // no rooms
return this.candidates.splice(Math.random() * this.candidates.length | 0, 1)[0];
},
openRoom(room) { // open room and add ajoining rooms to candidate array
room.open = true;
this.open.push(room);
const dist = room.dist + 1; //Candidates are 1 more away
for (const dir of directions) {
const r = this.roomAt(room.col + dir.x, room.row + dir.y); // r for room
if (r && !r.open && !r.candidate) {
r.distColor(dist);
r.candidate = true;
this.candidates.push(r);
}
}
}
}

var testD = new Dungeon(dungeonSize);



Надеюсь, что это помогает, возьмите то, что вы хотите, это реально быстрая гайка я должен был сделать что-то, как ваш код выглядел так, что нужно перезагружать.

1
ответ дан 2 апреля 2018 в 05:04 Источник Поделиться