added hunger and graph

This commit is contained in:
Awen Lelu
2026-01-06 14:11:06 +01:00
parent 386e7cb656
commit 3e28ea4d9f
8 changed files with 4671 additions and 152 deletions

View File

@@ -1,4 +0,0 @@
# Used by "mix format"
[
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
]

View File

@@ -1,88 +1,163 @@
# Installer Jason pour JSON si besoin
Mix.install([ Mix.install([
{:jason, "~> 1.4"} {:jason, "~> 1.4"},
{:matplotex, "~> 0.4.71"}
]) ])
defmodule ZombieApocalypse do 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" @state_file "states.json"
@final_file "states_final.json"
# =====================================
# Démarrage de la simulation # Démarrage de la simulation
def start(humans \\ 10, zombies \\ 3, max_hours \\ 200) do # =====================================
# Vider le fichier temporaire def start(humans \\ 10, zombies \\ 3, max_hours \\ 100) do
File.write!(@state_file, "") File.write!(@state_file, "[]")
IO.puts("🧟 Lancement de la simulation...")
IO.puts("🧟 Lancement de la simulation Zombie Apocalypse !") zombie_list = for _ <- 1..max(zombies, 0), do: 0
# Ouvrir le fichier en continu pour append rapide loop(0, humans, zombie_list, max_hours, humans, zombies)
{:ok, file} = File.open(@state_file, [:write, :utf8])
# Boucle principale # Génération graphique
loop(0, humans, zombies, max_hours, file) plot_final()
IO.puts("✅ Simulation terminée. JSON et graphique générés (zombie_final.png).")
# Fermer le fichier
File.close(file)
# Générer JSON final
finalize_json()
end end
# =====================================
# Boucle principale # Boucle principale
defp loop(hour, humans, zombies, max_hours, file) when hour < max_hours do # =====================================
defp loop(hour, humans, zombie_list, max_hours, initial_humans, initial_zombies)
when hour < max_hours do
state = %{ state = %{
hour: hour, hour: hour,
humans: humans, humans: humans,
zombies: zombies, zombies: length(zombie_list),
timestamp: DateTime.utc_now() timestamp: DateTime.utc_now()
} }
# Append rapide append_state(state)
IO.write(file, Jason.encode!(state) <> "\n")
# Afficher seulement toutes les 10 heures percent_humans =
if rem(hour, 10) == 0 do if initial_humans > 0, do: humans / initial_humans * 100, else: 0
IO.puts("Heure #{hour} | Humains: #{humans} | Zombies: #{zombies}")
end 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 cond do
humans <= 0 -> humans <= 0 or length(zombie_list) == 0 ->
IO.puts("💀 Tous les humains ont été infectés.") {humans, zombie_list}
zombies <= 0 ->
IO.puts("🎉 Tous les zombies ont été éliminés.")
true -> true ->
{new_humans, new_zombies} = simulate_hour(humans, zombies) {new_humans, new_zombies} = simulate_hour(humans, zombie_list)
loop(hour + 1, new_humans, new_zombies, max_hours, file) loop(hour + 1, new_humans, new_zombies, max_hours, initial_humans, initial_zombies)
end end
end end
defp loop(_hour, _humans, _zombies, _max_hours, _file), do: :ok defp loop(_, humans, zombie_list, _, _, _), do: {humans, zombie_list}
# Simulation d'une heure (calcul probabiliste) # =====================================
defp simulate_hour(humans, zombies) do # Simulation d'une heure
attacks = zombies # =====================================
# Humains infectés : chaque attaque a 40% de chance defp simulate_hour(humans, zombie_list) do
humans_lost = Enum.count(1..attacks, fn _ -> :rand.uniform(100) <= 40 end) new_zombie_list = Enum.map(zombie_list, &(&1 + 5))
# Zombies tués : chaque attaque a 15% de chance
zombies_lost = Enum.count(1..attacks, fn _ -> :rand.uniform(100) <= 15 end)
humans = max(humans - humans_lost, 0) {remaining_humans, zombies_after_hour} =
zombies = max(zombies - zombies_lost + humans_lost, 0) # infections Enum.reduce(new_zombie_list, {humans, []}, fn hunger, {hum_left, acc} ->
{humans, zombies} 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 end
# Générer le JSON final complet # =====================================
defp finalize_json do # Sauvegarde JSON
# =====================================
defp append_state(state) do
states = states =
File.stream!(@state_file) File.read!(@state_file)
|> Stream.map(&Jason.decode!/1) |> Jason.decode!()
|> Enum.to_list()
File.write!(@final_file, Jason.encode!(states, pretty: true)) File.write!(@state_file, Jason.encode!(states ++ [state], pretty: false))
IO.puts("✅ JSON final créé : #{@final_file}") 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
end end
# Lancer la simulation # =====================================
ZombieApocalypse.start(7000000000, 3, 200) # Lancement
# =====================================
ZombieApocalypse.start(10_000_000, 3, 100)

