/* built with Studio Sketchpad:
* https://sketchpad.cc
*
* observe the evolution of this sketch:
* https://cisc1600.sketchpad.cc/sp/pad/view/ro.KbmAOz2-b1e/rev.274
*
* authors:
* Harry
* John Loeffler
* license (unless otherwise specified):
* creative commons attribution-share alike 3.0 license.
* https://creativecommons.org/licenses/by-sa/3.0/
*/
/*
// John Loeffler
// CISC 1600 - W6
// 11/11/2015
// Project # 2
//
//
// OSMOS - Animation and Basic Functionality and Interactivity
//
//
//
*/
// Sets Global Max Constant
int MAX = 40;
int fRate = 30;
float playerRadius = 20;
// Declares Array of cell objects
Cell [] cells;
// Declares Global Vairables
float xGlobal = 0, yGlobal = 0;
float countUp = 0, countDn = 0, countLt = 0, countRt = 0;
boolean xyChange = false;
float dTRate = 0.05;
int colorChflag = 0;
int end = fRate * 5;
int cellCount = 1, finalCount = 0, globalCount = 0;
boolean gameOver = false;
int gameState = 0;
// Setting up the Canvas
void setup()
{
size(900, 900);
background(150);
frameRate(fRate);
// Allocates the new Array of cells
cells = new Cell[MAX];
// Declares variables needed to construct objects
float rs, rx1, ry1, rxs, rys;
int rx, ry;
boolean flag = false;
// Declares the "Player," right now Player is always the biggest cell
cells[0] = new Cell(playerRadius, width/2, height/2, 0.0, 0.0);
cells[0].setPlayer(true);
// Populates rest of the Array
for(int k = 1; k < MAX; k++)
{
//Keeps new cells within an appropriate size range relative to player size
rs = random((2*playerRadius)/6, playerRadius*1.50);
rx1 = random (width);
ry1 = random (height);
rx = int(rx1);
ry = int(ry1);
//Flag helps keep the distribution of cells from bunching too badly
flag = false;
do
{
rxs = random(1.0);
}while(rxs == 0);
if(rx % 2 == 0)
{
rxs *= -1;
}
do
{
rys = random(1.0);
}while(rys == 0);
if(ry % 2 == 0)
{
rys *= -1;
}
if(rx > 300 && rx < 600) // Keeps cells from populating on top of player
{ //
flag = true; //
if(k % 2 == 0) //
{
rx -= 200;
}
else //
{
rx += 200;
}
}
if(!flag) // Keeps cells from overpopulating top left corner
{
if(ry > 310 && ry < 590)
{
if(k % 3 == 0)
{
ry -= 200;
}
else
{
ry += 200;
}
}
}
// Creates Cell object in array
cells[k] = new Cell(rs, rx, ry, rxs, rys);
// Increments cellCount
cellCount++;
}
}
void draw()
{
//Increments global counter
globalCount++;
switch(gameState)
{
//Starts game on the Introscreen
case 0:
Introscreen();
break;
case 1:
// Pinkish tone simulates body interior
background(175, 130, 130);
// text(globalCount + " " + gameState, 20, 20); // FPS count for debugging
//Calls to see if Global X/Y speeds have changed from user input
changed();
//Draws the Cells
for(int j = 0; j < MAX; j++)
{
cells[j].draw();
if(cells[j].isDestroyed() == true)
{
cells[j].disintegrate();
if(cells[j].isPlayer)
{
gameOver = true;
finalCount++;
}
}
}
//Checks for Cell Contact
checkCollision();
//Resets Global X/Y speed
xGlobal = 0;
yGlobal = 0;
xyChange = false;
if(finalCount >= 2*fRate || cells[0].radius > width/2)
{
gameState = 2;
}
break;
case 2:
GameOverScreen();
break;
}
/*
if(keyPressed)
{
if(key == CODED)
{
if(keyCode == ENTER)
{
gameState = 1;
}
}
}
*/
if(globalCount == 120)
{
gameState = 1;
}
}
//Listens for User input and changes Global X/Y values accordingly
void changed()
{
if(keyPressed)
{
if(key == CODED)
{
if(keyCode == UP)
{
if(countUp < 2.6) //Less-than checks create a speed limit for the cells
{
countUp+= 0.2; // countDirection increments/decrements allow for
countDn-= 0.2; // changes in speed within the specified range;
yGlobal+= 0.2; // when one extreme limit is reached, allows for
xyChange = true; // full direction reversal
}
}
if(keyCode == DOWN)
{
if(countDn < 2.6)
{
countUp-= 0.2;
countDn+= 0.2;
yGlobal-= 0.2;
xyChange = true;
}
}
if(keyCode == LEFT)
{
if(countLt < 2.6)
{
countRt-= 0.2;
countLt+= 0.2;
xGlobal+= 0.2;
xyChange = true;
}
}
if(keyCode == RIGHT)
{
if(countRt < 2.6)
{
countRt+= 0.2;
countLt-= 0.2;
xGlobal-= 0.2;
xyChange = true;
}
}
}
}
}
// Checks for Cell contact
void checkCollision()
{
//Creates nested for-loops to check each cell against the others
for (int i = 0; i < MAX; i++)
{
for (int j = i+1; j < MAX; j++)
{
int small = -1, large = -1;
// Distance Formula to determine cell contact
float distX = cells[j].getX() - cells[i].getX();
float distY = cells[j].getY() - cells[i].getY();
float dist = sqrt(distX * distX + distY * distY);
// Determines Bigger Cell
if ((cells[j].getRadius() + cells[i].getRadius()) > (dist*2)
&& (!cells[j].isDestroyed) && (!cells[j].isDestroyed))
{
if(cells[i].getRadius() < cells[j].getRadius())
{
small = i;
large = j;
}
else
{
large = i;
small = j;
}
// If the larger cell is at least 1.5 times the size of the smaller
if(2 * cells[large].radius >= 3 * cells[small].radius)
{
// Sets a cell size cap of twice the size of the player
// If larger cell is smaller than cap, adds to cell size relative to
// Size of the smaller cell, creating the appearance that one cell is
// absorbing the other
if(2 * cells[large].radius <= 4 * playerRadius && !cells[large].isPlayer)
cells[large].setRadius(cells[small].radius*0.05);
if(cells[large].isPlayer)
{
// Player "eats" a larger share of consumed cells for gameplay balance
// Also keeps track of player stats for scoring performance
cells[large].setRadius(cells[small].radius*0.1);
cells[large].grewBy += cells[small].radius*0.1;
cells[large].absorbed++;
}
// Initiates the "destruction" sequence of the smaller cell
cells[small].setDestroyed();
}
else
{
// If smaller cell is not eligible for absorbsion, bounces it away
cells[small].isBounced();
}
}
}
}
}
// Introduction Screen Function
void Introscreen()
{
background(175, 130, 130);
stroke(225 + 30, 125+30, 125+30);
strokeWeight(width*.05);
fill(225, 125, 125);
ellipse(width/2, height/2, width - width*.2, (width - width*.2)*0.9);
textSize(48);
textAlign(CENTER);
fill(51, 0, 17);
text("OSMOS", width/2, height/3);
textSize(19);
text("Move your cell using the UP, DOWN, LEFT, and RIGHT arrow keys", width/2, height/3 + 50);
text("Avoid Cells bigger than you and absorb cells smaller than you!", width/2, height/3 + 100);
text("GET READY!", width/2, height/2);
}
// Game Over Screen
void GameOverScreen()
{
int score;
// Calculates player score
score = int(cells[0].grewBy) * cells[0].absorbed;
background(175 + 50, 130 + 50, 130 + 50);
stroke(225 + 30, 125+30, 125+30);
strokeWeight(width*.05);
fill(225, 125, 125);
ellipse(width/2, height/2, width - width*.2, (width - width*.2)*0.9);
// Prints Win or Loss followed by stats and total score
textSize(48);
textAlign(CENTER);
fill(51, 0, 17);
if(gameOver)
{
text("GAME OVER!", width/2, height/3);
}
else
{
text("YOU WIN!", width/2, height/3);
}
textSize(19);
text("Total mass absorbed: " + int(cells[0].grewBy), width/2, height/3 + 50);
text("Total cells absorbed: " + cells[0].absorbed, width/2, height/3 + 100);
text("SCORE : " + score, width/2, height/2);
}
// CELL CLASS DECLARATION
class Cell
{
// Data Members
float radius, xSpeed, ySpeed;
float pX, pY, cX, cY;
float transparency;
float alpha = map(transparency, 0, 1, 0, 255);
int red, blue, green, endCount, Count, destroyedCount, spawnCount, colorType;
float grewBy;
int score, absorbed;
boolean isPlayer, isDestroyed;
// Constructor
Cell(float radi, int x, int y, float xSp, float ySp)
{
isPlayer = false; // Defaults to Non-player Cell
isDestroyed = false; // Not a disintegrating cell
setColor(); // Sets semi-random color
transparency = 1.0; // Transparency not yet implimented, but retained here
// for later implementation
this.radius = radi; // Sets Radius of Cell
this.cX = x; // Sets Position
this.cY = y; //
this.xSpeed = xSp; // Sets Speed
this.ySpeed = ySp; //
this.pX = this.cX; // Retained value for as-yet unimplemented acceleration
this.pY = this.cY; // effects (shadows, trails, etc.)
endCount = 0; // End of "Destruction" sequence count.
destroyedCount = 0; // "Destruction" sequence count.
}
// Draw Function
void draw()
{
// Checks for user input for global Cell Shift
if(xyChange == true)
{
setXspeed(xGlobal);
setYspeed(yGlobal);
}
// Draws Cells
strokeWeight(radius*0.1);
stroke(red+30, green+30, blue+30);
fill(red, green, blue);
if(this.radius > cells[0].radius)
{
rect(cX, cY, radius, radius);
}
else if(this.radius < cells[0].radius && this.radius > .75 * cells[0].radius)
{
triangle(cX - this.radius/2, cY, cX, cY - this.radius, cX + this.radius/2, cY);
}
else
{
ellipse(cX, cY, radius, radius*.9);
}
// Moves the cell by Global X/Y change if the cell is NOT the player cell
// keeping the Player cell centered in the canvas but giving the appearance
// of Movement
if(!isPlayer)
move();
}
// Governs Cell movement and wrapping around the convas
void move()
{
this.cX = this.cX + this.xSpeed;
this.cY = this.cY + this.ySpeed;
if(this.cX <= 0 - this.radius * 2.5)
this.cX = width + this.radius * 2.5;
if(this.cX >= width + this.radius * 2.5)
this.cX = 0 - this.radius * 2.5;
if(this.cY <= 0 - this.radius * 2.5)
this.cY = height + this.radius * 2.5 ;
if(this.cY >= height + this.radius * 2.5)
this.cY = 0 - this.radius * 2.5;
}
// "Destruction" sequence for cells
void disintegrate()
{
// Increments timer for "destruction" events
this.destroyedCount++;
// Actual "destruction" process
if(this.destroyedCount == 3)
{
// Equality is used rather than Modulo, as Modulo was creating
// mysterious, unpredictable events
//
// When equality is reached:
// - Decelerates the cells velocity
// - Shrinks the cell's size
// - increases lower RGB Color values until "white"
this.xSpeed *= 0.75;
this.ySpeed *= 0.75;
this.radius -= this.radius * 0.05;
if(this.red > this.green || this.blue > this.green)
{
this.green += 5;
}
if(this.red > this.blue || this.green > this.blue)
{
this.blue += 5;
}
if(this.red < this.green || this.red < this.blue)
{
this.red += 5;
}
// Resets count for pseudo-loop, increments count for termination of
// "destruction" sequence
this.destroyedCount = 0;
this.endCount++;
}
// When "destruction" sequence reaches the end...
if(this.endCount >= 20)
{
// CELL ISN'T ACTUALLY DESTROYED! HA! IT'S JUST RESET OFF-CANVAS!
if(this.isPlayer == true)
{
gameState = 2;
}
// Resets Radius to Range relative to player size
this.radius = random(playerRadius*0.95, playerRadius * 1.55);
// Gives it a new color
setColor();
// "Randomizes" new spawning position
if(this.red > 125)
{
this.cX = random(-25, -50);
this.xSpeed = random(1.0); // Forces new cell to move toward visible canvas
}
else
{
this.cX = random(width+25, width + 50 );
this.xSpeed = random(-1.0); // Forces new cell to move toward visible canvas
}
if(this.blue < 225)
{
this.cY = random(-25, -50);
this.ySpeed = random(1.0); // Forces new cell to move toward visible canvas
}
else
{
this.cY = random(height+25, height+50);
this.ySpeed = random(-1.0); // Forces new cell to move toward visible canvas
}
// Unimplemented acceleration effect values
this.pX = this.cX;
this.pY = this.cY;
// Completes reset of the Cell so it can be destroyed again
this.destroyedCount = 0;
this.endCount = 0;
this.isDestroyed = false;
}
}
// Setters and Getters
void setColor()
{
switch(colorChflag) // Switch operation cycles through available color "pool"
{ // So colors are evenly distributed
case 0:
this.colorType = 0;
this.red = 225;
this.green = 125; // Red
this.blue = 125;
colorChflag++;
break;
case 1:
this.colorType = 1;
this.red = 125;
this.green = 225; // Green
this.blue = 125;
colorChflag++;
break;
case 2:
this.colorType = 2;
this.red = 125;
this.green = 125; // Blue
this.blue = 225;
colorChflag++;
break;
case 3:
this.colorType = 3;
this.red = 225;
this.green = 225; // Yellow
this.blue = 125;
colorChflag++;
break;
case 4:
this.colorType = 4;
this.red = 225;
this.green = 125; // Purple
this.blue = 225;
colorChflag++;
break;
case 5:
this.colorType = 5;
this.red = 125;
this.green = 225; // Cyan
this.blue = 225;
colorChflag = 0;
break;
}
}
// Sets a specific color value rather than randomized
void setColor(int r, int g, int b)
{
this.red = r;
this.green = g;
this.blue = b;
}
// Sets rate of change in X position
void setXspeed (float xChange)
{
this.xSpeed += xChange;
}
// Sets rate of change in Y position
void setYspeed (float yChange)
{
this.ySpeed = (yChange + this.ySpeed);
}
// Sets rate of change in X position by multiples (-1 for reversal)
void factorXSpeed(float f)
{
this.xSpeed *= f;
}
// Sets rate of change in Y position by multiples (-1 for reversal)
void factorYSpeed(float f)
{
this.ySpeed *= f;
}
// Flags the cell as the Player Cell
void setPlayer(boolean x)
{
this.isPlayer = x;
}
// Flags the cell as in the process of being destroyed
void setDestroyed()
{
this.isDestroyed = true;
// Helps to keep the cell from travelling too far from consuming cell
// to maintain realistic absorbtion effect
this.xSpeed *= -0.1;
this.ySpeed *= -0.1;
}
void isBounced()
{
// Yet to be implemented "Arcing bounce" effect
/*for(int i = 0; i < 2; i++)
{
for(int j = 0; count < 30;
}*/
// Bounces the cell away from a Larger cell
this.xSpeed *= -0.75;
this.ySpeed *= -0.75;
}
// Changes the size of the cell
void setRadius(float r)
{
this.radius += r;
}
// Returns the size of the cell
float getRadius()
{
return this.radius;
}
// Returns the X position of the cell
float getX ()
{
return this.cX;
}
// Returns the Y position of the cell
float getY ()
{
return this.cY;
}
// Returns the rate of change of the X position of the cell
float getXSpeed()
{
return this.xSpeed;
}
// Returns the rate of change of the Y position of the cell
float getYSpeed()
{
return this.ySpeed;
}
// Returns whether the cell is in the process of being destroyed
boolean isDestroyed()
{
return this.isDestroyed;
}
//int getCount()
//{
// return this.Count;
//}
}