Mix.install([ {:jason, "~> 1.4"}, {:matplotex, "~> 0.4.71"} ]) defmodule ZombieApocalypse do @moduledoc """ Simulation zombie complète. - Chaque zombie a un niveau de faim. - Faim augmente de 5 chaque heure. - Si un zombie mange un humain, sa faim revient à 0 et l'humain devient un zombie. - Certains zombies peuvent mourir. - Sauvegarde JSON à chaque heure. - Affichage dans le terminal avec pourcentages. - Génération graphique final avec Matplotex. """ @state_file "states.json" # ===================================== # Démarrage de la simulation # ===================================== def start(humans \\ 10, zombies \\ 3, max_hours \\ 100) do File.write!(@state_file, "[]") IO.puts("🧟 Lancement de la simulation...") zombie_list = for _ <- 1..max(zombies, 0), do: 0 loop(0, humans, zombie_list, max_hours, humans, zombies) # Génération graphique plot_final() IO.puts("✅ Simulation terminée. JSON et graphique générés (zombie_final.png).") end # ===================================== # Boucle principale # ===================================== defp loop(hour, humans, zombie_list, max_hours, initial_humans, initial_zombies) when hour < max_hours do state = %{ hour: hour, humans: humans, zombies: length(zombie_list), timestamp: DateTime.utc_now() } append_state(state) percent_humans = if initial_humans > 0, do: humans / initial_humans * 100, else: 0 percent_zombies = if initial_zombies > 0, do: length(zombie_list) / initial_zombies * 100, else: 0 IO.puts( "Heure #{hour} : Humains = #{humans} (#{Float.round(percent_humans, 1)}%), " <> "Zombies = #{length(zombie_list)} (#{Float.round(percent_zombies, 1)}%)" ) cond do humans <= 0 or length(zombie_list) == 0 -> {humans, zombie_list} true -> {new_humans, new_zombies} = simulate_hour(humans, zombie_list) loop(hour + 1, new_humans, new_zombies, max_hours, initial_humans, initial_zombies) end end defp loop(_, humans, zombie_list, _, _, _), do: {humans, zombie_list} # ===================================== # Simulation d'une heure # ===================================== defp simulate_hour(humans, zombie_list) do new_zombie_list = Enum.map(zombie_list, &(&1 + 5)) {remaining_humans, zombies_after_hour} = Enum.reduce(new_zombie_list, {humans, []}, fn hunger, {hum_left, acc} -> if hum_left > 0 and :rand.uniform() <= 0.4 + hunger / 100 do # zombie mange humain → nouveau zombie {hum_left - 1, [0, 0 | acc]} else {hum_left, [hunger | acc]} end end) # Certains zombies peuvent mourir killed = Enum.count(zombies_after_hour, fn _ -> :rand.uniform() <= 0.15 end) final_zombies = Enum.drop(zombies_after_hour, killed) {remaining_humans, final_zombies} end # ===================================== # Sauvegarde JSON # ===================================== defp append_state(state) do states = File.read!(@state_file) |> Jason.decode!() File.write!(@state_file, Jason.encode!(states ++ [state], pretty: false)) end # ===================================== # Graphique final # ===================================== defp plot_final do states = File.read!(@state_file) |> Jason.decode!() humans = Enum.map(states, & &1["humans"]) |> Enum.filter(&is_number/1) zombies = Enum.map(states, & &1["zombies"]) |> Enum.filter(&is_number/1) hours = Enum.map(states, & &1["hour"]) |> Enum.filter(&is_number/1) if length(hours) > 0 and length(humans) == length(hours) and length(zombies) == length(hours) do infection_rate = if length(humans) >= 2 do humans |> Enum.chunk_every(2, 1, :discard) |> Enum.map(fn [prev, next] -> prev - next end) else [] end inf_hours = Enum.drop(hours, 1) fig = Matplotex.plot(hours, humans, color: "blue", label: "Humains") fig = Matplotex.plot(fig, hours, zombies, color: "red", label: "Zombies") if infection_rate != [] and length(inf_hours) == length(infection_rate) do fig = Matplotex.plot(fig, inf_hours, infection_rate, color: "green", label: "Vélocité Infection" ) end fig_opts = %{figsize: {8, 5}} fig = Matplotex.figure(fig, fig_opts) fig = Matplotex.set_title(fig, "Simulation Zombie Apocalypse") fig = Matplotex.set_xlabel(fig, "Heure") fig = Matplotex.set_ylabel(fig, "Population / Infection rate") svg = Matplotex.show(fig) File.write!("zombie_final.svg", svg) System.cmd("rsvg-convert", ["zombie_final.svg", "-o", "zombie_final.png"]) IO.puts("🖼️ Graphique généré : zombie_final.png") else IO.puts("⚠️ Données insuffisantes pour tracer le graphique.") end end end # ===================================== # Lancement # ===================================== ZombieApocalypse.start(10_000_000, 3, 100)