【问题标题】:Python Drawing ASCII MapPython 绘制 ASCII 地图
【发布时间】:2012-07-27 01:43:36
【问题描述】:

我需要在我用 python 构建的 MUD(或更多,如果可能的话)中从玩家的当前房间绘制一个 2 半径地图。房间设置为带有self.exits = {'west':1, 'north':2} 的容器,其中键是值(相邻房间的 UID)所在的方向。房间仅以这种方式链接。 self.location 为 0 的玩家可以键入“n”,然后根据上述变量,他们的位置将为 2,并且该房间的内容会将玩家的 UID 附加到其内容中。

所以,我希望根据上述变量显示如下所示的地图,其中“u”是玩家的当前位置..

    [ ]
     |
[ ]-[u]

我已经完成了 this 部分,因为这只是 1 的半径。这是我如何做到这一点的一个小(经过大量修改)sn-p,你会看看我为什么要发布,因为它的代码很糟糕。

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 个不同的方向(对角线,不包括向上或向下)上完成了这项工作。但是我必须循环我刚刚用我的第一个 for 循环解析的房间,然后绘制它们,然后将它们全部隔开,然后考虑到 (sp)ace 的重叠,例如 '\' 或 ' |'如果有相互交叉的路径。这个小任务很快就变成了噩梦,在我完成之前就变成了 200 行。

另一个障碍是我只能逐行打印。因此,如果地图的高度为 50 个字符,我必须在 50 行上有 player.hear(),我并不反对。在发布答案之前请记住这一点。

我对格式也不挑剔。我只是想要一个“一目了然的地图”来帮助玩家环游世界。

谢谢各位。我希望我提供了足够的信息。让我知道,如果没有。 (这是我引用的整个(未完成和可怕的)模块的链接。Map.py

【问题讨论】:

  • "因此,如果地图的高度为 50 个字符,我必须将 player.hear() 放在 50 行上,我不反对。"但你应该是!
  • 为了简洁起见,我忽略了我确实反对它,理论上我可以做一些 for 循环打印,但我的大脑很难弄清楚如何现在做这件事,因为我更关心建立工作地图,并在此之后对其进行微调。
  • 我正在撰写答案。您是否希望玩家只看到他去过的房间?
  • 这实际上是一个很酷的功能,tbo。但归根结底,这只是作为导航的辅助工具。如果可以实现历史,我不会反对。
  • 需要几分钟。

标签: python python-2.7 mud online-game


【解决方案1】:

这段代码有严重的问题。让我们从头开始设计。这有望成为如何设计和构建类和数据结构的一个很好的教训。

首先,您应该围绕Map 类组织代码,然后将您的房间表示为网格。您不应考虑“房间 1”、“房间 2”等(在地图上很难跟踪),而应根据坐标考虑房间。

现在,我们在开始时会忽略一些可能的特征,包括玩家只能看到他去过的房间、留在地图中心的玩家以及对角线路径。如果您想要它们,您可以稍后在基本功能正常工作后将它们放入。目前,我们的目标是看起来有点像这样:

[ ]-[u] [ ] [ ]
 |
[ ]-[ ]-[ ] [ ]
     |
[ ]-[ ]-[ ] [ ]
 |
[ ]-[ ]-[ ]-[ ]

也就是说,我们将其表示为一个网格,其中一些房间相连,而另一些房间则没有。让我们让每个房间都有一个坐标对,有点像这样:

      0   1   2   3
   0 [ ]-[u] [ ] [ ]
      |
   1 [ ]-[ ]-[ ] [ ]
          |
   2 [ ]-[ ]-[ ] [ ]
      |
   3 [ ]-[ ]-[ ]-[ ]

让 x 位于顶部,y 位于侧面。左上角是(0, 0),里面有[u]的是(0, 1)。

现在,Map 类的组件是什么?

  1. 地图高度:整数

  2. 地图宽度:整数)

  3. player_x, player_y:玩家坐标

  4. 可能的路径:我们可以在它们之间移动的成对房间列表。上面的地图将表示为:

    [((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_mapmove 和一个初始化程序。初始化很简单:只需设置我们上面列出的四个属性:

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,您会看到它展开得很好。

ETA:我应该注意到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)])

但这有点激烈。

【讨论】:

  • 不错的迷你教程!当你说你在处理这个案子时,我停止了自己的工作。我可能已经将方向抽象到move 中的名称和 (dx, dy) 对的字典中,尽管:4 是边界,但添加对角线会使它成为 8..
  • sys.stdout.write 可以在 MUD 客户端上工作吗?该游戏旨在仅逐行打印。我也没有在这个答案中看到房间的出口字典。这很关键,因为它不是地图功能,而是游戏如何链接对象。基本上,如果不重写我的整个游戏,我就无法解决这个问题。不幸的是,我看不到如何实现您提供的内容。您愿意解释一下如何做到这一点吗?
  • @jtsmith1287:我刚刚发布的 print_map 的较短版本不需要 sys.stdout(无论如何通过逐步构建字符串很容易解决)
  • 至于房间:您真的应该为您的房间使用坐标系。使用字典将房间映射到房间正是您绘制地图如此头痛的原因。例如,如果您只有一个字典映射房间到房间,您最终可能会遇到非法循环。通过绘制地图和填充房间来设计游戏不是比计算每个游戏与之前所有游戏的关系更简单吗?
  • 你是对的,当然。不幸的是,当我们开始这个项目时,我不知道任何编程并且是在捎带一个合作者。有一张小地图会很不错,但并不像重写我们整个游戏的功能那么重要。我道歉。我应该更清楚地知道字典出口是必须的。不过,您的回答很棒,我正在绞尽脑汁思考如何实施。
【解决方案2】:

我这样做是为了让房间和网格“打印自己”。我将此添加到讨论中,因为最终使用更大的网格可能更容易实现。

ASCII 映射

+++++++++++++++
+++++++++++++++
+++++++++++++++
++++++   ++++++
++++++ 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 会很好地显示出来。

本系统的优点:

  • 轻松添加和删除房间
  • 房间的定义是人类可读的
  • 输出函数过载__str__,因此房间和网格“打印自己”,这可能对未来的调试或适应未来的格式有用,例如作为 HTML 表格中的单元格。

【讨论】:

    【解决方案3】:

    基于坐标的地图有很多优势,但是考虑到很多优质的mud使用传统的基于房间的世界,并且人们已经为许多mud和mud客户端制作了自动映射器,因此为a制作自动贴图并非不可能。没有坐标的泥。您只需要根据具体情况处理冲突。

    但是,您仍然可以使用@david-robinson 的答案。你想要做的是保持一个小地图大致以玩家为中心,并使用出口数据动态更新它。不要试图保存整个区域的地图;通过动态更新,您将避免一些地理冲突。

    要将地图写入泥客户端,您需要做的就是将地图行以适当的间距写入并用新行终止。您将所有地图线放入一个列表中,以便将其作为一组线发送(您不希望在地图的线之间插入一些其他线,例如当它发送出套接字时),并且任何泥客户端都会打印它正确(当然使用等宽字体)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-12-16
      • 1970-01-01
      • 2019-04-04
      • 2013-10-09
      • 2019-02-03
      • 1970-01-01
      • 2021-08-14
      • 1970-01-01
      相关资源
      最近更新 更多