Python描画ASCIIマップ
-
13-12-2019 - |
質問
私はpythonで構築している泥の中で、プレイヤーの現在の部屋から2つの半径のマップを描画する必要があります(可能であればそれ以上)。部屋はaの容器としてセットアップされます self.exits = {'west':1, 'north':2}
ここで、キーは、値(隣接する部屋のUID)が配置されている方向です。部屋はこの方法でのみリンクされています。自己を持つプレイヤー。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つの異なる方向(対角線、上下を含まない)すべてでこの作業を行うことができました。しかし、最初のforループで解析した部屋をforループし、それらを描画してからすべてのスペースを空けてから、(sp)エースの重複を考慮に入れる必要があります。この小さなタスクはすぐに悪夢に変わり、私が完了する前に200行になりました。
もう一つのハードルは、私は行単位でしか印刷できないということです。したがって、マップの高さが50文字の場合、私は持っている必要があります player.hear()
私は反対していない50行で。答えを投稿する前に、それを覚えておいてください。
私はフォーマットについてもうるさいわけではありません。私は単に世界中を旅しながらプレイヤーを支援するために"一目で地図"をしたいだけです。
ありがとうみんな。私は十分な情報を提供したことを願っています。そうでない場合は、私に知らせてください。(ここに私が参照している(未完成で恐ろしい)モジュール全体へのリンクがあります。 Map.py
解決
このコードは深刻な問題を抱えています。ゼロからデザインを始めましょう。これは、クラスとデータ構造を設計および構築する方法の良い教訓として役立つことを願っています。
まず、コードをaの周りに整理する必要があります Map
クラスは、グリッドとしてあなたの部屋を表します。「部屋1」、「部屋2」など(地図上で追跡するのは非常に難しい)について考えるべきではなく、座標の観点から部屋を考えるべきです。
今、私たちは最初に無視しているいくつかの可能な機能があります,彼が行ったことがある部屋だけを見てプレイヤーを含む,マップの中心に残っていあなたがそれらをしたい場合は、基本的な機能が動作したら、後でそれらを置くことができます。今のところ、私たちは少しこのように見えるものを目指しています:
[ ]-[u] [ ] [ ]
|
[ ]-[ ]-[ ] [ ]
|
[ ]-[ ]-[ ] [ ]
|
[ ]-[ ]-[ ]-[ ]
つまり、一部の部屋が接続されていて、他の部屋が接続されていないグリッドとして表現しています。各部屋にこのような座標ペアを持たせましょう:
0 1 2 3
0 [ ]-[u] [ ] [ ]
|
1 [ ]-[ ]-[ ] [ ]
|
2 [ ]-[ ]-[ ] [ ]
|
3 [ ]-[ ]-[ ]-[ ]
Xを上に沿って、yを横に沿ってとします。左上は(0,0)で、 [u]
その中には(0、1)があります。
さて、私たちのコンポーネントは何ですか Map
クラス?
マップの高さ:整数
マップ幅:整数)
player_x,player_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
この状況では、文字列の後に改行またはスペースが自動的に配置されるためです。代わりに、私たちは使用します シスィス...標準出力。書き込み.
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)])
しかし、これはもう少し激しいです。
他のヒント
私は室とグリッドが「印刷する」という運動としてこれをしました。最終的に大きなグリッドを使用して実装が容易である場合があるため、これを議論に追加します。
ASCIIマップ
+++++++++++++++
+++++++++++++++
+++++++++++++++
++++++ ++++++
++++++ 2 ++++++
++++++/| ++++++
+++ / | ++++++
+++ 3--1 ++++++
+++ \++++++
+++++++++\ +++
+++++++++ 4 +++
+++++++++ +++
+++++++++++++++
+++++++++++++++
+++++++++++++++
.
このグリッド内の各セルは、 '+'標識の3つのセットです。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テーブル内のセルとして。
座標ベースの地図には多くの利点がありますが、多くの品質泥が伝統的な部屋ベースの世界を使用し、人々は多くの泥や泥の顧客のために自動的なものを作りました。座標のない泥。ケースベースで競合を扱う必要があります。
しかし、あなたはまだ@ David-Robinsonによって答えを使うことができます。あなたがやりたいことは、最低限のプレーヤーを中心とし、出口データを使って動的に更新されます。格納されているエリア全体のマップを保持しようとしないでください。動的に更新することで、地理的な競合を避けます。
マップクライアントにマップを書くためには、あなたがする必要があるのはあなたの地図ラインを正しく間隔をあけて新しい行で終わって書き込むことです。それが単一の行のグループとして送信されるようにすべてのマップ行をリストに入れます(たとえば、Socketの送信時にマップの行の行の間に他の行の中に挿入されたくない)、任意のMUDクライアントが印刷されます。正しく(もちろんモノスペースフォントで)。