// BlockyFroggy
// Copyright © 2017 John Ryland.
// All rights reserved.
#include <cstdio>
#include <cstdlib>
#include <ctime>
#include "GameSim.h"
#define TIME_BASED 1
/*
To make simulation deterministic if required, floats should be avoided/removed.
To enforce this, including a header like this first would be helpful to ensure they aren't used.
NoFloat.h
---------
#include "acceptable headers.h"
#define float errorType
#define double errorType
*/
using namespace GameSim;
namespace details {
bool rectangleIntersect(int ax1, int ay1, int ax2, int ay2, int bx1, int by1, int bx2, int by2)
{
// Early out
//if (ax1 > bx2 || bx1 > ax2 || ay1 > by2 || by1 > ay2)
// return false;
bool overlayInX = (ax1 > bx1 && ax1 < bx2) || (ax2 > bx1 && ax2 < bx2) || (bx1 > ax1 && bx1 < ax2) || (bx2 > ax1 && bx2 < ax2);
if (!overlayInX)
return false;
bool overlayInY = (ay1 > by1 && ay1 < by2) || (ay2 > by1 && ay2 < by2) || (by1 > ay1 && by1 < ay2) || (by2 > ay1 && by2 < ay2);
if (!overlayInY)
return false;
return true; // overlaps in X and Y dimensions, therefore intersect
}
float mod(float x, float y)
{
return x - y * floor(x/y);
}
bool objectsIntersect(float a_time, const GameObject& a_objA, const GameObject& a_objB)
{
#ifndef TIME_BASED
return rectangleIntersect(a_objA.m_x, a_objA.m_y, a_objA.m_x + a_objA.m_w, a_objA.m_y + a_objA.m_h,
a_objB.m_x, a_objB.m_y, a_objB.m_x + a_objB.m_w, a_objB.m_y + a_objB.m_h);
#else
int x1 = mod(a_objB.m_x + a_objB.m_w + a_time*a_objB.m_dx, 1000.0 + a_objB.m_w) - a_objB.m_w;
int x2 = mod(a_objB.m_x + a_objB.m_w + a_time*a_objB.m_dx, 1000.0 + a_objB.m_w) - 0;
/*
int x1 = (int(a_objB.m_x + a_objB.m_w + a_time*a_objB.m_dx) % int(1000 + a_objB.m_w)) - a_objB.m_w;
int x2 = (int(a_objB.m_x + a_objB.m_w + a_time*a_objB.m_dx) % int(1000 + a_objB.m_w)) - 0;
*/
return rectangleIntersect(a_objA.m_x, a_objA.m_y, a_objA.m_x + a_objA.m_w, a_objA.m_y + a_objA.m_h,
x1, a_objB.m_y, x2, a_objB.m_y + a_objB.m_h);
#endif
}
}
GameLogic::GameLogic()
{
m_objLists.push_back(&m_backgroundLevel);
m_objLists.push_back(&m_populationLevel);
m_objLists.push_back(&m_movingObjects);
m_objLists.push_back(&m_player);
reset();
}
GameLogic::~GameLogic()
{
}
void GameLogic::reset()
{
std::vector<GameObjectList*> lists = objectLists();
for (GameObjectList* list : lists)
list->clear();
//m_carObjects.clear();
generateLevel();
m_gameOver = false;
m_won = false;
m_time = 0.0f;
}
void GameLogic::movePlayer(Movement a_movement)
{
if (m_gameOver)
return;
float origX = m_player.front().m_x;
float origY = m_player.front().m_y;
if (a_movement == Forward)
{
m_player.front().m_y += 10;
}
else if (a_movement == Backward)
{
m_player.front().m_y -= 10;
}
else if (a_movement == Left)
{
m_player.front().m_x -= 10;
}
else if (a_movement == Right)
{
m_player.front().m_x += 10;
}
bool onWater = false;
//bool willHitObstacle = false;
// reset
for (GameObject& tile : m_backgroundLevel)
{
if (tile.m_type == Water || tile.m_type == WaterLane)
{
if (details::objectsIntersect(m_time, m_player.front(), tile))
{
onWater = true;
break;
}
}
}
for (GameObject& tile : m_populationLevel)
{
if (details::objectsIntersect(m_time, m_player.front(), tile))
{
if (onWater)
{
// maybe game over if we aren't on a craft
}
else
{
// rollback movement
m_player.front().m_x = origX;
m_player.front().m_y = origY;
}
}
}
}
int heightForType(ObjectType t)
{
if (t == Ground)
return -4;
return (t == Water || t == WaterLane) ? -3 : ((t == Rail || t == Ground) ? -1 : -2);
}
int GameLogic::getLaneHeight(int y)
{
int lane = y / 10;
if (lane < 0 || lane >= m_lanes)
return 0;
ObjectType t = m_laneFlags[lane];
return heightForType(t);
}
int GameLogic::getPlayerHeight()
{
int z = getLaneHeight(m_player.front().m_y);
if (m_player.front().m_dx)
return z + 15;
return z;
}
void GameLogic::generateLevel()
{
for (int y = 0; y < m_lanes; y++)
{
int laneWidth = m_rng.generate(0, 5);
ObjectType laneFlag = ObjectType(m_rng.generate(0, 4));
if (y && m_laneFlags[y-1] == laneFlag)
{
y--;
continue;
}
if (laneFlag == Water)
laneWidth = 1;
if (laneFlag == Rail && laneWidth > 3)
laneWidth = 3;
for (int i = 0; i < laneWidth; i++)
{
m_laneFlags[y] = laneFlag;
y++;
if (y >= m_lanes)
{
break;
}
}
y--;
}
m_laneFlags[0] = Ground;
m_laneFlags[1] = Ground;
m_laneFlags[2] = Ground;
m_laneFlags[3] = Ground;
m_laneFlags[4] = Ground;
m_laneFlags[5] = Ground;
m_laneFlags[6] = Ground;
//for (int x = 0; x < 100; x++)
{
for (int y = (m_lanes-1); y >= 0; )//y--)
{
GameObject tile;
tile.m_lane = y;
tile.m_type = m_laneFlags[y];
//int y1 = y;
int laneWidth = 0;
while (y>=0 && m_laneFlags[y] == tile.m_type)
{
y--;
laneWidth++;
}
tile.m_x = 0;//x * 10;
tile.m_y = (y+1) * 10;
tile.m_z = heightForType(tile.m_type);
tile.m_w = 10*100; //10;
tile.m_h = 10*laneWidth;
tile.m_d = 6; // height for terrain
tile.m_dx = 0;
tile.m_dy = 0;
tile.m_color = 0x808020 + (m_laneFlags[y]*5);
m_backgroundLevel.push_back(tile);
}
}
for (int y = (m_lanes-1); y >= 0; y--)
{
if (m_laneFlags[y] == Ground) // if Ground, place obstacles
{
for (int x = 0; x < 100; x++)
{
if (m_rng.generate(0, 19) == 0)
{
GameObject tile;
tile.m_lane = y;
tile.m_type = ObjectType(Tree + m_rng.generate(0, 3));//Obstacle;
tile.m_x = x * 10;
tile.m_y = y * 10;
tile.m_z = heightForType(m_laneFlags[y]) + 6;// + 6;
tile.m_w = 10;
tile.m_h = 10;
tile.m_d = 10; // height of terrain
tile.m_dx = 0;
tile.m_dy = 0;
tile.m_color = 0xf08080;
m_populationLevel.push_back(tile);
}
}
}
else if (m_laneFlags[y] == Water)
{
for (int x = 0; x < 100; x++)
{
if (m_rng.generate(0, 19) == 0)
{
GameObject tile;
tile.m_lane = y;
tile.m_type = ObjectType(LilyPad + m_rng.generate(0, 1));//Landing
tile.m_x = x * 10;
tile.m_y = y * 10;
tile.m_z = heightForType(m_laneFlags[y]) + 3;//+ 6;
tile.m_w = 10;
tile.m_h = 10;
tile.m_d = 2; // height of terrain
tile.m_dx = 0;
tile.m_dy = 0;
tile.m_color = 0xf08080;
m_populationLevel.push_back(tile);
}
}
}
}
for (int y = (m_lanes-1); y >= 0; y--)
{
if (m_laneFlags[y] != Ground && m_laneFlags[y] != Water) // if not Ground, place vehicles
{
ObjectType laneType = m_laneFlags[y];
int width = m_rng.generate(1, 4);
int speed = m_rng.generate(1, 9);
int count = m_rng.generate(0, 15);
int direction = m_rng.generate(0, 1);
int x = m_rng.generate(0, 9);
if (laneType == Rail)
{
width = 30;
speed = 50;
count = 1;
x = m_rng.generate(0, 69);
}
int oldWidth = width;
for (int i = 0; i < count; i++)
{
ObjectType vtype = Ground;
float z = 0.0;
z = heightForType(laneType) + 3;//+ 6;
if (laneType == Rail)
z += 2.0f;
if (laneType == Road) {
vtype = ObjectType(Bike + m_rng.generate(0, 3));
} else if (laneType == Rail) {
vtype = ObjectType(Train1 + m_rng.generate(0, 3));
} else if (laneType == WaterLane) {
vtype = ObjectType(Log + m_rng.generate(0, 2));
//tile.m_type = (laneType == Water) ? FloatingPlatform : Vehicle;
}
switch (vtype)
{
case Bike: width = 15; break;
case Car: width = 15; break;
case Van: width = 18; break;
case Truck: width = 50; break;
case Train1: width = 130; break;
case Train2: width = 130; break;
case Train3: width = 130; break;
case Train4: width = 130; break;
default:
width *= 10; break;
}
/*
if (vtype == Car)
{
GameObject tile;
tile.m_lane = y;
tile.m_type = vtype;
tile.m_z = z;
tile.m_x = x * 10;
tile.m_y = y * 10;
tile.m_w = width;
tile.m_h = 10;
tile.m_d = 15; // height for terrain
tile.m_dx = (direction) ? speed : -speed;
tile.m_dy = 0;
tile.m_color = (0x80 << 16) | ((0x10*width)<<8) | (0x10*speed);
m_carObjects.push_back(tile);
}
else
*/
{
GameObject tile;
tile.m_lane = y;
tile.m_type = vtype;
tile.m_z = z;
tile.m_x = x * 10;
tile.m_y = y * 10;
tile.m_w = width;
tile.m_h = 10;
tile.m_d = 15; // height for terrain
tile.m_dx = (direction) ? speed : -speed;
tile.m_dy = 0;
tile.m_color = (0x80 << 16) | ((0x10*width)<<8) | (0x10*speed);
m_movingObjects.push_back(tile);
}
x += width + 1;
int avgWidthBetweenObjects = 100 / count;
x += avgWidthBetweenObjects / 2;
x += m_rng.generate(0, avgWidthBetweenObjects-1);
if (x > 100)
{
break;
}
width = oldWidth;
}
}
}
GameObject tile;
tile.m_lane = 5;
tile.m_type = Player;
tile.m_x = 732;
tile.m_y = 52;
tile.m_z = 0;
tile.m_w = 6;
tile.m_h = 6;
tile.m_d = 20; // height for terrain
tile.m_dx = 0;
tile.m_dy = 0;//1;
tile.m_color = 0xffffff;
m_player.push_back(tile);
}
const std::vector<GameObjectList*>& GameLogic::objectLists()
{
return m_objLists;
}
bool GameLogic::checkGameOver()
{
GameObject player = m_player.front();
if (player.m_x < 0 || player.m_x > 1000)
{
return true;
}
// reset
m_player.front().m_dx = 0;
uint32_t playerLane = player.m_y / 10.0;
for (GameObject& tile : m_movingObjects)
{
if (playerLane == tile.m_lane)
if (details::objectsIntersect(m_time, player, tile) /* || details::objectsIntersect(tile, m_player.front()) */ )
{
if (tile.m_type >= Bike && tile.m_type <= Train4)
{
return true;
}
if (tile.m_type == Crocodile)
{
//! @todo more logic if near head of croc
//if (tile.m_dx > 0 && tile.m_x + )
return true;
}
if (tile.m_type == Log || tile.m_type == Boat) // || tile.m_type == LilyPad || tile.m_type == SteppingStone)
{
// moves with the log or boat
m_player.front().m_dx = tile.m_dx;
return false;
}
}
}
// Check if on water
bool onWater = false;
for (GameObject& tile : m_backgroundLevel)
{
//if (playerLane == tile.m_lane)
{
if (tile.m_type == Water || tile.m_type == WaterLane)
{
if (details::objectsIntersect(m_time, player, tile))
{
onWater = true;
break;
}
}
}
}
// Check if on lilypad or similar
bool safeOnWater = false;
for (GameObject& tile : m_populationLevel)
{
//if (playerLane == tile.m_lane)
{
if (tile.m_type == LilyPad || tile.m_type == SteppingStone)
{
if (details::objectsIntersect(m_time, player, tile))
{
safeOnWater = true;
break;
}
}
}
}
if (onWater && !safeOnWater)
{
return true;
}
/*
if (player.m_y > 500)
{
m_won = true;
return true;
}
*/
return false;
/*
// Terrain
Ground,
Road,
Rail,
WaterLane,
Water,
// Obstacle
Tree,
Shrub,
Building,
Rocks,
LilyPad,
SteppingStone,
// Pickup
Coin,
Food,
Life,
Bonus,
*/
}
void GameLogic::update(float a_dt)
{
if (m_gameOver)
return;
m_time += a_dt;
#ifndef TIME_BASED
for (GameObject& tile : m_movingObjects)
{
tile.m_x += tile.m_dx * a_dt;
tile.m_y += tile.m_dy * a_dt;
if (tile.m_dx > 0)
{
if (tile.m_x > 1000)
tile.m_x -= 1000 + tile.m_w;
}
else
{
if ((tile.m_x + tile.m_w) < 0)
tile.m_x += 1000 + tile.m_w;
}
}
#endif
for (GameObject& tile : m_player)
{
tile.m_x += tile.m_dx * a_dt;
tile.m_y += tile.m_dy * a_dt;
}
m_gameOver = checkGameOver();
}