25 Years of Programming
An open source source for C, C++, OWL, BASIC, MDB, XLS, DOT, and more...
Home   Projects   Up   Sitemap   Search   Blog   Forum+Chat   About Us   Privacy   Terms of Use   Feedback   FAQ   Images   Services   Ads   Donate   Humor

Traject.cpp - gravity simulation

This program was derived from adapt.cpp, but has a completely different purpose. It creates one agent (cell) and shoots it from the left side of the screen at a random angle trajectory. Its horizontal velocity stays constant, but its vertical velocity changes similarly to the way a real object does when pulled down by gravity. If it is travelling upward, its vertical velocity decreases, and as it travels downward, it accelerates. The result is a parabolic path which the object traverses at a constantly changing velocity.

It is hardly a remarkable demonstration, except that it was so easily derived from adapt.cpp by ripping out most of the code and giving the agent a different motion behavior. On the other hand, if I had set out to write this program from scratch using this type of program structure used by adapt.cpp, that would really have been overkill for what is needed here!

Borland C++ 4.0 for MSDOS.

traject.cpp

/*	traject.cpp				6-26-97
	Copyright (C)1995-1997 Steven Whitney.
	Initially published by http://25yearsofprogramming.com.

	This program is free software; you can redistribute it and/or
	modify it under the terms of the GNU General Public License (GPL)
	Version 2 as published by the Free Software Foundation.
	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 Street, Fifth Floor, Boston, MA  02110-1301, USA.

Plots the trajectory of an object (dot) launched from the ground with a given
velocity and angle.  Simulates effect of gravity.  Based on adapt.cpp.

------
to do:

try launching a dot from a circle (planet) at screen center & plot orbits.
("Does God Play Dice?" has sections on gravity & pendulums, which may be
related. Says this calculation is very difficult.)

*/
#include <math.h>
#include <graphics.h>
#include <classlib\arrays.h>
#pragma hdrstop

#include "c:\bcs\my.h"
#include "c:\bcs\mylib.cpp"

/////////////////////////////////////////////////////////////////////////
// global variables

BOOL beepon = TRUE;			// whether to use sound effects, test is in play()
BOOL delayon = FALSE;		// whether to impose delay for better viewing
BOOL trailon = TRUE;		// whether to show trail as a point moves
BOOL lookon = FALSE;		// whether to show direction of sight
BOOL autoclear = TRUE;     	// whether to auto-clear screen every ## loops

Trigtable t;               	// so they only have to be calculated once

/////////////////////////////////////////////////////////////////////////
// entity, shown as a screen dot, that can move around, interact with others, etc.
class agent
{
public:
	agent();									// constructor
	~agent();									// destructor
	BOOL operator == (const agent& other) const;
	BOOL operator < (const agent& other) const;

	double calcdistance(class agent* other);	// distance from this to another agent
	class agent* identifynearestneighbor();
	class agent* move();
	int run();
	BOOL sense();								// update all sensors with current surroundings
	int mapneighbors();

	double x;          	// this cell's screen location
	double y;
	double heading;		// direction of motion (and sight): 0=North=Up, 90=East
	double velocity;	// dies when 0
	int color;      	// dot leaves a TRAIL in this color (the dot itself is always white)
};
/////////////////////////////////////////////////////////////////////////
// global array of agents.  Must define here because some agent:: functions use it.
TIArrayAsVector<agent> ag(5,0,0);

