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