Python 绘制 ASCII 地图
-
13-12-2019 - |
题
我需要在我用 python 构建的 MUD 中从玩家当前的房间绘制一个 2 半径的地图(或者更多,如果可能的话)。房间被设置为容器,并带有 self.exits = {'west':1, 'north':2}
其中key是值(相邻房间的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)Aces的重叠,例如'''或' |'如果有彼此交叉的路径。这个小任务立刻变成了噩梦,在我完成之前就已经变成了 200 行。
另一个障碍是我只能逐行打印。所以如果地图有 50 个字符高,我必须有 player.hear()
50行,我并不反对。在发布答案之前请记住这一点。
我对格式也不挑剔。我只是想要一张“地图一目了然”来帮助玩家环游世界。
多谢你们。我希望我提供了足够的信息。如果没有,请告诉我。(这是我引用的整个(未完成且可怕的)模块的链接。 地图.py
解决方案
这段代码有严重的问题。让我们从头开始设计。希望这将成为如何设计和构建类和数据结构的一个很好的教训。
首先,您应该围绕以下内容组织您的代码 Map
类,然后将您的房间表示为网格。您不应该考虑“房间 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
“north”的函数只是检查是否有一条路径通往我们所在房间上方的房间。
现在是最有趣的部分:打印地图。您可以通过循环行(0 到 self.height
)以及列(0 到 self.width
). (笔记:你不能使用 print
在这种情况下,因为它会自动在字符串后面添加换行符或空格。相反,我们使用 系统标准输出写入.
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 MAP
+++++++++++++++
+++++++++++++++
+++++++++++++++
++++++ ++++++
++++++ 2 ++++++
++++++/| ++++++
+++ / | ++++++
+++ 3--1 ++++++
+++ \++++++
+++++++++\ +++
+++++++++ 4 +++
+++++++++ +++
+++++++++++++++
+++++++++++++++
+++++++++++++++
.
该网格中的每个单元格是三组“+”符号。四个房间以ID值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
.
移动例程未实现,并且对于此表示工作,只有单个字符ID将显示出很好。
该系统的优点:
- 易于添加房间并删除它们
- 房间的定义是人类可读
- 输出函数过载生物阳码码代码,因此房间和网格“打印本身”,这可能有助于未来调试或适应未来格式。作为HTML表中的细胞。
基于坐标的地图具有很多优点,但考虑到许多质量泥浆使用传统的基于房间的世界,而且人们为许多泥浆和泥客户提供了自动处理者,这并不是出于一个自动的问题没有坐标的泥土。您只需根据案例处理冲突。
但是,您仍然可以通过@ David-Robinson使用答案。您想要做的是保持最小的焦点,并使用退出数据动态更新它。不要试图保留存储的整个区域的地图;通过动态更新,您将避免一些地理冲突。要将地图写入泥客户端,您需要做的就是将您的地图线路写入正确间隔并用新行终止它。您将所有地图行放入列表中,以便它作为单个行组发送(您不希望在地图的行之间插入某些其他行,例如,当它发送出套接字时,任何MUD客户端都会打印适当(当然是一个单座的字体)。