//-----------------------------------------------------------------------
// constructor
agent::agent()
{
	x = 0.;                           	// starts at lower left corner
	y = 479.;
	heading = (double)(random(90));     // straight up to straight out
	velocity = (double)(random(200));	// random starting velocity
	color = LIGHTGREEN;					// trail color
	putpixel((int)x,(int)y,WHITE);		// display on screen
}
//-----------------------------------------------------------------------
// destructor, removes from screen.  OK if a dead cell's velocity is zero,
// but if residual velocity is ever allowed, it might be better to leave lying
// around for another cell to eat (scavenger).
agent::~agent()
{
	putpixel((int)x,(int)y,BLACK);
}
//-----------------------------------------------------------------------
// == currently isn't used for anything, but there should be a way to make it useful.
BOOL agent::operator == (const agent& other) const
{
	return(&other == this);
}
//-----------------------------------------------------------------------
// < Sorts agents by their Manhattan distance from (0,0).  Not currently used.
BOOL agent::operator < (const agent& other) const
{
	if((x+y) < (other.x + other.y))
		return(TRUE);
	return(FALSE);
}
//-----------------------------------------------------------------------
// computes distance between two cells
double agent::calcdistance(class agent* other)
{
	double xdif = fabs(x - other->x);
	double ydif = fabs(y - other->y);
	return(sqrt((xdif * xdif) + (ydif * ydif)));
}
//-----------------------------------------------------------------------
// identify which agent is occupying a pixel & return pointer
// returns pointer, or ZERO if match not found.
// generally, you should know that the pixel IS occupied before calling, because this is slow.
// This is NOT a member function, because it's needed outside the class.
class agent* identify(int findx, int findy)
{
	int count = ag.GetItemsInContainer();
	for(int i = 0 ; i < count ; i++)
		if(((int)(ag[i]->x) == findx) && ((int)(ag[i]->y) == findy))
			return(ag[i]);
	return(0);
}
//-----------------------------------------------------------------------
// move a cell along its heading until it hits the ground.
// Can only move 1 pixel at a time so it knows whether next step will hit something.
// returns pointer to the cell that blocked this one's path, if any,
// or ZERO if path was clear.
class agent* agent::move()
{
double xvelocity = velocity * cosine(heading);
double yvelocity = velocity * sine(heading);
int xi = (int)x;
int yi = (int)y;

// 1 loop = .1 second elapsed
do
{
	delay(25);
	putpixel((int)x,(int)y,trailon ? color : BLACK);	// show or erase previous position

	x += xvelocity * .1;   		// x velocity doesn't change
	y -= yvelocity * .1;   		// y axis upside down
	yvelocity -= 3.2;			// change due to gravity, in ft/sec

	if(y < -1000)
		break;

	while(x > 639.)            // screen wraps around
		x -= 639.;

	if(y > 479.)               // below bottom (landed)
		y = 479.;
	xi = (int)x;
	yi = (int)y;

	putpixel(0,yi,LIGHTRED);   // show x & y positions on axes
	putpixel(xi,479,LIGHTRED);

	putpixel(xi,yi,WHITE);		   // show new position

}
while(y < 479.);	// until it hits the ground

return(0);

/*
	double targetx = x;
	double targety = y;

	// must get away from current pixel, or it'll be shown as "occupied" (by itself!)
	while(((int)targetx == (int)x) && ((int)targety == (int)y))
	{
		targetx += t.sines[(int)heading];
		targety -= t.cosines[(int)heading];		// screen's y axis is upside down
	}
										// Screen wraps around.
	if((int)targetx >= 640) targetx -= 640.;
	if((int)targetx <= -1)  targetx += 640.;
	if((int)targety >= 480) targety -= 480.;
	if((int)targety <= -1)  targety += 480.;

	// if new location is occupied, can't move there
	// This also causes the dots to stick when they hit, just like in DLA!
	if(getpixel((int)targetx,(int)targety) == WHITE)
		return(identify((int)targetx,(int)targety));
*/
}					// end move
//-----------------------------------------------------------------------
// create a map of neighboring pixels
// each pixel encoded in 1 bit (excluding pixel occupied by itself).
// use if(mapneighbors()) to determine if *any* neighboring pixel is occupied.
// use if(mapneighbors() & 1) to determine if below right pixel is occupied.
// For use in DLA and in 2nd version of Life.
int agent::mapneighbors()
{
int xi = (int)x;
int yi = (int)y;
int map = 0;
											// getpixel(offscreen) returns 0
if(getpixel(xi-1,yi-1) == WHITE) map |= 128; 	// above left
if(getpixel(xi  ,yi-1) == WHITE) map |= 64;		// above
if(getpixel(xi+1,yi-1) == WHITE) map |= 32;		// above right
if(getpixel(xi-1,yi  ) == WHITE) map |= 16;		// to left
if(getpixel(xi+1,yi  ) == WHITE) map |= 8;		// to right
if(getpixel(xi-1,yi+1) == WHITE) map |= 4;		// below left
if(getpixel(xi  ,yi+1) == WHITE) map |= 2;      // below
if(getpixel(xi+1,yi+1) == WHITE) map |= 1;		// below right

return(map);
}
//-----------------------------------------------------------------------
// update all sensors with current surroundings
// determine if there is another cell in the current line of sight along our heading
// similar to move() except it just looks, doesn't go anywhere, and vision doesn't wrap
// beyond edge of screen.
// returns TRUE if another cell is in view, FALSE if not.
BOOL agent::sense()
{
return(TRUE);
// mapneighbors();			// construct a map of the neighborhood

/*
double targetx = x;
double targety = y;
while(1)              	// exits loop if an object sighted or vision ran off screen
{                       // because it returns, this loop must be last thing sense() does.
	double oldx = targetx;
	double oldy = targety;
	// must get away from current pixel, or it'll be shown as "occupied" (by itself!)
	while(((int)targetx == (int)oldx) && ((int)targety == (int)oldy))
	{
		targetx += t.sines[(int)heading];
		targety -= t.cosines[(int)heading];		// screen's y axis is upside down
	}
	// our "vision" ran off screen, so we're through looking.
	if(((int)targetx >= 640) || ((int)targetx <= -1) ||
		((int)targety >= 480) || ((int)targety <= -1))
			return(FALSE);
	// if new location is occupied, it is the object we're looking at.
	if(getpixel((int)targetx,(int)targety) == WHITE)
	{
		inview = identify((int)targetx,(int)targety);
		if(inview)
			return(TRUE);
		printf("ERROR: Object was sighted that isn't in array.\n");
		return(FALSE);
	}
	if(lookon)  // didn't sight anything or run off screen: show where we're looking
		putpixel((int)targetx,(int)targety,BLUE);
}               // end while(1)
*/
}				// end sense
//-----------------------------------------------------------------------
// find mass center of all points on screen. uses average, could use least squares
// This could be an agent:: member function.
void calccenterofmass(double* x, double* y)		// variables to fill
{
	double sumx, sumy;
	sumx = sumy = 0.;
	int count = ag.GetItemsInContainer();
	for(int i = 0 ; i < count ; i++)
	{
		sumx += ag[i]->x;
		sumy += ag[i]->y;
	}
	*x = sumx / (double)count;
	*y = sumy / (double)count;
}                   // end calccenterofmass
//-----------------------------------------------------------------------
// returns pointer to nearest cell neighbor, or 0 if none
class agent* agent::identifynearestneighbor()
{
	double distance;
	double mindistance = 1e6;
	int nearest = -1;
	int count = ag.GetItemsInContainer();
	for(int i = 0 ; i < count ; i++)
		if(ag[i] != this)				// don't find itself
		{
			distance = calcdistance(ag[i]);
			if(distance < mindistance)
			{
				mindistance = distance;
				nearest = i;
			}
		}
	return((nearest == -1) ? 0 : ag[nearest]);
}
//-----------------------------------------------------------------------
int agent::run()
{
	return(TRUE);		// survived alive
}                   // end run
//-----------------------------------------------------------------------
					// end class agent
