Created
August 6, 2017 09:18
-
-
Save deontologician/c8851755669f463f97a9715dfae6eb62 to your computer and use it in GitHub Desktop.
Bad RPG
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import random | |
from collections import Counter | |
from pprint import pprint | |
import string | |
DAMAGE_TYPES = [ | |
'fire', | |
'water', | |
'earth', | |
'air', | |
'plant', | |
'biological', | |
'crystal', | |
'acid', | |
'magma', | |
'divine', | |
'cursed', | |
'spirit', | |
'necro', | |
'living', | |
'mechanical', | |
'electrical', | |
'nuclear', | |
'quantum', | |
'bone', | |
'emotional', | |
'psychological', | |
] | |
MAX_HP = 300 | |
def rand_name(): | |
vowels = "aeiouy" | |
consonants = "".join(set(string.ascii_lowercase) - set(vowels)) | |
name = "" | |
for i in range(0, random.randint(4, 6)): | |
if i % 2 == 0: | |
name += random.choice(consonants) | |
else: | |
name += random.choice(vowels) | |
return name.title() | |
def print_hist(hist, indent=4): | |
for type, dmg in hist.most_common(): | |
print(' '*(indent-1), type, ':', dmg) | |
def make_dmg_table(): | |
types = DAMAGE_TYPES[:] | |
random.shuffle(types) | |
table = {} | |
for t in types: | |
for i, x in enumerate(types): | |
if t == x: | |
table.setdefault(t, {})[x] = 0 | |
elif i > len(types) // 2: | |
table.setdefault(t, {})[x] = -(random.randint(1, 4)) | |
else: | |
table.setdefault(t, {})[x] = random.randint(1, 4) | |
return table | |
class Player: | |
def __init__(self): | |
self.reset_hp() | |
self.name = rand_name() | |
self.defend_hist = Counter({t: MAX_HP // 2 for t in DAMAGE_TYPES}) | |
self.attack_hist = Counter({t: MAX_HP // 2 for t in DAMAGE_TYPES}) | |
self.wins = 0 | |
self.losses = 0 | |
def reset_hp(self): | |
self.hp = MAX_HP | |
def attack(self): | |
return self._select(self.attack_hist) | |
def defend(self): | |
return self._select(self.defend_hist) | |
def attack_result(self, type, dmg): | |
self.attack_hist[type] = max(0, self.attack_hist[type] + dmg) | |
def defend_result(self, type, dmg): | |
self.defend_hist[type] = max(0, self.attack_hist[type] - dmg) | |
self.hp = min(self.hp + dmg, MAX_HP) | |
def alive(self): | |
return self.hp > 0 | |
def _select(self, hist): | |
total = sum(hist.values()) | |
stop = random.randint(0, total) | |
running_total = 0 | |
for type, dmg in hist.items(): | |
running_total += dmg | |
if running_total >= stop: | |
return type | |
class Game: | |
def __init__(self): | |
self.attacker = Player() | |
print('Player A is {a.name}'.format(a=self.attacker)) | |
self.defender = Player() | |
print('Player B is {b.name}'.format(b=self.defender)) | |
self.dmg_table = make_dmg_table() | |
self.round_num = 1 | |
self.match_num = 1 | |
def next_match(self): | |
self.attacker.reset_hp() | |
self.defender.reset_hp() | |
self.round_num = 1 | |
self.match_num += 1 | |
def play_round(self): | |
#print('\n== Round', self.round_num, '=>\n') | |
atck = self.attacker.attack() | |
dfnd = self.defender.defend() | |
b_dmg = self.dmg_table[atck][dfnd] * random.choice([1, 1, 1, 2, 2, 3]) | |
self.attacker.attack_result(atck, b_dmg) | |
self.defender.defend_result(dfnd, b_dmg) | |
#self.describe_round(atck, dfnd, b_dmg) | |
self.round_num += 1 | |
def do_match(self): | |
while True: | |
self.play_round() | |
if not self.defender.alive(): | |
print(self.defender.name, 'died, so the match is over.') | |
break | |
else: | |
self.defender, self.attacker = self.attacker, self.defender | |
self.attacker.wins += 1 | |
self.defender.losses += 1 | |
self.summarize_match() | |
def summarize_match(self): | |
# defender has always lost when we summarize | |
print('Match {self.match_num} is over. After {self.round_num} rounds ' | |
'{self.attacker.name} has killed {self.defender.name}' | |
.format(self=self)) | |
def describe_round(self, atck, dfnd, b_dmg): | |
args = { | |
'a': self.attacker.name, | |
'b': self.defender.name, | |
'atck': atck.title(), | |
'dfnd': dfnd.title(), | |
'b_hp_b': self.defender.hp - b_dmg, | |
'disp': 'Luckily' if b_dmg >= 0 else 'Unfortunately', | |
'dir': 'increased' if b_dmg >= 0 else 'reduced', | |
'b_hp_a': self.defender.hp, | |
} | |
print('{a} attacked {b} with {atck}, {b} defended with {dfnd}.'.format(**args)) | |
print('{disp}, this {dir} {b}\'s health from {b_hp_b} to {b_hp_a}'.format(**args)) | |
def main(): | |
game = Game() | |
for _ in range(10): | |
game.do_match() | |
game.next_match() | |
print(game.attacker.name, "'s histograms") | |
print("attacker") | |
print_hist(game.attacker.attack_hist) | |
print("defender") | |
print_hist(game.attacker.defend_hist) | |
print(game.defender.name, "'s histograms") | |
print("attacker") | |
print_hist(game.defender.attack_hist) | |
print("defender") | |
print_hist(game.defender.defend_hist) | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment