Рисунок ASCII-карты на Python
-
13-12-2019 - |
Вопрос
Мне нужна карта радиусом 2, нарисованная от текущей комнаты игрока в MUD, который я создаю на Python (или больше, если возможно).Комнаты представляют собой контейнеры с self.exits = {'west':1, 'north':2}
где ключ — это направление, в котором находится значение (UID соседней комнаты).Комнаты соединены между собой только таким образом.Игрок с self.location, равным 0, может ввести «n», и его местоположение, основанное на указанной выше переменной, тогда будет равно 2, а к содержимому этой комнаты будет добавлен UID игрока.
Итак, я хотел бы, чтобы на основе указанной выше переменной отображалась карта, которая выглядела бы следующим образом, где «u» — текущее местоположение игрока.
[ ]
|
[ ]-[u]
я достиг этот часть, так как это всего лишь радиус 1.Вот небольшой (сильно измененный для публикации здесь) фрагмент того, как я это сделал, и вы поймете, почему я публикую это плохой код.
mloc = '[u]'
mn = ' '
mw = ' '
spn= ' '
spw= ' '
for Exit in room.exits.keys():
if Exit == 'north':
mn = '[ ]'
spn = '|'
if Exit == 'west':
mw = '[ ]-'
# player.hear() is our function for printing a line to the player's screen
player.hear(' '+mn)
player.hear(' '+sp)
player.hear(mw+mloc)
В своем безумии мне удалось сделать эту работу со всеми 8 разными направлениями (диагонали, не считая вверх и вниз).Но тогда мне придется зацикливаться на комнатах, которые я просто проанализировал со своим первым для цикла, а затем нарисовать их, а затем расстаться, а затем принять во внимание перекрытие (SP) таких тазовых тапов, таких как «» или ' | '' Если есть пути, которые пересекают друг друга.Эта небольшая задача сразу же превратилась в кошмар, и прежде чем я закончил, потребовалось еще 200 строк.
Еще одним препятствием является то, что я могу печатать только построчно.Итак, если высота карты составляет 50 символов, мне нужно иметь player.hear()
на 50 строк, против чего я не против.Просто имейте это в виду, прежде чем публиковать ответ.
Я также не придирчив к форматированию.Мне просто нужна «карта с первого взгляда», которая поможет игрокам во время путешествий по миру.
Спасибо ребята.Надеюсь, я предоставил достаточно информации.Дайте мне знать, если нет.(Вот ссылка на весь (незаконченный и УЖАСНЫЙ) модуль, на который я ссылаюсь. Map.py
Решение
Этот код находится в серьезной проблеме.Начнем проектирование с нуля.Мы надеемся, что это послужит хорошим уроком о том, как проектировать и создавать классы и структуры данных.
Для начала вам следует организовать свой код вокруг Map
class, который затем представляет ваши комнаты в виде сетки.Вам не следует думать о «комнате 1», «комнате 2» и т. д. (что очень сложно отследить на карте), а лучше думать о комнатах с точки зрения координат.
Итак, есть несколько возможных функций, которые мы игнорируем вначале, в том числе игрок, который видит только те комнаты, в которых он был, игрок, остающийся в центре карты, и диагональные пути.Если они вам нужны, вы можете добавить их позже, когда основные функции заработают.На данный момент мы стремимся к чему-то примерно такому:
[ ]-[u] [ ] [ ]
|
[ ]-[ ]-[ ] [ ]
|
[ ]-[ ]-[ ] [ ]
|
[ ]-[ ]-[ ]-[ ]
То есть мы представляем это в виде сетки, в которой одни комнаты соединены, а другие нет.Пусть у каждой комнаты будет пара координат, примерно так:
0 1 2 3
0 [ ]-[u] [ ] [ ]
|
1 [ ]-[ ]-[ ] [ ]
|
2 [ ]-[ ]-[ ] [ ]
|
3 [ ]-[ ]-[ ]-[ ]
Пусть x будет сверху, а y — сбоку.Вверху слева — (0, 0), тот, у которого [u]
в нем (0, 1).
Теперь, каковы компоненты нашего Map
сорт?
высота карты:целое число
ширина карты:целое число)
игрок_x, игрок_y:координаты игрока
возможные пути:список пар комнат, между которыми мы можем перемещаться.Приведенная выше карта будет представлена как:
[((0, 0), (1, 0)), ((0, 0), (1, 0)), ((1, 0), (1, 1)), ((1, 1), (2, 1)), ((1, 0), (1, 2)), ((0, 2), (1, 2)), ((1, 2), (2, 2)), ((0, 2), (0, 3)), ((0, 3), (1, 3)), ((1, 3), (2, 3)), ((2, 3), (3, 3))]
Обратите внимание, что я расположил каждую пару так, чтобы больший кортеж шел первым (это важно позже).
Итак, теперь, когда у нас есть дизайн, давайте напишем это Map
сорт!
Я могу придумать четыре метода, которые нам нужны: print_map
, move
, и инициализатор.Инициализация проста:просто установите четыре атрибута, которые мы перечислили выше:
class Map:
def __init__(self, height, width, player_x, player_y, paths):
self.height = height
self.width = width
self.x = player_x
self.y = player_y
self.paths = paths
Сейчас, move
это довольно просто.Учитывая направление n/e/s/w:
def move(self, direction):
if direction == "n":
if ((self.x, self.y - 1), (self.x, self.y)) not in self.paths:
print "Cannot go north"
else:
self.y -= 1
А move
функция для «севера» просто проверяет, есть ли путь к комнате над той, в которой мы находимся.
Теперь самое интересное:распечатка карты.Вы делаете это, перебирая строки (от 0 до self.height
) и по столбцам (от 0 до self.width
). (Примечание:ты не можешь использовать print
в этой ситуации, поскольку после строки автоматически помещается новая строка или пробел.Вместо этого мы используем sys.stdout.write.
def print_map(self):
for y in range(0, self.height):
# print the yth row of rooms
for x in range(0, self.width):
if self.x == x and self.y == y:
sys.stdout.write("[u]") # this is the player's room
else:
sys.stdout.write("[ ]") # empty room
# now see whether there's a path to the next room
if ((x, y), (x + 1, y)) in self.paths:
sys.stdout.write("-")
else:
sys.stdout.write(" ")
# now that we've written the rooms, draw paths to next row
print # newline
for x in range(0, self.width):
sys.stdout.write(" ") # spaces for above room
if ((x, y), (x, y + 1)) in self.paths:
sys.stdout.write("| ")
else:
sys.stdout.write(" ")
print
А теперь давайте соберем все это вместе и попробуем.Вот код:
import sys
class Map:
def __init__(self, height, width, player_x, player_y, paths):
self.height = height
self.width = width
self.x = player_x
self.y = player_y
self.paths = paths
def move(self, direction):
if direction == "n":
if ((self.x, self.y - 1), (self.x, self.y)) not in self.paths:
print "Cannot go north"
else:
self.y -= 1
if direction == "s":
if ((self.x, self.y), (self.x, self.y + 1)) not in self.paths:
print "Cannot go south"
else:
self.y += 1
if direction == "e":
if ((self.x, self.y), (self.x + 1, self.y)) not in self.paths:
print "Cannot go east"
else:
self.x += 1
if direction == "w":
if ((self.x - 1, self.y), (self.x, self.y)) not in self.paths:
print "Cannot go west"
else:
self.x -= 1
def print_map(self):
for y in range(0, self.height):
# print the yth row of rooms
for x in range(0, self.width):
if self.x == x and self.y == y:
sys.stdout.write("[u]") # this is the player's room
else:
sys.stdout.write("[ ]") # empty room
# now see whether there's a path to the next room
if ((x, y), (x + 1, y)) in self.paths:
sys.stdout.write("-")
else:
sys.stdout.write(" ")
# now that we've written the rooms, draw paths to next row
print # newline
for x in range(0, self.width):
sys.stdout.write(" ") # spaces for above room
if ((x, y), (x, y + 1)) in self.paths:
sys.stdout.write("| ")
else:
sys.stdout.write(" ")
print
paths = [((0, 0), (1, 0)), ((0, 0), (1, 0)), ((1, 0), (1, 1)), ((1, 1),
(2, 1)), ((1, 1), (1, 2)), ((0, 2), (1, 2)), ((1, 2), (2, 2)),
((0, 2), (0, 3)), ((0, 3), (1, 3)), ((1, 3), (2, 3)), ((2, 3),
(3, 3))]
m = Map(4, 4, 0, 0, paths)
while True:
m.print_map()
direction = raw_input("What direction do you want to move? [n/e/s/w] ")
m.move(direction)
Обратите внимание, что я добавил внизу раздел, который создает карту и позволяет игроку перемещаться по ней.Вот как это выглядит во время работы:
Davids-MacBook-Air:test dgrtwo$ python Map.py
[u]-[ ] [ ] [ ]
|
[ ] [ ]-[ ] [ ]
|
[ ]-[ ]-[ ] [ ]
|
[ ]-[ ]-[ ]-[ ]
What direction do you want to move? [n/e/s/w] e
[ ]-[u] [ ] [ ]
|
[ ] [ ]-[ ] [ ]
|
[ ]-[ ]-[ ] [ ]
|
[ ]-[ ]-[ ]-[ ]
What direction do you want to move? [n/e/s/w] s
[ ]-[ ] [ ] [ ]
|
[ ] [u]-[ ] [ ]
|
[ ]-[ ]-[ ] [ ]
|
[ ]-[ ]-[ ]-[ ]
What direction do you want to move? [n/e/s/w] w
Cannot go west
[ ]-[ ] [ ] [ ]
|
[ ] [u]-[ ] [ ]
|
[ ]-[ ]-[ ] [ ]
|
[ ]-[ ]-[ ]-[ ]
What direction do you want to move? [n/e/s/w] e
[ ]-[ ] [ ] [ ]
|
[ ] [ ]-[u] [ ]
|
[ ]-[ ]-[ ] [ ]
|
[ ]-[ ]-[ ]-[ ]
В этот код можно внести множество улучшений (в частности, move
метод повторяется), но это хорошее начало.Попробуйте сделать карту размером 20x20, и вы увидите, что она прекрасно расширяется.
Расчетное время прибытия:Я должен отметить, что print_map
можно было бы переписать в гораздо более короткой форме примерно так:
def print_map(self):
for y in range(0, self.height):
print "".join(["[%s]%s" %
("u" if self.x == x and self.y == y else " ",
"-" if ((x, y), (x + 1, y)) in self.paths else " ")
for x in range(0, self.width)])
print " " + " ".join(["|" if ((x, y), (x, y + 1)) in self.paths
else " " for x in range(0, self.width)])
Но это немного более интенсивно.
Другие советы
Я сделал это в качестве упражнения, в котором комнаты и сетка «распечатываются».Я добавляю это к обсуждению, поскольку, возможно, это будет проще реализовать с помощью более крупной сетки.
Карта ASCII
+++++++++++++++
+++++++++++++++
+++++++++++++++
++++++ ++++++
++++++ 2 ++++++
++++++/| ++++++
+++ / | ++++++
+++ 3--1 ++++++
+++ \++++++
+++++++++\ +++
+++++++++ 4 +++
+++++++++ +++
+++++++++++++++
+++++++++++++++
+++++++++++++++
Каждая ячейка в этой сетке представляет собой набор знаков «+» размером три на три.Четыре комнаты реализованы со значениями идентификаторов от 1 до 4.Соединения между комнатами представлены косыми чертами, обратными косыми чертами и трубами.
Код
class Room(object):
def __init__(self, id, loc, exits):
self.id = id # unique identifier, may be a name
self.row = loc[0] # loc is tuple of (row, col)
self.col = loc[1]
# exits is a list where 'X' means no exit and
# any other value is id of destination
self.exits = exits
def __str__(self):
directions = '\\|/- -/|\\'
room = [ e if e == 'X' else ' ' for e in self.exits ]
for idx in range(len(room)):
if room[idx] == ' ':
room[idx] = directions[idx]
if room[idx] == 'X':
room[idx] = ' '
room[4] = self.id[0] # only print first char of id
return ''.join(room)
class Map(object):
def __init__(self, rows, cols, rooms):
self.rows = rows
self.cols = cols
self.rooms = rooms
def __str__(self):
world = []
for i in range(self.rows * 3):
world.append( ['+++'] * self.cols )
for room in self.rooms:
ascii = str(room)
x = room.col
y = room.row
for idx in range(0, 3):
cell = ascii[idx*3:idx*3+3]
world[y*3+idx][x] = cell
return '\n'.join( [ ''.join(row) for row in world ] )
if __name__ == '__main__':
# set up four rooms
# each room has unique id (string of any length) and coordinates
# it also has a set of 8 possible exits, represented as a list where
# 'X' means exit is blocked and valid exits contain the id of the target room
r1 = Room(id='1', loc=(2,2), exits=['X','2','X',
'3',' ','X',
'X','X','4',])
r2 = Room(id='2', loc=(1,2), exits=['X','X','X',
'X',' ','X',
'3','1','X',])
r3 = Room(id='3', loc=(2,1), exits=['X','X','2',
'X',' ','1',
'X','X','X',])
r4 = Room(id='4', loc=(3,3), exits=['1','X','X',
'X',' ','X',
'X','X','X',])
# initialize Map with a list of these four rooms
map = Map(rows = 5, cols=5, rooms=[r1, r2, r3, r4])
print map
Процедура перемещения не реализована, и для того, чтобы это представление работало, хорошо будут отображаться только идентификаторы отдельных символов.
Преимущества этой системы:
- легко добавлять комнаты и удалять их
- определение комнаты удобочитаемо для человека
- функции вывода перегружаются
__str__
и, следовательно, комнаты и сетка «распечатываются», и это может оказаться полезным для будущей отладки или адаптации к будущим форматам, например.как ячейки в таблице HTML.
Карты на основе координат имеют много преимуществ, но, учитывая, что многие качественные грязи используют традиционный мир, основанный на комнатах, и люди создали автокарты для многих грязей и грязевых клиентов, не исключено создание автокарты для грязи без координат. .Вам просто придется решать конфликты в каждом конкретном случае.
Однако вы все равно можете использовать ответ @david-robinson.Что вам нужно сделать, так это держать мини-карту примерно по центру игрока и динамически обновлять ее, используя данные выхода.Не пытайтесь сохранить карту всей территории;динамически обновляя, вы избежите некоторых географических конфликтов.
Для записи карты в грязевой клиент все, что вам нужно сделать, это написать строку карты с правильным интервалом и завершить ее новой строкой.Вы помещаете все строки карты в список, чтобы он отправлялся как одна группа строк (вы не хотите, чтобы между строками карты была вставлена какая-то другая строка, например, когда она отправляется из сокета), и любой грязевой клиент распечатает ее. правильно (разумеется, с моноширинным шрифтом).