From 89318e732141f8c44798b56c3e12a87577175939 Mon Sep 17 00:00:00 2001 From: Kwarde Date: Wed, 17 Jan 2024 16:06:56 +0100 Subject: [PATCH 01/10] Convert spell objects to a dictionary --- spells.py | 53 ++++++++++++++++++++++++----------------------------- 1 file changed, 24 insertions(+), 29 deletions(-) diff --git a/spells.py b/spells.py index 83a1bbb..ac70f27 100644 --- a/spells.py +++ b/spells.py @@ -22,11 +22,8 @@ __INVALID_SPELL = ".@wizardduel@__spell_invalid__" #Internal usage only ## Spell class ## class Spell: - spellList = [] - #Class special methods """ - name (str) Name of the spell as used in combat speed (int) Speed of the spell. This will determine what spell is casted first. damage (int) Damage that the spell does. Negative damage takes away moves from opponent (eg -2 = cancel 2 moves of opponent) @@ -39,16 +36,13 @@ class Spell: - SPELL_TYPE_POWER: Powerful combat spell - deals alot of damage or takes away a few moves from opponent - SPELL_TYPE_UNFORGIVABLE: deals alot of damage or takes away alot of moves from opponent """ - def __init__(self, name: str, speed: int, damage: int, succes_rate: int, description: str, type: int = SPELL_TYPE_COMMON): - self.name = name + def __init__(self, speed: int, damage: int, succes_rate: int, description: str, type: int = SPELL_TYPE_COMMON): self.speed = speed self.damage = damage self.succes_rate = succes_rate self.description = description self.type = type - Spell.spellList.append(self) - def __repr__(self): return " ['{spell}']\n\t{desc}\n\tSUCCES RATE: {srate}%\tSPEED: {speed}\tDAMAGE: {dmg}".format(spell=self.name, type=type, desc=self.description, srate=self.succes_rate, speed=self.speed, dmg=self.damage) @@ -61,38 +55,39 @@ class Spell: ## ## Spells ## -# Useless spells - These don't do anything useful in combat -spell_lumos = Spell("Lumos", 100, 000, 100, "Creates a small light at the tip of your wand", SPELL_TYPE_USELESS) -spell_nox = Spell("Nox", 100, 000, 100, "Counter spell of Lumos", SPELL_TYPE_USELESS) -spell_rennervate = Spell("Rennervate", 100, 000, 100, "Revives your opponent if they are stunned", SPELL_TYPE_USELESS) -spell_igni = Spell("Igno", 100, 000, 100, "Damages an enemy using fire. Except, this is a Witcher sign. It thus has no effect at all", SPELL_TYPE_USELESS) +spell = { + # Useless spells - These don't do anything useful in combat + "Lumos", Spell(100, 000, 100, "Creates a small light at the tip of your wand", SPELL_TYPE_USELESS), + "Nox", Spell(100, 000, 100, "Counter spell of Lumos", SPELL_TYPE_USELESS), + "Rennervate", Spell(100, 000, 100, "Revives your opponent if they are stunned", SPELL_TYPE_USELESS), + "Igni", Spell(100, 000, 100, "Damages an enemy using fire. Except, this is a Witcher sign. It thus has no effect at all", SPELL_TYPE_USELESS), # Defensive spell. Each cast from this category has a 5% chance of completely restoring health or 25% chance to heal 5% of maximum health -spell_finite_incantatem = Spell("Finite Incantatem", 100, 000, 45, "Cancel all effects casted upon you. If you are stunned/silenced, there's a 10% chance this spell might work", SPELL_TYPE_DEFENSE) -spell_impendimenta = Spell("Impendimenta", 94, 000, 60, "Slows your opponent. EFFECT: Decrease opponent's spell speed by 33% in their next offensive move", SPELL_TYPE_DEFENSE) -spell_lumos_solem = Spell("Lumos Solem", 94, 000, 60, "Blinds your opponent. EFFECT: Decrease opponent's spell damage by 33% in their next offensive move", SPELL_TYPE_DEFENSE) -spell_protego = Spell("Protego", 100, 000, 80, "Create a shield that blocks most attacks", SPELL_TYPE_DEFENSE) + "Finite Incantatem", Spell(100, 000, 45, "Cancel all effects casted upon you. If you are stunned/silenced, there's a 10% chance this spell might work", SPELL_TYPE_DEFENSE), + "Impendimenta", Spell(94, 000, 60, "Slows your opponent. EFFECT: Decrease opponent's spell speed by 33% in their next offensive move", SPELL_TYPE_DEFENSE), + "Lumos Solem", Spell(94, 000, 60, "Blinds your opponent. EFFECT: Decrease opponent's spell damage by 33% in their next offensive move", SPELL_TYPE_DEFENSE), + "Protego", Spell(100, 000, 80, "Create a shield that blocks most attacks", SPELL_TYPE_DEFENSE), # Common combat spells. High chance of succes, deals some damage -spell_reducto = Spell("Reducto", 75, 150, 85, "Blast an object near your opponent") -spell_rictusempra = Spell("Rictusempra", 85, 90, 90, "Causes your opponent to curl up in laughter, tiring them out") -spell_stupefy = Spell("Stupefy", 95, 75, 95, "Knock over your opponent") + "Reducto", Spell(75, 150, 85, "Blast an object near your opponent"), + "Rictusempra", Spell(85, 90, 90, "Causes your opponent to curl up in laughter, tiring them out"), + "Stupefy", Spell(95, 75, 95, "Knock over your opponent"), # Powerful combat spells. Medium chance of succes, deals more damage or stuns opponents -spell_bombarda = Spell("Bombarda", 50, 180, 75, "Creates an explosion near your opponent", SPELL_TYPE_POWERFUL) -spell_confringo = Spell("Confringo", 50, 200, 70, "Creates an explosion directly at your opponent", SPELL_TYPE_POWERFUL) -spell_mimblewimble = Spell("Mimblewimble", 50, -1, 70, "Ties a knot in your opponents tongue, causing them to be unable to cast a spell for 1 (more) move", SPELL_TYPE_POWERFUL) -spell_sectumsempra = Spell("Sectumsempra", 90, 400, 35, "Slices your opponent", SPELL_TYPE_POWERFUL) -spell_silencio = Spell("Silencio", 35, -3, 55, "Silences your opponent, causing them unable to cast spells for 3 moves. Only works if opponent is not stunned yet", SPELL_TYPE_POWERFUL) + "Bombarda", Spell(50, 180, 75, "Creates an explosion near your opponent", SPELL_TYPE_POWERFUL), + "Confringo", Spell(50, 200, 70, "Creates an explosion directly at your opponent", SPELL_TYPE_POWERFUL), + "Mimblewimble", Spell(50, -1, 70, "Ties a knot in your opponents tongue, causing them to be unable to cast a spell for 1 (more) move", SPELL_TYPE_POWERFUL), + "Sectumsempra", Spell(90, 400, 35, "Slices your opponent", SPELL_TYPE_POWERFUL), + "Silencio", Spell(35, -3, 55, "Silences your opponent, causing them unable to cast spells for 3 moves. Only works if opponent is not stunned yet", SPELL_TYPE_POWERFUL), # Unforgivable spells. Very low chance of success, instantly kills or deals alot of damage/stun amount -spell_avada_kedavra = Spell("Avada Kedavra", 999, 999, 2, "Instantly end your opponent", SPELL_TYPE_UNFORGIVABLE) -spell_crucio = Spell("Crucio", 999, 500, 5, "Cause excruciating pain to your opponent, causing alot of damage and making them unable to cast spells for 5 moves", SPELL_TYPE_UNFORGIVABLE) -spell_imperio = Spell("Imperio", 999, -1, 3, "Muddle with your opponent's mind, convincing them to stop casting spells for 10 moves", SPELL_TYPE_UNFORGIVABLE) + "Avada Kedavra", Spell(999, 999, 2, "Instantly end your opponent", SPELL_TYPE_UNFORGIVABLE), + "Crucio", Spell(999, 500, 5, "Cause excruciating pain to your opponent, causing alot of damage and making them unable to cast spells for 5 moves", SPELL_TYPE_UNFORGIVABLE), + "Imperio", Spell(999, -1, 3, "Muddle with your opponent's mind, convincing them to stop casting spells for 10 moves", SPELL_TYPE_UNFORGIVABLE), # Internal usage -spell_object_none = Spell(__INVALID_SPELL, 0, 0, 0, "(internal) invalid spell", SPELL_TYPE_NONE) -spell_object_stunned = Spell(__INVALID_SPELL, 0, 0, 0, "(internal) object when stunned", SPELL_TYPE_NONE) + __INVALID_SPELL, Spell(__INVALID_SPELL, 0, 0, 0, "(internal) invalid spell", SPELL_TYPE_NONE) +} ## ## Standalone spell functions From c138250c1c0a5eb0a344a1e6c706abc623a1b3ef Mon Sep 17 00:00:00 2001 From: Kwarde Date: Wed, 17 Jan 2024 16:14:04 +0100 Subject: [PATCH 02/10] FIX: Actually make spell dictionary. ('key': value) and not ('key', value) --- spells.py | 41 +++++++++++++------------ tmp.py | 91 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 112 insertions(+), 20 deletions(-) create mode 100644 tmp.py diff --git a/spells.py b/spells.py index ac70f27..af3007c 100644 --- a/spells.py +++ b/spells.py @@ -37,6 +37,7 @@ class Spell: - SPELL_TYPE_UNFORGIVABLE: deals alot of damage or takes away alot of moves from opponent """ def __init__(self, speed: int, damage: int, succes_rate: int, description: str, type: int = SPELL_TYPE_COMMON): + self.speed = speed self.damage = damage self.succes_rate = succes_rate @@ -57,36 +58,36 @@ class Spell: ## spell = { # Useless spells - These don't do anything useful in combat - "Lumos", Spell(100, 000, 100, "Creates a small light at the tip of your wand", SPELL_TYPE_USELESS), - "Nox", Spell(100, 000, 100, "Counter spell of Lumos", SPELL_TYPE_USELESS), - "Rennervate", Spell(100, 000, 100, "Revives your opponent if they are stunned", SPELL_TYPE_USELESS), - "Igni", Spell(100, 000, 100, "Damages an enemy using fire. Except, this is a Witcher sign. It thus has no effect at all", SPELL_TYPE_USELESS), + "Lumos": Spell(100, 000, 100, "Creates a small light at the tip of your wand", SPELL_TYPE_USELESS), + "Nox": Spell(100, 000, 100, "Counter spell of Lumos", SPELL_TYPE_USELESS), + "Rennervate": Spell(100, 000, 100, "Revives your opponent if they are stunned", SPELL_TYPE_USELESS), + "Igni": Spell(100, 000, 100, "Damages an enemy using fire. Except, this is a Witcher sign. It thus has no effect at all", SPELL_TYPE_USELESS), # Defensive spell. Each cast from this category has a 5% chance of completely restoring health or 25% chance to heal 5% of maximum health - "Finite Incantatem", Spell(100, 000, 45, "Cancel all effects casted upon you. If you are stunned/silenced, there's a 10% chance this spell might work", SPELL_TYPE_DEFENSE), - "Impendimenta", Spell(94, 000, 60, "Slows your opponent. EFFECT: Decrease opponent's spell speed by 33% in their next offensive move", SPELL_TYPE_DEFENSE), - "Lumos Solem", Spell(94, 000, 60, "Blinds your opponent. EFFECT: Decrease opponent's spell damage by 33% in their next offensive move", SPELL_TYPE_DEFENSE), - "Protego", Spell(100, 000, 80, "Create a shield that blocks most attacks", SPELL_TYPE_DEFENSE), + "Finite Incantatem": Spell(100, 000, 45, "Cancel all effects casted upon you. If you are stunned/silenced, there's a 10% chance this spell might work", SPELL_TYPE_DEFENSE), + "Impendimenta": Spell(94, 000, 60, "Slows your opponent. EFFECT: Decrease opponent's spell speed by 33% in their next offensive move", SPELL_TYPE_DEFENSE), + "Lumos Solem": Spell(94, 000, 60, "Blinds your opponent. EFFECT: Decrease opponent's spell damage by 33% in their next offensive move", SPELL_TYPE_DEFENSE), + "Protego": Spell(100, 000, 80, "Create a shield that blocks most attacks", SPELL_TYPE_DEFENSE), # Common combat spells. High chance of succes, deals some damage - "Reducto", Spell(75, 150, 85, "Blast an object near your opponent"), - "Rictusempra", Spell(85, 90, 90, "Causes your opponent to curl up in laughter, tiring them out"), - "Stupefy", Spell(95, 75, 95, "Knock over your opponent"), + "Reducto": Spell(75, 150, 85, "Blast an object near your opponent"), + "Rictusempra": Spell(85, 90, 90, "Causes your opponent to curl up in laughter, tiring them out"), + "Stupefy": Spell(95, 75, 95, "Knock over your opponent"), # Powerful combat spells. Medium chance of succes, deals more damage or stuns opponents - "Bombarda", Spell(50, 180, 75, "Creates an explosion near your opponent", SPELL_TYPE_POWERFUL), - "Confringo", Spell(50, 200, 70, "Creates an explosion directly at your opponent", SPELL_TYPE_POWERFUL), - "Mimblewimble", Spell(50, -1, 70, "Ties a knot in your opponents tongue, causing them to be unable to cast a spell for 1 (more) move", SPELL_TYPE_POWERFUL), - "Sectumsempra", Spell(90, 400, 35, "Slices your opponent", SPELL_TYPE_POWERFUL), - "Silencio", Spell(35, -3, 55, "Silences your opponent, causing them unable to cast spells for 3 moves. Only works if opponent is not stunned yet", SPELL_TYPE_POWERFUL), + "Bombarda": Spell(50, 180, 75, "Creates an explosion near your opponent", SPELL_TYPE_POWERFUL), + "Confringo": Spell(50, 200, 70, "Creates an explosion directly at your opponent", SPELL_TYPE_POWERFUL), + "Mimblewimble": Spell(50, -1, 70, "Ties a knot in your opponents tongue, causing them to be unable to cast a spell for 1 (more) move", SPELL_TYPE_POWERFUL), + "Sectumsempra": Spell(90, 400, 35, "Slices your opponent", SPELL_TYPE_POWERFUL), + "Silencio": Spell(35, -3, 55, "Silences your opponent, causing them unable to cast spells for 3 moves. Only works if opponent is not stunned yet", SPELL_TYPE_POWERFUL), # Unforgivable spells. Very low chance of success, instantly kills or deals alot of damage/stun amount - "Avada Kedavra", Spell(999, 999, 2, "Instantly end your opponent", SPELL_TYPE_UNFORGIVABLE), - "Crucio", Spell(999, 500, 5, "Cause excruciating pain to your opponent, causing alot of damage and making them unable to cast spells for 5 moves", SPELL_TYPE_UNFORGIVABLE), - "Imperio", Spell(999, -1, 3, "Muddle with your opponent's mind, convincing them to stop casting spells for 10 moves", SPELL_TYPE_UNFORGIVABLE), + "Avada Kedavra": Spell(999, 999, 2, "Instantly end your opponent", SPELL_TYPE_UNFORGIVABLE), + "Crucio": Spell(999, 500, 5, "Cause excruciating pain to your opponent, causing alot of damage and making them unable to cast spells for 5 moves", SPELL_TYPE_UNFORGIVABLE), + "Imperio": Spell(999, -1, 3, "Muddle with your opponent's mind, convincing them to stop casting spells for 10 moves", SPELL_TYPE_UNFORGIVABLE), # Internal usage - __INVALID_SPELL, Spell(__INVALID_SPELL, 0, 0, 0, "(internal) invalid spell", SPELL_TYPE_NONE) + __INVALID_SPELL: Spell(0, 0, 0, "(internal) invalid spell", SPELL_TYPE_NONE) } ## diff --git a/tmp.py b/tmp.py new file mode 100644 index 0000000..49d18e6 --- /dev/null +++ b/tmp.py @@ -0,0 +1,91 @@ +import random + +SPELL_TYPE_NONE = -1 +SPELL_TYPE_USELESS = 0 +SPELL_TYPE_DEFENSE = 1 +SPELL_TYPE_COMMON = 2 +SPELL_TYPE_POWERFUL = 3 +SPELL_TYPE_UNFORGIVABLE = 4 + +# If SPELL_TYPE_DEFENSE is casted, always these chances to heal partly (restore 5% of max hp) or completely +CHANCE_HEAL_PARTLY = 25 +CHANCE_HEAL_FULLY = 5 + +#Maximum Levenshtein distance. Eg if a user casts 'Pritrgo' instead of 'Protego', distance would be 2 and Protego would still be cast if MAX_LEVENSHTEIN_DISTANCE is at least 2 +#Set to 0 to disable +MAX_LEVENSHTEIN_DISTANCE = 3 + +__INVALID_SPELL = ".@wizardduel@__spell_invalid__" #Internal usage only + +## +## Spell class +## +class Spell: + #Class special methods + """ + speed (int) Speed of the spell. This will determine what spell is casted first. + damage (int) Damage that the spell does. + Negative damage takes away moves from opponent (eg -2 = cancel 2 moves of opponent) + succes_rate (int) How much chance for the spell to be succesfully cast. If it fails, spell won't be cast (thus spells with a succes_rate of 0 would never be executed, and 100=always succes) + description (str) Description of the spell + type (int) Type of the spell: + - SPELL_TYPE_USELESS: Spell that do nothing in combat + - SPELL_TYPE_DEFENSE: Defensive spell - Prevent incoming damage. Has 25% chance to restore 5% of caster total health and 5% chance to fully restore health + - SPELL_TYPE_COMMON: Common combat spell - Deals some damage + - SPELL_TYPE_POWER: Powerful combat spell - deals alot of damage or takes away a few moves from opponent + - SPELL_TYPE_UNFORGIVABLE: deals alot of damage or takes away alot of moves from opponent + """ + def __init__(self, speed: int, damage: int, succes_rate: int, description: str, type: int = SPELL_TYPE_COMMON): + self.speed = speed + self.damage = damage + self.succes_rate = succes_rate + self.description = description + self.type = type + + def __repr__(self): + return " ['todo']\n\t{desc}\n\tSUCCES RATE: {srate}%\tSPEED: {speed}\tDAMAGE: {dmg}".format(type=type, desc=self.description, srate=self.succes_rate, speed=self.speed, dmg=self.damage) + + def chance_heal_partly_succes(self): + return self.type == SPELL_TYPE_DEFENSE and CHANCE_HEAL_PARTLY > random.random() * 100 + + def chance_heal_fully_succes(self): + return self.type == SPELL_TYPE_DEFENSE and CHANCE_HEAL_FULLY > random.random() * 100 + +## +## Spells +## +spellz = { + # Useless spells - These don't do anything useful in combat + "Lumos": Spell(100, 000, 100, "Creates a small light at the tip of your wand", SPELL_TYPE_USELESS), + "Nox": Spell(100, 000, 100, "Counter spell of Lumos", SPELL_TYPE_USELESS), + "Rennervate": Spell(100, 000, 100, "Revives your opponent if they are stunned", SPELL_TYPE_USELESS), + "Igni": Spell(100, 000, 100, "Damages an enemy using fire. Except, this is a Witcher sign. It thus has no effect at all", SPELL_TYPE_USELESS), + +# Defensive spell. Each cast from this category has a 5% chance of completely restoring health or 25% chance to heal 5% of maximum health + "Finite Incantatem": Spell(100, 000, 45, "Cancel all effects casted upon you. If you are stunned/silenced, there's a 10% chance this spell might work", SPELL_TYPE_DEFENSE), + "Impendimenta": Spell(94, 000, 60, "Slows your opponent. EFFECT: Decrease opponent's spell speed by 33% in their next offensive move", SPELL_TYPE_DEFENSE), + "Lumos Solem": Spell(94, 000, 60, "Blinds your opponent. EFFECT: Decrease opponent's spell damage by 33% in their next offensive move", SPELL_TYPE_DEFENSE), + "Protego": Spell(100, 000, 80, "Create a shield that blocks most attacks", SPELL_TYPE_DEFENSE), + +# Common combat spells. High chance of succes, deals some damage + "Reducto": Spell(75, 150, 85, "Blast an object near your opponent"), + "Rictusempra": Spell(85, 90, 90, "Causes your opponent to curl up in laughter, tiring them out"), + "Stupefy": Spell(95, 75, 95, "Knock over your opponent"), + +# Powerful combat spells. Medium chance of succes, deals more damage or stuns opponents + "Bombarda": Spell(50, 180, 75, "Creates an explosion near your opponent", SPELL_TYPE_POWERFUL), + "Confringo": Spell(50, 200, 70, "Creates an explosion directly at your opponent", SPELL_TYPE_POWERFUL), + "Mimblewimble": Spell(50, -1, 70, "Ties a knot in your opponents tongue, causing them to be unable to cast a spell for 1 (more) move", SPELL_TYPE_POWERFUL), + "Sectumsempra": Spell(90, 400, 35, "Slices your opponent", SPELL_TYPE_POWERFUL), + "Silencio": Spell(35, -3, 55, "Silences your opponent, causing them unable to cast spells for 3 moves. Only works if opponent is not stunned yet", SPELL_TYPE_POWERFUL), + +# Unforgivable spells. Very low chance of success, instantly kills or deals alot of damage/stun amount + "Avada Kedavra": Spell(999, 999, 2, "Instantly end your opponent", SPELL_TYPE_UNFORGIVABLE), + "Crucio": Spell(999, 500, 5, "Cause excruciating pain to your opponent, causing alot of damage and making them unable to cast spells for 5 moves", SPELL_TYPE_UNFORGIVABLE), + "Imperio": Spell(999, -1, 3, "Muddle with your opponent's mind, convincing them to stop casting spells for 10 moves", SPELL_TYPE_UNFORGIVABLE), + +# Internal usage + __INVALID_SPELL: Spell(0, 0, 0, "(internal) invalid spell", SPELL_TYPE_NONE) +} + +print(spellz["Imperio"]) \ No newline at end of file From 355eab297cdec51432e9a1a43f03bd8acef06f15 Mon Sep 17 00:00:00 2001 From: Kwarde Date: Wed, 17 Jan 2024 16:50:38 +0100 Subject: [PATCH 03/10] Make spells.py compatible with the new dictionary. Also: only import distance from Levenshtein if MAX_LEVENSHTEIN_DISTANCE > 0 --- spells.py | 49 ++++++++++++++++++++++++++++--------------------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/spells.py b/spells.py index af3007c..87033de 100644 --- a/spells.py +++ b/spells.py @@ -1,5 +1,4 @@ import random -from Levenshtein import distance SPELL_TYPE_NONE = -1 SPELL_TYPE_USELESS = 0 @@ -16,6 +15,9 @@ CHANCE_HEAL_FULLY = 5 #Set to 0 to disable MAX_LEVENSHTEIN_DISTANCE = 3 +if MAX_LEVENSHTEIN_DISTANCE > 0: + from Levenshtein import distance + __INVALID_SPELL = ".@wizardduel@__spell_invalid__" #Internal usage only ## @@ -45,7 +47,7 @@ class Spell: self.type = type def __repr__(self): - return " ['{spell}']\n\t{desc}\n\tSUCCES RATE: {srate}%\tSPEED: {speed}\tDAMAGE: {dmg}".format(spell=self.name, type=type, desc=self.description, srate=self.succes_rate, speed=self.speed, dmg=self.damage) + return "\n\t{desc}\n\tSUCCES RATE: {srate}%\tSPEED: {speed}\tDAMAGE: {dmg}".format(type=type, desc=self.description, srate=self.succes_rate, speed=self.speed, dmg=self.damage) def chance_heal_partly_succes(self): return self.type == SPELL_TYPE_DEFENSE and CHANCE_HEAL_PARTLY > random.random() * 100 @@ -94,18 +96,18 @@ spell = { ## Standalone spell functions ## def random_combat_spell(): - return random.choice([i for i in Spell.spellList if i.type == SPELL_TYPE_COMMON]) + return random.choice([i for i in spell.items() if i[1].type == SPELL_TYPE_COMMON]) # note: returns tuple ('spell_name', spell_obj) -def find_spell_by_name(input: str): # Returns a list with: [spell_object, levenshtein_distance]. If distance is greater than 0 (typos were made), damage goes down - for i in Spell.spellList: - if input.title() == i.name.title(): - return [i, 0] - else: - if MAX_LEVENSHTEIN_DISTANCE > 0: - dist = distance(i.name.title(), input.title()) - if dist < MAX_LEVENSHTEIN_DISTANCE: - return [i, dist] - return [spell_object_none, 0] +def find_spell_by_name(input: str): # Returns a multidimensional tuple: ( ('spell_name', spell_object), levenshtein_distance ) + ret = (input, spell.get(input.title(), spell[__INVALID_SPELL])) + dist = 0 + if ret[1] == spell[__INVALID_SPELL] and MAX_LEVENSHTEIN_DISTANCE > 0: + for i in spell.items(): + dist = distance(i[0].title(), input.title()) + if dist <= MAX_LEVENSHTEIN_DISTANCE: + ret = i + break + return (ret, dist) def print_spells(): header_spells_useless = "== USELESS SPELLS ==" @@ -114,24 +116,29 @@ def print_spells(): header_spells_powerful = "== POWERFUL COMBAT SPELLS ==" header_spells_unforgivable = "== UNFORGIVABLE CURSES ==" - for i in Spell.spellList: - if i.type == SPELL_TYPE_UNFORGIVABLE or i.type == SPELL_TYPE_USELESS or i.type == SPELL_TYPE_NONE: + for i in spell.items(): + if i[1].type == SPELL_TYPE_UNFORGIVABLE or i[1].type == SPELL_TYPE_USELESS or i[1].type == SPELL_TYPE_NONE: continue - if i.type == SPELL_TYPE_USELESS and header_spells_useless: + if i[1].type == SPELL_TYPE_USELESS and header_spells_useless: print("\n"+header_spells_useless) header_spells_useless = "" - elif i.type == SPELL_TYPE_DEFENSE and header_spells_defensive: + elif i[1].type == SPELL_TYPE_DEFENSE and header_spells_defensive: print("\n"+header_spells_defensive) header_spells_defensive = "" - elif i.type == SPELL_TYPE_COMMON and header_spells_common: + elif i[1].type == SPELL_TYPE_COMMON and header_spells_common: print("\n"+header_spells_common) header_spells_common = "" - elif i.type == SPELL_TYPE_POWERFUL and header_spells_powerful: + elif i[1].type == SPELL_TYPE_POWERFUL and header_spells_powerful: print("\n"+header_spells_powerful) header_spells_powerful = "" - elif i.type == SPELL_TYPE_UNFORGIVABLE and header_spells_unforgivable: + elif i[1].type == SPELL_TYPE_UNFORGIVABLE and header_spells_unforgivable: print("\n"+header_spells_unforgivable) header_spells_unforgivable = "" - print(i) \ No newline at end of file + print( + str(i) + .strip('(') + .strip(')') + .replace(',', ':', 1) + ) \ No newline at end of file From c4bb8f7d80c801d8df2520a3a669f75934626b9e Mon Sep 17 00:00:00 2001 From: Kwarde Date: Wed, 17 Jan 2024 16:52:00 +0100 Subject: [PATCH 04/10] Remove tmp.py: Used it for a quick testrun since I had a confusion with the new dictionary --- tmp.py | 91 ---------------------------------------------------------- 1 file changed, 91 deletions(-) delete mode 100644 tmp.py diff --git a/tmp.py b/tmp.py deleted file mode 100644 index 49d18e6..0000000 --- a/tmp.py +++ /dev/null @@ -1,91 +0,0 @@ -import random - -SPELL_TYPE_NONE = -1 -SPELL_TYPE_USELESS = 0 -SPELL_TYPE_DEFENSE = 1 -SPELL_TYPE_COMMON = 2 -SPELL_TYPE_POWERFUL = 3 -SPELL_TYPE_UNFORGIVABLE = 4 - -# If SPELL_TYPE_DEFENSE is casted, always these chances to heal partly (restore 5% of max hp) or completely -CHANCE_HEAL_PARTLY = 25 -CHANCE_HEAL_FULLY = 5 - -#Maximum Levenshtein distance. Eg if a user casts 'Pritrgo' instead of 'Protego', distance would be 2 and Protego would still be cast if MAX_LEVENSHTEIN_DISTANCE is at least 2 -#Set to 0 to disable -MAX_LEVENSHTEIN_DISTANCE = 3 - -__INVALID_SPELL = ".@wizardduel@__spell_invalid__" #Internal usage only - -## -## Spell class -## -class Spell: - #Class special methods - """ - speed (int) Speed of the spell. This will determine what spell is casted first. - damage (int) Damage that the spell does. - Negative damage takes away moves from opponent (eg -2 = cancel 2 moves of opponent) - succes_rate (int) How much chance for the spell to be succesfully cast. If it fails, spell won't be cast (thus spells with a succes_rate of 0 would never be executed, and 100=always succes) - description (str) Description of the spell - type (int) Type of the spell: - - SPELL_TYPE_USELESS: Spell that do nothing in combat - - SPELL_TYPE_DEFENSE: Defensive spell - Prevent incoming damage. Has 25% chance to restore 5% of caster total health and 5% chance to fully restore health - - SPELL_TYPE_COMMON: Common combat spell - Deals some damage - - SPELL_TYPE_POWER: Powerful combat spell - deals alot of damage or takes away a few moves from opponent - - SPELL_TYPE_UNFORGIVABLE: deals alot of damage or takes away alot of moves from opponent - """ - def __init__(self, speed: int, damage: int, succes_rate: int, description: str, type: int = SPELL_TYPE_COMMON): - self.speed = speed - self.damage = damage - self.succes_rate = succes_rate - self.description = description - self.type = type - - def __repr__(self): - return " ['todo']\n\t{desc}\n\tSUCCES RATE: {srate}%\tSPEED: {speed}\tDAMAGE: {dmg}".format(type=type, desc=self.description, srate=self.succes_rate, speed=self.speed, dmg=self.damage) - - def chance_heal_partly_succes(self): - return self.type == SPELL_TYPE_DEFENSE and CHANCE_HEAL_PARTLY > random.random() * 100 - - def chance_heal_fully_succes(self): - return self.type == SPELL_TYPE_DEFENSE and CHANCE_HEAL_FULLY > random.random() * 100 - -## -## Spells -## -spellz = { - # Useless spells - These don't do anything useful in combat - "Lumos": Spell(100, 000, 100, "Creates a small light at the tip of your wand", SPELL_TYPE_USELESS), - "Nox": Spell(100, 000, 100, "Counter spell of Lumos", SPELL_TYPE_USELESS), - "Rennervate": Spell(100, 000, 100, "Revives your opponent if they are stunned", SPELL_TYPE_USELESS), - "Igni": Spell(100, 000, 100, "Damages an enemy using fire. Except, this is a Witcher sign. It thus has no effect at all", SPELL_TYPE_USELESS), - -# Defensive spell. Each cast from this category has a 5% chance of completely restoring health or 25% chance to heal 5% of maximum health - "Finite Incantatem": Spell(100, 000, 45, "Cancel all effects casted upon you. If you are stunned/silenced, there's a 10% chance this spell might work", SPELL_TYPE_DEFENSE), - "Impendimenta": Spell(94, 000, 60, "Slows your opponent. EFFECT: Decrease opponent's spell speed by 33% in their next offensive move", SPELL_TYPE_DEFENSE), - "Lumos Solem": Spell(94, 000, 60, "Blinds your opponent. EFFECT: Decrease opponent's spell damage by 33% in their next offensive move", SPELL_TYPE_DEFENSE), - "Protego": Spell(100, 000, 80, "Create a shield that blocks most attacks", SPELL_TYPE_DEFENSE), - -# Common combat spells. High chance of succes, deals some damage - "Reducto": Spell(75, 150, 85, "Blast an object near your opponent"), - "Rictusempra": Spell(85, 90, 90, "Causes your opponent to curl up in laughter, tiring them out"), - "Stupefy": Spell(95, 75, 95, "Knock over your opponent"), - -# Powerful combat spells. Medium chance of succes, deals more damage or stuns opponents - "Bombarda": Spell(50, 180, 75, "Creates an explosion near your opponent", SPELL_TYPE_POWERFUL), - "Confringo": Spell(50, 200, 70, "Creates an explosion directly at your opponent", SPELL_TYPE_POWERFUL), - "Mimblewimble": Spell(50, -1, 70, "Ties a knot in your opponents tongue, causing them to be unable to cast a spell for 1 (more) move", SPELL_TYPE_POWERFUL), - "Sectumsempra": Spell(90, 400, 35, "Slices your opponent", SPELL_TYPE_POWERFUL), - "Silencio": Spell(35, -3, 55, "Silences your opponent, causing them unable to cast spells for 3 moves. Only works if opponent is not stunned yet", SPELL_TYPE_POWERFUL), - -# Unforgivable spells. Very low chance of success, instantly kills or deals alot of damage/stun amount - "Avada Kedavra": Spell(999, 999, 2, "Instantly end your opponent", SPELL_TYPE_UNFORGIVABLE), - "Crucio": Spell(999, 500, 5, "Cause excruciating pain to your opponent, causing alot of damage and making them unable to cast spells for 5 moves", SPELL_TYPE_UNFORGIVABLE), - "Imperio": Spell(999, -1, 3, "Muddle with your opponent's mind, convincing them to stop casting spells for 10 moves", SPELL_TYPE_UNFORGIVABLE), - -# Internal usage - __INVALID_SPELL: Spell(0, 0, 0, "(internal) invalid spell", SPELL_TYPE_NONE) -} - -print(spellz["Imperio"]) \ No newline at end of file From 7d28ac961cbc0ff34bbe28336aa66f8cd28fbdde Mon Sep 17 00:00:00 2001 From: Kwarde Date: Thu, 18 Jan 2024 07:05:31 +0100 Subject: [PATCH 05/10] Convert wands to dict --- wands.py | 32 +++++++++++++------------------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/wands.py b/wands.py index fdc5fe8..734cbef 100644 --- a/wands.py +++ b/wands.py @@ -7,12 +7,7 @@ WAND_WOOD_ELDER = 1 #Speed -4% WAND_WOOD_APPLE = 2 #Damage -5% class Wand: - _next_wand_id = 1 - wandList = [] - def __init__(self, wand_core: int, wand_wood: int): - self.id = Wand._next_wand_id - self.core = wand_core self.wood = wand_wood @@ -34,12 +29,9 @@ class Wand: elif self.wood == WAND_WOOD_APPLE: self.damage *= 0.95 - Wand.wandList.append(self) - Wand._next_wand_id += 1 - def __repr__(self): - return"\t{id}: {wood} wand with core: {core}\n\t\t-- SPEED: {info_speed}\tDAMAGE: {info_dmg}\tSUCCES RATE: {info_srate}".format( - id=self.id, wood=self.get_wand_wood(), core=self.get_wand_core(), info_srate=round(self.succes_rate, 2), info_speed=self.speed, info_dmg=round(self.damage, 2) + return"\t{wood} wand with core: {core}\n\t\t-- SPEED: {info_speed}\tDAMAGE: {info_dmg}\tSUCCES RATE: {info_srate}".format( + wood=self.get_wand_wood(), core=self.get_wand_core(), info_srate=round(self.succes_rate, 2), info_speed=self.speed, info_dmg=round(self.damage, 2) ) def get_wand_core(self): @@ -57,14 +49,16 @@ class Wand: ## ## Wands ## -wand_1 = Wand(WAND_CORE_UNICORN, WAND_WOOD_ASH) -wand_2 = Wand(WAND_CORE_UNICORN, WAND_WOOD_ELDER) -wand_3 = Wand(WAND_CORE_UNICORN, WAND_WOOD_APPLE) +wand = { + 1: Wand(WAND_CORE_UNICORN, WAND_WOOD_ASH), + 2: Wand(WAND_CORE_UNICORN, WAND_WOOD_ELDER), + 3: Wand(WAND_CORE_UNICORN, WAND_WOOD_APPLE), -wand_4 = Wand(WAND_CORE_PHOENIX, WAND_WOOD_ASH) -wand_5 = Wand(WAND_CORE_PHOENIX, WAND_WOOD_ELDER) -wand_6 = Wand(WAND_CORE_PHOENIX, WAND_WOOD_APPLE) + 4: Wand(WAND_CORE_PHOENIX, WAND_WOOD_ASH), + 5: Wand(WAND_CORE_PHOENIX, WAND_WOOD_ELDER), + 6: Wand(WAND_CORE_PHOENIX, WAND_WOOD_APPLE), -wand_7 = Wand(WAND_CORE_DRAGON, WAND_WOOD_ASH) -wand_8 = Wand(WAND_CORE_DRAGON, WAND_WOOD_ELDER) -wand_9 = Wand(WAND_CORE_DRAGON, WAND_WOOD_APPLE) \ No newline at end of file + 7: Wand(WAND_CORE_DRAGON, WAND_WOOD_ASH), + 8: Wand(WAND_CORE_DRAGON, WAND_WOOD_ELDER), + 9: Wand(WAND_CORE_DRAGON, WAND_WOOD_APPLE) +} \ No newline at end of file From aea461499c723ba3567301f43afe9cecf6526a16 Mon Sep 17 00:00:00 2001 From: Kwarde Date: Thu, 18 Jan 2024 15:30:08 +0100 Subject: [PATCH 06/10] Make player.py compatible with wands and spells dicts --- player.py | 58 +++++++++++++++++++++++++++++-------------------------- spells.py | 14 ++++++++------ 2 files changed, 39 insertions(+), 33 deletions(-) diff --git a/player.py b/player.py index d32a6b8..4c1553f 100644 --- a/player.py +++ b/player.py @@ -1,5 +1,6 @@ from wands import Wand -from spells import * +from spells import SPELL_TYPE_NONE, SPELL_TYPE_USELESS, SPELL_TYPE_DEFENSE, SPELL_TYPE_UNFORGIVABLE#, SPELL_TYPE_COMMON, SPELL_TYPE_POWERFUL +from spells import Spell, spells, _INVALID_SPELL MAX_PLAYER_HEALTH = 1000 MAX_STUNNED_ROUNDS = 10 @@ -10,7 +11,7 @@ class Player: self.health = MAX_PLAYER_HEALTH self.wand = wand - self.active_spell = spell_object_none + self.active_spell = spells[_INVALID_SPELL] self.active_spell_succes = False self.active_spell_levenshtein_distance = 0 # Penalty => If >0 then damage reduction, 15 per distance @@ -56,57 +57,60 @@ class Player: return output def cast_spell_result(self, spell: Spell, succes: bool): + if spell == None: + spell[None].type = SPELL_TYPE_NONE + if succes: message = "{name} casted '{spell}' with succes" else: if spell.type == SPELL_TYPE_UNFORGIVABLE: message = "{name} tried to cast '{spell}' but didn't truly mean it. Cast FAILED!" elif spell.type == SPELL_TYPE_NONE: - if spell == spell_object_none: + if spell == spells[_INVALID_SPELL]: return "{name} must not be feeling well, since they casted a non existing spell. Cast FAILED!".format(name=self.name) - elif spell == spell_object_stunned: - return " {name} can't cast anything but {spell} since they are stunned".format(name=self.name, spell=spell_finite_incantatem.name) + elif spell == None: + return " {name} can't cast anything but {spell} since they are stunned".format(name=self.name, spell=spells["Finite Incantatem"].name) else: message = "{name} tried to cast '{spell}' but mispronounced it. Cast FAILED!" - return message.format(name=self.name, spell=spell.name) + return message.format(name=self.name, spell=spell.get_spell_name()) def cast_spell(self, opponent): #: Player ? - spell_name = self.active_spell.name.lower() + spell_name = self.active_spell.get_spell_name() - if self.active_spell is spell_object_none: + if self.active_spell is None: print("- {name} does nothing".format(name=self.name)) return - if self.stunned_rounds > 0 and self.active_spell is not spell_finite_incantatem: + if self.stunned_rounds > 0 and self.active_spell is not spells["Finite Incantatem"]: print(" {name} tries to cast a spell but fails because they are stunned!".format(name=self.name)) return if self.active_spell.type == SPELL_TYPE_USELESS: - if self.active_spell is spell_lumos: + if self.active_spell is spells["Lumos"]: if self.lumos: print("- {name} casts {spell} with no effect: {name} already has a brutal shining light at the tip of their wand!".format(name=self.name, spell=spell_name)) else: print("- {name} casts {spell}! Look at that brutal shining light at the tip of their wand. Absolutely gorgeous!".format(name=self.name, spell=spell_name)) self.lumos = True - elif self.active_spell is spell_nox: + elif self.active_spell is spells["Nox"]: if not self.lumos: print("- {name} shouts: {spell}! Nothing happened".format(name=self.name, spell=spell_name.upper())) else: print("- {name} shouts {spell}! Their brutal shining light dissapears".format(name=self.name, spell=spell_name.upper())) self.lumos = False - elif self.active_spell is spell_rennervate: + elif self.active_spell is spells["Rennervate"]: if opponent.stunned_rounds == 0: print("- {name} casts {spell} with no effect, because {name_o} is not currently stunned!".format(name=self.name, spell=spell_name, name_o=opponent.name)) else: print("- {name} casts {spell}! {name_o} is no longer stunned!".format(name=self.name, spell=spell_name, name_o=opponent.name)) opponent.stunned_rounds = 0 - elif self.active_spell is spell_igni: + elif self.active_spell is spells["Igni"]: print("- Geralt casts Igni. Wait. Geralt? White Wolf? The butcher of Blaviken? What is he doing here? Is he even here? What is going on?") else: print(" [debug]{name} casted SPELL_TYPE_USELESS {spell}. Behaviour unscripted!".format(name=self.name, spell=spell_name)) elif self.active_spell.type == SPELL_TYPE_DEFENSE: spell_succes = True - if self.active_spell is spell_finite_incantatem: + if self.active_spell is spells["Finite Incantatem"]: if self.stunned_rounds > 0: if not 10 > random.random() * 100: spell_succes = False @@ -125,42 +129,42 @@ class Player: print("- {name} casts {spell}. {name_o} is looking confused. What spell did {name} try to cancel?".format(name=self.name, spell=spell_name, name_o=opponent.name)) elif not spell_succes: print("- {name}: (nothing, still stunned)".format(name=self.name)) - elif self.active_spell is spell_impendimenta: - if opponent.active_spell is spell_protego and opponent.active_spell_succes: + elif self.active_spell is spells["Impendimenta"]: + if opponent.active_spell is spells["Protego"] and opponent.active_spell_succes: print("- {name} casts {spell} on {name_o}. FAILURE! {name_o} blocks the attack!".format(name=self.name, name_o=opponent.name, spell=spell_name)) else: print("- {name} casts {spell} on {name_o}. {name_o} is slowed and their next offensive move will have a 33% slower spell speed!".format(name=self.name, name_o=opponent.name, spell=spell_name)) opponent.decreased_spell_speed = True - elif self.active_spell is spell_lumos_solem: + elif self.active_spell is spells["Lumos Solem"]: # Light still goes through the shield, therefor always succeed blinding attempts print("- {name} casts {spell} on {name_o}. {name_o} is blinded and their next offensive move will have 33% less damage!".format(name=self.name, name_o=opponent.name, spell=spell_name)) opponent.decreased_spell_damage = True - elif self.active_spell is spell_protego and self.active_spell_succes: + elif self.active_spell is spells["Protego"] and self.active_spell_succes: print("- {name} casts {spell}".format(name=self.name, spell=spell_name)) else: print(" [debug]{name} casted SPELL_TYPE_DEFENSE {spell}. Behaviour unscripted!".format(name=self.name, spell=spell_name)) elif self.active_spell.type == SPELL_TYPE_UNFORGIVABLE: - if self.active_spell is spell_avada_kedavra: + if self.active_spell is spells["Avada Kedavra"]: print("- THE NERVE! {name} casts the killing curse! See you in the after life {name_o}!".format(name=self.name, name_o=opponent.name)) opponent.health = 0 - elif self.active_spell is spell_crucio: + elif self.active_spell is spells["Crucio"]: print("- THE NERVE! {name} casts the torture curse. {name_o} is greatly hurt and falls to the ground. They are stunned for 5 (more) moves".format(name=self.name, name_o=opponent.name)) opponent.add_stunned_rounds(5) opponent.take_health(self.active_spell.damage) - elif self.active_spell is spell_imperio: + elif self.active_spell is spells["Imperio"]: print("- THE NERVE! {name} casts the Imperius curse. \"Why don't you take a nice nap for {max_stunned_rounds} moves, {name_o}?\". {name_o} submits with pleasure".format(name=self.name, name_o=opponent.name, max_stunned_rounds=MAX_STUNNED_ROUNDS)) opponent.add_stunned_rounds(MAX_STUNNED_ROUNDS) else: - if self.active_spell is spell_mimblewimble: - if opponent.active_spell is spell_protego and opponent.active_spell_succes: + if self.active_spell is spells["Mimblewimble"]: + if opponent.active_spell is spells["Protego"] and opponent.active_spell_succes: print("- {name} casts {spell} on {name_o}. FAILURE! {name_o} blocks the attack!".format(name=self.name, spell=spell_name, name_o=opponent.name)) else: print("- {name} casts {spell} on {name_o}. {name_o}'s tongue is tied in a knot. That's annoying! {name_o} is silenced for 1 (more) move".format(name=self.name, spell=spell_name, name_o=opponent.name)) opponent.add_stunned_rounds(-self.active_spell.damage) - elif self.active_spell is spell_silencio: - if opponent.active_spell is spell_protego and opponent.active_spell_succes: + elif self.active_spell is spells["Silencio"]: + if opponent.active_spell is spells["Protego"] and opponent.active_spell_succes: print("- {name} casts {spell} on {name_o}. FAILURE! {name_o} blocks the attack!".format(name=self.name, spell=spell_name, name_o=opponent.name)) else: if opponent.stunned_rounds == 0: @@ -181,8 +185,8 @@ class Player: total_damage = self.active_spell.damage * self.wand.damage * damage_modifier - damage_penalty if total_damage < 0: total_damage = 0 - if opponent.active_spell is spell_protego and opponent.active_spell_succes: + if opponent.active_spell is spells["Protego"] and opponent.active_spell_succes: print("- {name} casts {spell} on {name_o}. FAILURE! {name_o} blocks the attack!".format(name=self.name, spell=spell_name, name_o=opponent.name)) else: print("- {name} casts {spell} on {name_o} causing {dmg} damage!".format(name=self.name, spell=spell_name, name_o=opponent.name, dmg=total_damage)) - opponent.take_health(total_damage) \ No newline at end of file + opponent.take_health(total_damage) \ No newline at end of file diff --git a/spells.py b/spells.py index 87033de..ec3d534 100644 --- a/spells.py +++ b/spells.py @@ -18,7 +18,7 @@ MAX_LEVENSHTEIN_DISTANCE = 3 if MAX_LEVENSHTEIN_DISTANCE > 0: from Levenshtein import distance -__INVALID_SPELL = ".@wizardduel@__spell_invalid__" #Internal usage only +_INVALID_SPELL = ".@wizardduel@__spell_invalid__" #Internal usage only ## ## Spell class @@ -39,7 +39,6 @@ class Spell: - SPELL_TYPE_UNFORGIVABLE: deals alot of damage or takes away alot of moves from opponent """ def __init__(self, speed: int, damage: int, succes_rate: int, description: str, type: int = SPELL_TYPE_COMMON): - self.speed = speed self.damage = damage self.succes_rate = succes_rate @@ -55,10 +54,13 @@ class Spell: def chance_heal_fully_succes(self): return self.type == SPELL_TYPE_DEFENSE and CHANCE_HEAL_FULLY > random.random() * 100 + def get_spell_name(self): + return str(list(i for i in spells if spells[i] == self)).strip("[]'") + ## ## Spells ## -spell = { +spells = { # Useless spells - These don't do anything useful in combat "Lumos": Spell(100, 000, 100, "Creates a small light at the tip of your wand", SPELL_TYPE_USELESS), "Nox": Spell(100, 000, 100, "Counter spell of Lumos", SPELL_TYPE_USELESS), @@ -89,7 +91,7 @@ spell = { "Imperio": Spell(999, -1, 3, "Muddle with your opponent's mind, convincing them to stop casting spells for 10 moves", SPELL_TYPE_UNFORGIVABLE), # Internal usage - __INVALID_SPELL: Spell(0, 0, 0, "(internal) invalid spell", SPELL_TYPE_NONE) + _INVALID_SPELL: Spell(0, 0, 0, "(internal) invalid spell", SPELL_TYPE_NONE) } ## @@ -99,9 +101,9 @@ def random_combat_spell(): return random.choice([i for i in spell.items() if i[1].type == SPELL_TYPE_COMMON]) # note: returns tuple ('spell_name', spell_obj) def find_spell_by_name(input: str): # Returns a multidimensional tuple: ( ('spell_name', spell_object), levenshtein_distance ) - ret = (input, spell.get(input.title(), spell[__INVALID_SPELL])) + ret = (input, spell.get(input.title(), spell[_INVALID_SPELL])) dist = 0 - if ret[1] == spell[__INVALID_SPELL] and MAX_LEVENSHTEIN_DISTANCE > 0: + if ret[1] == spell[_INVALID_SPELL] and MAX_LEVENSHTEIN_DISTANCE > 0: for i in spell.items(): dist = distance(i[0].title(), input.title()) if dist <= MAX_LEVENSHTEIN_DISTANCE: From 1485f171befaa9761c9b2c6be8b48a6ea2440fe6 Mon Sep 17 00:00:00 2001 From: Kwarde Date: Thu, 18 Jan 2024 16:31:43 +0100 Subject: [PATCH 07/10] Make game.py compatible with the new dicts --- game.py | 54 +++++++++++++++++++++++++++++------------------------- player.py | 7 ++++--- spells.py | 22 ++++++++++++---------- wands.py | 2 +- 4 files changed, 46 insertions(+), 39 deletions(-) diff --git a/game.py b/game.py index c99e496..3f9001f 100644 --- a/game.py +++ b/game.py @@ -1,5 +1,8 @@ +#import random from player import * from wands import * +from spells import SPELL_TYPE_COMMON, SPELL_TYPE_POWERFUL, _INVALID_SPELL +from spells import random_combat_spell, print_spells, find_spell_by_name ## ## Definitions @@ -34,19 +37,20 @@ def get_player_spell_from_input(player: Player): player_input = input(print_turn_message(player)) if not player_input: - return [random_combat_spell(), 0] + spell_name, spell_obj = random_combat_spell() + return ((spell_name, spell_obj), 0) else: if player_input == "help": print_spells() continue elif player_input.find("help", 0) != -1: find_what = player_input[5:] - spell = find_spell_by_name(find_what)[0] + spell = spells.get(find_spell_by_name(find_what)[0][0], None) - if spell is spell_object_none: + if spell is spells[_INVALID_SPELL]: print(" Spell '{what}' does not exist!".format(what=find_what)) else: - print(spell) + print("'{spell_name}':{spell_desc}".format(spell_name=find_what, spell_desc=spells[find_what])) continue else: return find_spell_by_name(player_input) @@ -72,8 +76,8 @@ while True: #GET: WANDS print("Welcome {p1_name} and {p2_name}! You're about to choose a wand to use in this duel! Available wands are:".format(p1_name=player1_name, p2_name=player2_name)) -for i in Wand.wandList: - print(i) +for i in wands.items(): + print("{wand_id}:{wand_desc}".format(wand_id=i[0], wand_desc=i[1])) #Player 1 while (True): @@ -81,10 +85,10 @@ while (True): wand_input = int(input("What wand do you want {name}? (Enter one of the numbers): ".format(name=player1_name))) except ValueError: continue - if wand_input < 1 or wand_input > len(Wand.wandList): + if wand_input < 1 or wand_input > len(wands): continue - player1_wand = Wand.wandList[wand_input-1] + player1_wand = wands[wand_input] break #Player 2 while (True): @@ -92,10 +96,10 @@ while (True): wand_input = int(input("What wand do you want {name}? (Enter one of the numbers): ".format(name=player2_name))) except ValueError: continue - if wand_input < 1 or wand_input > len(Wand.wandList): + if wand_input < 1 or wand_input > len(wands): continue - player2_wand = Wand.wandList[wand_input-1] + player2_wand = wands[wand_input] break player1 = Player(player1_name, player1_wand) @@ -131,13 +135,18 @@ try: round_end() print("== Round {round} ==".format(round=current_round)) - player1.active_spell, player1.active_spell_levenshtein_distance = get_player_spell_from_input(player1) - player2.active_spell, player2.active_spell_levenshtein_distance = get_player_spell_from_input(player2) + spell, dist = get_player_spell_from_input(player1) + player1.active_spell = spells.get(spell[0]) + player1.active_spell_levenshtein_distance = dist - if (player1.stunned_rounds > 0 and player1.active_spell is not spell_finite_incantatem): - player1.active_spell = spell_object_stunned - if (player2.stunned_rounds > 0 and player2.active_spell is not spell_finite_incantatem): - player2.active_spell = spell_object_stunned + spell, dist = get_player_spell_from_input(player2) + player2.active_spell = spells.get(spell[0]) + player2.active_spell_levenshtein_distance = dist + + if (player1.stunned_rounds > 0 and player1.active_spell is not spells["Finite Incantatem"]): + player1.active_spell = None + if (player2.stunned_rounds > 0 and player2.active_spell is not spells["Finite Incantatem"]): + player2.active_spell = None # OUTCOME: SPELLS # > Get spell succes @@ -150,17 +159,17 @@ try: # > Add health if defensive spell was lucky (partial heal, fully heal) if player1.active_spell_succes and player1.active_spell.chance_heal_partly_succes(): player1.give_health(MAX_PLAYER_HEALTH * 0.05) - print(" {name} casted {spell} above expectations, and receives {hp} health!".format(name=player1.name, spell=player1.active_spell.name, hp=MAX_PLAYER_HEALTH*0.05)) + print(" {name} casted {spell} above expectations, and receives {hp} health!".format(name=player1.name, spell=player1.active_spell.get_spell_name(), hp=MAX_PLAYER_HEALTH*0.05)) elif player1.active_spell_succes and player1.active_spell.chance_heal_fully_succes() and player1.health < MAX_PLAYER_HEALTH: player1.give_health(MAX_PLAYER_HEALTH) - print(" {name} casted {spell} outstanding! {name} now has full health!".format(name=player1.name, spell=player1.active_spell.name, hp=MAX_PLAYER_HEALTH*0.05)) + print(" {name} casted {spell} outstanding! {name} now has full health!".format(name=player1.name, spell=player1.active_spell.get_spell_name(), hp=MAX_PLAYER_HEALTH*0.05)) # if player2.active_spell_succes and player2.active_spell.chance_heal_partly_succes(): player2.give_health(MAX_PLAYER_HEALTH * 0.05) - print(" {name} casted {spell} above expectations, and receives {hp} health!".format(name=player2.name, spell=player2.active_spell.name, hp=MAX_PLAYER_HEALTH*0.05)) + print(" {name} casted {spell} above expectations, and receives {hp} health!".format(name=player2.name, spell=player2.active_spell.get_spell_name(), hp=MAX_PLAYER_HEALTH*0.05)) elif player2.active_spell_succes and player2.active_spell.chance_heal_fully_succes() and player2.health < MAX_PLAYER_HEALTH: player2.give_health(MAX_PLAYER_HEALTH) - print(" {name} casted {spell} outstanding! {name} now has full health!".format(name=player2.name, spell=player2.active_spell.name, hp=MAX_PLAYER_HEALTH*0.05)) + print(" {name} casted {spell} outstanding! {name} now has full health!".format(name=player2.name, spell=player2.active_spell.get_spell_name(), hp=MAX_PLAYER_HEALTH*0.05)) # > Determine instant winner or skip to next round if not player1.active_spell_succes and not player2.active_spell_succes: @@ -207,11 +216,6 @@ try: if slowest_caster.health > 0: slowest_caster.cast_spell(fastest_caster) - fastest_caster.active_spell = spell_object_none - fastest_caster.active_spell_levenshtein_distance = 0 - slowest_caster.active_spell = spell_object_none - slowest_caster.active_spell_levenshtein_distance = 0 - except KeyboardInterrupt: print() print(" Duel ended because both {} and {} suddenly decided to test out their apparition skill!".format(player1.name, player2.name)) \ No newline at end of file diff --git a/player.py b/player.py index 4c1553f..a0a43e1 100644 --- a/player.py +++ b/player.py @@ -1,3 +1,4 @@ +import random from wands import Wand from spells import SPELL_TYPE_NONE, SPELL_TYPE_USELESS, SPELL_TYPE_DEFENSE, SPELL_TYPE_UNFORGIVABLE#, SPELL_TYPE_COMMON, SPELL_TYPE_POWERFUL from spells import Spell, spells, _INVALID_SPELL @@ -39,6 +40,8 @@ class Player: self.stunned_rounds = MAX_STUNNED_ROUNDS def get_spell_succes_rate(self, spell: Spell): + if spell == None: + return 0 return 1 * self.wand.succes_rate * spell.succes_rate def get_queued_effects(self): @@ -58,7 +61,7 @@ class Player: def cast_spell_result(self, spell: Spell, succes: bool): if spell == None: - spell[None].type = SPELL_TYPE_NONE + return " {name} can't cast anything but Finite Incantatem since they are stunned".format(name=self.name) if succes: message = "{name} casted '{spell}' with succes" @@ -68,8 +71,6 @@ class Player: elif spell.type == SPELL_TYPE_NONE: if spell == spells[_INVALID_SPELL]: return "{name} must not be feeling well, since they casted a non existing spell. Cast FAILED!".format(name=self.name) - elif spell == None: - return " {name} can't cast anything but {spell} since they are stunned".format(name=self.name, spell=spells["Finite Incantatem"].name) else: message = "{name} tried to cast '{spell}' but mispronounced it. Cast FAILED!" return message.format(name=self.name, spell=spell.get_spell_name()) diff --git a/spells.py b/spells.py index ec3d534..8660d01 100644 --- a/spells.py +++ b/spells.py @@ -13,7 +13,7 @@ CHANCE_HEAL_FULLY = 5 #Maximum Levenshtein distance. Eg if a user casts 'Pritrgo' instead of 'Protego', distance would be 2 and Protego would still be cast if MAX_LEVENSHTEIN_DISTANCE is at least 2 #Set to 0 to disable -MAX_LEVENSHTEIN_DISTANCE = 3 +MAX_LEVENSHTEIN_DISTANCE = 0 if MAX_LEVENSHTEIN_DISTANCE > 0: from Levenshtein import distance @@ -98,17 +98,19 @@ spells = { ## Standalone spell functions ## def random_combat_spell(): - return random.choice([i for i in spell.items() if i[1].type == SPELL_TYPE_COMMON]) # note: returns tuple ('spell_name', spell_obj) + return random.choice([i for i in spells.items() if i[1].type == SPELL_TYPE_COMMON]) # note: returns tuple ('spell_name', spell_obj) def find_spell_by_name(input: str): # Returns a multidimensional tuple: ( ('spell_name', spell_object), levenshtein_distance ) - ret = (input, spell.get(input.title(), spell[_INVALID_SPELL])) + ret = (input, spells.get(input.title(), spells[_INVALID_SPELL])) dist = 0 - if ret[1] == spell[_INVALID_SPELL] and MAX_LEVENSHTEIN_DISTANCE > 0: - for i in spell.items(): - dist = distance(i[0].title(), input.title()) - if dist <= MAX_LEVENSHTEIN_DISTANCE: - ret = i - break + if ret[1] == spells[_INVALID_SPELL]: + ret = (_INVALID_SPELL, ret[1]) + if MAX_LEVENSHTEIN_DISTANCE > 0: + for i in spells.items(): + dist = distance(i[0].title(), input.title()) + if dist <= MAX_LEVENSHTEIN_DISTANCE: + ret[1] = i + break return (ret, dist) def print_spells(): @@ -118,7 +120,7 @@ def print_spells(): header_spells_powerful = "== POWERFUL COMBAT SPELLS ==" header_spells_unforgivable = "== UNFORGIVABLE CURSES ==" - for i in spell.items(): + for i in spells.items(): if i[1].type == SPELL_TYPE_UNFORGIVABLE or i[1].type == SPELL_TYPE_USELESS or i[1].type == SPELL_TYPE_NONE: continue diff --git a/wands.py b/wands.py index 734cbef..834539d 100644 --- a/wands.py +++ b/wands.py @@ -49,7 +49,7 @@ class Wand: ## ## Wands ## -wand = { +wands = { 1: Wand(WAND_CORE_UNICORN, WAND_WOOD_ASH), 2: Wand(WAND_CORE_UNICORN, WAND_WOOD_ELDER), 3: Wand(WAND_CORE_UNICORN, WAND_WOOD_APPLE), From 7cb65f5895a9cfe7219f6eac9f42b4e1dee88640 Mon Sep 17 00:00:00 2001 From: Kwarde Date: Thu, 18 Jan 2024 16:43:20 +0100 Subject: [PATCH 08/10] Better import usage, adds game_config.py, adds a debug mode (sets all spell chances to 100%) --- game.py | 8 +++++--- game_config.py | 16 ++++++++++++++++ player.py | 4 +--- spells.py | 12 ++++++++---- 4 files changed, 30 insertions(+), 10 deletions(-) create mode 100644 game_config.py diff --git a/game.py b/game.py index 3f9001f..6bd3edd 100644 --- a/game.py +++ b/game.py @@ -1,8 +1,10 @@ -#import random -from player import * -from wands import * +import random +from player import Player +from wands import wands from spells import SPELL_TYPE_COMMON, SPELL_TYPE_POWERFUL, _INVALID_SPELL from spells import random_combat_spell, print_spells, find_spell_by_name +from spells import spells +from game_config import MAX_PLAYER_HEALTH ## ## Definitions diff --git a/game_config.py b/game_config.py new file mode 100644 index 0000000..e04bf08 --- /dev/null +++ b/game_config.py @@ -0,0 +1,16 @@ +## +## Player config +## +MAX_PLAYER_HEALTH = 1000 # Maximum health +MAX_STUNNED_ROUNDS = 10 # Max amount of rounds a player can be stunned + +## +## Spells config +## +CHANCE_HEAL_PARTLY = 25 # Percentage chance a player is healed with 5% of max health when using a defensive spell +CHANCE_HEAL_FULLY = 5 # Percentage chance a player is fully healed when using a defensive spell + +## +## Misc +## +DEBUG_MODE = False # Enable or disable debug mode. Sets all spell chances to 100% when enabled \ No newline at end of file diff --git a/player.py b/player.py index a0a43e1..db91502 100644 --- a/player.py +++ b/player.py @@ -2,9 +2,7 @@ import random from wands import Wand from spells import SPELL_TYPE_NONE, SPELL_TYPE_USELESS, SPELL_TYPE_DEFENSE, SPELL_TYPE_UNFORGIVABLE#, SPELL_TYPE_COMMON, SPELL_TYPE_POWERFUL from spells import Spell, spells, _INVALID_SPELL - -MAX_PLAYER_HEALTH = 1000 -MAX_STUNNED_ROUNDS = 10 +from game_config import MAX_PLAYER_HEALTH, MAX_STUNNED_ROUNDS class Player: def __init__(self, name: str, wand: Wand): diff --git a/spells.py b/spells.py index 8660d01..5422e95 100644 --- a/spells.py +++ b/spells.py @@ -1,4 +1,5 @@ import random +from game_config import CHANCE_HEAL_PARTLY, CHANCE_HEAL_FULLY, DEBUG_MODE SPELL_TYPE_NONE = -1 SPELL_TYPE_USELESS = 0 @@ -7,9 +8,7 @@ SPELL_TYPE_COMMON = 2 SPELL_TYPE_POWERFUL = 3 SPELL_TYPE_UNFORGIVABLE = 4 -# If SPELL_TYPE_DEFENSE is casted, always these chances to heal partly (restore 5% of max hp) or completely -CHANCE_HEAL_PARTLY = 25 -CHANCE_HEAL_FULLY = 5 + #Maximum Levenshtein distance. Eg if a user casts 'Pritrgo' instead of 'Protego', distance would be 2 and Protego would still be cast if MAX_LEVENSHTEIN_DISTANCE is at least 2 #Set to 0 to disable @@ -94,6 +93,11 @@ spells = { _INVALID_SPELL: Spell(0, 0, 0, "(internal) invalid spell", SPELL_TYPE_NONE) } +# Set succes rates to 100% if debug mode is enabled +if DEBUG_MODE: + for i in spells.items(): + i[1].succes_rate = 100 + ## ## Standalone spell functions ## @@ -145,4 +149,4 @@ def print_spells(): .strip('(') .strip(')') .replace(',', ':', 1) - ) \ No newline at end of file + ) \ No newline at end of file From fc8bee2f731b679b7fe7a2f2e150fbb91f82d723 Mon Sep 17 00:00:00 2001 From: Kwarde Date: Thu, 18 Jan 2024 16:57:32 +0100 Subject: [PATCH 09/10] Move MAX_LEVENSHTEIN_DISTANCE to game_config.py --- game_config.py | 5 +++-- spells.py | 14 ++++---------- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/game_config.py b/game_config.py index e04bf08..c76a6cc 100644 --- a/game_config.py +++ b/game_config.py @@ -7,8 +7,9 @@ MAX_STUNNED_ROUNDS = 10 # Max amount of rounds a player can be stunned ## ## Spells config ## -CHANCE_HEAL_PARTLY = 25 # Percentage chance a player is healed with 5% of max health when using a defensive spell -CHANCE_HEAL_FULLY = 5 # Percentage chance a player is fully healed when using a defensive spell +CHANCE_HEAL_PARTLY = 25 # Percentage chance a player is healed with 5% of max health when using a defensive spell +CHANCE_HEAL_FULLY = 5 # Percentage chance a player is fully healed when using a defensive spell +MAX_LEVENSHTEIN_DISTANCE = 3 # Max Levenshtein distance (max amount of typos a user can make) before considering a spell fails. Setting this to 0 disables it (and in that case also doesn't require the levenshtein package) ## ## Misc diff --git a/spells.py b/spells.py index 5422e95..e8dffd1 100644 --- a/spells.py +++ b/spells.py @@ -1,5 +1,8 @@ import random -from game_config import CHANCE_HEAL_PARTLY, CHANCE_HEAL_FULLY, DEBUG_MODE +from game_config import CHANCE_HEAL_PARTLY, CHANCE_HEAL_FULLY, DEBUG_MODE, MAX_LEVENSHTEIN_DISTANCE + +if MAX_LEVENSHTEIN_DISTANCE > 0: + from Levenshtein import distance SPELL_TYPE_NONE = -1 SPELL_TYPE_USELESS = 0 @@ -8,15 +11,6 @@ SPELL_TYPE_COMMON = 2 SPELL_TYPE_POWERFUL = 3 SPELL_TYPE_UNFORGIVABLE = 4 - - -#Maximum Levenshtein distance. Eg if a user casts 'Pritrgo' instead of 'Protego', distance would be 2 and Protego would still be cast if MAX_LEVENSHTEIN_DISTANCE is at least 2 -#Set to 0 to disable -MAX_LEVENSHTEIN_DISTANCE = 0 - -if MAX_LEVENSHTEIN_DISTANCE > 0: - from Levenshtein import distance - _INVALID_SPELL = ".@wizardduel@__spell_invalid__" #Internal usage only ## From 1dff4f524bb19089a5993318cef85614c19bea75 Mon Sep 17 00:00:00 2001 From: Kwarde Date: Thu, 18 Jan 2024 17:03:21 +0100 Subject: [PATCH 10/10] FIX: KeyError when returning a mistyped spell with Levenshtein distance --- game.py | 8 ++++---- spells.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/game.py b/game.py index 6bd3edd..98ee94c 100644 --- a/game.py +++ b/game.py @@ -47,12 +47,12 @@ def get_player_spell_from_input(player: Player): continue elif player_input.find("help", 0) != -1: find_what = player_input[5:] - spell = spells.get(find_spell_by_name(find_what)[0][0], None) + spell_name, spell_obj = find_spell_by_name(find_what)[0] - if spell is spells[_INVALID_SPELL]: - print(" Spell '{what}' does not exist!".format(what=find_what)) + if spell_obj is spells[_INVALID_SPELL]: + print(" Spell '{what}' does not exist!".format(what=spell_name)) else: - print("'{spell_name}':{spell_desc}".format(spell_name=find_what, spell_desc=spells[find_what])) + print("'{spell_name}':{spell_desc}".format(spell_name=spell_name, spell_desc=spell_obj)) continue else: return find_spell_by_name(player_input) diff --git a/spells.py b/spells.py index e8dffd1..bb3a9b0 100644 --- a/spells.py +++ b/spells.py @@ -107,7 +107,7 @@ def find_spell_by_name(input: str): # Returns a multidimensional tuple: ( ('spel for i in spells.items(): dist = distance(i[0].title(), input.title()) if dist <= MAX_LEVENSHTEIN_DISTANCE: - ret[1] = i + ret = (i[0], i[1]) break return (ret, dist)