1 /*
2  *  ULife, the old computer simulation of Life.
3  *  Copyright (C) 1998 by Ian Lane (email: lanei@ideal.net.au)
4  *
5  *  Distribution: This control is free for public use and components may be
6  *  freely descended from it as long as credit is given to the author.
7  *
8  *  Converted to C#: 20/07/2011, Sergey V. Zhdanovskih.
9  */
10 
11 using System;
12 using System.Drawing;
13 using System.Drawing.Drawing2D;
14 using System.Windows.Forms;
15 using GKUI.Components;
16 
17 namespace GKLifePlugin.ConwayLife
18 {
19     public class LifeViewer : UserControl
20     {
21         private bool fAcceptMouseClicks;
22         private int fGeneration;
23         private Color fGridLineColor;
24         private DashStyle fGridLineStyle;
25         private NotifyEvent fOnChange;
26         private DoesCellLiveEvent fOnDoesCellLive;
27         private bool fShowGridLines;
28 
29         private readonly LifeGrid fGrid;
30         private readonly LifeHistory fHistory;
31         private readonly LifeOptions fOptions;
32         private readonly LifeRules fRules;
33 
34 
35         public short this[int X, int Y]
36         {
37             get {
38                 return fGrid[X, Y];
39             }
40             set {
41                 if (fGrid[X, Y] != value) {
42                     SetCell(X, Y, value);
43                     ResetGeneration();
44                     fHistory.Clear();
45                 }
46             }
47         }
48 
49         public int Generation
50         {
51             get { return fGeneration; }
52         }
53 
54         public LifeHistory History
55         {
56             get { return fHistory; }
57         }
58 
59         public int LiveCellCount
60         {
61             get { return fGrid.LiveCellCount; }
62         }
63 
64         public bool AcceptMouseClicks
65         {
66             get { return fAcceptMouseClicks; }
67             set {
68                 if (value != fAcceptMouseClicks) {
69                     fAcceptMouseClicks = value;
70                     Change();
71                 }
72             }
73         }
74 
75         public int GridHeight
76         {
77             get { return fGrid.GridHeight; }
78             set { SetGridSize(GridWidth, value); }
79         }
80 
81         public Color GridLineColor
82         {
83             get { return fGridLineColor; }
84             set {
85                 if (value != fGridLineColor) {
86                     fGridLineColor = value;
87                     Invalidate();
88                 }
89             }
90         }
91 
92         public DashStyle GridLineStyle
93         {
94             get { return fGridLineStyle; }
95             set {
96                 if (value != fGridLineStyle) {
97                     fGridLineStyle = value;
98                     Invalidate();
99                 }
100             }
101         }
102 
103         public int GridWidth
104         {
105             get { return fGrid.GridWidth; }
106             set { SetGridSize(value, GridHeight); }
107         }
108 
109         public int MaxNumberOfHistoryLevels
110         {
111             get { return fHistory.MaxLevels; }
112             set {
113                 if (value < 1)
114                     throw new IndexOutOfRangeException("MaxNumberOfHistoryLevels must be greater than 0");
115                 if (value > LifeConsts.MaxNumberOfHistoryLevels)
116                     throw new IndexOutOfRangeException(string.Format("MaxNumberOfHistoryLevels must be greater than {0}", LifeConsts.MaxNumberOfHistoryLevels));
117 
118                 fHistory.MaxLevels = value;
119             }
120         }
121 
122         public bool ShowGridLines
123         {
124             get { return fShowGridLines; }
125             set {
126                 if (value != fShowGridLines) {
127                     fShowGridLines = value;
128                     Invalidate();
129                 }
130             }
131         }
132 
133         public LifeOptions Options
134         {
135             get { return fOptions; }
136         }
137 
138         public NotifyEvent OnChange
139         {
140             get { return fOnChange; }
141             set { fOnChange = value; }
142         }
143 
144         public DoesCellLiveEvent OnDoesCellLive
145         {
146             get { return fOnDoesCellLive; }
147             set { fOnDoesCellLive = value; }
148         }
149 
150         public LifeRules Rules
151         {
152             get { return fRules; }
153         }
154 
LifeViewer()155         public LifeViewer()
156         {
157             DoubleBuffered = true;
158 
159             fOptions = new LifeOptions();
160             fRules = new LifeRules();
161             fGrid = new LifeGrid(LifeConsts.DefaultGridWidth, LifeConsts.DefaultGridHeight);
162             fHistory = new LifeHistory(LifeConsts.DefaultNumberOfHistoryLevels);
163             fGridLineColor = LifeConsts.DefaultGridLineColor;
164             fGridLineStyle = LifeConsts.DefaultGridLineStyle;
165         }
166 
Dispose(bool disposing)167         protected override void Dispose(bool disposing)
168         {
169             if (disposing) {
170                 fGrid.Dispose();
171                 fHistory.Dispose();
172             }
173             base.Dispose(disposing);
174         }
175 
CellAtPos(int X, int Y)176         protected Point CellAtPos(int X, int Y)
177         {
178             int ClientWidth = Width;
179             int ClientHeight = Height;
180 
181             if ((X < 0) || (X >= ClientWidth))
182                 throw new IndexOutOfRangeException("X coordinate is outside the control's bounds");
183             if ((Y < 0) || (Y >= ClientHeight))
184                 throw new IndexOutOfRangeException("Y coordinate is outside the control's bounds");
185 
186             Point result = new Point();
187 
188             int cellWidth = ClientWidth / GridWidth;
189             int offsetX = (ClientWidth % GridWidth) / 2;
190 
191             if (X <= offsetX * (cellWidth + 1)) {
192                 result.X = X / (cellWidth + 1);
193             } else {
194                 result.X = offsetX + (X - offsetX * (cellWidth + 1)) / cellWidth;
195             }
196 
197             int cellHeight = ClientHeight / GridHeight;
198             int offsetY = (ClientHeight % GridHeight) / 2;
199 
200             if (Y <= offsetY * (cellHeight + 1)) {
201                 result.Y = Y / (cellHeight + 1);
202             } else {
203                 result.Y = offsetY + (Y - offsetY * (cellHeight + 1)) / cellHeight;
204             }
205 
206             return result;
207         }
208 
CellEdge(int coordinate, int fieldSize, int divisions)209         private int CellEdge(int coordinate, int fieldSize, int divisions)
210         {
211             int cellSize = fieldSize / divisions;
212             int remainder = (fieldSize % divisions) / 2;
213 
214             int result = (coordinate * cellSize) + remainder;
215             return result;
216         }
217 
CreateRect(int left, int top, int right, int bottom)218         private static Rectangle CreateRect(int left, int top, int right, int bottom)
219         {
220             Rectangle result = new Rectangle(left, top, right - left + 1, bottom - top + 1);
221             return result;
222         }
223 
CellCoords(int X, int Y)224         protected Rectangle CellCoords(int X, int Y)
225         {
226             int ClientWidth = Width;
227             int ClientHeight = Height;
228 
229             if (X >= GridWidth) throw new IndexOutOfRangeException("X parameter out of range");
230             if (Y >= GridHeight) throw new IndexOutOfRangeException("Y parameter out of range");
231 
232             Rectangle result = CreateRect(
233                 CellEdge(X, ClientWidth, GridWidth),
234                 CellEdge(Y, ClientHeight, GridHeight),
235                 CellEdge(X + 1, ClientWidth, GridWidth),
236                 CellEdge(Y + 1, ClientHeight, GridHeight));
237             return result;
238         }
239 
Change()240         protected void Change()
241         {
242             Invalidate();
243 
244             if (fOnChange != null) fOnChange(this);
245         }
246 
DoesCellLive(int X, int Y, LifeGrid grid)247         protected bool DoesCellLive(int X, int Y, LifeGrid grid)
248         {
249             bool result = grid.DoesCellLive(X, Y);
250             if (fOnDoesCellLive != null) fOnDoesCellLive(this, X, Y, grid, ref result);
251             return result;
252         }
253 
InvalidateCell(int X, int Y)254         protected void InvalidateCell(int X, int Y)
255         {
256             if (X >= GridWidth)
257                 throw new IndexOutOfRangeException("X parameter out of range");
258             if (Y >= GridHeight)
259                 throw new IndexOutOfRangeException("Y parameter out of range");
260 
261             Rectangle rect = CellCoords(X, Y);
262             Invalidate(new Region(rect));
263         }
264 
OnMouseUp(MouseEventArgs e)265         protected override void OnMouseUp(MouseEventArgs e)
266         {
267             if (AcceptMouseClicks && (e.Button == MouseButtons.Left)) {
268                 Point pt = CellAtPos(e.X, e.Y);
269                 short val = this[pt.X, pt.Y];
270                 this[pt.X, pt.Y] = (short)((val > 0) ? 0 : 1);
271             }
272 
273             base.OnMouseUp(e);
274         }
275 
DrawGridLines(Graphics gfx, Pen pen)276         private void DrawGridLines(Graphics gfx, Pen pen)
277         {
278             int clientWidth = Width;
279             int clientHeight = Height;
280             int coord, i;
281 
282             for (i = 1; i < GridWidth; i++) {
283                 coord = CellEdge(i, clientWidth, GridWidth);
284                 gfx.DrawLine(pen, coord, 0, coord, clientHeight);
285             }
286 
287             for (i = 1; i < GridHeight; i++) {
288                 coord = CellEdge(i, clientHeight, GridHeight);
289                 gfx.DrawLine(pen, 0, coord, clientWidth, coord);
290             }
291         }
292 
OnPaint(PaintEventArgs e)293         protected override void OnPaint(PaintEventArgs e)
294         {
295             Graphics gfx = e.Graphics;
296 
297             if (fShowGridLines) {
298                 using (Pen pen = new Pen(Color.Black)) {
299                     DrawGridLines(gfx, pen);
300                 }
301             }
302 
303             Color cellColor = fOptions.LivingCellColor;
304             Color bordColor = UIHelper.Lighter(cellColor, 0.5f);
305 
306             // Draw all the live cells
307             using (Brush brush = new SolidBrush(cellColor))
308             {
309                 using (Pen pen = new Pen(bordColor))
310                 {
311                     for (int y = 0; y < GridHeight; y++) {
312                         for (int x = 0; x < GridWidth; x++) {
313                             if (this[x, y] > 0) {
314                                 Rectangle r = CellCoords(x, y);
315                                 r.Inflate(-1, -1);
316                                 gfx.FillEllipse(brush, r);
317                                 gfx.DrawEllipse(pen, r);
318                             }
319                         }
320                     }
321                 }
322             }
323 
324             base.OnPaint(e);
325         }
326 
OnResize(EventArgs e)327         protected override void OnResize(EventArgs e)
328         {
329             Invalidate();
330             base.OnResize(e);
331         }
332 
SetCell(int X, int Y, short value)333         protected void SetCell(int X, int Y, short value)
334         {
335             if (this[X, Y] != value) {
336                 fGrid[X, Y] = value;
337                 //InvalidateCell(X, Y);
338             }
339         }
340 
ClearCells()341         public void ClearCells()
342         {
343             fGrid.Clear();
344             fHistory.Clear();
345             Change();
346         }
347 
RandomCells()348         public void RandomCells()
349         {
350             Random rnd = new Random();
351 
352             for (int x = 0; x < GridWidth; x++) {
353                 for (int y = 0; y < GridHeight; y++) {
354                     this[x, y] = (byte)((rnd.NextDouble() < 0.4) ? 1 : 0);
355                 }
356             }
357 
358             Invalidate();
359         }
360 
NextGeneration()361         public int NextGeneration()
362         {
363             LifeGrid MostRecentGrid = fHistory.Add(fGrid);
364 
365             for (int y = 0; y < GridHeight; y++) {
366                 for (int x = 0; x < GridWidth; x++) {
367                     bool live = DoesCellLive(x, y, MostRecentGrid);
368                     SetCell(x, y, (short)((live) ? 1 : 0));
369                 }
370             }
371 
372             fGeneration++;
373             Change();
374 
375             int result = fHistory.Contains(fGrid) + 1;
376             return result;
377         }
378 
SetGridSize(int newGridWidth, int newGridHeight)379         public void SetGridSize(int newGridWidth, int newGridHeight)
380         {
381             if (newGridWidth != GridWidth || newGridHeight != GridHeight) {
382                 fHistory.Clear();
383                 fGrid.SetGridSize(newGridWidth, newGridHeight);
384 
385                 Change();
386             }
387         }
388 
ResetGeneration()389         public void ResetGeneration()
390         {
391             fGeneration = 0;
392             Change();
393         }
394     }
395 }
396