Update system
This commit is contained in:
parent
27e6cf6854
commit
7259616b8b
|
|
@ -245,6 +245,10 @@ func _replace_fainted() -> void:
|
|||
|
||||
#---------- requetes (utilisees par les controleurs et TargetResolver) ----------
|
||||
|
||||
func random_pick(items : Array) -> Variant:
|
||||
#tirage dans le generateur du combat (et non le hasard global) : un meme seed rejoue exactement le meme combat
|
||||
return items[rng.randi_range(0, items.size() - 1)]
|
||||
|
||||
func get_slot(id : int) -> BattleSlot:
|
||||
return slots[id] if id >= 0 and id < slots.size() else null
|
||||
|
||||
|
|
|
|||
|
|
@ -1,20 +1,33 @@
|
|||
class_name BattleDebug
|
||||
extends Node
|
||||
|
||||
#banc d'essai : simule un combat complet IA contre IA.
|
||||
#le journal n'est PAS ecrit dans la console (trop lent) : il est garde en memoire, puis
|
||||
#affiche a l'ecran et/ou ecrit dans un fichier a la fin du combat. La console ne recoit qu'un resume.
|
||||
#ajoute ce node a une scene, regle le format dans l'inspecteur, lance la scene.
|
||||
#utilise des equipes aleatoires (par defaut) : n'abime pas ton equipe de jeu.
|
||||
#banc d'essai du moteur de combat (IA contre IA). Trois modes :
|
||||
#
|
||||
#1. COMBAT UNIQUE (par defaut) : format reglable dans l'inspecteur, journal complet.
|
||||
#2. SIMULATION DE MASSE : mass_simulation_count > 0 -> N combats aleatoires (formats, equipes et niveaux varies),
|
||||
# sans affichage. Le rapport liste les combats suspects (limite de tours atteinte, changements de Pokemon
|
||||
# excessifs...), chacun avec son "seed".
|
||||
#3. REJEU : replay_seed = un seed du rapport -> rejoue exactement ce combat, avec le journal complet.
|
||||
#
|
||||
#le journal n'est PAS ecrit dans la console (trop lent) : il est affiche a l'ecran et/ou ecrit dans un fichier.
|
||||
#la console ne recoit que de courtes lignes de progression.
|
||||
|
||||
@export var run_on_ready : bool = true
|
||||
@export var max_turns : int = 100 #securite : match nul au-dela (0 = illimite)
|
||||
|
||||
@export_group("Combat unique")
|
||||
@export_range(1, 3) var player_slots : int = 1 #emplacements du joueur
|
||||
@export_range(1, 5) var enemy_count : int = 1 #nombre d'adversaires (chacun est un participant)
|
||||
@export_range(1, 3) var enemy_slots_each : int = 1 #emplacements de chaque adversaire
|
||||
@export_range(1, 6) var team_size : int = 3
|
||||
@export_range(1, 100) var level : int = 10
|
||||
@export var use_game_team : bool = false #true : utilise GameData.player_team (qui sera modifiee !)
|
||||
@export var max_turns : int = 100 #securite : match nul au-dela (0 = illimite)
|
||||
|
||||
@export_group("Simulation de masse")
|
||||
@export var mass_simulation_count : int = 0 #0 = desactivee
|
||||
@export_range(1, 100) var level_min : int = 5
|
||||
@export_range(1, 100) var level_max : int = 50
|
||||
@export var replay_seed : int = 0 #0 = desactive ; sinon rejoue le combat de ce seed
|
||||
|
||||
@export_group("Sortie")
|
||||
@export var show_log_on_screen : bool = true
|
||||
|
|
@ -24,39 +37,27 @@ const LOG_PATH : String = "user://battle_debug.log"
|
|||
var _log_layer : CanvasLayer = null
|
||||
|
||||
func _ready() -> void:
|
||||
if run_on_ready:
|
||||
if !run_on_ready: return
|
||||
await get_tree().create_timer(0.5).timeout
|
||||
run_test()
|
||||
if mass_simulation_count > 0: run_mass_simulation()
|
||||
elif replay_seed != 0: replay(replay_seed)
|
||||
else: run_test()
|
||||
|
||||
#---------- 1. combat unique ----------
|
||||
|
||||
func run_test() -> void:
|
||||
if use_game_team:
|
||||
GameData.load_data()
|
||||
|
||||
var player_team : PokemonTeam = GameData.player_team if use_game_team else _random_team(maxi(team_size, player_slots))
|
||||
|
||||
var setup := BattleSetup.new()
|
||||
setup.add(BattleSetup.player_participant(player_team, player_slots, null, BattleAI.new()))
|
||||
for i in enemy_count:
|
||||
var team : PokemonTeam = _random_team(enemy_slots_each + 1)
|
||||
var participant := BattleParticipant.new("Adversaire %d" % (i + 1), team, 1, BattleAI.new(), enemy_slots_each)
|
||||
setup.add(participant)
|
||||
setup.add(BattleParticipant.new("Adversaire %d" % (i + 1), team, 1, BattleAI.new(), enemy_slots_each))
|
||||
|
||||
if !setup.is_valid():
|
||||
push_error("BattleDebug : combat invalide (une equipe n'a aucun pokemon apte)")
|
||||
return
|
||||
|
||||
var presenter: BattleLogPresenter = BattleLogPresenter.new()
|
||||
var battle := Battle.new(setup, presenter)
|
||||
battle.max_turns = max_turns
|
||||
var outcome : Battle.Outcome = await battle.run()
|
||||
|
||||
var header : String = "===== Combat : %d contre %d =====" % [player_slots, enemy_count * enemy_slots_each]
|
||||
var summary : String = "Résultat : %s après %d tours (%d lignes de journal)" % [Battle.Outcome.keys()[outcome], battle.turn, presenter.lines.size()]
|
||||
var text : String = header + "\n" + "\n".join(presenter.lines) + "\n===== " + summary + " ====="
|
||||
|
||||
print("[BattleDebug] " + summary) #une seule ligne dans la console
|
||||
if write_log_file: _write_log(text)
|
||||
if show_log_on_screen: _show_log(text, summary)
|
||||
await _run_with_log(setup, "Combat : %s" % _describe(setup))
|
||||
|
||||
func _random_team(size : int) -> PokemonTeam:
|
||||
var team := PokemonTeam.new()
|
||||
|
|
@ -69,8 +70,137 @@ func _random_team(size : int) -> PokemonTeam:
|
|||
if species: team.add_pokemon(PokemonInstance.create(species, level))
|
||||
return team
|
||||
|
||||
#---------- 3. rejeu ----------
|
||||
|
||||
func replay(seed_value : int) -> void:
|
||||
var setup : BattleSetup = _build_setup(seed_value)
|
||||
if !setup.is_valid():
|
||||
push_error("BattleDebug : le seed %d ne donne pas de combat valide" % seed_value)
|
||||
return
|
||||
await _run_with_log(setup, "Rejeu du seed %d : %s" % [seed_value, _describe(setup)])
|
||||
|
||||
func _run_with_log(setup : BattleSetup, title : String) -> void:
|
||||
var presenter := BattleLogPresenter.new()
|
||||
var battle := Battle.new(setup, presenter)
|
||||
battle.max_turns = max_turns
|
||||
var outcome : Battle.Outcome = await battle.run()
|
||||
|
||||
var summary : String = "Résultat : %s après %d tours (%d lignes de journal)" % [Battle.Outcome.keys()[outcome], battle.turn, presenter.lines.size()]
|
||||
var text : String = "===== " + title + " =====\n" + "\n".join(presenter.lines) + "\n===== " + summary + " ====="
|
||||
print("[BattleDebug] " + summary)
|
||||
_output(text, summary)
|
||||
|
||||
#---------- 2. simulation de masse ----------
|
||||
|
||||
func run_mass_simulation() -> void:
|
||||
var base_seed : int = randi() % 900000 + 1
|
||||
var wins : int = 0
|
||||
var losses : int = 0
|
||||
var draws : int = 0
|
||||
var played : int = 0
|
||||
var total_turns : int = 0
|
||||
var max_turns_seen : int = 0
|
||||
var total_switches : int = 0
|
||||
var max_switches : int = 0
|
||||
var suspects : PackedStringArray = PackedStringArray()
|
||||
|
||||
for i in mass_simulation_count:
|
||||
var seed_value : int = base_seed + i
|
||||
var setup : BattleSetup = _build_setup(seed_value)
|
||||
if !setup.is_valid(): continue
|
||||
|
||||
var stats := BattleStatsPresenter.new()
|
||||
var battle := Battle.new(setup, stats)
|
||||
battle.max_turns = max_turns
|
||||
var outcome : Battle.Outcome = await battle.run()
|
||||
|
||||
played += 1
|
||||
total_turns += battle.turn
|
||||
max_turns_seen = maxi(max_turns_seen, battle.turn)
|
||||
var switches : int = stats.count(BattleEvent.Type.WITHDRAWN)
|
||||
total_switches += switches
|
||||
max_switches = maxi(max_switches, switches)
|
||||
|
||||
match outcome:
|
||||
Battle.Outcome.PLAYER_WON: wins += 1
|
||||
Battle.Outcome.PLAYER_LOST: losses += 1
|
||||
_: draws += 1
|
||||
|
||||
if outcome == Battle.Outcome.DRAW:
|
||||
suspects.append("seed %d | %s | match nul : %d tours sans vainqueur" % [seed_value, _describe(setup), battle.turn])
|
||||
elif switches > battle.turn:
|
||||
suspects.append("seed %d | %s | %d changements de Pokémon en %d tours" % [seed_value, _describe(setup), switches, battle.turn])
|
||||
|
||||
#le moteur tourne sans rendre la main : on laisse respirer l'editeur
|
||||
if i % 10 == 9:
|
||||
print("[BattleDebug] simulation %d/%d" % [i + 1, mass_simulation_count])
|
||||
await get_tree().process_frame
|
||||
|
||||
var n : float = maxf(played, 1)
|
||||
var lines : PackedStringArray = PackedStringArray()
|
||||
lines.append("===== Simulation de masse : %d combats =====" % played)
|
||||
lines.append("Victoires du camp 1 : %d | Défaites : %d | Nuls (limite de %d tours) : %d" % [wins, losses, max_turns, draws])
|
||||
lines.append("Tours : moyenne %.1f, maximum %d" % [total_turns / n, max_turns_seen])
|
||||
lines.append("Changements de Pokémon par combat : moyenne %.1f, maximum %d" % [total_switches / n, max_switches])
|
||||
lines.append("")
|
||||
if suspects.is_empty():
|
||||
lines.append("Aucun combat suspect.")
|
||||
else:
|
||||
lines.append("%d combat(s) suspect(s) :" % suspects.size())
|
||||
lines.append_array(suspects.slice(0, 50))
|
||||
lines.append("")
|
||||
lines.append("Pour en rejouer un : replay_seed = <seed>, mass_simulation_count = 0, puis relance.")
|
||||
|
||||
var summary : String = "%d combats, %d suspect(s)" % [played, suspects.size()]
|
||||
print("[BattleDebug] " + summary)
|
||||
_output("\n".join(lines), summary)
|
||||
|
||||
#---------- construction reproductible ----------
|
||||
|
||||
func _build_setup(seed_value : int) -> BattleSetup:
|
||||
#tout (format, equipes, niveaux, natures, combat) est derive du seed : meme seed = meme combat
|
||||
var rng := RandomNumberGenerator.new()
|
||||
rng.seed = seed_value
|
||||
var ids : Array = Pokedex.all_ids()
|
||||
var setup := BattleSetup.new()
|
||||
setup.seed_value = seed_value
|
||||
setup.can_flee = false
|
||||
if ids.is_empty():
|
||||
push_error("BattleDebug : le Pokedex ne contient aucun pokemon")
|
||||
return setup
|
||||
|
||||
var my_slots : int = rng.randi_range(1, 3)
|
||||
setup.add(BattleSetup.player_participant(_seeded_team(rng, ids, rng.randi_range(my_slots, 6)), my_slots, null, BattleAI.new()))
|
||||
|
||||
var enemy_participants : int = rng.randi_range(1, 3)
|
||||
for i in enemy_participants:
|
||||
var slots : int = rng.randi_range(1, 3)
|
||||
var team : PokemonTeam = _seeded_team(rng, ids, rng.randi_range(slots, 6))
|
||||
setup.add(BattleParticipant.new("Adversaire %d" % (i + 1), team, 1, BattleAI.new(), slots))
|
||||
return setup
|
||||
|
||||
func _seeded_team(rng : RandomNumberGenerator, ids : Array, size : int) -> PokemonTeam:
|
||||
var team := PokemonTeam.new()
|
||||
for i in size:
|
||||
var species : PokemonData = Pokedex.get_pokemon(ids[rng.randi_range(0, ids.size() - 1)])
|
||||
if species == null: continue
|
||||
team.add_pokemon(PokemonInstance.create(species, rng.randi_range(level_min, level_max), rng.randi_range(0, 24)))
|
||||
return team
|
||||
|
||||
func _describe(setup : BattleSetup) -> String:
|
||||
var mine : int = 0
|
||||
var enemies : PackedStringArray = PackedStringArray()
|
||||
for p in setup.participants:
|
||||
if p.side == 0: mine += p.slot_count
|
||||
else: enemies.append(str(p.slot_count))
|
||||
return "%d contre %s" % [mine, "+".join(enemies)]
|
||||
|
||||
#---------- sortie ----------
|
||||
|
||||
func _output(text : String, summary : String) -> void:
|
||||
if write_log_file: _write_log(text)
|
||||
if show_log_on_screen: _show_log(text, summary)
|
||||
|
||||
func _write_log(text : String) -> void:
|
||||
var file := FileAccess.open(LOG_PATH, FileAccess.WRITE)
|
||||
if file == null:
|
||||
|
|
@ -81,7 +211,7 @@ func _write_log(text : String) -> void:
|
|||
print("[BattleDebug] Journal ecrit dans : " + ProjectSettings.globalize_path(LOG_PATH))
|
||||
|
||||
func _show_log(text : String, summary : String) -> void:
|
||||
#panneau plein ecran avec le journal defilant, ferme par un bouton (ou B / Echap)
|
||||
#panneau plein ecran avec le journal defilant, ferme par un bouton
|
||||
if _log_layer != null: _log_layer.queue_free()
|
||||
_log_layer = CanvasLayer.new()
|
||||
_log_layer.layer = 100
|
||||
|
|
|
|||
12
game/battle/battle_stats_presenter.gd
Normal file
12
game/battle/battle_stats_presenter.gd
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
class_name BattleStatsPresenter
|
||||
extends BattlePresenter
|
||||
|
||||
#presentation "muette" pour la simulation de masse : ne garde que le nombre d'evenements par type
|
||||
|
||||
var counts : Dictionary = {}
|
||||
|
||||
func present(event : BattleEvent) -> void:
|
||||
counts[event.type] = int(counts.get(event.type, 0)) + 1
|
||||
|
||||
func count(type : BattleEvent.Type) -> int:
|
||||
return int(counts.get(type, 0))
|
||||
1
game/battle/battle_stats_presenter.gd.uid
Normal file
1
game/battle/battle_stats_presenter.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://cq1cg5ffpmhgy
|
||||
|
|
@ -38,7 +38,7 @@ static func resolve(battle : Battle, actor : BattleSlot, move : MoveData, chosen
|
|||
"all-opponents":
|
||||
result = opponents
|
||||
"random-opponent":
|
||||
if !opponents.is_empty(): result.append(opponents.pick_random())
|
||||
if !opponents.is_empty(): result.append(battle.random_pick(opponents))
|
||||
"all-other-pokemon":
|
||||
result.append_array(opponents)
|
||||
result.append_array(allies)
|
||||
|
|
@ -53,7 +53,7 @@ static func resolve(battle : Battle, actor : BattleSlot, move : MoveData, chosen
|
|||
if chosen != null and chosen.is_active() and (allies.has(chosen) or chosen == actor):
|
||||
result.append(chosen)
|
||||
elif !allies.is_empty():
|
||||
result.append(allies.pick_random())
|
||||
result.append(battle.random_pick(allies))
|
||||
elif get_target_type(move) == "user-or-ally":
|
||||
result.append(actor)
|
||||
"users-field", "opponents-field", "fainting-pokemon":
|
||||
|
|
@ -63,5 +63,5 @@ static func resolve(battle : Battle, actor : BattleSlot, move : MoveData, chosen
|
|||
if chosen != null and chosen != actor and chosen.is_active():
|
||||
result.append(chosen)
|
||||
elif !opponents.is_empty():
|
||||
result.append(opponents.pick_random())
|
||||
result.append(battle.random_pick(opponents))
|
||||
return result
|
||||
|
|
|
|||
|
|
@ -4,3 +4,4 @@
|
|||
|
||||
[node name="Battle" type="Node" unique_id=520840168]
|
||||
script = ExtResource("1_5bjgp")
|
||||
replay_seed = 881794
|
||||
|
|
|
|||
Loading…
Reference in a new issue