Découpage en fonctions
On le sait, et je le répète depuis le début : le code actuel de notre TP est très répétitif. Le but ici va donc être de le factoriser pour enfin gagner en lisibilité.
Pour cela on va se donner les différents objectifs suivants :
- Diviser et grouper les différentes commandes de saisie et de validation en fonctions.
- Ajouter une fonction pour initialiser un nouveau joueur.
- Ajouter une fonction pour réaliser un tour de jeu (joueur courant contre adversaire).
- Ajouter une fonction dédiée à l’application d’une attaque.
- Ajouter une fonction pour identifier le gagnant.
- Paramétrer ces fonctions selon les besoins.
Cette liste d’objectifs est bien sûr donnée à titre indicatif, n’hésitez pas à ajouter d’autres fonctions si vous les trouvez utiles.
Solution
Voici la solution que je propose pour ce TP.
Elle repose sur plusieurs fonctions, notamment get_choice_input(choices, error_message)
qui permet de demander une saisie à l’utilisateur et de la vérifier en fonction des choix prévus, et game_turn(player, opponent)
qui exécute un tour de jeu (sélection et application d’une attaque).
monsters = {
'pythachu': {
'name': 'Pythachu',
'attacks': ['tonnerre', 'charge'],
},
'pythard': {
'name': 'Pythard',
'attacks': ['jet-de-flotte', 'charge'],
},
'ponytha': {
'name': 'Ponytha',
'attacks': ['brûlure', 'charge'],
},
}
attacks = {
'charge': {'damages': 20},
'tonnerre': {'damages': 50},
'jet-de-flotte': {'damages': 40},
'brûlure': {'damages': 40},
}
def get_choice_input(choices, error_message):
entry = input('> ').lower()
while entry not in choices:
print(error_message)
entry = input('> ').lower()
return choices[entry]
def get_player(player_id):
print('Joueur', player_id, 'quel monstre choisissez-vous ?')
monster = get_choice_input(monsters, 'Monstre invalide')
pv = int(input('Quel est son nombre de PV ? '))
return {'id': player_id, 'monster': monster, 'pv': pv}
def get_players():
print('Monstres disponibles :')
for monster in monsters.values():
print('-', monster['name'])
return get_player(1), get_player(2)
def apply_attack(attack, opponent):
opponent['pv'] -= attack['damages']
if opponent['pv'] < 0:
opponent['pv'] = 0
def game_turn(player, opponent):
# Si le joueur est KO, il n'attaque pas
if player['pv'] <= 0:
return
monster_attacks = {}
print('Joueur', player['id'], 'quelle attaque utilisez-vous ?')
for name in player['monster']['attacks']:
# On récupère les attaques disponibles pour ce monstre
monster_attacks[name] = attacks[name]
print('-', name.capitalize(), -attacks[name]['damages'], 'PV')
attack = get_choice_input(monster_attacks, 'Attaque invalide')
apply_attack(attack, opponent)
print(
player['monster']['name'],
'attaque',
opponent['monster']['name'],
'qui perd',
attack['damages'],
'PV, il lui en reste',
opponent['pv'],
)
def get_winner(player1, player2):
if player1['pv'] > player2['pv']:
return player1
else:
return player2
player1, player2 = get_players()
print()
print(player1['monster']['name'], 'affronte', player2['monster']['name'])
print()
while player1['pv'] > 0 and player2['pv'] > 0:
game_turn(player1, player2)
game_turn(player2, player1)
winner = get_winner(player1, player2)
print('Le joueur', winner['id'], 'remporte le combat avec', winner['monster']['name'])
Tests
On va maintenant ajouter quelques tests à notre jeu, pour vérifier le bon comportement de certaines fonctions.
Malheureusement, beaucoup de nos fonctions attendent des saisies de l’utilisateur, et nous ne sommes pas en mesure de les tester automatiquement pour le moment.
Nos tests vont donc se résumer aux fonctions qui n’intéragissent pas avec l’utilisateur : dans ma solution il s’agit des fonctions apply_attack
et get_winner
.
Pour les autres fonctions, il faudra pour l’instant se contenter de tests manuels en exécutant le code du TP.
Pour la fonction apply_attack
, nous voulons nous assurer que les dégâts correspondants à l’attaque sont bien soustraits aux points de vie du joueur adverse.
Nous souhaitons aussi vérifier que les points de vie ne descendent jamais en dessous de zéro.
Pour ce qui est de get_winner
, on cherche à contrôler que c’est le joueur avec le plus de points de vie qui est identifié comme gagnant.
Le cas de l’égalité entre joueurs ne nous intéresse pas vraiment, car il ne peut pas se produire en jeu, mais on peut toujours le vérifier pour s’assurer que la fonction est cohérente (renvoie toujours le deuxième joueur par exemple).
Solution
Voilà donc les deux fonctions de tests que l’on peut ajouter et exécuter dans notre TP pour vérifier le comportement de nos fonctions.
def test_apply_attack():
player = {'id': 0, 'monster': monsters['pythachu'], 'pv': 100}
apply_attack(attacks['brûlure'], player)
assert player['pv'] == 60
apply_attack(attacks['tonnerre'], player)
assert player['pv'] == 10
apply_attack(attacks['charge'], player)
assert player['pv'] == 0
def test_get_winner():
player1 = {'id': 0, 'monster': monsters['pythachu'], 'pv': 100}
player2 = {'id': 0, 'monster': monsters['pythard'], 'pv': 0}
assert get_winner(player1, player2) == player1
assert get_winner(player2, player1) == player1
player2['pv'] = 120
assert get_winner(player1, player2) == player2
assert get_winner(player2, player1) == player2
player1['pv'] = player2['pv'] = 0
assert get_winner(player1, player2) == player2
assert get_winner(player2, player1) == player1