Click here to Skip to main content
Licence GPL3
First Posted 4 Aug 2005
Views 156,730
Downloads 5,034
Bookmarked 77 times

WaTor - An OpenGL based screensaver

By | 5 Mar 2007 | Article
An article on implementing a simple predator prey simulation using OpenGL and MFC.

Table of contents

  1. Table of contents
  2. Introduction
  3. Set of rules
  4. Screen saver basics
  5. Installation
  6. Configuration
  7. Limitations
  8. License
  9. References
  10. Release notes

Introduction

This article describes the implementation of an OpenGL screen saver based on a discrete simulation of predator-prey interaction. The simulation is called Wa-Tor. It was originally described by Alexander K. Dewdney in the Scientific American magazine.

It simulates the hypothetical toroidal Planet Wa-Tor (Water Torus) whose surface is completely covered with water, occupied by two species: fish and sharks. The sharks are the predators. They eat the fish. The fish exist on a never ending supply of plankton. Both sharks and fish live according to a strict set of rules. This simulation of a simple ecology is highly dynamic as both species are walking a thin line between continuing life and extinction.

The screensaver is based on a dialog based MFC application combined with OpenGL for the 2D drawing. As usual, for OpenGL, using the binary without a hardware or drivers correctly supporting it will result in very low frame rates. The source code requires Microsoft Visual Studio V7.1 in order to compile, MSVC6 is not supported.

Set of rules

In this paragraph, I will briefly explain the basic rules for a Wator simulation and describe the changes necessary in order to implement the screensaver. In general, the simulation is based on discrete time steps. The simulation runs on a rectangular grid. To represent the toroidal world, opposing sides of the grid are connected. If an individual moves out on one side of the simulation domain, it reenters immediately on the opposing side. Fish and shark move every time step (if possible) and interact according to the following set of rules:

Rules for fish

