我正在尝试制作一个基于文本的冒险游戏,我对传递对象和类以使其能够在一个房间之间移动而感到非常困惑。我弄乱了我的代码,我只是开始学习python。
我只尝试了一个变量字典,类本身,由于无法调用它们的属性而不断出错
问题是我希望能够将冒险家的位置更改为起始房间的“西”,然后在前后移动房间时能够使用该位置来查询世界词典。 / p>
destination = world[character.location].east
character.location = destination
这不起作用^
它不会更改character.location
import os
import time
import sys
class Adventurer:
def __init__(self, starting):
self.name = ''
self.inv = []
self.location = starting
self.game_over = False
class Item:
def __init__(self, name, info, location):
self.name = name
self.info = info
self.location = world[location]
class Room:
def __init__(self, name, description, north, south, east, west, roominv):
self.name = name
self.description = description
self.north = north
self.south = south
self.east = east
self.west = west
self.roominv = roominv
def prompt():
print("\n" + "=============================")
print("Please enter a command")
command = input("> ")
available_commands = ['east', 'west', 'look']
while command.lower() not in available_commands:
print("Unknown command, if you need help with commands try 'help'" + "\n")
command = input("> ")
if(command.lower() == 'quit'):
sys.exit()
elif(command.lower() in ['west', 'go west', 'w']):
if move_check(world[character.location].west):
destination = world[character.location].west
movement_handler(destination)
elif(command.lower() in ['east', 'go east', 'e']):
if move_check(world[character.location].east):
destination = world[character.location].east
movement_handler(destination)
elif(command.lower() == 'look'):
print(world[character.location].name)
def move_check(direction):
if direction == 'NA':
print("You cannot go this direction.\n")
return False
else:
return True
def movement_handler(destination):
print("You head to the " + destination + ".")
character.location = destination
def main_game_loop():
while character.game_over is False:
prompt()
def start_game():
os.system('cls')
question1 = ("What would you like to name your character?\n")
message_write(question1)
character_name = input("> ")
character.name = character_name
welcome = "Welcome " + character_name + "\n"
message_write(welcome)
speech1 = "You awake in a dark room with no memory of how you got there.\n"
speech2 = "You must find a way out or be stuck forever.\n"
message_write(speech1)
message_write(speech2)
time.sleep(1)
os.system('cls')
print("#############")
print("# START #")
print("#############")
print('\n')
main_game_loop()
def message_write(message):
for char in message:
sys.stdout.write(char)
sys.stdout.flush()
time.sleep(0.01)
starting = ''
candle = ''
world = {
starting: Room("Starting Room", "You are in a dark room with nothing around", "NA", "NA", "NA", 'candle', []),
candle: Room("Candle Room", "It is cold but there are unlit candles in the corner of the room", "NA", "NA", 'starting', "NA", [])
}
character = Adventurer(starting)
start_game()
答案 0 :(得分:1)
首先,一个明显的错误。
可能就是您遇到的问题> look
Traceback (most recent call last):
[ ... traceback omitted ... ]
print(world[character.location].name)
KeyError: 'starting'
这里发生了什么
starting = ''
candle = ''
world = {
starting: Room("Starting Room", "You are in a dark room with nothing around", "NA", "NA", "NA", 'candle', []),
candle: Room("Candle Room", "It is cold but there are unlit candles in the corner of the room", "NA", "NA", 'starting', "NA", [])
}
您在这里认为world
将是一个dict
,其中每个值都是一个房间,而该值的键是房间的标识符。这是有道理的……但是,尽管您希望标识符是字符串"starting"
,但您可以设置一个变量 starting
,其值为空字符串。
dict
对象的每个 one 值具有一个键,因此,由于starting
和candle
变量都是相同的值,空字符串,则实际上不能将它们都存储在同一键(即''
)下。
当您尝试引用房间world["candle"]
或world["starting"]
时发生错误,因为两者均未定义。仅定义world[""]
-两次-但这对您没有任何好处。您已经知道每个房间都需要唯一的名称,这样才能正常工作。
您想要的是文字标识符作为值:
world = {
"starting": Room("Starting Room", "You are in a dark room with nothing around", "NA", "NA", "NA", 'candle', []),
"candle": Room("Candle Room", "It is cold but there are unlit candles in the corner of the room", "NA", "NA", 'starting', "NA", [])
}
然后在下面再次
character = Adventurer(starting)
的计算结果为Adventurer("")
。你想要
character = Adventurer("starting").
如果愿意,可以将字符串保留在变量中;但是随后您必须将变量starting
设置为字符串"starting"
:
starting = "starting"
...
character = Adventurer(starting)
让我们深入探讨您问题中更有趣的部分。
我对传递对象和类感到非常困惑 能够从一个房间移到另一个房间等等。我弄乱了我的代码
您已经在使用类来创建变量的“所有权”。一个“房间”由其name
,description
,north
,south
,east
,west
和roominv
组成) 。您可能还说它“封装”或“包装”了所有数据,甚至“拥有”了数据。
所有权是我要探讨的观点,因为我想问一个问题:谁在类的外部定义函数中拥有代码的逻辑?类的定义只是变量的赋值。代码的所有命令部分都在用于构造代码的类的函数prompt
,start_game
等中 outside 中定义。因此,您觉得您的代码是“混乱”的;它是!尽管数据组织得很好,但是您的逻辑实际上并没有太多组织。
问题是,哪个类应该“拥有”什么逻辑?这是面向对象编程的有趣和棘手的部分-将程序的代码和数据组织成优雅地表达算法逻辑组织的类。如果您拥有所有权,那么您就不必绕过太多状态;您将能够利用所调用方法的类的所有权来隐式提供代码操作的上下文。
您的课程目前很有意义:
Room
知道其中的内容,并通过有选择地将ID存储到“东”,“西”等下的每个房间来“链接”到其他房间。Item
知道其名称,信息和位置; Adventurer
知道其名称,位置,库存和游戏结束标志但是我们也准备做一些观察:
谁拥有商品位置,Item
或Room
?显然Room
拥有其物品,因为Item
在房间中没有其他物品的清单。也许Item
根本不需要引用其位置。也许如果要与某个位置进行交互,那么它将拥有如何与某个位置进行交互的 logic ,而在发生交互时会被传递给它?现在,我们正在考虑谁除了变量之外还拥有逻辑。
Room
,Item
和Adventurer
是什么联系在一起?您是否受益于对象来表示您的Game
或World
?
我并不是建议您一定要尝试将每个函数放在一个类中-write_message
并不能完全适合您的范例,您在尝试推入该函数中也没有收获某处。
prompt
,但是,必须重新组织。它的名称意味着它从用户那里收集了预期的操作-但实际上它也处理该操作并将其应用于整个世界。如果您看一下这段代码:
available_commands = ['east', 'west', 'look']
while command.lower() not in available_commands:
print("Unknown command, if you need help with commands try 'help'" + "\n")
command = input("> ")
if(command.lower() == 'quit'):
sys.exit()
elif(command.lower() in ['west', 'go west', 'w']):
if move_check(world[character.location].west):
destination = world[character.location].west
movement_handler(destination)
elif(command.lower() in ['east', 'go east', 'e']):
if move_check(world[character.location].east):
destination = world[character.location].east
movement_handler(destination)
elif(command.lower() == 'look'):
print(world[character.location].name)
所有逻辑基本上都集中在确定给定 user input 字符串使用哪个 class属性方向上。换句话说,即使在模型中名称实际上是任意的,并且输入作为“#”输入,您也要竭尽全力为east
,west
等使用类属性。文字字串。为什么不像您对房间ID那样仅在方向上使用字符串标识符?每个房间可以有一个dict
的方向(现在不必是基本的,也可以是“梯子向上”,“溜槽”等)和下一个room
。
我建议的一般模式是“映射数据中的关系,而不是代码”。每当您发现自己遍历选项并希望将相同的代码应用于每个选项时,您就需要考虑这一点。
让我们详细介绍一下。
class Room:
def __init__(self, name, description, directions, roominv):
self.name = name
self.description = description
self.directions = directions
self.roominv = roominv
def move_check(self, direction):
if direction in self.directions.keys():
return True
print("You cannot go this direction.\n")
return False
def prompt():
print("\n" + "=============================")
print("Please enter a command")
# prompt knows about these commands
available_commands = ['quit','look']
# current room knows about these commands
available_commands.extend(world[character.location].directions.keys())
command = input("> ").lower()
if command not in available_commands:
print(f"Unknown command, try one of {available_commands}", "\n", "if you need help with commands try 'help'", "\n")
return
if command.lower() == 'quit':
sys.exit()
elif command == "look":
print(world[character.location].name)
elif command in world[character.location].directions.keys():
if world[character.location].move_check(command):
destination = world[character.location].directions[command]
movement_handler(destination)
world = {
"starting": Room("Starting Room", "You are in a dark room with nothing around", { "west": "candle" }, []),
"candle": Room("Candle Room", "It is cold but there are unlit candles in the corner of the room", { "east": "starting" }, [])
}
我刚刚取出了硬编码的属性Room.east
等,取而代之的是指示房间名称的指示。现在,我不再使用硬编码字符串输入和类属性之间的全部关系,而只是使用字符串作为Room.directions
的键。我将当前房间的direction.keys()
添加到可用命令列表中。
main_game_loop()
已经循环,因此在输入有效命令之前,无需将prompt()
与循环逻辑混淆。而且,我不再需要遍历硬编码的方向来匹配属性。
我只打一次lower()
,这进一步简化了事情。很容易看到您调用lower()
背后的逻辑,但是它在每行中的出现仍然掩盖了代码流背后更重要的逻辑。
此版本采用与原始代码相同的输入集,但这仅是因为您尚未向available_commands
添加别名“ go west” /“ w”。在这种更一般的情况下,决定如何别名将有助于您的代码组织。 (谁拥有别名?房间?方向?诸如“ go "direction"
之类的启发式方法总是与“方向”相匹配?Room.directions
字典甚至是如何做到最好的方法?)。
我正在以一种使我能够执行通用动作而不是硬编码动作的方式对数据建模。这使我可以根据必要的输入和输出来考虑代码,以做出“我们要去哪个房间”这样的决定。这有助于我决定如何组织代码以实现可扩展性。
我开始定义一些代码所有权。不难相信move_check
将“属于” Room
。也许随着代码添加功能,您会发现Room
本身不能确定是否可以进行移动,但是它肯定会成为等式的一部分。同时,我们对代码进行了更好的组织。
但是你可以走得更远。 look
和quit
命令呢?他们自己?谁拥有"look"
与获得Room.description
之间的关系?这取决于可能影响流量的因素。例如,如果Adventurer
具有提供照明的Item
,则可能look
是对Adventurer
所做的事情的引用,而Illumination
是对{{ 1}}可能会执行Item
要检查的事情。 Adventurer
是quit
可能会做的事情。
您可以Game
处理与特定地点相关的所有available_commands.extend(source)
条命令,例如source
的{{1}}命令或其他动作,房间的{{1 }}等
必须在Adventurer
中实现每个此类关系是笨拙且不可扩展的。但是,如果您以不同的方式对这些关系进行建模,则会出现更优雅,更可扩展的游戏引擎循环。很快use
无需更改即可实现新功能,这些功能在directions.keys()
,prompt
和prompt
的上下文中很容易表达。
但是在您走得更远之前,您必须重新考虑用户输入的机制。 Adventurer
是一个单字命令,但是如何使用项目将取决于您要在 on 上使用它。如果您愿意将命令“ west”强制变为“ gowest”,则Item
的工作方式相同。突然我们有了一个通用模式“”。我们可以将其余的“”传递给Room
的“ go”动作处理程序,以使Adventurer拥有如何use
在某处的逻辑。 go
通常可以提供Adventurer
,go
,Adventurer
等可以完成的操作的当前列表,并且可以拥有在活动项目的上下文中如何执行该操作,位置等。
您可以进一步将该过程的一部分委派给各个组件。如果蜡烛点燃,那么火炬可能会更好,头灯最好。您将希望go
拥有决定过程,以免use
必须了解每种类型的照明,就像look
必须了解每个潜在的方向一样。我猜想描述这种所有权流动将是您面临的下一个大型游戏挑战。描述chanin of responsibility的设计模式对于这篇文章来说太多了,但这就是我可能用来处理所有不同相关组件对一般游戏动作的“分层”处理的方式。
这应该揭示将逻辑与数据一起组织以更好地组织代码的新方法。
最后,尽管有一些见解,我还是要忽略它:
Item
在Mac上,就像在Linux或Windows以外的任何其他操作系统上一样,您的程序会将其吐出而不是清除屏幕。您可能会问自己,清除屏幕对您来说真的很重要吗?我喜欢在控制台历史记录中滚动的功能。在这样的基于文本的游戏中,不清除屏幕可能是有利的。无论如何,我得到了该错误消息,现在没有清晰的屏幕。
我想在下面包括该程序的新代码和示例输出:
Adventurer
prompt