home *** CD-ROM | disk | FTP | other *** search
- program PC_Life;
-
- {
- The rules of Life call for live cells with 2 or 3 neighbors to
- survive, and empty cells with three neighbors to be born.
- Another way to look at this is to find the sum of all nine cells
- in a three by three box, and say that life will result if the
- cell is live and the 9-sum is three or four, or if the cell is
- dead and the 9-sum is three. These 9-sums can be formed quickly
- by taking 3-sums on one axis, then adding three of these along
- the other axis to form 9-sums.
-
- This method of running life uses tables that form sums along
- rows by look-up rather than calculation. In these examples,
- the unused bits are shown as "x", while individual cell bits
- are shown as digits "0" to "B". Nibble boundaries are marked
- by a period.
-
- Life cells are stored in one nibble each, so three adjacent
- words showing 12 horizontal neighbors would look like this
- ( Note the transposition of the high and low bytes. )
- on screen :
- 0.1.2.3.4.5.6.7.8.9.A.B
- in RAM :
- 3xxx.2xxx.0xxx.1xxx, 6xxx.7xxx.4xxx.5xxx, Axxx.Bxxx.8xxx.9xxx
-
- To calculate the horizontal sum for the second word, the third
- word and the last cell of the first word are needed. The cells
- of the second word and the last cell of the first word have been
- formatted into a "residue" like this: xxxx.x345.xx67.xxxx
-
- The third word is then shifted in only one byte, and then
- logically or'ed into this residue like so:
- xAxx.xBxx
- 8xxx.9xxx
- xxxx.x345.xx67.xxxx
- ---------------------
- xxxx.x345.8A67.9Bxx
-
- At this location in the SideSum table there is a word that has
- the nibble sums for the horizontal neighbors:
- 5+6+7.6+7+8.3+4+5.4+5+6
-
- The same spot in the Residue table has the corresponding residue
- for the next word:
- xxxx.x789.xxAB.xxxx
-
- Sums along columns are done in the usual way, but because of the
- storage of four cell per word, each triple add forms four sums.
-
- The two dimensional sums are exclusive or'ed with the original
- cell array, and then discriminated. Only cells that have the value
- of 1011, 1010, or 0011 are "alive". This coresponds to alive with
- three neighbors, alive with two neighbors, or empty with three neighbors.
-
- (You may wonder why the tables are twice as big as needed. This
- allows a very cryptic folding of the two tables that can give a
- 3% speedup. I didn't feel it was worth it, as this process is
- hard enough to understand as is. You are welcome to try it as a
- puzzle though, if you like. RJN)
-
- }
-
- {$I+} { I/O checking on}
- {$N-} { No numeric coprocessor}
- {$R+} { Range checking on. }
- {$S+} { Stack checking on. }
-
- uses
- Crt, Dos;
-
- type
- str80 = string[80];
- Xindex = 0..159;
- Yindex = 0..199; { 100..199 are the odd lines. }
-
- var
- { Working on a 160x100 array. }
- Sum3 : array[0..3999] of word; { Triple sums. }
- Cells : array[0..3999] of word; { Fast double for slow screens. }
- Saver : array[0..3999] of word; { Cell safe storage. }
- Screen : array[0..8096] of word absolute $B800:$0000;
-
- { Look-up tables. }
- SumTab : array[0..1023] of integer; { Triple sum look-up. }
- Residue : array[0..1023] of integer; { Left over look-up. }
- LifeTest : array[0.. 255] of byte; { Discrimination look-up. }
- MachineId : byte absolute $F000:$FFFE;
-
- SlowScreen : boolean;
- AllDone : boolean;
- Regs : Registers;
- OldMode : integer; { Original video mode. }
-
- ForeGround : integer;
- BackGround : integer;
-
-
- { For each composite like this : xxxx.x345.8A67.9Bxx }
- { shift right 1 for an index like this : xxxx.xx34.58A6.79Bx }
- { to look-up a sum of three like this : 5+6+7.6+7+8.3+4+5.4+5+6 }
- procedure InitSideSum;
- var
- I : integer;
- Sum : integer;
- begin;
- for I := 0 to 1023
- do begin
- Sum := 0;
- if (I and $0200) <> 0 then Sum := Sum + $0010; {3}
- if (I and $0100) <> 0 then Sum := Sum + $0011; {4}
- if (I and $0080) <> 0 then Sum := Sum + $1011; {5}
- if (I and $0010) <> 0 then Sum := Sum + $1101; {6}
- if (I and $0008) <> 0 then Sum := Sum + $1100; {7}
- if (I and $0040) <> 0 then Sum := Sum + $0100; {8}
- SumTab[I] := Sum;
- end;
- end;
-
-
- { For each composite like this : xxxx.x345.8A67.9Bxx }
- { shift right 1 for an index like this : xxxx.xx34.58A6.79Bx }
- { to lookup a residue like this : xxxx.x789.xxAB.xxxx }
- procedure InitResidue;
- var
- I : integer;
- Bits : integer;
- begin;
- for I := 0 to 1023
- do begin
- Bits := 0;
- if (I and $0008) <> 0 then Bits := Bits + $0400; {7}
- if (I and $0040) <> 0 then Bits := Bits + $0200; {8}
- if (I and $0004) <> 0 then Bits := Bits + $0100; {9}
- if (I and $0020) <> 0 then Bits := Bits + $0020; {A}
- if (I and $0002) <> 0 then Bits := Bits + $0010; {B}
- Residue[I] := Bits;
- end;
- end;
-
-
- { Conway's game of life has few rules, only these cases will survive: }
- { $B = Live and two neighbors. }
- { $C = Live and three neighbors. }
- { $3 = Dead and three neighbors. }
- procedure InitLifeTest;
- var
- I : integer;
- BotLive, TopLive : boolean;
- begin
- for I := 0 to 255
- do begin
- if ((I shr 4) and $F) in [ $B, $C, $3]
- then begin { The high nibble alive. }
- if (I and $F) in [$B, $C, $3]
- then LifeTest[I] := $88
- else LifeTest[I] := $80;
- end
- else begin { the high nibble dead. }
- if (I and $F) in [$B, $C, $3]
- then LifeTest[I] := $08
- else LifeTest[I] := $00;
- end;
- end;
- end;
-
- {$I-,R-,S-}
- { Look up the horizontal three-cell sums. }
- procedure SideSum;
- var
- Tally : word;
- I : integer;
- begin
- Tally := $0000;
- for I := 1 to 3999 do begin
- Tally := ( Tally { Form three word composite. }
- or (hi(Screen[I]) shr 1)
- or lo(Screen[I])
- )
- shr 1;
- Sum3[I - 1] := SumTab[Tally]; { Look up middle word 3-sums. }
- Tally := Residue[Tally]; { Get new proto-composite. }
- end;
- Sum3[3999] := 0;
- end;
- {$I+,R+,S+}
-
- procedure SideSumEga; { Avoids slow screen by copy. }
- inline(
- $FC/ { cld ;Sting stuff to incr. }
- $B8/>$B800/ { mov ax, segScreen ;Establish screen seg. }
- $8E/$C0/ { mov es, ax }
- $BB/>0/ { mov bx, 0 ;Clear running tally. }
- $B9/>1999/ { mov cx, 3998/2 ;Do array as one line. }
- $BF/>SumTab/ { mov di, SumTab ;Fast reference. }
- $BE/>Cells + 2/ { mov si, Cells + 2 ;Start at the begining. }
- { H_LOOP }
- $AD/ { lodsw Cells[si] ;Get 4 cells. }
- $D0/$CC/ { ror ah, 1 ;Shift lsb 2 right by 1.}
- $0A/$D8/ { or bl, al ;Or lsb 2 into residue. }
- $0A/$DC/ { or bl, ah ;Or msb 2 into residue. }
- $8B/$01/ { mov ax, [di][bx] ;Table look-up sums. }
- $89/$84/>$E0BC/ { mov Sum3-Cells[si-4], ax ;Store sums. }
- $8B/$9F/>Residue/ { mov bx, Residue[bx] ;Get new residue. }
-
- { Repeat the block once to cut prefetch queue and looping over head. }
- $AD/$D0/$CC/$0A/$D8/$0A/$DC/$8B/$01/$89/$84/>$E0BC/$8B/$9F/>Residue/
-
- $E2/$DC); { loop H_LOOP ;do till done. }
-
-
- procedure SideSumCga; { Works directly off fast screen. }
- inline(
- $FC/ { cld ;Sting stuff to incr. }
- $B8/>$B800/ { mov ax, segScreen ;Establish screen seg. }
- $8E/$C0/ { mov es, ax }
- $BB/>0/ { mov bx, 0 ;Clear running tally. }
- $B9/>1999/ { mov cx, 3998/2 ;Do array as one line. }
- $BF/>SumTab/ { mov di, SumTab ;Fast reference. }
- $BE/>2/ { mov si, 2 ;Start at the begining. }
- { H_LOOP }
- $26/ { es: }
- $AD/ { lodsw Screen[si] ;Get 4 cells. }
- $D0/$CC/ { ror ah, 1 ;Shift lsb 2 right by 1.}
- $0A/$D8/ { or bl, al ;Or lsb 2 into residue. }
- $0A/$DC/ { or bl, ah ;Or msb 2 into residue. }
- $8B/$01/ { mov ax, [di][bx] ;Table look-up sums. }
- $89/$44/<-4/ { mov Sum3[si-4], ax ;Store sums. }
- $8B/$9F/>Residue/ { mov bx, Residue[bx] ;Get new residue. }
-
- { Rep once to cut c_loop and queue flush overhead. }
- $26/$AD/$D0/$CC/$0A/$D8/$0A/$DC/$8B/$01/$89/$44/<-4/$8B/$9F/>Residue/
-
- $E2/$DC); { loop H_LOOP ;do till done. }
-
- {$I-,R-,S-}
- { Make the nine-sums and xor with the original cells, and test. }
- procedure VertSum;
- var
- I : integer;
- Temp : word;
- HiB, Lob : word;
- begin;
- for I := 40 to (3999 - 40) do begin
- Temp := Screen[I] { Xor with screen for code. }
- xor { Make sum of nine neighbors. }
- ( Sum3[I-40] + Sum3[I] + Sum3[I+40] );
- Screen[I] := word(LifeTest[hi(Temp)] shl 8) { Test vitality. }
- or LifeTest[lo(Temp)];
- end;
- end;
- {$I+,R+,S+}
-
- { Note: this coding assumes Sum3 is at an offset of 0. }
- procedure VertSumEga; { Avoid slow screen with copy. }
- inline(
- $FC/ { cld ;Sting stuff to incr. }
- $B8/>$B800/ { mov ax, segScreen ;Establish screen segs. }
- $8E/$C0/ { mov es, ax }
- $BB/>LifeTest/ { mov bx, LifeTest ;Translate Table }
- $B9/>1920/ { mov cx, 3840/2 ;Do all in one line. }
- $BF/>80/ { mov di, 80 ;Start one line in. }
- { V_LOOP }
- $8B/$45/<-80/ { mov ax, Sum3 - 80 [di] ;Get one triple-sum. }
- $03/$05/ { add ax, Sum3[di] ;Add a second, }
- $03/$45/<+80/ { add ax, Sum3 + 80[di] ;And the third. }
- $33/$85/>Cells/ { xor ax, Cells[di], ;Mix with screen. }
- $D7/ { xlat bx [al] ;Change to 2 cells. }
- $86/$C4/ { xchg ah, al }
- $D7/ { xlat bx [al] ;Change to 2 cells. }
- $86/$C4/ { xchg ah, al }
- $89/$85/>Cells/ { mov Cells[di], ax ;save new generation. }
- $26/ { es: }
- $AB/ { stosw ;Store image. }
-
- { Repeat the block once to cut prefetch queue and looping over head. }
- $8B/$45/<-80/$03/$05/$03/$45/<+80/$33/$85/>Cells/
- $D7/$86/$C4/$D7/$86/$C4/$89/$85/>Cells/$26/$AB/
-
- $E2/$CE); { loop V_LOOP ;Do till done. }
-
-
- procedure VertSumCga; { Work directly with fast screen. }
- inline(
- $FC/ { cld ;Sting stuff to incr. }
- $B8/>$B800/ { mov ax, segScreen ;Establish screen segs. }
- $8E/$C0/ { mov es, ax }
- $BB/>LifeTest/ { mov bx, LifeTest ;Translate Table }
- $B9/>1920/ { mov cx, 3840/2 ;Do all in one line. }
- $BF/>80/ { mov di, 80 ;Start one line in. }
- { V_LOOP }
- $8B/$45/<-80/ { mov ax, Sum3 - 80 [di] ;Get one triple-sum. }
- $03/$05/ { add ax, Sum3[di] ;Add a second, }
- $03/$45/<+80/ { add ax, Sum3 + 80[di] ;And the third. }
- $26/ { es: }
- $33/$05/ { xor ax, Screen[di], ;Mix with screen. }
- $D7/ { xlat bx [al] ;Change to 2 cells. }
- $86/$C4/ { xchg ah, al }
- $D7/ { xlat bx [al] ;Change to 2 cells. }
- $86/$C4/ { xchg ah, al }
- $26/ { es: }
- $AB/ { stosw ;Store image. }
-
- { Rep once to cut c_loop and queue flush overhead. }
- $8B/$45/<-80/$03/$05/$03/$45/<+80/ { Form 9 sums. }
- $26/$33/$05/$D7/$86/$C4/$D7/$86/$C4/$26/$AB/ { Mash and translate. }
-
- $E2/$D8); { loop V_LOOP ;Do till done. }
-
-
-
-
- {***************************************************************}
- {* End of assembly mumbo-jumbo, I promise. *}
- {***************************************************************}
-
-
- { Palette control. }
- procedure SetBack(Color : integer);
- begin
- Regs.Ax := $B00;
- Regs.Bx := Color and $F;
- intr($10, Regs);
- end;
-
- procedure SetFore(Color : integer);
- begin
- Regs.Ax := $B00;
- Regs.Bx := $100 + (Color and 1);
- intr($10, Regs);
- end;
-
- { Only evey other dot on every other line is used on 320x200 mode 4. }
- procedure SetVideoMode(NewMode : integer);
- begin
- Regs.Ax := NewMode;
- intr($10, Regs);
- SetFore(ForeGround);
- SetBack(BackGround);
- end;
-
- { Save initial video mode. }
- function GetVideoMode : integer;
- begin
- Regs.Ax := $0F00;
- intr($10, Regs);
- GetVideoMode := Regs.ax and $FF;
- end;
-
- { Use Bios to print even those tough-to-print characters. }
- procedure BiosOut(PrtStr : str80);
- var
- OutChar : char;
- i : integer;
- begin
- for i := 1 to length(PrtStr) do begin;
- OutChar := PrtStr[i];
- Regs.ax := $0E00 + ord(OutChar); { TTY character out. }
- Regs.bx := $0002;
- intr($10,Regs);
- end;
- end;
-
- { Add carriage-return and line feed if desired. }
- procedure BiosLn(PrtStr : str80);
- begin
- BiosOut(PrtStr + #13 + #10);
- end;
-
- { Bios Output with automatic highlight on capital letters. }
- procedure HighLn(PrtStr : str80);
- var
- OutChar : char;
- i : integer;
- begin
- PrtStr := PrtStr + #13 + #10;
- for i := 1 to length(PrtStr) do begin;
- OutChar := PrtStr[i];
- Regs.ax := $0E00 + ord(OutChar); { TTY character out. }
- case OutChar of
- 'A'..'Z',
- #20..#30,
- '1'..'9',
- '<', '>' : Regs.bx := $0003;
- else Regs.bx := $0002;
- end;
- intr($10,Regs);
- end;
- end;
-
- { Recover realtime fractional seconds from BIOS. }
- function GetSecs : real;
- begin
- regs.ax := $0;
- intr($1A, regs); { 18.20648 Hz clock counts. }
- GetSecs := ((regs.cx * 65536.0) + regs.dx) * 0.05493;
- end;
-
- { Functional-style real format utility. }
- function RealToStr(InVal: real; Width, Decimals : integer) : str80;
- var
- WorkStr : str80;
- begin
- str(InVal :Width:Decimals, WorkStr);
- RealToStr := WorkStr;
- end;
-
- { Sync to any new key, and eat it. }
- procedure KeySync;
- var
- KeyS : char;
- begin
- while KeyPressed do KeyS := ReadKey;
- repeat until KeyPressed;
- while KeyPressed do KeyS := ReadKey;
- end;
-
- { Put Screen into Saver array. }
- procedure SaveScreen;
- var
- i : integer;
- begin
- Move(Screen, Saver, 8000);
- end;
-
- { Redisplay the Saver array on the screen. }
- procedure RestoreScreen;
- var
- i : integer;
- begin
- GotoXY(1,1);
- Move(Saver, Cells, 8000);
- Move(Saver, Screen, 8000);
- FillChar(Screen[4000], 8000, #0); { Blank unused lines. }
- end;
-
- { Clear the life cells out. }
- procedure ZeroScreen;
- var i : integer;
- begin
- for i := 0 to 3999 do begin
- Screen[i] := 0; { Clear the working lines. }
- Screen[i + 4000] := 0; { Clear the odd lines too. }
- Cells[i] := 0;
- Saver[i] := 0;
- Sum3[i] := 0; { Might as well clear sums too. }
- end;
- end;
-
- { Logical or data onto the screen. }
- procedure OrCell( Xpos: Xindex; Ypos: Yindex; Val: word);
- var
- I: integer;
- Mask : word;
- begin
- Val := Val and $F;
- Xpos := (Xpos + 160) mod 160; { Provide wrap around. }
- if Ypos < 100
- then I := (Ypos * 40) + (Xpos shr 2)
- else I := ((Ypos - 100) * 40) + 4096 + (Xpos shr 2);
- case (Xpos mod 4) of
- 0 : Mask := Val shl 4 ;
- 1 : Mask := Val ;
- 2 : Mask := Val shl 12;
- 3 : Mask := Val shl 8 ;
- end;
- Screen[I] := Screen[I] or Mask;
- if Ypos < 100
- then begin
- Saver[I] := Saver[I] or Mask;
- Cells[I] := Saver[I];
- end;
- end;
-
- { Logical mask data off of the screen. }
- procedure MaskCell( Xpos: Xindex; Ypos: Yindex; Val: word);
- var
- I: integer;
- Mask : word;
- begin
- Val := Val and $F;
- Xpos := (Xpos + 160) mod 160;
- if Ypos < 100
- then I := (Ypos * 40) + (Xpos shr 2)
- else I := ((Ypos - 100) * 40) + 4096 + (Xpos shr 2);
-
- case (Xpos mod 4) of
- 0 : Mask := $FF0F or (Val shl 4 );
- 1 : Mask := $FFF0 or (Val );
- 2 : Mask := $0FFF or (Val shl 12);
- 3 : Mask := $F0FF or (Val shl 8 );
- end;
- Screen[I] := Screen[I] and Mask;
- if Ypos < 100
- then begin
- Saver[I] := Saver[I] and Mask;
- Cells[I] := Saver[I];
- end;
- end;
-
- { Logical or a cursor box onto the screen. }
- procedure CursorOn(Xloc: Xindex; Yloc: Yindex);
- var
- PreXloc : Xindex;
- begin
- PreXloc := (Xloc + 159) mod 160;
-
- OrCell(PreXloc, Yloc + 99, $3);
- OrCell(Xloc, Yloc + 99, $F);
- OrCell(PreXloc, Yloc, $3);
- OrCell(Xloc, Yloc, $3);
- OrCell(PreXloc, Yloc + 100, $3);
- OrCell(Xloc, Yloc + 100, $F);
- end;
-
- { Remove unwanted cursor box from screen. }
- procedure CursorOff(Xloc: Xindex; Yloc: Yindex);
- var
- PreXloc : Xindex;
- begin
- PreXloc := (Xloc + 159) mod 160;
-
- MaskCell(PreXloc, Yloc + 99, $C);
- MaskCell(Xloc, Yloc + 99, $0);
- MaskCell(PreXloc, Yloc, $C);
- MaskCell(Xloc, Yloc, $C);
- MaskCell(PreXloc, Yloc + 100, $C);
- MaskCell(Xloc, Yloc + 100, $0);
- end;
-
- { Logical or a variable size box onto the screen. }
- procedure BoxOn(Left, Top, Right, Bot: integer);
- var
- I : integer;
- begin
- OrCell(Left - 1, Top + 99, $3); { Top and Bottom }
- OrCell(Left - 1, Bot + 100, $3);
- for i := Left to Right do begin
- OrCell(i, Top + 99, $F);
- OrCell(i, Bot + 100, $F);
- end;
- for i := Top to Bot do begin { Two sides. }
- OrCell(Left - 1, i, $3);
- OrCell(Left - 1, i + 100, $3);
- OrCell( Right, i, $3);
- OrCell( Right, i + 100, $3);
- end;
- end;
-
- {Remove a variable size box onto the screen. }
- procedure BoxOff(Left, Top, Right, Bot: integer);
- var
- I : integer;
- begin
- MaskCell(Left - 1, Top + 99, $C); { Top and Bottom }
- MaskCell(Left - 1, Bot + 100, $C);
- for i := Left to Right do begin
- MaskCell(i, Top + 99, $0);
- MaskCell(i, Bot + 100, $0);
- end;
- for i := Top to Bot do begin { Two sides. }
- MaskCell(Left - 1, i, $C);
- MaskCell(Left - 1, i + 100, $C);
- MaskCell( Right, i, $C);
- MaskCell( Right, i + 100, $C);
- end;
- end;
-
-
- { Force a life cell on. }
- procedure Birth(Xloc: Xindex; Yloc: Yindex);
- begin
- OrCell(Xloc, Yloc, 8);
- end;
-
- { Force a life cell off. }
- procedure LifeOff(Xloc: Xindex; Yloc: Yindex);
- begin
- MaskCell(Xloc, Yloc, 3);
- end;
-
-
- { Fetch a cell from the screen. }
- function GetCell( Xpos, Ypos : integer) : integer;
- var
- I: integer;
- begin
- if Ypos < 100
- then I := (Ypos * 40) + (Xpos shr 2)
- else I := ((Ypos - 100) * 40) + 4096 + (Xpos shr 2);
-
- case (Xpos mod 4) of
- 0 : GetCell := (Screen[I] shr 4) and $F;
- 1 : GetCell := (Screen[I] shr 0) and $F;
- 2 : GetCell := (Screen[I] shr 12) and $F;
- 3 : GetCell := (Screen[I] shr 8) and $F;
- end;
- end;
-
- { Move the current pointer position with checks. }
- function IncX(Xpos : Xindex) : Xindex;
- begin
- if Xpos >= 159 then IncX := 0 else IncX := Xpos + 1;
- end;
- function DecX(Xpos : Xindex) : integer;
- begin
- if Xpos <= 0 then DecX := 159 else DecX := Xpos - 1;
- end;
- function IncY(Ypos: Yindex) : Yindex;
- begin
- if Ypos >= 99 then IncY := 99 else IncY := Ypos + 1;
- end;
- function DecY(Ypos : Yindex) : Yindex;
- begin
- if Ypos <= 1 then DecY := 1 else DecY:= Ypos - 1;
- end;
-
- { Install a pulsar. }
- procedure Pulsar(X, Y: integer);
- begin
- Birth(X, Y); Birth(X+1, Y); Birth(X+2, Y); Birth(X+3, Y); Birth(X+4, Y);
- Birth(X, Y+1); Birth(X+4, Y+1);
- end;
-
- { Install an R pentamino. }
- procedure Pentamino(X, Y: integer);
- begin
- Birth(X+1, Y ); Birth(X+2, Y);
- Birth(X , Y+1); Birth(X+1, Y+1);
- Birth(X+1, Y+2);
- end;
-
- { Determine the existence of a file. }
- function FileExists(FileName: str80): boolean;
- var
- EXIST : file of byte;
- begin
- {$I-}
- assign(EXIST, FileName);
- reset(EXIST);
- close(EXIST);
- {$I+}
- if (IOresult = 0) and (FileName <> '')
- then FileExists := true
- else FileExists := false;
- end;
-
-
- { Initial screen is in the default mode so it can always be read. }
- procedure IntroScreen;
- var KeyI : char;
- begin
- writeln;
- writeln(' PC LIFE version 1.0 ');
- writeln;
- writeln(' This program plays John Horton Conway''s game of Life at ');
- writeln(' the rate of more than 300,000 cell-generations/second on ');
- writeln(' an IBM PS/2 model 50. You will need a color monitor and a ');
- writeln(' CGA, EGA, VGA, or other adapter with video mode 4 to play. ');
- writeln(' Send your questions, comments, and gratuities to: ');
- writeln;
- writeln(' Robert Norton ');
- writeln(' 706 Copeland St. ');
- writeln(' Madison, WI 53711 ');
- writeln;
- writeln;
- writeln(' To use the menu now, or while running, press <F1>. ');
- writeln;
- writeln;
- writeln;
- end;
-
- { Give a brief history of the rules and story of Life. }
- procedure History;
- var
- KeyH : char;
- begin
- SetVideoMode(OldMode); { Use old mode for text. }
- ClrScr;
- GotoXY(1,1);
- writeln(' ');
- writeln(' -- About life -- ');
- writeln(' ');
- writeln(' The game of Life mimics the constantly changing status of cells in ');
- writeln(' a colony over the course of many generations. Like real cells, ');
- writeln(' some groups of Life cells have interesting and unexpected properties. ');
- writeln(' A collection of well-known groups is included with this program. ');
- writeln(' To observe one of these groups in action, read its file (*.LIF) to the');
- writeln(' screen from the edit menu. ');
- writeln(' ');
- writeln(' The rules of Life are deceptively simple. The game is played on a ');
- writeln(' grid of squares, each of which is empty or contains a cell. Each ');
- writeln(' square has eight neighbor squares: four orthogonal and four ');
- writeln(' diagonal. ');
- writeln(' ');
- writeln(' The future of each square is determined solely by the number of cells ');
- writeln(' that surround it. A cell persists into the next generation only if ');
- writeln(' it has two or three neighbors. An empty square stays empty unless it ');
- writeln(' has exactly three living neighbors. If an empty square has three ');
- writeln(' neighbors, it will be filled with a cell on the next generation. ');
- writeln(' ');
- writeln(' ');
- writeln(' Press <SPACE> for more . . . ');
- KeySync;
- ClrScr;
- GotoXY(1, 1);
- writeln(' ');
- writeln(' -- About life -- ');
- writeln(' ');
- writeln(' The game of Life is the creation of John Horton Conway, a ');
- writeln(' distinguished mathematician at the University of Cambridge. Although ');
- writeln(' I am not sure of the exact date, it appears that this set of rules ');
- writeln(' was first published in early 1970. ');
- writeln(' ');
- writeln(' The popular science writer Martin Gardner has done much to bring ');
- writeln(' this fascinating pastime to the attention of computer enthusiasts. I ');
- writeln(' recommend his book "Wheels, Life, and Other Mathematical Amusements" ');
- writeln(' as an enjoyable introduction to the game of Life. I hope this ');
- writeln(' program brings you many hours of lively diversion. ');
- writeln(' ');
- writeln(' All of the actual English and much of the user interface of this ');
- writeln(' program is brought to you through the courtesy of Bennett Berson. ');
- writeln(' ');
- writeln(' Robert Norton ');
- writeln(' ');
- writeln(' ');
- writeln(' Press <SPACE> for the main menu. ');
- KeySync;
- SetVideoMode(4); { 320x200. }
- RestoreScreen;
- end;
-
-
- { Save a collection of cells to disk. }
- procedure DoWriteFile;
- var
- MoveDone : boolean;
- LeftX, RightX, ScanX: Xindex;
- TopY, BottomY, ScanY: Yindex;
- Title : string[40];
- FileName : string[12];
- WriteFile : text;
- KeyW : char;
- begin
- RestoreScreen;
- BiosLn(' -- Write a Life File. --');
- BiosLn(' Give a brief description of the area');
- BiosLn(' that you are going to save on disk: ');
- BiosOut('[ ]' + #13);
- BiosOut('['); readln(Title);
- BiosLn(' Give a DOS name for the save file.');
- BiosOut('[_ .LIF]' + #13);
- BiosOut('['); readln(FileName);
- if pos('.', FileName) = 0 then FileName := Filename + '.LIF';
-
- RestoreScreen;
- LeftX := 40; TopY := 50;
- CursorOn(LeftX, TopY);
- GotoXY(1, 1);
- BiosLn('Move to upper left corner of the area ');
- HighLn('to be saved to disk, then press <SPACE>.');
-
- MoveDone := false;
- repeat
- KeyW := ReadKey;
- case KeyW of
- #0 : if keypressed
- then begin
- KeyW := ReadKey;
- if KeyW in [#77, #75, #72, #80]
- then begin
- CursorOff(LeftX, TopY);
- case KeyW of
- #77 : LeftX := IncX(LeftX); { right }
- #75 : LeftX := DecX(LeftX); { left }
- #72 : TopY := DecY(TopY); { up }
- #80 : TopY := IncY(TopY); { down }
- end;
- if TopY < 15 then RestoreScreen;
- CursorOn(LeftX, TopY);
- end;
- end;
-
- ' ', #27 : MoveDone := true;
- else MoveDone := true;
- end {of case on keys };
- until MoveDone;
-
- RestoreScreen;
- RightX := LeftX; BottomY := TopY;
- BoxOn(LeftX, TopY, RightX, BottomY);
-
- GotoXY(1, 1);
- BiosLn('Stretch the box to cover the area to ');
- HighLn('be saved to disk, then press <SPACE>. ');
-
- MoveDone := false;
- repeat
- KeyW := ReadKey;
- case KeyW of
- #0 : if keypressed
- then begin
- KeyW := ReadKey;
- if KeyW in [#77, #75, #72, #80]
- then begin
- BoxOff(LeftX, TopY, RightX, BottomY);
- case KeyW of
- #77 : RightX := IncX(RightX); { right }
- #75 : RightX := DecX(RightX); { left }
- #72 : BottomY := DecY(BottomY); { up }
- #80 : BottomY := IncY(BottomY); { down }
- end;
- if TopY < 15 then RestoreScreen;
- BoxOn(LeftX, TopY, RightX, BottomY)
- end;
- end;
- ' ', #27 : MoveDone := true;
- else MoveDone := true;
- end {of case on keys };
- until MoveDone;
- BoxOff(LeftX, TopY, RightX, BottomY);
-
- assign(WriteFile, FileName); { Scan region for life. }
- rewrite(WriteFile);
- writeln(WriteFile, Title);
- for ScanY := TopY to BottomY
- do begin
- for ScanX := LeftX to RightX
- do begin
- if GetCell(ScanX, ScanY) = 8
- then writeln(WriteFile, ScanX - LeftX, ' ', ScanY - TopY);
- end;
- end;
- close(WriteFile);
- end;
-
-
- { Read a previously saved file from the disk. }
- procedure DoReadFile;
- var
- MoveDone : boolean;
- LeftX, DeltaX, MaxX: Xindex;
- TopY, DeltaY, MaxY: Yindex;
- Title : string[40];
- FileName : string[12];
- ReadFile : text;
- KeyW : char;
- Missing : boolean;
- begin
- Missing := false;
- repeat
- RestoreScreen;
- if Missing then BiosLn('Can''t find ' + FileName);
-
- BiosLn(' -- Read a Life File -- ');
- BiosLn('Give the DOS name for the saved file');
- BiosLn('that you wish to restore: ');
- BiosOut('[_ .LIF]' + #13);
- BiosOut('['); readln(FileName);
- if pos('.', FileName) = 0 then FileName := Filename + '.LIF';
- if FileExists(FileName)
- then Missing := false
- else Missing := true;
- until not Missing;
-
- MaxX := 1; MaxY := 1;
- assign(ReadFile, FileName);
- reset(ReadFile);
- readln(ReadFile, Title);
- while not EOF(ReadFile)
- do begin
- readln(ReadFile, DeltaX, DeltaY);
- if DeltaX > MaxX then MaxX := DeltaX;
- if DeltaY > MaxY then MaxY := DeltaY;
- end;
- close(ReadFile);
-
- RestoreScreen;
- GotoXY(1, 1);
- BiosLn('[' + Title + ']');
- BiosLn('Move the box to the area you want to ');
- HighLn('restore from disk, then press <SPACE>. ');
-
- MaxX := 1; MaxY := 1;
- assign(ReadFile, FileName);
- reset(ReadFile);
- readln(ReadFile, Title);
- while not EOF(ReadFile)
- do begin
- readln(ReadFile, DeltaX, DeltaY);
- if DeltaX > MaxX then MaxX := DeltaX;
- if DeltaY > MaxY then MaxY := DeltaY;
- end;
- close(ReadFile);
-
- LeftX := 80 - (MaxX div 2); { Start at center of screen. }
- TopY := 50 - (MaxY div 2);
- BoxOn(LeftX, TopY, LeftX + MaxX, TopY + MaxY);
- repeat until KeyPressed;
-
- MoveDone := false;
- repeat
- KeyW := ReadKey;
- case KeyW of
- #0 : if keypressed
- then begin
- KeyW := ReadKey;
- if KeyW in [#77, #75, #72, #80]
- then begin
- BoxOff(LeftX, TopY, LeftX + MaxX, TopY + MaxY);
- case KeyW of
- #77 : LeftX := IncX(LeftX); { right }
- #75 : LeftX := DecX(LeftX); { left }
- #72 : TopY := DecY(TopY); { up }
- #80 : TopY := IncY(TopY); { down }
- end;
- if TopY < 15 then RestoreScreen;
- BoxOn(LeftX, TopY, LeftX + MaxX, TopY + MaxY)
- end;
- end;
-
- ' ' : MoveDone := true;
- else MoveDone := true;
- end {of case on keys };
- until MoveDone;
- BoxOff(LeftX, TopY, LeftX + MaxX, TopY + MaxY);
-
- assign(ReadFile, FileName);
- reset(ReadFile);
- readln(ReadFile, Title);
- while not EOF(ReadFile)
- do begin
- readln(ReadFile, DeltaX, DeltaY);
- OrCell(LeftX + DeltaX, TopY + DeltaY, 8);
- end;
- close(ReadFile);
- end;
-
-
- { Allow manual entry or removal of cells from screen. }
- procedure EditCells;
- var
- KeyE : char;
- i, j : integer;
- Editing : boolean;
- CurX, CurY :integer;
- MenuOn : boolean;
-
- begin
- CurX := 80; CurY := 50;
- RestoreScreen;
- MenuOn := false;
- Editing := true;
- while Editing do begin
- if (CurY < 25) and MenuOn { Blank menu when too high. }
- then begin
- RestoreScreen;
- MenuOn := false;
- end;
- if (CurY > 30) and (not MenuOn) { Restore menu if needed. }
- then begin
- SaveScreen;
- GotoXY(1, 1);
- BiosLn(' -- Edit Menu -- ');
- HighLn( #24 + ' ' + #25 + ' ' + #27 + ' ' + #26
- + ' moves box Clears all cells');
- HighLn('<INS> adds a cell Reads a disk file ');
- HighLn('<DEL> zaps a cell Writes a disk file');
- HighLn('Adds random cells Single step ');
- HighLn(' <F1> for the main menu. ');
- MenuOn := true;
- end;
-
- CursorOn(CurX, CurY);
- KeyE := ReadKey;
- case KeyE of { Support MS-mouse with default.com loaded. }
- #0 : begin
- KeyE := ReadKey;
- case KeyE of
- #59 : Editing := false;
-
- #82 : Birth(CurX, CurY); { insert key. }
- #83 : LifeOff(CurX, CurY); { delete }
-
- #61 : begin { mouse left button. }
- if (GetCell(CurX, CurY) and 8) = 0
- then Birth(CurX, CurY)
- else LifeOff(CurX, CurY);
- end;
-
- #77, #75, #72, #80,
- #73, #81, #71, #79 : { cursor moves. }
- begin
- CursorOff(CurX, CurY);
- case KeyE of
- #77 : CurX := IncX(CurX); { right }
- #75 : CurX := DecX(CurX); { left }
- #72 : CurY := DecY(CurY); { up }
- #80 : CurY := IncY(CurY); { down }
- #73 : begin { pg up }
- CurX := IncX(CurX);
- CurY := DecY(CurY)
- end;
- #81 : begin { pg dn }
- CurX := IncX(CurX);
- CurY := IncY(CurY);
- end;
- #71 : begin { home }
- CurX := DecX(CurX);
- CurY := DecY(CurY)
- end;
- #79 : begin { end }
- CurX := DecX(CurX);
- CurY := IncY(CurY);
- end;
- end;
- end;
- else Editing := false;
- end;
- end;
-
- 'M', 'm', #27 : Editing := false;
-
- #13 : begin { mouse right button. }
- if (GetCell(CurX, CurY) and 8) = 0
- then Birth(CurX, CurY)
- else LifeOff(CurX, CurY);
- end;
-
- 'C', 'c' : begin { Clear. }
- ZeroScreen;
- MenuOn := false;
- end;
-
- 'D', 'd' : for i := CurX - 2 to CurX + 2 do begin
- for j := CurY - 2 to CurY + 2 do begin
- LifeOff((i + 160) mod 160,
- (j + 100) mod 100 );
- end;
- end;
-
- 'R', 'r' : begin
- CursorOff(CurX, CurY);
- DoReadFile;
- RestoreScreen;
- MenuOn := false;
- end;
-
- 'W', 'w' : begin
- CursorOff(CurX, CurY);
- DoWriteFile;
- RestoreScreen;
- MenuOn := false;
- end;
-
- ' ', 'S', 's' : begin
- CursorOff(CurX, CurY);
- if MenuOn
- then begin
- RestoreScreen;
- MenuOn := false;
- CurX := 20;
- CurY := 20;
- end;
- if SlowScreen
- then begin SideSumEga; VertsumEga end
- else begin SideSumCga; VertSumCga end;
- SaveScreen;
- end;
-
- 'A', 'a' : for i := 1 to 100 { Add random. }
- do Birth(Random(160), 2 + random(94));
-
- else Editing := false;
- end {of case on keys };
- end; { of while Editing. }
-
- CursorOff(CurX, CurY);
- end;
-
- { Allow manual adjustment of palette. }
- procedure DoPalette;
- var
- KeyP : char;
- I : integer;
- PaletteDone : boolean;
- begin
- ZeroScreen;
- Pulsar(100, 50);
- Pulsar( 80, 50);
- Pulsar( 60, 50);
-
- GotoXY(1, 1);
- BiosLn(' -- Color Menu -- ');
- HighLn( #24 + ' or ' + #25 + ' to select background color.');
- HighLn( #27 + ' or ' + #26 + ' to select foreground color.');
- BiosLn('Please try to keep this text legible. ');
- HighLn(' <F1> for the main menu. ');
-
- while keypressed do KeyP := ReadKey;
- repeat until keypressed;
-
- PaletteDone := false;
- repeat
- KeyP := ReadKey;
- case KeyP of
-
- #0 : begin
- KeyP := ReadKey;
- case KeyP of
- #59 : PaletteDone := true;
- #77 : ForeGround := 1; {rt}
- #75 : ForeGround := 0; {lt}
- #72 : BackGround := (BackGround + 1) mod 16; {up}
- #80 : Background := (BackGround - 1) mod 16; {dn}
- end;
- SetBack(BackGround);
- SetFore(ForeGround);
- end;
-
- 'A'..'P' : begin
- BackGround := ord(KeyP) - ord('A');
- SetBack(BackGround)
- end;
-
- 'a'..'p' : begin
- BackGround := ord(KeyP) - ord('a');
- SetBack(BackGround);
- end;
-
- ' ', #27 : PaletteDone := true;
-
- '1', '2' : begin
- ForeGround := ord(KeyP) - ord('1');
- SetFore(ForeGround);
- end;
-
- end {of case on keys };
- until PaletteDone;
- RestoreScreen;
- end;
-
-
- { 1000 generation speed test; }
- procedure SpeedTest1000(LifeMode : char); { Ega, Cga, or Pascal}
- var
- i : integer;
- KeyS : char;
- StartSecs : real;
- TotalSecs : real;
- OutStr : str80;
- begin
- ZeroScreen;
- Pulsar(10, 60);
- Pulsar(10, 80);
- Pulsar(150, 10);
- Pulsar(150, 30);
- Pentamino(70, 40);
- {$I-,R-,S-}
- if LifeMode = 'E'
- then begin
- Sound(220); Delay(100); NoSound;
- StartSecs := GetSecs;
- for i := 1 to 200 do begin
- SideSumEga; VertSumEga;
- SideSumEga; VertSumEga;
- SideSumEga; VertSumEga;
- SideSumEga; VertSumEga;
- SideSumEga; VertSumEga;
- end;
- TotalSecs := Getsecs - StartSecs;
- Sound(220); Delay(100); NoSound;
- end;
-
- if LifeMode = 'C'
- then begin
- Sound(220); Delay(100); NoSound;
- StartSecs := GetSecs;
- for i := 1 to 200 do begin
- SideSumCga; VertSumCga;
- SideSumCga; VertSumCga;
- SideSumCga; VertSumCga;
- SideSumCga; VertSumCga;
- SideSumCga; VertSumCga;
- end;
- TotalSecs := Getsecs - StartSecs;
- Sound(220); Delay(100); NoSound;
- end;
-
- if LifeMode = 'P'
- then begin
- Sound(220); Delay(100); NoSound;
- StartSecs := GetSecs;
- for i := 1 to 200 do begin
- SideSum; VertSum;
- SideSum; VertSum;
- SideSum; VertSum;
- SideSum; VertSum;
- SideSum; VertSum;
- end;
- TotalSecs := Getsecs - StartSecs;
- Sound(220); Delay(100); NoSound;
- end;
- {$I+,R+,S+}
-
- GotoXY(1,1);
- BiosLn(' --Test Results--');
- BiosLn(' ');
- BiosLn(' Cells per second = '+ RealToStr(16000000.0/TotalSecs, 7, 0));
- BiosLn(' Elapsed time = '+ RealToStr(TotalSecs, 6, 3));
- BiosLn('Generations per second = '+ RealToStr(1000.0 /TotalSecs, 6, 3));
- BiosLn(' 1 cell generation = '+ RealToStr(TotalSecs/16.0, 5, 3) + 'uS');
- BiosLn(' ');
- if SlowScreen
- then BiosLn(' Currently in Ega/Vga display mode. ')
- else BiosLn(' Currently in Cga display mode.');
- BiosLn(' ');
- HighLn (' <F1> for the main menu. ');
-
- KeySync;
- end;
-
- procedure SpeedStuff;
- var
- KeyS : char;
- begin
- RestoreScreen;
- BiosLn (' -- Speed Menu --');
- HighLn ('Test the speed Ega/Vga card mode ');
- HighLn ('<F1> main menu Cga card mode ');
- BiosLn(' ');
- if SlowScreen
- then BiosLn(' Currently in Ega/Vga display mode. ')
- else BiosLn(' Currently in Cga display mode.');
-
- while KeyPressed do KeyS := ReadKey;
- KeyS := ReadKey;
- case KeyS of
- 'E', 'e',
- 'V', 'v' : begin
- SlowScreen := true;
- RestoreScreen;
- GotoXY(1,1);
- BiosLn (' -- Now in Ega/Vga mode-- ');
- BiosLn ('This mode is optimized to work with');
- BiosLn ('display adapters that use tons of');
- BiosLn ('wait states, like the Vga or Ega.');
- HighLn (' <F1> for the main menu.');
-
- KeySync;
- end;
-
- 'C', 'c' : begin
- SlowScreen := false;
- RestoreScreen;
- BiosLn (' -- Now in CGA mode-- ');
- BiosLn ('This mode is optimized to work with');
- BiosLn ('display adapters that do not add many');
- BiosLn ('wait states, like the CGA card.');
- HighLn (' <F1> for the main menu.');
-
- KeySync;
- end;
-
-
- 'P', 'p' : SpeedTest1000('P');
-
- 'T', 't' : if SlowScreen
- then SpeedTest1000('E')
- else SpeedTest1000('C');
-
- end;
-
- while KeyPressed do KeyS := ReadKey;
- RestoreScreen;
- end;
-
-
-
-
- { This is the main level menu. }
- procedure HandleMenu;
- var
- i : integer;
- KeyM : char;
- MenuDone : boolean;
- begin
- while keypressed do KeyM := ReadKey;
- if KeyM = ' ' then KeyM := '?'; { suppress initial single step spaces. }
- MenuDone := false;
- SaveScreen;
- repeat
-
- Biosln (' -- Main Menu -- ');
- HighLn (' Edit image Colors Speed ');
- HighLn (' Run life About life Quit ');
- KeyM := ReadKey;
- case KeyM of
- #0 : begin
- KeyM := ReadKey;
- MenuDOne := true;
- end;
-
- 'Q', 'q' : begin
- SaveScreen; { Saves the text, too! }
- AllDone := true;
- MenuDone := true;
- end;
-
- 'E', 'e' : EditCells;
-
- 'A', 'a' : History;
-
- 'R', 'r' : begin
- MenuDone := true;
- end;
-
- ' ' : begin
- RestoreScreen;
- if SlowScreen
- then begin SideSumEga; VertSumEga; end
- else begin SideSumCga; VertSumCga; end;
- SaveScreen;
- end;
-
- 'C', 'c' : DoPalette;
-
- 'S', 's' : SpeedStuff;
-
- end;
-
- RestoreScreen;
- until MenuDone;
- end;
-
-
- procedure Final; { Play with the menu text as a life array. }
- var
- i : integer;
- begin
- if SlowScreen
- then for i := 1 to 30 do begin
- SideSumEga; VertSumEga;
- end
- else for i := 1 to 30 do begin
- SideSumCga; VertSumCga;
- end;
- end;
-
-
- begin
- OldMode := GetVideoMode;
-
- IntroScreen;
-
- InitResidue; { Initialize look-up tables. }
- InitSideSum;
- InitLifeTest;
-
- SlowScreen := true;
- if MachineId = $FD
- then begin
- ForeGround := 1;
- BackGround := 0;
- end
- else begin
- ForeGround := 1;
- BackGround := 15;
- end;
-
- repeat until keypressed;
-
- SetVideoMode(4); { 320x200, clear the way. }
- ZeroScreen;
-
- Pulsar(10, 85);
- Pentamino(80, 50);
- AllDone := false;
-
- HandleMenu;
- if SlowScreen
- then repeat
-
- SideSumEga; VertSumEga;
- SideSumEga; VertSumEga;
- SideSumEga; VertSumEga;
-
- if keypressed then HandleMenu;
-
- until AllDone
- else repeat
-
- SideSumCga; VertSumCga;
- SideSumCga; VertSumCga;
- SideSumCga; VertSumCga;
-
- if keypressed then HandleMenu;
-
- until AllDone;
-
- Final;
- SetVideoMode(OldMode); { Nice guys restore the mode. }
- end.