/////////////////////////////////////////////////////////////////////////
// global functions
//-----------------------------------------------------------------------
// display a short program description
void intro()
{
}
//-----------------------------------------------------------------------
// show the help screen
void drawhelp()
{
cout << "\nThese commands are available while program is running:\n\n";
cout << "A  AUTO-CLEAR screen between launches (toggle, now "
										<< (autoclear ? "ON" : "OFF") << ")\n";
cout << "B  BEEP (sound effects) (toggle)\n";
cout << "C  CLEAR screen once\n";
cout << "D  DELAY for better viewing (toggle)\n";
cout << "H  HELP (show this screen)\n";
cout << "L  LINE-of-sight display (toggle)\n";
cout << "P  PAUSE (any key to resume)\n";
cout << "T  TRAIL display (toggle)\n";
cout << "<ESC> Quit\n\n";
presskey();
}
//-----------------------------------------------------------------------
// redraw all the agents on the screen
void drawagents()
{
	clearviewport();
	int count = ag.GetItemsInContainer();
	for(int i = 0 ; i < count ; i++)
		putpixel((int)(ag[i]->x),(int)(ag[i]->y),WHITE);
}
//////////////////////////////////////////////////////////////////////////////
int main()
{
randomize();

int ch, grmode, passcount, passlimit = 1;

// intro();
// drawhelp();

grmode = initgraf();
while(1)
{
	passcount = 0;
	while(1)
	{
		//----------------------------------------------------------------------------
		// user input section
		if(kbhit())
		{
			ch = tolower(getch());
			switch(ch)
			{
				case 27:                				// user quit
					ag.Flush(TShouldDelete::Delete);
					nosound();				// just in case
					closegraph();
					return(0);
				case 'a': autoclear = !autoclear; break;
				case 'b': beepon = !beepon; break;				// toggle sound effects
				case 'c': clearviewport();	break;	// clear screen this pass
				case 'd': delayon = !delayon; break;    		// toggle delay
				case 'h':										// help
					restorecrtmode();
					drawhelp();
					setgraphmode(grmode);
					break;
				case 'l': lookon = !lookon; break;		// toggle showing where cell is looking
				case 'p': getch(); break;				// user pause
				case 't': trailon = !trailon; break;	// toggle trail display
			}
		}					// end if(kbhit())
		//----------------------------------------------------------------------------
		if(autoclear)
			if(++passcount >= passlimit)
			{
				clearviewport();
				passcount = 0;
			}
		ag.Flush(TShouldDelete::Delete);
		ag.Add(new agent);
		ag[0]->move();						// plot its course until it crashes

}					// end inner while(1)
}					// end outer while(1)
}				// end main()

 

 

Valid HTML 4.01 Transitional Valid CSS
View content labeling at ICRA.
Copyright ©2007 Steven Whitney. Last modified 12/20/2007.