(* Tower-Defense, with a Console Interface,
   with French comments and French variable and function names.
 *)
(* Created with: Claude-3-Sonnet (2024) *)
(* And Edited by: Florent Monnier *)

#directory "+unix"
#load "unix.cma"

(* Position pour représenter les coordonnées (x, y) *)
type position = int * int

(* Ennemi avec une position et des points de vie (pv), et un chemin *)
type ennemi = {
  e_pos : position;
  pv : int;
  chm : position list;
}

(* Tour avec une position, une portée et des dommages *)
type tour = {
  t_pos : position;
  portee : int;
  dommages : int;
}

(* État principal du jeu regroupant les divers éléments *)
type jeu = {
  carte : bool array array;
  ennemis : ennemi list;
  tours : tour list;
}

(* Chemin pour les ennemis *)
let chemin = [ (0, 0); (1, 0); (2, 0); (2, 1); (2, 2); (2, 3); (2, 4);
  (2, 5); (3, 5); (4, 5); (5, 5); (5, 6); (6, 6); (7, 6); (8, 6);
  (8, 7); (8, 8); (8, 9); (9, 9); (10, 10); ]


(* Afficher l'état du jeu *)
let afficher_jeu jeu =
  let afficher_case x y =
    let position = (x, y) in
    if List.exists (fun e -> e.e_pos = position) jeu.ennemis then
      print_string "E"
    else if List.exists (fun t -> t.t_pos = position) jeu.tours then
      print_string "T"
    else if jeu.carte.(y).(x) then
      print_string "#"
    else
      print_string "."
  in

  let hauteur = Array.length jeu.carte in
  let largeur = Array.length jeu.carte.(0) in

  for y = 0 to hauteur - 1 do
    for x = 0 to largeur - 1 do
      afficher_case x y
    done;
    print_newline ()
  done;

  List.iter (fun e -> Printf.printf "E: %d\n" e.pv) jeu.ennemis;

  print_newline ()


(* Créer un nouvel ennemi *)
let new_ennemi () =
  { e_pos = (0, 0); pv = 60; chm = chemin }


(* Déplacer un ennemi *)
let deplacer_ennemi e =
  let next_pos, chm =
    match e.chm with
    | hd :: tl -> (hd, tl)
    | _ -> (e.e_pos, e.chm)
  in
  { e with e_pos = next_pos; chm; }


(* Destination atteinte *)
let atteint_destination p =
  let x, y = p in
  (x = 10 || y = 10)


(* Calcule la distance euclidienne entre deux positions *)
let distance (x1, y1) (x2, y2) =
  sqrt (float (x1 - x2) ** 2. +. float (y1 - y2) ** 2.)


(* Vérifie si un ennemi est dans la portée d'une tour *)
let dans_portee tour ennemi =
  distance tour.t_pos ennemi.e_pos < float tour.portee


(* Boucle de jeu *)
let rec game_loop game =
  (* Déplacer les ennemis *)
  let ennemis = List.map (fun enn -> deplacer_ennemi enn ) game.ennemis in

  (* Attaquer les ennemis dans la portée des tours *)
  let tours, ennemis =
    List.fold_left
      (fun (tours, ennemis) tour ->
         let ennemis_vises, autres_ennemis = List.partition (dans_portee tour) ennemis in
         let ennemis_vises = List.map (fun e -> {e with pv = e.pv - tour.dommages}) ennemis_vises in
         (tour :: tours, ennemis_vises @ autres_ennemis))
      ([], ennemis) game.tours
  in

  (* Retirer les ennemis atteints *)
  let ennemis = List.filter (fun e -> e.pv > 0) ennemis in

  (* Afficher l'état du jeu *)
  afficher_jeu game;

  (* Faire une pause d'une seconde *)
  Unix.sleep 1;

  (* 12% de chance qu'un nouvel ennemi arrive *)
  let ennemis =
    if Random.int 100 > 12 then ennemis
    else (new_ennemi () :: ennemis)
  in

  (* Conditions de fin de partie *)
  if ennemis = [] then
    print_endline "Vous avez gagné !"
  else if List.exists (fun e -> atteint_destination e.e_pos) ennemis then
    print_endline "Vous avez perdu !"
  else
    game_loop { game with ennemis; tours; }
;;


(* La carte est représentée par une grille 2d *)
let carte = Array.make_matrix 10 10 false

let ennemis_initiaux = [
  { e_pos = (0, 0); pv = 60; chm = chemin };
]

let tours_initiales = [
  { t_pos = (4, 6); portee = 3; dommages = 10; };
  { t_pos = (6, 4); portee = 3; dommages = 10; };
]

let jeu_initial = { carte; ennemis = ennemis_initiaux; tours = tours_initiales; }

let () = game_loop jeu_initial ;;