Diseño basado en datos
Envié algo como esta pregunta a la revisión de código recientemente.
Después de algunas sugerencias y mejoras, el resultado fue un código simple que permitiría cierta flexibilidad relativa en la creación de armas basada en un diccionario (o JSON). Los datos se interpretan en tiempo de ejecución y la Weapon
clase misma realiza verificaciones simples , sin la necesidad de contar con un intérprete de script completo.
El diseño basado en datos, a pesar de que Python es un lenguaje interpretado (tanto los archivos de origen como los de datos se pueden editar sin la necesidad de volver a compilarlos), parece ser lo correcto en casos como el que usted presentó. Esta pregunta entra en más detalles sobre el concepto, sus pros y sus contras. También hay una buena presentación sobre la Universidad de Cornell al respecto.
En comparación con otros lenguajes, como C ++, que probablemente usarían un lenguaje de secuencias de comandos (como LUA) para manejar la interacción de datos x el motor y las secuencias de comandos en general, y un cierto formato de datos (como XML) para almacenar los datos, Python realmente puede hacer todo por sí mismo (considerando el estándar dict
pero también weakref
, este último específicamente para la carga de recursos y el almacenamiento en caché).
Sin embargo, un desarrollador independiente no puede llevar el enfoque basado en datos al extremo como se sugiere en este artículo :
¿Qué tanto sobre el diseño basado en datos soy yo? No creo que un motor de juego deba contener una sola línea de código específico del juego. Ni uno. No hay tipos de armas codificadas. No hay diseño HUD codificado. Sin unidad codificada AI. Nada Cremallera. Zilch
Tal vez, con Python, uno podría beneficiarse de lo mejor del enfoque orientado a objetos y basado en datos, con el objetivo de la productividad y la extensibilidad.
Procesamiento simple de muestras
En el caso específico discutido en la revisión de código, un diccionario almacenaría tanto los "atributos estáticos" como la lógica a interpretar, en caso de que el arma tenga un comportamiento condicional.
En el ejemplo a continuación, una espada debe tener algunas habilidades y estadísticas en manos de los personajes de la clase 'antipaladin', y ningún efecto, con estadísticas más bajas cuando son utilizadas por otros personajes):
WEAPONS = {
"bastard's sting": {
# magic enhancement, weight, value, dmg, and other attributes would go here.
"magic": 2,
# Those lists would contain the name of effects the weapon provides by default.
# They are empty because, in this example, the effects are only available in a
# specific condition.
"on_turn_actions": [],
"on_hit_actions": [],
"on_equip": [
{
"type": "check",
"condition": {
'object': 'owner',
'attribute': 'char_class',
'value': "antipaladin"
},
True: [
{
"type": "action",
"action": "add_to",
"args": {
"category": "on_hit",
"actions": ["unholy"]
}
},
{
"type": "action",
"action": "add_to",
"args": {
"category": "on_turn",
"actions": ["unholy aurea"]
}
},
{
"type": "action",
"action": "set_attribute",
"args": {
"field": "magic",
"value": 5
}
}
],
False: [
{
"type": "action",
"action": "set_attribute",
"args": {
"field": "magic",
"value": 2
}
}
]
}
],
"on_unequip": [
{
"type": "action",
"action": "remove_from",
"args": {
"category": "on_hit",
"actions": ["unholy"]
},
},
{
"type": "action",
"action": "remove_from",
"args": {
"category": "on_turn",
"actions": ["unholy aurea"]
},
},
{
"type": "action",
"action": "set_attribute",
"args": ["magic", 2]
}
]
}
}
Para fines de prueba, creé simples Player
y Weapon
clases: el primero en sostener / equipar el arma (llamando así a su configuración condicional on_equip) y el último como una sola clase que recuperaría los datos del diccionario, en función del nombre del elemento pasado como un argumento durante la Weapon
inicialización. No reflejan el diseño adecuado de las clases de juego, pero aún pueden ser útiles para probar los datos:
class Player:
"""Represent the player character."""
inventory = []
def __init__(self, char_class):
"""For this example, we just store the class on the instance."""
self.char_class = char_class
def pick_up(self, item):
"""Pick an object, put in inventory, set its owner."""
self.inventory.append(item)
item.owner = self
class Weapon:
"""A type of item that can be equipped/used to attack."""
equipped = False
action_lists = {
"on_hit": "on_hit_actions",
"on_turn": "on_turn_actions",
}
def __init__(self, template):
"""Set the parameters based on a template."""
self.__dict__.update(WEAPONS[template])
def toggle_equip(self):
"""Set item status and call its equip/unequip functions."""
if self.equipped:
self.equipped = False
actions = self.on_unequip
else:
self.equipped = True
actions = self.on_equip
for action in actions:
if action['type'] == "check":
self.check(action)
elif action['type'] == "action":
self.action(action)
def check(self, dic):
"""Check a condition and call an action according to it."""
obj = getattr(self, dic['condition']['object'])
compared_att = getattr(obj, dic['condition']['attribute'])
value = dic['condition']['value']
result = compared_att == value
self.action(*dic[result])
def action(self, *dicts):
"""Perform action with args, both specified on dicts."""
for dic in dicts:
act = getattr(self, dic['action'])
args = dic['args']
if isinstance(args, list):
act(*args)
elif isinstance(args, dict):
act(**args)
def set_attribute(self, field, value):
"""Set the specified field with the given value."""
setattr(self, field, value)
def add_to(self, category, actions):
"""Add one or more actions to the category's list."""
action_list = getattr(self, self.action_lists[category])
for action in actions:
if action not in action_list:
action_list.append(action)
def remove_from(self, category, actions):
"""Remove one or more actions from the category's list."""
action_list = getattr(self, self.action_lists[category])
for action in actions:
if action in action_list:
action_list.remove(action)
Con alguna mejora futura, espero que esto me permita tener un sistema de fabricación dinámico algún día, procesando componentes de armas en lugar de armas enteras ...
Prueba
- El personaje A escoge un arma, la equipa (imprimimos sus estadísticas) y luego la suelta;
- El personaje B elige la misma arma, la equipa (e imprimimos sus estadísticas nuevamente para mostrar en qué se diferencian).
Me gusta esto:
def test():
"""A simple test.
Item features should be printed differently for each player.
"""
weapon = Weapon("bastard's sting")
player1 = Player("bard")
player1.pick_up(weapon)
weapon.toggle_equip()
print("Enhancement: {}, Hit effects: {}, Other effects: {}".format(
weapon.magic, weapon.on_hit_actions, weapon.on_turn_actions))
weapon.toggle_equip()
player2 = Player("antipaladin")
player2.pick_up(weapon)
weapon.toggle_equip()
print("Enhancement: {}, Hit effects: {}, Other effects: {}".format(
weapon.magic, weapon.on_hit_actions, weapon.on_turn_actions))
if __name__ == '__main__':
test()
Debería imprimir:
Para un bardo
Mejora: 2, Efectos de golpe: [], Otros efectos: []
Para un antipaladin
Mejora: 5, Efectos de golpe: ['impío'], Otros efectos: ['aurea impía']