View File

@@ -25,6 +25,8 @@
elixir elixir
erlang erlang
elixir-ls elixir-ls
librsvg
]; ];
shellHook = '' shellHook = ''

File diff suppressed because one or more lines are too long

View File

@@ -1,38 +1,356 @@
[ [
{ {
"hour": 0, "hour": 0,
"humans": 10, "humans": 5000000,
"timestamp": "2026-01-06T11:18:27.762962Z", "timestamp": "2026-01-06T12:14:41.180887Z",
"zombies": 3 "zombies": 3
}, },
{ {
"hour": 1, "hour": 1,
"humans": 10, "humans": 5000000,
"timestamp": "2026-01-06T11:18:27.823728Z", "timestamp": "2026-01-06T12:14:41.188896Z",
"zombies": 3 "zombies": 3
}, },
{ {
"hour": 2, "hour": 2,
"humans": 8, "humans": 4999998,
"timestamp": "2026-01-06T11:18:27.874694Z", "timestamp": "2026-01-06T12:14:41.188929Z",
"zombies": 5 "zombies": 5
}, },
{ {
"hour": 3, "hour": 3,
"humans": 6, "humans": 4999996,
"timestamp": "2026-01-06T11:18:27.925686Z", "timestamp": "2026-01-06T12:14:41.188949Z",
"zombies": 7 "zombies": 7
}, },
{ {
"hour": 4, "hour": 4,
"humans": 3, "humans": 4999993,
"timestamp": "2026-01-06T11:18:27.976690Z", "timestamp": "2026-01-06T12:14:41.188963Z",
"zombies": 10 "zombies": 9
}, },
{ {
"hour": 5, "hour": 5,
"humans": -3, "humans": 4999990,
"timestamp": "2026-01-06T11:18:28.027762Z", "timestamp": "2026-01-06T12:14:41.188984Z",
"zombies": 15 "zombies": 11
},
{
"hour": 6,
"humans": 4999983,
"timestamp": "2026-01-06T12:14:41.189001Z",
"zombies": 17
},
{
"hour": 7,
"humans": 4999975,
"timestamp": "2026-01-06T12:14:41.189020Z",
"zombies": 23
},
{
"hour": 8,
"humans": 4999961,
"timestamp": "2026-01-06T12:14:41.189040Z",
"zombies": 32
},
{
"hour": 9,
"humans": 4999948,
"timestamp": "2026-01-06T12:14:41.189075Z",
"zombies": 37
},
{
"hour": 10,
"humans": 4999931,
"timestamp": "2026-01-06T12:14:41.189109Z",
"zombies": 45
},
{
"hour": 11,
"humans": 4999910,
"timestamp": "2026-01-06T12:14:41.189157Z",
"zombies": 60
},
{
"hour": 12,
"humans": 4999885,
"timestamp": "2026-01-06T12:14:41.189188Z",
"zombies": 67
},
{
"hour": 13,
"humans": 4999845,
"timestamp": "2026-01-06T12:14:41.189218Z",
"zombies": 92
},
{
"hour": 14,
"humans": 4999803,
"timestamp": "2026-01-06T12:14:41.189268Z",
"zombies": 116
},
{
"hour": 15,
"humans": 4999747,
"timestamp": "2026-01-06T12:14:41.189316Z",
"zombies": 149
},
{
"hour": 16,
"humans": 4999659,
"timestamp": "2026-01-06T12:14:41.189366Z",
"zombies": 202
},
{
"hour": 17,
"humans": 4999563,
"timestamp": "2026-01-06T12:14:41.189412Z",
"zombies": 249
},
{
"hour": 18,
"humans": 4999444,
"timestamp": "2026-01-06T12:14:41.189465Z",
"zombies": 324
},
{
"hour": 19,
"humans": 4999288,
"timestamp": "2026-01-06T12:14:41.189529Z",
"zombies": 413
},
{
"hour": 20,
"humans": 4999096,
"timestamp": "2026-01-06T12:14:41.189635Z",
"zombies": 512
},
{
"hour": 21,
"humans": 4998841,
"timestamp": "2026-01-06T12:14:41.189864Z",
"zombies": 636
},
{
"hour": 22,
"humans": 4998553,
"timestamp": "2026-01-06T12:14:41.189989Z",
"zombies": 772
},
{
"hour": 23,
"humans": 4998179,
"timestamp": "2026-01-06T12:14:41.190176Z",
"zombies": 981
},
{
"hour": 24,
"humans": 4997721,
"timestamp": "2026-01-06T12:14:41.190360Z",
"zombies": 1245
},
{
"hour": 25,
"humans": 4997118,
"timestamp": "2026-01-06T12:14:41.190594Z",
"zombies": 1568
},
{
"hour": 26,
"humans": 4996382,
"timestamp": "2026-01-06T12:14:41.190901Z",
"zombies": 1954
},
{
"hour": 27,
"humans": 4995446,
"timestamp": "2026-01-06T12:14:41.191312Z",
"zombies": 2425
},
{
"hour": 28,
"humans": 4994294,
"timestamp": "2026-01-06T12:14:41.191968Z",
"zombies": 3052
},
{
"hour": 29,
"humans": 4992830,
"timestamp": "2026-01-06T12:14:41.192710Z",
"zombies": 3834
},
{
"hour": 30,
"humans": 4990950,
"timestamp": "2026-01-06T12:14:41.194421Z",
"zombies": 4869
},
{
"hour": 31,
"humans": 4988664,
"timestamp": "2026-01-06T12:14:41.196273Z",
"zombies": 6024
},
{
"hour": 32,
"humans": 4985753,
"timestamp": "2026-01-06T12:14:41.197687Z",
"zombies": 7587
},
{
"hour": 33,
"humans": 4982215,
"timestamp": "2026-01-06T12:14:41.199289Z",
"zombies": 9390
},
{
"hour": 34,
"humans": 4977787,
"timestamp": "2026-01-06T12:14:41.201555Z",
"zombies": 11751
},
{
"hour": 35,
"humans": 4972174,
"timestamp": "2026-01-06T12:14:41.203873Z",
"zombies": 14819
},
{
"hour": 36,
"humans": 4965252,
"timestamp": "2026-01-06T12:14:41.207054Z",
"zombies": 18508
},
{
"hour": 37,
"humans": 4956468,
"timestamp": "2026-01-06T12:14:41.213896Z",
"zombies": 23339
},
{
"hour": 38,
"humans": 4945253,
"timestamp": "2026-01-06T12:14:41.219102Z",
"zombies": 29327
},
{
"hour": 39,
"humans": 4931259,
"timestamp": "2026-01-06T12:14:41.227398Z",
"zombies": 36780
},
{
"hour": 40,
"humans": 4913827,
"timestamp": "2026-01-06T12:14:41.236964Z",
"zombies": 46208
},
{
"hour": 41,
"humans": 4892019,
"timestamp": "2026-01-06T12:14:41.252259Z",
"zombies": 57797
},
{
"hour": 42,
"humans": 4864582,
"timestamp": "2026-01-06T12:14:41.265113Z",
"zombies": 72434
},
{
"hour": 43,
"humans": 4830165,
"timestamp": "2026-01-06T12:14:41.282722Z",
"zombies": 90838
},
{
"hour": 44,
"humans": 4786828,
"timestamp": "2026-01-06T12:14:41.305416Z",
"zombies": 114058
},
{
"hour": 45,
"humans": 4732682,
"timestamp": "2026-01-06T12:14:41.344774Z",
"zombies": 142846
},
{
"hour": 46,
"humans": 4665123,
"timestamp": "2026-01-06T12:14:41.382320Z",
"zombies": 178719
},
{
"hour": 47,
"humans": 4580281,
"timestamp": "2026-01-06T12:14:41.432100Z",
"zombies": 224117
},
{
"hour": 48,
"humans": 4473784,
"timestamp": "2026-01-06T12:14:41.499129Z",
"zombies": 281086
},
{
"hour": 49,
"humans": 4340194,
"timestamp": "2026-01-06T12:14:41.584992Z",
"zombies": 352257
},
{
"hour": 50,
"humans": 4173153,
"timestamp": "2026-01-06T12:14:41.690964Z",
"zombies": 441440
},
{
"hour": 51,
"humans": 3962221,
"timestamp": "2026-01-06T12:14:41.811665Z",
"zombies": 554491
},
{
"hour": 52,
"humans": 3698707,
"timestamp": "2026-01-06T12:14:41.964906Z",
"zombies": 695545
},
{
"hour": 53,
"humans": 3368228,
"timestamp": "2026-01-06T12:14:42.159879Z",
"zombies": 872160
},
{
"hour": 54,
"humans": 2953399,
"timestamp": "2026-01-06T12:14:42.424475Z",
"zombies": 1093196
},
{
"hour": 55,
"humans": 2432909,
"timestamp": "2026-01-06T12:14:42.785990Z",
"zombies": 1370897
},
{
"hour": 56,
"humans": 1780644,
"timestamp": "2026-01-06T12:14:43.164048Z",
"zombies": 1718976
},
{
"hour": 57,
"humans": 963391,
"timestamp": "2026-01-06T12:14:43.642602Z",
"zombies": 2156863
},
{
"hour": 58,
"humans": 0,
"timestamp": "2026-01-06T12:14:44.297738Z",
"zombies": 2652703
} }
] ]

1
states_live.json Normal file
View File

@@ -0,0 +1 @@
[{"timestamp":"2026-01-06T12:28:29.675146Z","hour":0,"humans":10,"zombies":3}]

BIN
zombie_final.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

4208
zombie_final.svg Normal file

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 83 KiB