In every time step, a fish moves randomly to one of the four neighboring fields, provided it is empty. Every fish has a predefined "breed time". On exceeding this time, it gives birth to a new fish in one of the neighboring cells, provided this randomly selected cell is free. (If not nothing happens.) Breed time counter of both the original and the descendant fish will be reset to zero. Technically fish never die. They live until they reach the breed time, then they clone and both parent as well as offspring restart their life cycle. (To clarify: according to the simulation the parent dies and creates two offspring individuals, but I don't delete the parent, I just reset it.)

The following picture shows options for prey movement. Arrows indicate possible movements. Fish are not allowed to move to cells occupied by sharks. If there are no free neighboring cells no movement occurs.

Rules for sharks

Sharks move randomly to fields that are either free or occupied by fish. Every round they lose one point of energy. If they enter a field occupied by a fish they eat the fish and gain a defined amount of energy. If the energy level drops below zero the shark dies. If the energy exceeds a predefined value sharks create an offspring in a free neighboring field. The energy is split evenly between the parent and the child.

Modifications made to the original algorithm

In order to turn this simulation into a screensaver, I made some changes to the original set of rules. According to the original algorithm, sharks do not move randomly. Instead they move specifically to cells in their neighborhood containing fish. Testing this is slow, thus I allow only random movement. Surprisingly there is no visible difference. Alexander K. Dewdney suggested an implementation very similar to a cellular automata. If you implement Wa-Tor that way and by looping over all cells in the simulation domain, you are forced to include cells that do not contain any individual in the calculation. Since this is too slow I chose to implement it as a linked list, thus instead of looping over all cells, my algorithm loops over existing individuals only.

Theoretical background

The information provided here is taken from the Wikipedia article on Lotka-Volterra differential equations. For a more detailed explanation I refer you to this article.

Drawing the number of individuals per species versus time yields curves characteristic for the so called Lotka-Volterra equations (also known as predator-prey equations).

Diagram: Population size vs. time for a predator prey model..

This behaviour can be described by two differential equations. There usual form is:

where::

  • y is the number of some predator (here: sharks)
  • x is the number of its prey (here: fish)
  • t represents the development of the two populations against time
  • alpha, beta, gamma and delta are parameters representing the interaction of the two species.

Although the solution to these differential equations show a periodic behaviour, they can not be expressed in terms of normal trigonometric functions. However, an approximate linearised solution yields a simple harmonic motion with the population of predators leading that of prey by 90%.

Screen saver basics

Technically a Windows screensaver is a normal executable with the extension scr. Additionally it has to have a defined command line interface in order to display itself in configuration mode, in preview mode or in screen saver mode.

Command line optionn Saver action
/P:### Start the screensaver in preview mode. Treat ### as the decimal representation of a HWND. Popup a child window of this HWND and run in preview mode inside that window.
/C:### Open the screen saver configuration dialog. If ### is not present GetForegroundWindow() should be the parent of this dialog. Otherwise ### should be treated as the decimal representation of a HWND, which should be used as a parent.
/S Run as screen saver in full screen mode. When there is some kind of keyboard or mouse activity in the screen saver mode, the screen saver is ended.

Table 1: Command line options.

Installation

Download the archive with the binary and copy the file WatorSaver.scr to your Windows/System322 subdirectory. I do not provide a setup program due to the simplicity of the installation process.

Configuration

After moving the screen saver to your windows/system32 directory, you can edit its settings like any other screen saver settings. Go to the Windows desktop, right click, and in the context menu, select the display options entry.

The configuration dialog allows setting up the simulation parameters described above as well as additional settings for the display options.

Screensaver parameters

All changes made to the parameters will be reflected instantly in the preview window. The following table lists the parameters and their meaning for the simulation process:

Prey breed time The number of time steps necessary before a prey can create an offspring.
Energy per hunted prey The energy a predator gets for each prey individual it eats. In each time step, the predator loses one energy point. It dies when the energy falls below zero.
Energy to create offspring The minimum amount of energy a predator must have in order to create an offspring. When creating an offspring, the energy is evenly split between the newly created offspring and the parent.
Pixelsize The size of an individual in pixels. Reducing this number will require very high performance!

Limitations

Like most screensavers, this is a program featuring a controlled amusing misuse of system resources. It is currently a beta version and you use it without warranty of any kind!

Currently the screen saver does not support multiple monitors. Moreover it will only save your screen if you disable all static text displays in its configuration dialog. This won't change in future versions but I don't consider this a problem since today's TFT Displays don't need a screensaver anyway.

You use it at your own risk, no warranties of any kind!

Licence

Wator Screensaver
Copyright 2007 Ingo Berg

This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA

References

A more detailed introduction on screen saver programming and issues related to this article can be found at the following places:

Release Notes

Rev 0.90 (Beta): 31/07/2005

First version of the screensaver released. This is a beta version released for testing purposes only. There is much room for improvement like adding multiple monitor support.

Rev 1.0: 18/08/2005

This release contains bug fixes and slight modifications that make the screensaver look prettier (nothing spectacular).

  • Fixed bug that prevented the screensaver from running on ATI graphics cards.
  • Added a background pattern.
  • Changed title bitmap.
  • Statistic is now scaled between its minimum and maximum.

Rev 1.01: 21/01/2006

  • Support for multiple monitors added.
  • The screensaver can now be deactivated by mouse moves.

Rev 1.02: 03/03/2007

  • License changed to GPL-II.

License

This article, along with any associated source code and files, is licensed under The GNU General Public License (GPLv3)

About the Author

iberg

Software Developer

Germany Germany

Member



Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board. (secure sign-in)
 
Search this forum  
 FAQ
    Noise  Layout  Per page   
  Refresh
GeneralThird Leg, Plankton PinmemberRDABC10:39 11 Apr '07  
GeneralRe: Third Leg, Plankton Pinmemberiberg7:52 13 Apr '07  
GeneralEnhancement: Filing populaton in and out Pinmembertbrammer3:18 4 Dec '06  
I love the WaTor saver - except for the fact, that it always starts with a random population. Many times all sharks die, leaving one single shark behind. It takes a while until a "stable dynamic population" is established.
 
So I implemented filing of the population. Saving and loading is quite fast. Much less than a second even for large populations.
 
Here is how:
 
CWatorGL::InitPopulation() and CWatorGL::OnDestroy() must be modified. We load the population in InitPopulation() and save it in OnDestroy(), if we are not in the preview mode.
And we need 5 new methods in CWatorGL:
 

-------------------------------------------------------
In "WatorWnd.h":
 

class CWatorGL : public CWnd
{
private:
...
bool GetPopulationFile(CString &csFileName);
void writeFish(FILE *fp, Fish *pFish);
void writeState(FILE *fp);
void readFish(FILE *fp, Fish *pFish);
void readState(FILE *fp);
...
};

-------------------------------------------------------
In WatorWnd.cpp:
 

...
#include "WatorApp.h"
 
void CWatorGL::InitPopulation(int a_iNumPred, int a_iNumPrey)
{
FILE *fp = NULL;
CString csFileName;
if (GetPopulationFile(csFileName))
fp = fopen(csFileName, "rb");

if (fp)
{
readState(fp);
fclose(fp);
return;
}
...
}
 
void CWatorGL::OnDestroy()
{
if (!theApp.isPreview())
{
FILE *fp = NULL;
CString csFileName;
if (GetPopulationFile(csFileName))
fp = fopen(csFileName, "wb");

if (fp)
{
writeState(fp);
fclose(fp);
}
}
...
}
 
//Here are the new methodes:
//
// This method just gives us a path to a population-file. It will be the path
//to the saver-executable with the extension set to ".pop"
bool CWatorGL::GetPopulationFile(CString &csFileName)
{
CWinApp *pApp=AfxGetApp();
TCHAR filename[MAX_PATH+5];
DWORD dwResult = GetModuleFileName(pApp->m_hInstance, filename, MAX_PATH);
bool bResult=false;
if (dwResult>0)
{
TCHAR *p = filename + _tcslen(filename) - 1;
for (; p!=filename; p--)
{
if (*p==_T('.'))
{
_tcscpy(p, _T(".pop"));
bResult = true;
csFileName = filename;
break;
}
}
}
return bResult;
}
 
// Here we read the population from a file
void CWatorGL::readState(FILE *fp)
{
Fish rFish(tpPREY, 0, 0, 0, m_iBreedTime), *pNewFish, *pLastFish=NULL;
SeaStates kind=tpPREY;

for ( ; ; )
{
readFish(fp, &rFish);
if (rFish.x < 0)
{
if (kind!=tpPRED)
{
kind = tpPRED;
pLastFish = NULL;
}
else
break;
}
else
{
if (rFish.x < m_iDimX && rFish.y < m_iDimY)
{
pNewFish = NewFish(kind, rFish.x, rFish.y,
rFish.energy, rFish.maxage);
pNewFish->age = rFish.age;
pNewFish->next = NULL;
pNewFish->pred = pLastFish;
if (pLastFish)
pLastFish->next = pNewFish;
pLastFish = pNewFish;
if (!pRoot[kind])
{
pRoot[kind] = pNewFish;
pRoot[kind]->pred = NULL;
}
}
}
}
}
 
// Here we write the population to a file
void CWatorGL::writeState(FILE *fp)
{
fish_type *pFish,
EndFish(0,0,0,0,0);
EndFish.x = EndFish.y = -1;

for (pFish = pRoot[tpPREY]; pFish; pFish=pFish->next)
{
writeFish(fp, pFish);
}
 
writeFish(fp, &EndFish);
 
for (pFish = pRoot[tpPRED]; pFish; pFish=pFish->next)
{
writeFish(fp, pFish);
}
 
writeFish(fp, &EndFish);
}
 
// Writing a single fish
void CWatorGL::writeFish(FILE *fp, Fish *pFish)
{
fwrite(&(pFish->x), 1, sizeof(int), fp);
fwrite(&(pFish->y), 1, sizeof(int), fp);
fwrite(&(pFish->age), 1, sizeof(int), fp);
fwrite(&(pFish->energy), 1, sizeof(int), fp);
fwrite(&(pFish->maxage), 1, sizeof(int), fp);
}
 
// Reading a single fish
void CWatorGL::readFish(FILE *fp, Fish *pFish)
{
fread(&(pFish->x), 1, sizeof(int), fp);
fread(&(pFish->y), 1, sizeof(int), fp);
fread(&(pFish->age), 1, sizeof(int), fp);
fread(&(pFish->energy), 1, sizeof(int), fp);
fread(&(pFish->maxage), 1, sizeof(int), fp);
}

 
Enjoy
-- tbrammer
 

GeneralRe: Enhancement: Filing populaton in and out Pinmemberiberg11:21 4 Dec '06  
AnswerRe: Enhancement: Filing populaton in and out Pinmembertbrammer21:55 4 Dec '06  
GeneralRe: Enhancement: Filing populaton in and out Pinmemberiberg11:56 5 Dec '06  
QuestionBugBug PinmemberHighcooley12:45 13 Nov '06  
AnswerRe: BugBug Pinmemberiberg21:01 13 Nov '06  
GeneralRe: BugBug PinmemberHighcooley22:56 13 Nov '06  
QuestionWhat Dewdney meant, maybe. PinmemberTadeusz Westawic16:28 3 Feb '06  
AnswerRe: What Dewdney meant, maybe. Pinmemberiberg3:22 4 Feb '06  
GeneralRe: What Dewdney meant, maybe. PinmemberTadeusz Westawic6:32 5 Feb '06  
GeneralRe: What Dewdney meant, maybe. Pinmemberiberg13:24 11 Feb '06  
GeneralRe: What Dewdney meant, maybe. PinmemberJasmine250118:52 27 Nov '06  
GeneralLike it! PinmemberArinir22:47 24 Aug '05  
GeneralRe: Like it! Pinmemberiberg23:07 24 Aug '05  
GeneralRe: Like it! PinmemberJasmine25018:51 29 Nov '06  
GeneralRe: Like it! Pinmemberiberg15:16 1 Dec '06  
GeneralGreat presentation PinmemberMartin.Holzherr21:10 24 Aug '05  
GeneralRe: Great presentation Pinmemberiberg21:32 24 Aug '05  
GeneralATI card problems Pinmemberiberg10:59 16 Aug '05  
GeneralRe: ATI card problems PinmemberRyan Beesley12:32 23 Aug '05  
GeneralRe: ATI card problems Pinmemberiberg1:42 24 Aug '05  
General"Thus you have an infinite world on a finite grid."?! PinmemberPedroMC3:43 11 Aug '05  
GeneralFISHes PinmemberJouat1:05 11 Aug '05  

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Mobile
Web04 | 2.5.120529.1 | Last Updated 5 Mar 2007
Article Copyright 2005 by iberg
Everything else Copyright © CodeProject, 1999-2012
Terms of Use
Layout: fixed | fluid