{ -- Dreadnaut's Life 0.1 "textmode" --
   l'ennesima implementazione del solito vecchio gioco :-)

 Comandi:
  <Spazio> - avvia e ferma l'animazione continua
  <K>ill   - svuota il mondo e ferma l'animazione
  <P>asso  - esegue un singolo passo di animazione
  <R>andom - aggiunge organismi al mondo
  <Q>uit   - esce dal programma

  Ad animazione ferma, con il mouse e' possibile aggiungere
  o togliere organismi dal mondo
}

uses crt, fmouse;

{ --[ gestione diretta della memoria video ]-------- }
 type
  CrtCell = record Ch,At: byte; end;
  CrtMap  = array [1..50,1..80] of CrtCell;

 const
  Text50 : word    = 259;

 var
  CrtMem : CrtMap absolute $B800:$0000;
{ -------------------------------------------------- }

 const
{ costanti per i vari stati di ogni cella. Sono usati anche per riconoscere
  lo stato `vitale' quindi _devono_ essere diversi fra loro                 }
  ALIVE = 15; { vivo                    }
  DYING =  7; { morto al prossimo passo }
  BORN  = 10; { vivo al prossimo passo  }
  EMPTY =  2; { cella vuota / morto     }

{ numero di organismi da generare all'avvio e a ogni nascita casuale }
  RB_NUM = 500;

{ costanti per le posizioni relative dei vicini }
  vicini : array[0..7] of record x,y: shortint; end =
     ( (x:-1; y: 1), (x: 0; y: 1), (x: 1; y: 1),
       (x:-1; y: 0),               (x: 1; y: 0),
       (x:-1; y:-1), (x: 0; y:-1), (x: 1; y:-1) );

{ data una cella restituisce il numero di celle vicine occupate }
 function ContaVicini(y,x: byte): byte;
  var
   n,num : byte;
  begin
   num := 0;
   for n := 0 to 7 do
{ il mod serve per uscire da un lato ed entrare dall'altro come se il
  `mondo' fosse la superficie di una ciambella                        }
    if (CrtMem[ 1+ (y+49 + vicini[n].y) mod 50 ,
                1+ (x+79 + vicini[n].x) mod 80 ].At = ALIVE) or
       (CrtMem[ 1+ (y+49 + vicini[n].y) mod 50 ,
                1+ (x+79 + vicini[n].x) mod 80 ].At = DYING) then inc(num);
   ContaVicini := num;
  end;

{ esegue un passo di calcolo }
 procedure PassoLife;
  var
   x,y: byte;
  begin
   HideMouse;
   for y := 1 to 50 do
    for x := 1 to 80 do
     begin
      case ContaVicini(y,x) of
       { meno di due vicini - morte per solitudine }
       0,1,
       { piu' di tre vicini - morte per sovraffollamento }
       4,5,6,7,8: if CrtMem[y,x].At = ALIVE then CrtMem[y,x].At := DYING;
       { menage a trois - nascita }
       3        : if CrtMem[y,x].At = EMPTY then CrtMem[y,x].At := BORN;
       { due vicini - stabile, non faccio niente }
      end;
     end;

  { sopprimo i morenti e confermo i nuovi nati }
   for y := 1 to 50 do
    for x := 1 to 80 do
     if CrtMem[y,x].At = DYING then CrtMem[y,x].At := EMPTY
      else if CrtMem[y,x].At = BORN then CrtMem[y,x].At := ALIVE;

   ShowMouse;
  end;

 var
  m,n,b  : integer;

{ strage di massa - svuota il mondo }
 procedure MassKill;
  begin
   HideMouse;
   for n := 1 to 50 do
    for m := 1 to 80 do
     begin
      CrtMem[n,m].Ch := ord('Û');
      CrtMem[n,m].At := EMPTY; {}
     end;
   ShowMouse;
  end;

{ nascita casuale - aggiunge RB_NUM celle vive }
 procedure RandomBirth;
  begin
   HideMouse;
   for b := 1 to RB_NUM do
    CrtMem[1+random(50), 1+random(80)].At := ALIVE;
   ShowMouse;
  end;

var
 c      : char;
 running: boolean;
 oldmode: integer;

begin
{ modalita' testo, 80x50 caratteri. Teniamo da parte quella originale }
 oldmode := LastMode;
 TextMode(Text50);

{ inizializza il mondo }
 MassKill;
 RandomBirth;

{ inizializza la gestione del mouse }
 InitEvents;

 repeat
  if keypressed then c := readkey;

  if (c <> #0) and (c <> 'q') then
   begin
    case c of
{ <Spazio> - avvia e ferma l'animazione continua }
     ' ': begin
           running := not running;
           if running then HideMouse else ShowMouse;
          end;
{ <k> - svuota il mondo e ferma l'animazione }
     'k': begin
           MassKill;
           running := false;
           ShowMouse;
          end;
{ <p> - esegue un singolo passo di animazione }
     'p': begin
           if not running then HideMouse
            else running := false;
           PassoLife;
           ShowMouse;
          end;
{ <r> - aggiunge RB_NUM organismi al mondo }
     'r': RandomBirth;
    end;
    c := #0;
   end;

{ ritorna le informazioni sulla pressione del mouse }
  MousePressed(m,n,b);

{ alla pressione di un qualsiasi tasto del mouse si ferma l'animazione
  e si passa alla modalita' di modifica del mondo - clickando si riempie
  o svuota una cella                                                     }
  if b <> 0 then
   begin
    if not running then
     begin
      HideMouse;
      if CrtMem[1+n shr 3,1+m shr 3].At = ALIVE
       then CrtMem[1+n shr 3,1+m shr 3].At := EMPTY
       else CrtMem[1+n shr 3,1+m shr 3].At := ALIVE;
     end;
    running := false;
    ShowMouse;
   end;

{ se e' attiva l'animazione continua esegue un passo }
  if running then PassoLife;

{ <q> - esce dal programma }
 until c = 'q';

{ ritorna alla modalita' testo precedente }
 TextMode(oldmode);
end.
