> show canvas only <


/* 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;
    //}      
 }