home *** CD-ROM | disk | FTP | other *** search
- {**************************************************}
- { Life 1.0 }
- { Written in }
- { Turbo Pascal for Windows }
- { Copyright (c) 1991 }
- { Zack Urlocker }
- { 05/02/91 }
- {**************************************************}
-
- program PLife;
-
- { This is a simple implementation of the Game of Life written
- in Turbo Pascal for Windows using the ObjectWindows application
- framework. The program is divided into three main object types:
-
- TLifeApplication --creates and shows the main window
- TLifeWindow --responds to Windows messages, menu commands,
- keyboard and mouse events
- TLifeCells --mutates and draws the cells in the window
- }
-
- {$R PLife.res} { Link in resources }
-
- {$IFDEF Final} { Remove debug code for final version}
- {$D-,I-,L-,R-,S-}
- {$ELSE}
- {$D+,I+,L+,R+,S+}
- {$ENDIF}
-
- uses WObjects, WinTypes, WinProcs, Strings, StdDlgs;
-
- const
- cm_Clear = 201; { command menu constant IDs }
- cm_Go = 202;
- cm_Trace = 203;
- cm_Stop = 204;
- cm_Exit = 209;
- cm_About = 210;
- cm_Timer = 301;
- cm_Grid = 302;
- cm_Zoom = 303;
- cm_Random = 401;
- cm_Bloom = 402;
- cm_Walker = 403;
- cm_Help = 501;
- cm_CmdMode = 601; { For Lotus style slash (/) key commands }
-
- XMax = 100; { Maximum matrix size }
- YMax = 100; { Only visible portion is used }
- MaxGrid = 50; { Maximum grid size for Zoom }
- MinGrid = 10; { Minimum grid size for Zoom }
-
- Dead = False; { cell values }
- Born = True;
-
- Black = $000000; { Windows color constants }
- White = $FFFFFF;
- Blue = $FF0000;
-
-
- type
-
- { The application defines startup behavior for the window. }
- TLifeApplication = object(TApplication)
- procedure InitInstance; virtual;
- procedure InitMainWindow; virtual;
- end;
-
- Matrix = array [0..XMax, 0..YMax] of Boolean;
-
- { The cells are responsible for mutating and drawing in a window.
- The cells will be notified whenever the size of the grid or
- number of rows and columns in the window changes. }
- TLifeCells = object(TObject)
- cells : matrix; { actual cells }
- scratchCells : matrix; { scratch work area }
- rows : integer; { visible rows }
- cols : integer; { visible columns }
- gridSize : integer; { for drawing a cell }
- constructor init; { initialize cells }
- procedure mutate(DC:HDC); { mutate all cells }
- procedure draw(DC:HDC); { draw all cells }
- procedure setCell(i,j:Integer; alive: Boolean);
- function aliveCell(i,j:Integer): Boolean;
- procedure walker(i,j:Integer);
- procedure bloom(i,j:Integer);
- procedure mutateCell(DC:HDC; i,j: integer);
- procedure drawCell(DC:HDC; i, j:Integer; alive: Boolean);
- end;
-
- { The window handles keyboard, mouse messages and controls cells. }
- PLifeWindow = ^TLifeWindow;
- TLifeWindow = object(TWindow)
- cells : TLifeCells; { cells being mutated }
- speed : Integer; { timer speed }
- running : Boolean; { is timer running? }
- rows : Integer; { visible rows }
- cols : Integer; { visible columns }
- grid : Boolean; { is grid turned on? }
- gridSize : Integer; { for drawing a cell }
- mouseDown : Boolean; { is mouse down? }
- xDown : Integer; { x location in grid }
- yDown : Integer; { y location in grid }
- mutateDC : HDC; { draw each mutation }
- mouseMoveDC : HDC; { draw mouse moves }
- constructor Init(AParent: PWindowsObject; ATitle: PChar);
- procedure GetWindowClass(var WndClass: TWndClass); virtual;
-
- { menu response methods }
- procedure Clear(var Msg: TMessage); virtual cm_First + cm_Clear;
- procedure Randomize(var Msg: TMessage); virtual cm_First + cm_Random;
- procedure Bloom(var Msg: TMessage); virtual cm_First + cm_Bloom;
- procedure Walker(var Msg: TMessage); virtual cm_First + cm_Walker;
- procedure Go(var Msg: TMessage); virtual cm_First + cm_Go;
- procedure Trace(var Msg: TMessage); virtual cm_First + cm_Trace;
- procedure Stop(var Msg: TMessage); virtual cm_First + cm_Stop;
- procedure Exit(var Msg: TMessage); virtual cm_First + cm_Exit;
- procedure About(var Msg: TMessage); virtual cm_First + cm_About;
- procedure Timer(var Msg: TMessage); virtual cm_First + cm_Timer;
- procedure GridToggle(var Msg: TMessage); virtual cm_First + cm_Grid;
- procedure Zoom(var Msg: TMessage); virtual cm_First + cm_Zoom;
- procedure Help(var Msg: TMessage); virtual cm_First + cm_Help;
- procedure CmdMode(var Msg: TMessage); virtual cm_First + cm_CmdMode;
-
- { windows message response methods }
- procedure Paint(DC: HDC; var PaintInfo: TPaintStruct); virtual;
- procedure DrawGrid(DC: HDC);
- procedure wmSetFocus(var Msg: TMessage); virtual wm_SetFocus;
- procedure wmKillFocus(var Msg: TMessage); virtual wm_KillFocus;
- procedure wmKeyDown(var Msg: TMessage); virtual wm_KeyDown;
- procedure wmLButtonDown(var Msg: TMessage); virtual wm_LButtonDown;
- procedure wmLButtonUp(var Msg: TMessage); virtual wm_LButtonUp;
- procedure wmLButtonDblClk(var Msg: TMessage); virtual wm_LButtonDblClk;
- procedure wmMouseMove(var Msg: TMessage); virtual wm_MouseMove;
- procedure wmRButtonDown(var Msg: TMessage); virtual wm_RButtonDown;
- procedure wmTimer(var Msg: TMessage); virtual wm_Timer + wm_First;
- procedure wmSize(var Msg: TMessage); virtual wm_Size;
- procedure wmGetMinMaxInfo(var Msg: TMessage); virtual wm_GetMinMaxInfo;
- procedure wmDestroy(var Msg: TMessage); virtual wm_Destroy;
- end;
-
-
- {--------------------------------------------------}
- { TLifeApplication's method implementations: }
- {--------------------------------------------------}
-
- { Load the accelerator table for hotkeys }
- procedure TLifeApplication.InitInstance;
- begin
- Tapplication.InitInstance;
- HAccTable := LoadAccelerators(HInstance, 'LifeKeys');
- end;
-
- { Start the main window }
- procedure TLifeApplication.InitMainWindow;
- begin
- MainWindow := New(PLifeWindow, Init(nil, 'PLife'));
- end;
-
-
- {--------------------------------------------------}
- { TLifeCell's method implementations: }
- {--------------------------------------------------}
-
- { Clear out the cell matrices }
- constructor TLifeCells.Init;
- begin
- fillchar(cells, sizeOf(cells), 0);
- fillchar(scratchCells, sizeOf(scratchCells), 0);
- end;
-
- { Is the cell alive? }
- function TLifeCells.aliveCell(i,j:Integer) : Boolean;
- begin
- aliveCell := cells[i,j];
- end;
-
- { Set the cell to born or dead state }
- procedure TLifeCells.setCell(i,j:Integer; alive:Boolean);
- begin
- cells[i, j] := alive;
- end;
-
- { Create an interesting pattern that "walks" across the screen }
- procedure TLifeCells.walker(i, j:Integer);
- begin
- cells[i,j+2] := Born;
- cells[i+1,j+2] := Born;
- cells[i+2,j+2] := Born;
- cells[i+2,j+1] := Born;
- cells[i+1,j] := Born;
- end;
-
- { Create an interesting pattern that "blooms" across the screen }
- procedure TLifeCells.bloom(i, j:Integer);
- begin
- cells[i+1,j] := Born;
- cells[i,j+1] := Born;
- cells[i,j+2] := Born;
- cells[i,j+3] := Born;
- cells[i+1,j+3] := Born;
- cells[i+2,j+3] := Born;
- cells[i+2,j+2] := Born;
- cells[i+2,j+1] := Born;
- end;
-
- { Draw a single cell as a borderless rectangle }
- procedure TLifeCells.drawCell(DC: HDC; i, j: Integer; alive: Boolean);
- var xScreen, yScreen : Integer;
- color : TColorRef;
- begin
- xScreen := i * gridSize;
- yScreen := j * gridSize;
- if alive then
- color := Blue
- else
- color := White;
- SelectObject(DC, CreateSolidBrush(color));
- rectangle(DC, xScreen+1, yScreen+1, xScreen+gridSize-1, yScreen+gridSize-1);
- DeleteObject(SelectObject(DC, GetStockObject(Black_Brush)));
- end;
-
- { Redraw active cells on screen }
- procedure TLifeCells.draw(DC:HDC);
- var i, j, xScreen, yScreen : Integer;
- begin
- for i:= 1 to cols do
- for j := 1 to rows do
- if cells[i,j] then
- drawCell(DC, i, j, born);
- end;
-
- { Determine how the cell should mutate by the number of neighbors
- it has. Too few or too many means it should die. }
- procedure TLifeCells.mutateCell(DC:HDC; i, j : integer);
- var neighbors : Integer;
- temp : Integer;
- begin
- neighbors := 0;
- if cells[i-1, j] then inc(neighbors);
- if cells[i+1, j] then inc(neighbors);
- if cells[i, j-1] then inc(neighbors);
- if cells[i, j+1] then inc(neighbors);
- if cells[i-1, j-1] then inc(neighbors);
- if cells[i+1, j+1] then inc(neighbors);
- if cells[i-1, j+1] then inc(neighbors);
- if cells[i+1, j-1] then inc(neighbors);
-
- if not cells[i, j] then { it's a dead cell }
- if neighbors = 3 then { bring it to life }
- begin
- scratchCells[i, j] := Born;
- drawCell(DC, i, j, Born);
- end
- else
- scratchCells[i, j] := cells[i, j]
-
- else { it's a live cell }
-
- if (neighbors < 2) or (neighbors > 3) then { kill it }
- begin
- scratchCells[i,j] := Dead;
- drawCell(DC, i, j, Dead);
- end
- else
- scratchCells[i,j] := cells[i,j];
- end;
-
- { Mutate all of the visible cells }
- procedure TLifeCells.mutate(DC:HDC);
- var i, j : Integer;
- begin
- for i:= 1 to cols do
- for j := 1 to rows do
- mutateCell(DC, i, j);
- { update the real matrix }
- cells := scratchCells;
- end;
-
-
- {--------------------------------------------------}
- { TLifeWindow's method implementations: }
- {--------------------------------------------------}
-
- { Initialize all fields to starting values, set attributes }
- constructor TLifeWindow.Init(AParent: PWindowsObject; ATitle: PChar);
- begin
- TWindow.Init(AParent, ATitle);
- cells.init;
- running := False;
- speed := 500;
- grid := True;
- gridSize := 20;
- cells.gridSize := 20;
- mouseDown := False;
- with attr do
- begin
- w:=400; { Force window size }
- h:=300;
- end;
- end;
-
- { Override default cursor, icon, menu and style }
- procedure TLifeWindow.GetWindowClass(var WndClass: TWndClass);
- begin
- TWindow.GetWindowClass(WndClass);
- WndClass.Style := CS_DBLCLKS; { Respond to double click }
- WndClass.hCursor := LoadCursor(hInstance, 'LifeCur');
- WndClass.hIcon := LoadIcon(hInstance, 'LifeIco');
- WndClass.lpszMenuName := 'LifeMenu';
- end;
-
- { Create a display context for drawing and mutate the cells.
- Use a white pen for the border, then set it back when done. }
- procedure TLifeWindow.wmTimer(var Msg: TMessage);
- begin
- mutateDC:=getDC(HWindow);
- selectObject(mutateDC, GetStockObject(White_Pen));
- cells.mutate(mutateDC);
- selectObject(mutateDC, GetStockObject(Black_Pen));
- releaseDC(HWindow, mutateDC);
- end;
-
- { Single step by stopping the timer and then mutate once }
- procedure TLifeWindow.Trace(var Msg: TMessage);
- var DC : HDC;
- begin
- stop(Msg);
- wmTimer(Msg);
- end;
-
- { Randomly create a starting pattern }
- procedure TLifeWindow.Randomize(var Msg: TMessage);
- var i, j : integer;
- begin
- clear(Msg);
- for i:= 1 to cols do
- for j := 1 to rows do
- if random(100) < 25 then
- cells.setCell(i, j, born);
- invalidateRect(HWindow, nil, True);
- end;
-
- { Create a non-random starting pattern }
- procedure TLifeWindow.Bloom(var Msg: TMessage);
- var i, j : Integer;
- begin
- clear(Msg);
- for i := 0 to cols div 7 do
- for j := 0 to rows div 7 do
- if not odd(i+j) then
- cells.bloom(4+I*7, 2+j*7);
- invalidateRect(HWindow, nil, True);
- end;
-
- { Create a non-random starting pattern }
- procedure TLifeWindow.Walker(var Msg: TMessage);
- var i, j : Integer;
- begin
- clear(Msg);
- for i := 0 to cols div 7 do
- for j := 0 to rows div 7 do
- if not odd(i+j) then
- cells.Walker(2+I*7, 2+j*7);
- invalidateRect(HWindow, nil, True);
- end;
-
- { Start the timer and update the menus }
- procedure TLifeWindow.Go(var Msg: TMessage);
- begin
- if SetTimer(HWindow, 1, speed, nil) <> 0 then
- begin
- running := True;
- modifyMenu(GetMenu(HWindow), cm_Go, mf_ByCommand or mf_Grayed,
- cm_Go, '&Go' + #9 + '^G');
- modifyMenu(GetMenu(HWindow), cm_Stop, mf_ByCommand or mf_Enabled,
- cm_Stop, '&Stop'+ #9 + '^S');
- end
- else
- begin
- running := False;
- messageBeep(0);
- messageBox(HWindow, 'No timers left to run Life;' + #13 +
- 'Close some windows and retry!' ,
- 'Error', mb_Ok + mb_IconStop);
- end;
- end;
-
- { Stop the timers and update the menus }
- procedure TLifeWindow.Stop(var Msg: TMessage);
- begin
- modifyMenu(GetMenu(HWindow), cm_Go, mf_ByCommand or mf_Enabled,
- cm_Go, '&Go'+#9 + '^G');
- modifyMenu(GetMenu(HWindow), cm_Stop, mf_ByCommand or mf_Grayed,
- cm_Stop, '&Stop'+ #9 + '^S');
- running := False;
- killTimer(HWindow, 1);
- end;
-
- { Exit the program }
- procedure TLifeWindow.Exit(var Msg: TMessage);
- begin
- postQuitMessage(0);
- end;
-
- { Display About box }
- procedure TLifeWindow.About(var Msg: TMessage);
- var Dlg: TDialog;
- begin
- Dlg.Init(@Self, 'AboutDlg');
- Dlg.Execute;
- Dlg.Done;
- end;
-
- { Stop current timer, prompt for new speed, restart }
- procedure TLifeWindow.Timer(var Msg: TMessage);
- var
- inputText: array[0..9] of Char;
- newSpeed, errorPos: Integer;
- begin
- stop(Msg);
- str(speed, inputText);
- if application^.ExecDialog(New(PInputDialog,
- Init(@Self, 'Timer Speed', 'Input new time (milliseconds):',
- inputText, sizeOf(inputText)))) = id_Ok then
- begin
- val(InputText, newSpeed, errorPos);
- if errorPos = 0 then
- speed := newSpeed
- else
- messageBeep(0);
- end;
- go(Msg);
- end;
-
- { Stop, clear the matrix, restart }
- procedure TLifeWindow.Clear(var Msg: TMessage);
- var paused : Boolean;
- begin
- paused := running;
- stop(Msg);
- cells.init;
- invalidateRect(HWindow, nil, True);
- if paused then
- go(Msg);
- end;
-
- { Toggle the displaying of the grid and redraw }
- procedure TLifeWindow.GridToggle(var Msg: TMessage);
- var style : word;
- begin
- grid := not grid;
- if grid then
- style := mf_Checked
- else
- style := mf_Unchecked;
- checkMenuItem(GetMenu(HWindow), cm_Grid, style);
- drawMenuBar(HWindow);
- invalidateRect(HWindow, nil, True);
- end;
-
- { Zoom the display, update internal info then redraw }
- procedure TLifeWindow.Zoom(var Msg: TMessage);
- begin
- gridSize := gridSize * 2;
- if gridSize > MaxGrid then
- gridSize := MinGrid;
- cols := attr.w div gridSize;
- rows := attr.h div gridSize;
- { update the cells }
- cells.rows := rows;
- cells.cols := cols;
- cells.gridSize := gridSize;
- invalidateRect(HWindow, nil, True);
- end;
-
- procedure TLifeWindow.Help(var Msg: TMessage);
- var Dlg: TDialog;
- begin
- Dlg.Init(@Self, 'HelpDlg');
- Dlg.Execute;
- Dlg.Done;
- end;
-
- { Respond to Lotus style commands from slash (/) accelerator }
- procedure TLifeWindow.CmdMode(var Msg: TMessage);
- begin
- sendMessage(HWindow, WM_SYSCOMMAND, $F100, 0);
- end;
-
- { Draw the grid and the cells }
- procedure TLifeWindow.Paint(DC: HDC; var PaintInfo: TPaintStruct);
- var i : integer;
- begin
- selectObject(DC, GetStockObject(Black_Pen));
- if grid then DrawGrid(DC);
- selectObject(DC, GetStockObject(White_Pen));
- cells.draw(DC);
- end;
-
- { Draw the grid background. }
- procedure TLifeWindow.DrawGrid(DC: HDC);
- var i : integer;
- begin
- for i := 1 to rows do
- begin
- moveTo(DC, 0, i*gridSize);
- lineTo(DC, attr.w, i*gridSize);
- end;
- for i := 1 to cols do
- begin
- moveTo(DC, i*gridSize, 0);
- lineTo(DC, i*gridSize, attr.h);
- end;
- end;
-
- { Ensure that cursor is visible even when no mouse }
- procedure TLifeWindow.wmSetFocus(var Msg: TMessage);
- begin
- ShowCursor(True);
- end;
-
- { Return cursor to previous state for other windows }
- procedure TLifeWindow.wmKillFocus(var Msg: TMessage);
- begin
- ShowCursor(False);
- end;
-
- { Use keyboard to simulate mouse events. Accelerator keys
- are handled as response methods. }
- procedure TLifeWindow.wmKeyDown(var Msg: TMessage);
- var x, y : Integer;
- pos : TPoint;
- key : word;
- begin
- { Determine position of cursor in Window }
- getCursorPos(pos);
- screenToClient(HWindow, pos);
- x:=pos.x;
- y:=pos.y;
- { move the cursor position }
- key := Msg.WParam;
- case key of
- VK_UP : y := y - gridSize;
- VK_DOWN : y := y + gridSize;
- VK_RIGHT : x := x + gridSize;
- VK_LEFT : x := x - gridSize;
- VK_HOME :
- begin
- x := gridSize div 2;
- y := gridSize div 2;
- end;
- VK_END :
- begin
- x := attr.w - gridSize div 2;
- y := attr.h - gridSize div 2;
- end;
- VK_RETURN,
- VK_SPACE :
- begin
- { Simulate mouse pressing at cursor position }
- Msg.LParam := LongInt(pos);
- wmLButtonDown(Msg);
- wmLButtonUp(Msg);
- end;
- end;
- { Update position of cursor in window with clipping }
- if x < 0 then x := gridSize div 2;
- if y < 0 then y := gridSize div 2;
- if x > cols * gridSize then x:= attr.w - gridSize div 2;
- if y > rows * gridSize then y:= attr.h - gridSize div 2;
- pos.x := x;
- pos.y := y;
- clientToScreen(HWindow, pos);
- setCursorPos(pos.x, pos.y);
- end;
-
- { Begin capturing mouse movement when the left button is pressed.
- A display context is taken; it is freed in the wmLButtonUp method. }
-
- procedure TLifeWindow.wmLButtonDown(var Msg: TMessage);
- begin
- if not mouseDown then
- begin
- xDown := -1; { sentinal values to track movement }
- yDown := -1;
- mouseDown := True;
- mouseMoveDC := GetDC(HWindow);
- selectObject(mouseMoveDC, GetStockObject(White_Pen));
- end;
- end;
-
- { Update the cells as the mouse is dragged }
- procedure TLifeWindow.WMMouseMove(var Msg: TMessage);
- var
- xScreen, yScreen, x, y : Integer;
- state : Boolean;
- begin
- if mouseDown then
- begin
- { determine where clicked }
- xScreen := Msg.LParamLo;
- yScreen := Msg.LParamHi;
- { translate into cell coordinates }
- x := xScreen div gridSize;
- y := yScreen div gridSize;
- if (x <> xDown) or (y <> yDown) then { a new position }
- begin
- { Invert the cell's state, then redraw }
- xDown := x; { store position }
- yDown := y;
- state := not(cells.aliveCell(x, y));
- cells.setCell(x, y, state);
- cells.drawCell(mouseMoveDC, x, y, state)
- end;
- end;
- end;
-
- { Stop capturing mouse movement when mouse is released }
- procedure TLifeWindow.wmLButtonUp(var Msg: TMessage);
- begin
- wmMouseMove(Msg); { force drawing in same spot }
- if mouseDown then
- begin
- mouseDown := False;
- selectObject(mouseMoveDC, GetStockObject(Black_Pen));
- releaseDC(HWindow, mouseMoveDC);
- end;
- end;
-
- { Turn off the grid on a double click }
- procedure TLifeWindow.wmLButtonDblClk(var Msg: TMessage);
- begin
- gridToggle(Msg);
- end;
-
- { Zoom when right mouse button is pressed }
- procedure TLifeWindow.wmRButtonDown(var Msg: TMessage);
- begin
- zoom(Msg);
- end;
-
- { update internal information when resizing then redraw }
- procedure TLifeWindow.wmSize(var Msg: TMessage);
- begin
- rows := Msg.lParamHi div gridSize;
- cols := Msg.lParamLo div gridSize;
- { update the cells information }
- cells.rows := rows;
- cells.cols := cols;
- attr.h := Msg.lParamHi;
- attr.w := Msg.lParamLo;
- invalidateRect(HWindow, nil, True);
- end;
-
- type
- { In the wmGetMinMaxInfo message, LParam points to an
- array [0..4] of Points. The last one can be set to
- the maximum tracking size. }
- PPointArray = ^TPointArray;
- TPointArray = Array[0..4] of TPoint;
-
- { Prevent window from becoming larger than maximum array size }
- procedure TLifeWindow.wmGetMinMaxInfo(var Msg: TMessage);
- var MaxSize : TPoint;
- begin
- MaxSize.x := xMax * MinGrid;
- MaxSize.y := yMax * MinGrid;
- PPointArray(Msg.LParam)^[4]:= MaxSize;
- end;
-
- { When the window is destroyed, stop any timers }
- procedure TLifeWindow.wmDestroy(var Msg: TMessage);
- begin
- KillTimer(HWindow, 1);
- TWindow.WMDestroy(Msg);
- end;
-
- {--------------------------------------------------}
- { Main program: }
- {--------------------------------------------------}
-
- var
- Life : TLifeApplication;
-
- begin
- Life.Init('PLife');
- Life.Run;
- Life.Done;
- end.