![]() |
OOP vs functional - - elaborate turn based RPG game (Derek Banas Udemy course again) - Printable Version +- Python Forum (https://python-forum.io) +-- Forum: Python Coding (https://python-forum.io/forum-7.html) +--- Forum: General Coding Help (https://python-forum.io/forum-8.html) +--- Thread: OOP vs functional - - elaborate turn based RPG game (Derek Banas Udemy course again) (/thread-32877.html) |
OOP vs functional - - elaborate turn based RPG game (Derek Banas Udemy course again) - Drone4four - Mar-12-2021 I’m learning OOP. Derek Banas is the instructor in the online (self directed, non-credit) Udemy course titled, “Python Programming Bootcamp” that I am taking. He demonstrates OOP well with a CLI game where two warriors with 100 health take turns attacking each other, reducing health by 20-50 points at each turn. The first warrior that reaches 0 health, loses. I am able to follow along and understand the basic principles of OOP demonstrated by the instructor. But for the fun of it and as an exercise, I decided to port the script from class methods/attributes to functions/variables as a personal challenge. My script works but only sometimes. Here is some sample output: Quote:$ python script.py Above, that output is expected. However below, the output is wrong: Quote:$ python script.py This is wrong because Ironman’s health is less than Loki’s yet the winner is declared to be Ironman. How might you people alter my script to better account for this discrepancy? Here is my script: import random import math health = 100 attack_max = 50 first_warrior = "Ironman" second_warrior = "Loki" def attack(attack_max): attack_amount = attack_max * (random.random() +.5) return attack_amount def fight(first_warrior_health, second_warrior_health): while True: warrior_A_attack_amount = attack(attack_max) warrior_B_attack_amount = attack(attack_max) damage_to_warrior_B = math.ceil(warrior_A_attack_amount) damage_to_warrior_A = math.ceil(warrior_B_attack_amount) first_warrior_health = health - damage_to_warrior_A second_warrior_health = health - damage_to_warrior_B first_warrior_health = first_warrior_health - damage_to_warrior_A second_warrior_health = second_warrior_health - damage_to_warrior_B print(f"{first_warrior}'s health is: {first_warrior_health}") print(f"{second_warrior}'s health is: {second_warrior_health}") if second_warrior_health <= 0: print(f"The winner is: {first_warrior} with {first_warrior_health} health remaining") break elif first_warrior_health <= 0: print(f"The winner is: {second_warrior} with {second_warrior_health} health remaining") break def main(): fight(100, 100) main()I see that the instructor’s OOP demo (below) is more dynamic and feature rich. It’s easier to read and follow along too. OOP is clearly the better way to go in this situation. But how might you people improve my attempt at implementing this game via functions/variables (above)? Here is the instructor’s original: ''' This original script was provided by Derek Banas in his Udemy course in Section 19 - Classes and Objects. Derek provided two scripts, demonstrating how to model classes of a video game. I have ventured to re-write the functionality of this OOP script but using functions/variables instead of class methods/objects ''' # We will create classes for both a Warrior and a Battle class # The Warrior class will simulate both the attributes and capabilities of a Warrior # The Battle class will however simulate the actions that occur in a battle such as starting the fight and getting the results import random import math # Warriors will have names, health, and attack and block maximums # They will have the capabilities to attack and block random amounts class Warrior: def __init__(self, name="warrior", health=0, attk_max=0, block_max=0): self.name = name self.health = health self.attk_max = attk_max self.block_max = block_max def attack(self): # Randomly calculate the attack amount # random() returns a value from 0.0 to 1.0 attk_amt = self.attk_max * (random.random() + .5) return attk_amt def block(self): # Randomly calculate how much of the attack was blocked block_amt = self.block_max * (random.random() + .5) return block_amt # The Battle class will have the capability to loop until 1 Warrior dies # The Warriors will each get a turn to attack each turn class Battle: def start_fight(self, warrior1, warrior2): # Continue looping until a Warrior dies switching back and # forth as the Warriors attack each other while True: if self.get_attack_result(warrior1, warrior2) == "Game Over": print("Game Over") break if self.get_attack_result(warrior2, warrior1) == "Game Over": print("Game Over") break # A function will receive each Warrior that will attack the other # Have the attack and block amounts be integers to make the results clean # Output the results of the fight as it goes # If a Warrior dies return that result to end the looping in the # above function # Make this method static because we don't need to use self @staticmethod def get_attack_result(warriorA, warriorB): warrior_a_attk_amt = warriorA.attack() warrior_b_block_amt = warriorB.block() damage_2_warrior_b = math.ceil(warrior_a_attk_amt - warrior_b_block_amt) warriorB.health = warriorB.health - damage_2_warrior_b print("{} attacks {} and deals {} damage".format(warriorA.name, warriorB.name, damage_2_warrior_b)) print("{} is down to {} health".format(warriorB.name,warriorB.health)) if warriorB.health <= 0: print("{} has Died and {} is Victorious".format(warriorB.name, warriorA.name)) return "Game Over" else: return "Fight Again" def main(): # Create 2 Warriors Batman = Warrior("Batman", 100, 40, 10) Joker = Warrior("Joker", 100, 40, 10) # Create Battle object battle = Battle() # Initiate Battle battle.start_fight(Batman, Joker) main()Here is some output from running the instructor's scritp: Quote:› python re-written-v1.py RE: OOP vs functional - - elaborate turn based RPG game (Derek Banas Udemy course again) - deanhystad - Mar-13-2021 You assume a dead warrior still has an attack. There may be other problems, but that is a big one. I would randomly choose which warrior goes first, otherwise one is always at a distinct disadvantage RE: OOP vs functional - - elaborate turn based RPG game (Derek Banas Udemy course again) - ndc85430 - Mar-13-2021 Note that functional programming isn't just about programming with functions. There are a whole bunch of other ideas - immutability, pure functions, referential transparency being some of them. Kelvin Henney has some talks on these things (they tend to be in other languages, but it's the ideas that are important): https://youtu.be/lNKXTlCOGEc https://youtu.be/NSzsYWckGd4 In case you're interested in how one does error handling in a functional style, I wrote a post about it here: https://python-forum.io/Thread-returning-an-error-code?pid=138294#pid138294. RE: OOP vs functional - - elaborate turn based RPG game (Derek Banas Udemy course again) - BashBedlam - Mar-13-2021 One way to go about it would be to make each warrior a dictionary. import random import math def get_attack_result (warriorA, warriorB) : warrior_a_attk_amt = warriorA ['attk_max'] * (random.random () + .5) warrior_b_block_amt = warriorB ['block_max'] * (random.random () + .5) damage_2_warrior_b = math.ceil(warrior_a_attk_amt - warrior_b_block_amt) warriorB ['health'] = warriorB ['health'] - damage_2_warrior_b print("{} attacks {} and deals {} damage" .format (warriorA ['name'], warriorB ['name'], damage_2_warrior_b)) print("{} is down to {} health" .format (warriorB ['name'], warriorB ['health'])) if warriorB ['health'] <= 0: print("{} has Died and {} is Victorious" .format (warriorB ['name'], warriorA ['name'])) return "Game Over" else: return "Fight Again" player_one = { 'name': 'Ironman', 'health': 100, 'attk_max': 40, 'block_max': 10 } player_two = { 'name': 'Loki', 'health': 100, 'attk_max': 40, 'block_max': 10 } while True: if get_attack_result(player_one, player_two) == "Game Over": print("Game Over") break if get_attack_result(player_two, player_one) == "Game Over": print("Game Over") break RE: OOP vs functional - - elaborate turn based RPG game (Derek Banas Udemy course again) - Drone4four - Mar-14-2021 Holy Smokes! Thank you, @BashBedlam, for taking the time to re-write the script. Your approach with dictionaries to model player properties is terrific. I'm going to have alotta fun playing around with your script in my Python debugger tonight. Thanks for showing a wonderful alternative! ![]() RE: OOP vs functional - - elaborate turn based RPG game (Derek Banas Udemy course again) - deanhystad - Mar-14-2021 I've worked on code that uses dictionaries where it should be using classes. I cannot stress strongly enough how bad this kind of code is to work with. Not only is it difficult to read, but it is hard to trace when dictionary values are set. A good linter will find where you made a typo in an attribute name, but I don't think any tool will catch the mistake when you set player2['Health']=80. RE: OOP vs functional - - elaborate turn based RPG game (Derek Banas Udemy course again) - ndc85430 - Mar-14-2021 collections.namedtuple is good for immutable value objects.
|