Skip to content

Instantly share code, notes, and snippets.

@nickyreinert
Last active January 28, 2023 14:49
Show Gist options
  • Save nickyreinert/05dc269fc5091d9ecc7b03836970cab0 to your computer and use it in GitHub Desktop.
Save nickyreinert/05dc269fc5091d9ecc7b03836970cab0 to your computer and use it in GitHub Desktop.
Display the source blob
Display the rendered blob
Raw
{
"cells": [
{
"cell_type": "code",
"execution_count": 314,
"metadata": {},
"outputs": [],
"source": [
"import random\n",
"from pprint import pprint as pp\n",
"from tqdm import tqdm\n",
"import csv\n",
"import json\n",
"import math\n",
"import sys\n",
"\n",
"class GameEngine:\n",
"\n",
" characters = {}\n",
" characters_variations = []\n",
" variable_attributes = {}\n",
" bonus_attack_activator = [1, 2, 3, 4, 5, 6]\n",
" bonus_defense_activator = [1, 2, 3, 4, 5, 6]\n",
" escape_activator = [2, 3, 4, 5]\n",
" dice_range = [1, 2, 3, 4, 5, 6]\n",
" debug = False\n",
" stats_summary = []\n",
" stats = []\n",
" stats_per_character = []\n",
"\n",
" def calculateAttackValue(self, character):\n",
" \n",
" attack_value = (1 + character['attributes']['attribute_1']) * (1 + character['attributes']['attribute_3'])\n",
" eleveated_attack_value = attack_value\n",
" # roll the attack dice\n",
" if 'attack' in character['bonus']:\n",
" if random.choice(self.dice_range) in self.bonus_attack_activator:\n",
" dice_1 = random.choice(self.dice_range)\n",
" dice_2 = random.choice(self.dice_range)\n",
" \n",
" if dice_1 > dice_2:\n",
" eleveated_attack_value *= dice_1\n",
" else:\n",
" eleveated_attack_value *= dice_2\n",
"\n",
" if self.debug : print(f'\\tbase attack: {attack_value}, elevated attack {eleveated_attack_value}')\n",
"\n",
" return eleveated_attack_value, attack_value\n",
"\n",
" def calculateDefenseValue(self, character):\n",
"\n",
" defense_value = character['attributes']['attribute_2'] + character['attributes']['attribute_4']\n",
" elevated_defense_value = defense_value\n",
" \n",
" # roll the defense dice\n",
" if 'defense' in character['bonus']:\n",
" if random.choice(self.dice_range) in self.bonus_defense_activator:\n",
" \n",
" dice_1 = random.choice(self.dice_range)\n",
" dice_2 = random.choice(self.dice_range)\n",
" \n",
" if dice_1 > dice_2:\n",
" elevated_defense_value += dice_1\n",
" else:\n",
" elevated_defense_value += dice_2\n",
"\n",
" if self.debug : print(f'\\tbase defense: {defense_value}, elevated defense {elevated_defense_value}')\n",
"\n",
" return elevated_defense_value, defense_value\n",
"\n",
" def calculateEscapeValue(self, character):\n",
" # roll the escape dice\n",
" escape_success = False\n",
" if 'escape' in character['bonus']:\n",
" if random.choice(self.dice_range) in self.bonus_attack_activator:\n",
" escape_bonus = random.choice(self.dice_range)\n",
"\n",
" if escape_bonus in self.escape_activator:\n",
" escape_success = True \n",
" else:\n",
" escape_success = False\n",
" \n",
" if self.debug : print(f'\\tescape success: {escape_success}')\n",
"\n",
" return escape_success\n",
"\n",
" def __init__(self, \n",
" debug=False, \n",
" bonus_attack_activator = [1, 2, 3, 4, 5, 6],\n",
" bonus_defense_activator = [1, 2, 3, 4, 5, 6],\n",
" escape_activator = [1, 2, 3, 4, 5, 6],\n",
" dice_range = [1, 2, 3, 4, 5, 6],\n",
" variable_attributes = {}\n",
" ):\n",
"\n",
" self.debug = debug\n",
" self.bonus_attack_activator = bonus_attack_activator\n",
" self.bonus_defense_activator = bonus_defense_activator\n",
" self.escape_activator = escape_activator\n",
" self.dice_range = dice_range\n",
" self.variable_attributes = variable_attributes\n",
"\n",
" self.stats_summary = []\n",
" self.stats = []\n",
" self.stats_per_character = []\n",
" \n",
" with open(f'characters.json', 'r') as file:\n",
" self.characters = json.load(file)\n",
"\n",
" def preparePlayers(self, players = 4, characters_per_stack = 0, sibblings_per_character = 1):\n",
" \n",
" if self.debug == True: print(f'Creating {players} players with {characters_per_stack} characters per stack from a deck of {sibblings_per_character * len(self.characters)} characters ({len(self.characters)} unique)')\n",
" \n",
" max_characters_per_stack = math.floor(sibblings_per_character * len(self.characters) / players)\n",
" if characters_per_stack > max_characters_per_stack:\n",
" print(f'Error: too many characters per stack. Maximum is {max_characters_per_stack}')\n",
" sys.exit()\n",
"\n",
" # put all characters x sibblings per characters to a list\n",
" available_characters = sibblings_per_character * list(self.characters.keys())\n",
" # create a dict that contains a unique id for every card\n",
" full_stack = {index: character_id for index, character_id in enumerate(available_characters)}\n",
" # create a second dict that contanins a unique id for available cards only\n",
" available_stack = full_stack.copy()\n",
"\n",
" self.player_stacks = {}\n",
" for player in range(1, players + 1, 1):\n",
" \n",
" # get a sample of unique ids for all characters from the available stack\n",
" current_stack = random.sample(list(available_stack.keys()), characters_per_stack)\n",
"\n",
" # add sampled stack of characters to the player stack\n",
" # almost the same list comphrehension as above, but index IN current_stack instead of index NOT IN current_stack\n",
" self.player_stacks['player_no_' + str(player)] = {index: full_stack[index] for index in available_stack if index in current_stack}\n",
" \n",
" # remove selected characters from the available stack, to not select them again\n",
" # use unique ids from current_stack\n",
" available_stack = {index: full_stack[index] for index in available_stack if index not in current_stack}\n",
"\n",
" if self.debug == True: print(f'\\tDone, {len(available_stack)} characters left in the deck')\n",
"\n",
" def reindex_player_stacks(self):\n",
" for player in self.player_stacks:\n",
" reindexed_stack = {}\n",
" index = 0\n",
" for card in self.player_stacks[player]:\n",
" reindexed_stack[index] = self.player_stacks[player][card]\n",
" index += 1\n",
"\n",
" self.player_stacks[player] = reindexed_stack\n",
"\n",
" def simulateRealGames(self, games = 1, round_limit = 100, characters_per_stack = 0, sibblings_per_character = 1, players = 4):\n",
"\n",
" if self.debug == True: print(f'Start simulation of {games} games for {players} players and {len(self.characters_variations)} character variations')\n",
"\n",
" # first loop through all character variations\n",
" for character_variation in self.characters_variations:\n",
"\n",
" # now loop through n games\n",
" for game in range(1, games + 1):\n",
" \n",
" stats_per_character = {}\n",
"\n",
" self.preparePlayers(players, characters_per_stack, sibblings_per_character)\n",
"\n",
" game_is_running = True\n",
" current_player_index = 0\n",
" current_round = 0\n",
" # finally loop through rounds until one player wins\n",
" \n",
" while game_is_running:\n",
"\n",
" current_round += 1\n",
" if current_round > round_limit:\n",
" game_is_running = False\n",
" if self.debug == True: print(f'#########\\nGame {game} is over, round limit of {round_limit} reached')\n",
" if self.debug == True: \n",
" print(f'Game stats:')\n",
" for player in self.player_stacks:\n",
" print(f'\\t{player}: {len(self.player_stacks[player])} cards left')\n",
" self.stats_summary.append({\n",
" 'characters_variation': character_variation['variation'],\n",
" 'game': game,\n",
" 'rounds_played': round_limit,\n",
" 'winner': None\n",
" })\n",
" break\n",
"\n",
" # re-index player stacks after each round\n",
" self.reindex_player_stacks()\n",
"\n",
" # ['player_no_1', 'player_no_2', 'player_no_3', 'player_no_4']\n",
" # ['player_no_1', 'player_no_2', 'player_no_4']\n",
"\n",
" if current_player_index >= len(self.player_stacks): current_player_index = 0\n",
" \n",
" # look for one opponent\n",
" current_player = list(self.player_stacks.keys())[current_player_index]\n",
" available_opponents = list(self.player_stacks.keys())\n",
" available_opponents.remove(list(self.player_stacks.keys())[current_player_index])\n",
" current_opponent = random.choice(available_opponents)\n",
" \n",
" if self.debug: print(f'Round {current_round}, {current_player} vs {current_opponent}')\n",
"\n",
" stats_per_character = self.fight(character_variation=character_variation, attacker=current_player, defender=current_opponent, round=current_round, game=game, stats_per_character=stats_per_character)\n",
"\n",
" # check if one player has no cards left\n",
" for player in list(self.player_stacks.keys()):\n",
" if len(self.player_stacks[player]) == 0:\n",
" if self.debug: print(f'Player {player} has no cards left and leaves the game')\n",
" del self.player_stacks[player]\n",
" \n",
" # check if only one player is left\n",
" if len(self.player_stacks) == 1:\n",
" game_is_running = False\n",
" if self.debug: print(f'Game {game} is over after {current_round} rounds, {list(self.player_stacks.keys())[0]} wins')\n",
" self.stats_summary.append({\n",
" 'characters_variation': character_variation['variation'],\n",
" 'game': game,\n",
" 'rounds_played': current_round,\n",
" 'winner': list(self.player_stacks.keys())[0]\n",
" })\n",
"\n",
" break\n",
"\n",
" current_player_index += 1\n",
" \n",
" for character_id in stats_per_character:\n",
" self.stats_per_character.append(stats_per_character[character_id])\n",
"\n",
" def fight(self, character_variation, attacker, defender, round, game, stats_per_character):\n",
"\n",
" character_variation_id = character_variation['variation']\n",
" characters = character_variation['characters']\n",
"\n",
" # get the first character of each, attacker and defender stack\n",
" attacking_character = self.player_stacks[attacker][0]\n",
" defending_character = self.player_stacks[defender][0] \n",
"\n",
" if not attacking_character in stats_per_character:\n",
" stats_per_character[attacking_character] = {\n",
" 'flight': 'real_game',\n",
" 'character': attacking_character,\n",
" 'characters_variation': character_variation['variation'],\n",
" 'game': game,\n",
" 'round': round,\n",
" 'wins_count': 0,\n",
" 'losses_count': 0,\n",
" 'draws_count': 0,\n",
" 'escapes_count': 0\n",
" }\n",
"\n",
" if not defending_character in stats_per_character:\n",
" stats_per_character[defending_character] = {\n",
" 'flight': 'real_game',\n",
" 'character': defending_character,\n",
" 'characters_variation': character_variation['variation'],\n",
" 'game': game,\n",
" 'round': round,\n",
" 'wins_count': 0,\n",
" 'losses_count': 0,\n",
" 'draws_count': 0,\n",
" 'escapes_count': 0\n",
" }\n",
"\n",
" # calculate the base attack and defense values\n",
" attack_value, base_attack_value = self.calculateAttackValue(characters[attacking_character])\n",
" defense_value, base_defense_value = self.calculateDefenseValue(characters[defending_character])\n",
"\n",
" escape_success = self.calculateEscapeValue(characters[attacking_character])\n",
"\n",
" stats = {\n",
" 'flight': 'real_game',\n",
" 'characters_variation': character_variation_id,\n",
" 'game': game,\n",
" 'round': round,\n",
" 'attacker': attacker,\n",
" 'defender': defender,\n",
" 'attacking_character': attacking_character,\n",
" 'defending_character': defending_character,\n",
" 'base_attack_value': base_attack_value,\n",
" 'base_defense_value': base_defense_value,\n",
" 'attack_value': attack_value,\n",
" 'defense_value': defense_value,\n",
" 'escape_success': escape_success\n",
" }\n",
"\n",
" # finish the fight\n",
" if escape_success == False:\n",
"\n",
" if attack_value > defense_value:\n",
" \n",
" if self.debug : print(f'\\t{attacker} wins')\n",
" # move first card from defender stack to attacker stack\n",
" # same for first card from attacker stack\n",
" self.player_stacks[attacker][len(self.player_stacks[attacker])] = self.player_stacks[defender][0]\n",
" self.player_stacks[attacker][len(self.player_stacks[attacker])] = self.player_stacks[attacker][0]\n",
"\n",
" self.player_stacks[attacker] = {k: v for k, v in self.player_stacks[attacker].items() if k != 0}\n",
" self.player_stacks[defender] = {k: v for k, v in self.player_stacks[defender].items() if k != 0}\n",
"\n",
" stats['winner'] = attacker\n",
" stats['loser'] = defender\n",
" stats['winning_character'] = attacking_character\n",
" stats['losing_character'] = defending_character\n",
" stats_per_character[attacking_character]['wins_count'] += 1\n",
" stats_per_character[defending_character]['losses_count'] += 1\n",
"\n",
" elif attack_value <= defense_value:\n",
"\n",
" if self.debug : print(f'\\t{defender} wins')\n",
"\n",
" # move first card from attacker stack to defender stack\n",
" # same for first card from defender stack\n",
" self.player_stacks[defender][len(self.player_stacks[defender])] = self.player_stacks[attacker][0]\n",
" self.player_stacks[defender][len(self.player_stacks[defender])] = self.player_stacks[defender][0]\n",
"\n",
" self.player_stacks[attacker] = {k: v for k, v in self.player_stacks[attacker].items() if k != 0}\n",
" self.player_stacks[defender] = {k: v for k, v in self.player_stacks[defender].items() if k != 0}\n",
"\n",
" stats['winner'] = defender\n",
" stats['loser'] = attacker\n",
" stats['winning_character'] = defending_character\n",
" stats['losing_character'] = attacking_character\n",
"\n",
" stats_per_character[attacking_character]['losses_count'] += 1\n",
" stats_per_character[defending_character]['wins_count'] += 1\n",
"\n",
" else:\n",
"\n",
" if self.debug : print(f'\\t{attacker} and {defender} draw')\n",
" \n",
" # move first card from attacker stack to attacker stack\n",
" # move first card from attacker stack to attacker stack\n",
" self.player_stacks[attacker][len(self.player_stacks[attacker])] = self.player_stacks[attacker][0]\n",
" self.player_stacks[defender][len(self.player_stacks[defender])] = self.player_stacks[defender][0]\n",
"\n",
" self.player_stacks[attacker] = {k: v for k, v in self.player_stacks[attacker].items() if k != 0}\n",
" self.player_stacks[defender] = {k: v for k, v in self.player_stacks[defender].items() if k != 0}\n",
" \n",
" stats['winner'] = None\n",
" stats['loser'] = None\n",
" stats['winning_character'] = None\n",
" stats['losing_character'] = None\n",
"\n",
" stats_per_character[attacking_character]['draws_count'] += 1\n",
" stats_per_character[defending_character]['draws_count'] += 1\n",
"\n",
" else:\n",
"\n",
" if self.debug : print(f'\\t{defender} escapes')\n",
" \n",
" # move first card from attacker stack to attacker stack\n",
" # move first card from attacker stack to attacker stack\n",
" self.player_stacks[attacker][len(self.player_stacks[attacker])] = self.player_stacks[attacker][0]\n",
" self.player_stacks[defender][len(self.player_stacks[defender])] = self.player_stacks[defender][0]\n",
"\n",
" self.player_stacks[attacker] = {k: v for k, v in self.player_stacks[attacker].items() if k != 0}\n",
" self.player_stacks[defender] = {k: v for k, v in self.player_stacks[defender].items() if k != 0}\n",
"\n",
" stats['winner'] = None\n",
" stats['loser'] = None\n",
" stats['winning_character'] = None\n",
" stats['losing_character'] = None\n",
"\n",
" stats_per_character[defending_character]['escapes_count'] += 1\n",
"\n",
"\n",
" self.stats.append(stats)\n",
"\n",
" return stats_per_character\n",
" \n",
" def simulateAllvsAll(self, games):\n",
"\n",
" for character_variation in self.characters_variations:\n",
"\n",
" character_variation_id = character_variation['variation']\n",
" characters = character_variation['characters']\n",
"\n",
" for game in range(1, games + 1, 1):\n",
"\n",
" stats_per_character = {}\n",
"\n",
" for character_1 in characters:\n",
"\n",
" if not str(character_1) + str(character_variation_id) in stats_per_character:\n",
" stats_per_character[str(character_1) + str(character_variation_id)] = {\n",
" 'flight': 'all_vs_all',\n",
" 'character': character_1,\n",
" 'characters_variation': character_variation_id,\n",
" 'game': game,\n",
" 'round': None,\n",
" 'wins_count': 0,\n",
" 'losses_count': 0,\n",
" 'draws_count': 0,\n",
" 'escapes_count': 0\n",
" }\n",
"\n",
" # calculate the base attack and defense values\n",
" attack_value, base_attack_value = self.calculateAttackValue(characters[character_1])\n",
"\n",
" for character_2 in characters:\n",
"\n",
" if not str(character_2) + str(character_variation_id) in stats_per_character:\n",
" stats_per_character[str(character_2) + str(character_variation_id)] = {\n",
" 'flight': 'all_vs_all',\n",
" 'character': character_2,\n",
" 'characters_variation': character_variation_id,\n",
" 'game': game,\n",
" 'round': None,\n",
" 'wins_count': 0,\n",
" 'losses_count': 0,\n",
" 'draws_count': 0,\n",
" 'escapes_count': 0\n",
" }\n",
"\n",
" defense_value, base_defense_value = self.calculateDefenseValue(characters[character_2])\n",
"\n",
" escape_success = self.calculateEscapeValue(characters[character_2])\n",
"\n",
" stats = {\n",
" 'flight': 'all_vs_all',\n",
" 'characters_variation': character_variation_id,\n",
" 'game': game,\n",
" 'round': None,\n",
" 'attacking_character': character_1,\n",
" 'defending_character': character_2,\n",
" 'base_attack_value': base_attack_value,\n",
" 'base_defense_value': base_defense_value,\n",
" 'attack_value': attack_value,\n",
" 'defense_value': defense_value,\n",
" 'escape_success': escape_success\n",
"\n",
" }\n",
"\n",
" if escape_success == False:\n",
"\n",
" if attack_value > defense_value:\n",
"\n",
" stats_per_character[str(character_1) + str(character_variation_id)]['wins_count'] += 1\n",
" stats_per_character[str(character_2) + str(character_variation_id)]['losses_count'] += 1\n",
" stats['winner'] = None\n",
" stats['loser'] = None\n",
" stats['winning_character'] = character_1\n",
" stats['losing_character'] = character_2\n",
" \n",
" elif attack_value < defense_value:\n",
" \n",
" stats_per_character[str(character_1) + str(character_variation_id)]['losses_count'] += 1\n",
" stats_per_character[str(character_2) + str(character_variation_id)]['wins_count'] += 1\n",
" stats['winner'] = None\n",
" stats['loser'] = None\n",
" stats['winning_character'] = character_2\n",
" stats['losing_character'] = character_1\n",
"\n",
" else:\n",
" \n",
" stats_per_character[str(character_1) + str(character_variation_id)]['draws_count'] += 1\n",
" stats_per_character[str(character_2) + str(character_variation_id)]['draws_count'] += 1\n",
" stats['winner'] = None\n",
" stats['loser'] = None\n",
" stats['winning_character'] = None\n",
" stats['losing_character'] = None\n",
" else:\n",
"\n",
" stats_per_character[str(character_2) + str(character_variation_id)]['escapes_count'] += 1\n",
" stats['winner'] = None\n",
" stats['loser'] = None\n",
" stats['winning_character'] = None\n",
" stats['losing_character'] = None\n",
"\n",
" self.stats.append(stats)\n",
"\n",
" for character_id in stats_per_character:\n",
" self.stats_per_character.append(stats_per_character[character_id])\n",
"\n",
" def calculateWinLoseQuoteForCharacters(self, iteration, game):\n",
" \n",
" temp_game_stats = self.game_stats.copy()\n",
"\n",
" for character in self.characters.copy():\n",
" temp_game_stats.append({\n",
" 'iteration': iteration,\n",
" 'game': game,\n",
" 'character': character,\n",
" 'wins': self.characters[character]['stats']['wins'],\n",
" 'losses': self.characters[character]['stats']['losses'],\n",
" 'draws': self.characters[character]['stats']['draws'],\n",
" 'attacked': self.characters[character]['stats']['attacked'],\n",
" 'defended': self.characters[character]['stats']['defended'],\n",
" 'escaped': self.characters[character]['stats']['escaped']\n",
" })\n",
"\n",
" self.game_stats = temp_game_stats.copy()\n",
" \n",
" def exportGameStatsToCsv(self):\n",
" with open('stats.csv', 'w') as file:\n",
" writer = csv.DictWriter(file, fieldnames=self.stats[0].keys())\n",
"\n",
" writer.writeheader()\n",
" writer.writerows(self.stats)\n",
"\n",
" with open('stats_summary.csv', 'w') as file:\n",
" writer = csv.DictWriter(file, fieldnames=self.stats_summary[0].keys())\n",
"\n",
" writer.writeheader()\n",
" writer.writerows(self.stats_summary)\n",
"\n",
" with open('stats_per_character.csv', 'w') as file:\n",
" writer = csv.DictWriter(file, fieldnames=self.stats_per_character[0].keys())\n",
"\n",
" writer.writeheader()\n",
" writer.writerows(self.stats_per_character)\n",
"\n",
" def randomizeCharacterAttributes(self, variations=1):\n",
" '''Loop through all characters and randomize given attributes'''\n",
" if self.debug : print(f'Creating {variations} variations for {len(self.characters)} characters')\n",
"\n",
" self.characters_variations = []\n",
"\n",
" # append base defintion\n",
" self.characters_variations.append({'variation': 0, 'characters': self.characters.copy()})\n",
"\n",
" # increment by 1 - 0 is base definition\n",
" for variation in range(1, variations + 1, 1):\n",
" for character in self.characters:\n",
" for attribute in self.characters[character]['attributes']:\n",
" if attribute in self.variable_attributes:\n",
" self.characters[character]['attributes'][attribute] = random.choice(self.variable_attributes[attribute])\n",
" \n",
" self.characters_variations.append({'variation': variation, 'characters': self.characters.copy()})\n",
" \n",
" with open(f'characters_variations.json', 'w') as file:\n",
" json.dump(self.characters_variations, file)\n",
"\n",
" def showSummary(self):\n",
"\n",
" for header in list(self.stats_summary[0].keys()):\n",
" print(f'{header}', end='\\t')\n",
"\n",
" print('\\n--------------------------------------------------------------------\\n', end='')\n",
" \n",
" for row in self.stats_summary:\n",
" for column in list(row.keys()):\n",
" print(f'{row[column]}', end='\\t')\n",
" print('') \n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"variable_attributes = {\n",
" 'attribute_2': range(1, 50, 1), # min, max\n",
" 'attribute_4': range(1, 30, 1), # min, max\n",
"}\n",
"\n",
"gameEngine = GameEngine(\n",
" debug=False, \n",
" bonus_attack_activator=[1, 6],\n",
" bonus_defense_activator=[1, 2, 3, 4, 5, 6],\n",
" escape_activator=[3], \n",
" dice_range=[1, 2, 3, 4, 5, 6],\n",
" variable_attributes=variable_attributes)\n",
"\n",
"gameEngine.randomizeCharacterAttributes(variations=10)\n",
"\n",
"gameEngine.simulateRealGames(games=100, round_limit=100, players=4, characters_per_stack=5, sibblings_per_character=1)\n",
"\n",
"gameEngine.simulateAllvsAll(games=100)\n",
"\n",
"gameEngine.exportGameStatsToCsv()\n",
"\n",
"gameEngine.showSummary()"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.13"
},
"orig_nbformat": 4,
"vscode": {
"interpreter": {
"hash": "aee8b7b246df8f9039afb4144a1f6fd8d2ca17a180786b69acc140d282b71a49"
}
}
},
"nbformat": 4,
"nbformat_minor": 2
}
@nickyreinert
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment