wxWidgets/demos/life/game.cpp
Guillermo Rodriguez Garcia 2480be69b1 Split the sample in three source files + three header files, for improved
clarity. Now there is a 'life' module (main app), a 'game' module (game
logic) and a 'dialogs' module (guess what).

Also, speeded up drawing a lot.


git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@5309 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
2000-01-08 18:27:19 +00:00

227 lines
6.2 KiB
C++

/////////////////////////////////////////////////////////////////////////////
// Name: game.cpp
// Purpose: Life! game logic
// Author: Guillermo Rodriguez Garcia, <guille@iies.es>
// Modified by:
// Created: Jan/2000
// RCS-ID: $Id$
// Copyright: (c) 2000, Guillermo Rodriguez Garcia
// Licence: wxWindows licence
/////////////////////////////////////////////////////////////////////////////
// ==========================================================================
// declarations
// ==========================================================================
// --------------------------------------------------------------------------
// headers
// --------------------------------------------------------------------------
#ifdef __GNUG__
#pragma implementation "game.h"
#endif
// for compilers that support precompilation, includes "wx/wx.h"
#include "wx/wxprec.h"
#ifdef __BORLANDC__
#pragma hdrstop
#endif
// for all others, include the necessary headers
#ifndef WX_PRECOMP
#include "wx/wx.h"
#endif
#include "game.h"
// ==========================================================================
// implementation
// ==========================================================================
// --------------------------------------------------------------------------
// Life
// --------------------------------------------------------------------------
/* Format of the cell: (32 bit integer)
*
* 0yyyyyyy yyyyyy0x xxxxxxxxx xxx000ff
*
* y = y coordinate (13 bits)
* x = x coordinate (13 bits)
* f = flags (2 bits)
*
* OK, there is a reason for this, I promise :-). But I won't explain
* it now; it will be more clear when I implement the optimized life
* algorithm for large universes.
*/
Life::Life(int width, int height)
{
m_wrap = TRUE;
Create(width, height);
}
Life::~Life()
{
Destroy();
}
void Life::Create(int width, int height)
{
wxASSERT(width > 0 && height > 0 && m_cells.IsEmpty());
m_width = width;
m_height = height;
// preallocate memory to speed up initialization
m_cells.Alloc(m_width * m_height);
m_changed.Alloc(1000);
// add all cells
for (int j = 0; j < m_height; j++)
for (int i = 0; i < m_width; i++)
m_cells.Add( MakeCell(i, j, FALSE) );
}
void Life::Destroy()
{
m_cells.Clear();
m_changed.Clear();
}
void Life::Clear()
{
// set all cells in the array to dead
for (int j = 0; j < m_height; j++)
for (int i = 0; i < m_width; i++)
m_cells[j * m_width + i] &= ~CELL_ALIVE;
}
Cell Life::MakeCell(int i, int j, bool alive) const
{
return (Cell)((j << 18) | (i << 5) | (alive? CELL_ALIVE : CELL_DEAD));
}
bool Life::IsAlive(int i, int j) const
{
wxASSERT(i >= 0 && j >= 0 && i < m_width && j < m_height);
return (m_cells[j * m_width + i] & CELL_ALIVE);
}
bool Life::IsAlive(Cell c) const { return (c & CELL_ALIVE); };
int Life::GetX(Cell c) const { return (c >> 5) & 0x1fff; };
int Life::GetY(Cell c) const { return (c >> 18); };
Cell Life::SetCell(int i, int j, bool alive)
{
wxASSERT(i >= 0 && j >= 0 && i < m_width && j < m_height);
Cell c = MakeCell(i, j, alive);
m_cells[j * m_width + i] = c;
return c;
}
void Life::SetShape(LifeShape& shape)
{
wxASSERT((m_width >= shape.m_width) && (m_height >= shape.m_height));
int i0 = (m_width - shape.m_width) / 2;
int j0 = (m_height - shape.m_height) / 2;
char *p = shape.m_data;
Clear();
for (int j = j0; j < j0 + shape.m_height; j++)
for (int i = i0; i < i0 + shape.m_width; i++)
SetCell(i, j, *(p++) == '*');
}
bool Life::NextTic()
{
int i, j;
m_changed.Empty();
/* 1st pass. Find and mark deaths and births for this generation.
*
* Rules:
* An organism with <= 1 neighbors will die due to isolation.
* An organism with >= 4 neighbors will die due to starvation.
* New organisms are born in cells with exactly 3 neighbors.
*/
for (j = 0; j < m_height; j++)
for (i = 0; i < m_width; i++)
{
int neighbors = GetNeighbors(i, j);
bool alive = IsAlive(i, j);
/* Set CELL_MARK if this cell must change, clear it
* otherwise. We cannot toggle the CELL_ALIVE bit yet
* because all deaths and births are simultaneous (it
* would affect neighbouring cells).
*/
if ((!alive && neighbors == 3) ||
(alive && (neighbors <= 1 || neighbors >= 4)))
{
m_cells[j * m_width + i] |= CELL_MARK;
m_changed.Add( MakeCell(i, j, !alive) );
}
else
m_cells[j * m_width + i] &= ~CELL_MARK;
}
/* 2nd pass. Stabilize.
*/
for (j = 0; j < m_height; j++)
for (i = 0; i < m_width; i++)
{
/* Toggle CELL_ALIVE for those cells marked in the
* previous pass. Do not clear the CELL_MARK bit yet;
* it is useful to know which cells have changed and
* thus must be updated in the screen.
*/
if (m_cells[j * m_width + i] & CELL_MARK)
m_cells[j * m_width + i] ^= CELL_ALIVE;
}
return (!m_changed.IsEmpty());
}
int Life::GetNeighbors(int x, int y) const
{
wxASSERT(x >= 0 && y >= 0 && x < m_width && y < m_height);
int neighbors = 0;
int i0 = (x)? (x - 1) : 0;
int j0 = (y)? (y - 1) : 0;
int i1 = (x < (m_width - 1))? (x + 1) : (m_width - 1);
int j1 = (y < (m_height - 1))? (y + 1) : (m_height - 1);
if (m_wrap && ( !x || !y || x == (m_width - 1) || y == (m_height - 1)))
{
// this is an outer cell and wraparound is on
for (int j = y - 1; j <= y + 1; j++)
for (int i = x - 1; i <= x + 1; i++)
if (IsAlive( ((i < 0)? (i + m_width ) : (i % m_width)),
((j < 0)? (j + m_height) : (j % m_height)) ))
neighbors++;
}
else
{
// this is an inner cell, or wraparound is off
for (int j = j0; j <= j1; j++)
for (int i = i0; i <= i1; i++)
if (IsAlive(i, j))
neighbors++;
}
// do not count ourselves
if (IsAlive(x, y)) neighbors--;
return neighbors;
}