#include "game.h"
#include <onidev.h>
#include <onidev/graphics/render_state.h>
#include <stack>
#include <cstdlib>
#include <cmath>
od::Mat3 getCameraMatrix(const od::View& view, bool flip)
{
if(flip) {
return od::Mat3::ortho(
view.left(), view.right(),
view.bottom(), view.top());
} else {
return od::Mat3::ortho(
view.left(), view.right(),
view.top(), view.bottom());
}
}
namespace jewels
{
Game::Game(GameAssets& assets, od::Sizeu size):
_renderer(&assets.shader_pack, &assets.tex_pack),
_size(size),
_cells(size.wid*size.hei),
_stable(false),
_swapped(false)
{
srand(0);
for(unsigned i=0; i<size.wid; ++i)
for(unsigned j=0; j<size.hei; ++j)
{
_cells[i + j*size.wid].jewel = 1 + (rand() % Jewel::count());
}
}
bool Game::mouseCell(Posu& p) const
{
int mx = _mouse.x / CellSize;
int my = _mouse.y / CellSize;
if(mx >= 0 && unsigned(mx) < _size.wid
&& my >= 0 && unsigned(my) < _size.hei)
{
p = Posu(mx, my);
return true;
}
return false;
}
bool Game::mousePressCell(Posu& p) const
{
if(od::mousePressed(od::mb_left))
{
return mouseCell(p) && !at(p).empty();
}
return false;
}
Cell& Game::at(Posu p)
{
return _cells[p.x + p.y*_size.wid];
}
const Cell& Game::at(Posu p) const
{
return _cells[p.x + p.y*_size.wid];
}
bool Game::eat(Posu p, int min)
{
if(at(p).empty()) {
return false;
}
Jewel jewel = at(p).jewel;
// horizontal test
int left =-1, right =-1;
for(unsigned i=p.x; i<_size.wid; ++i)
{
if(!at(Posu(i, p.y)).dying() && at(Posu(i, p.y)).jewel == jewel) {
right++;
} else {
break;
}
}
for(int i=p.x; i>=0; --i)
{
if(!at(Posu(i, p.y)).dying() && at(Posu(i, p.y)).jewel == jewel) {
left++;
} else {
break;
}
}
// vertical test
int top=-1, bottom=-1;
for(unsigned i=p.y; i<_size.hei; ++i)
{
if(!at(Posu(p.x, i)).dying() && at(Posu(p.x, i)).jewel == jewel) {
bottom++;
} else {
break;
}
}
for(int i=p.y; i>=0; --i)
{
if(!at(Posu(p.x, i)).dying() && at(Posu(p.x, i)).jewel == jewel) {
top++;
} else {
break;
}
}
bool ret = false;
if(left + right + 1 >= 3)
{
for(int i=p.x-left; i<=int(p.x)+right; ++i)
{
at(Posu(i, p.y)).scale = 0.999f; // prepare to diiie
}
ret = true;
}
if(top + bottom + 1 >= 3)
{
for(int i=p.y-top; i<=int(p.y)+bottom; ++i)
{
at(Posu(p.x, i)).scale = 0.999f;
}
ret = true;
}
return ret;
}
void Game::swap()
{
std::swap(swap1, swap2);
_swapped = true;
}
void Game::updateGamePhysics(float dt)
{
float gravity = 0.3;
float maxVy = 16;
for(unsigned i=0; i<_size.wid; ++i)
for(unsigned j=0; j<_size.hei; ++j)
{
Cell& c = _cells[i + j*_size.wid];
if(!c.empty())
{
// if bottom is empty
Posu p(i, j+1);
if(j+1 != _size.hei
&& !at(p).dying()
&& (at(p).jewel == Jewel(0) || at(p).vy > 0))
{
Cell& down = _cells[i + (j+1)*_size.wid];
if(c.dy >= 16)
{
down.jewel = c.jewel;
down.dy = -32 + c.dy;
down.vy = c.vy;
c = Cell();
}
else
{
c.dy += c.vy;
c.vy += gravity * dt;
if(c.vy > maxVy) {
c.vy = maxVy;
}
}
_stable = false;
}
else if(c.dy < 0)
{
c.dy += c.vy;
c.vy += gravity * dt;
if(c.vy > maxVy) {
c.vy = maxVy;
}
if(c.dy > 0)
{
c.dy = 0;
c.vy = 0;
eat(Posu(i, j), 3);
}
_stable = false;
}
}
}
}
void Game::spawnGems()
{
for(unsigned i=0; i<_size.wid; ++i)
{
if(_cells[i].empty() && _cells[i+_size.wid].dy >= 0)
{
_cells[i] = Cell(1 + (rand()%Jewel::count()));
}
}
}
void Game::updateSoftDieEffect(float dt)
{
for(unsigned i=0; i<_size.wid*_size.hei; ++i)
{
if(_cells[i].scale < 1 && _cells[i].scale > 0)
{
_cells[i].scale -= 0.1 * dt;
}
if(_cells[i].scale < 0)
{
_cells[i] = Cell();
_cells[i].jewel = 0;
}
}
}
bool Game::swapSelectedGems(float dt)
{
if(!_valid1 || !_valid2) {
return false;
}
Cell& c1 = at(swap1);
Cell& c2 = at(swap2);
auto swapJewels = [&]() {
Jewel jewel = c1.jewel;
c1.jewel = c2.jewel;
c2.jewel = jewel;
};
if(c1.dy == 0)
{
float s = od::sign(c1.dx);
c1.dx += s * 2 * dt;
c2.dx -= s * 2 * dt;
if( std::fabs(c1.dx) >= 32 )
{
swapJewels();
if( _swapped || (eat(swap1, 3) | eat(swap2, 3)) )
{
c1.dx = c2.dx = 0;
swap1 = swap2 = Posu();
_valid1 = _valid2 = false;
}
else
{
c2.dx =-s * 2 * dt;
c1.dx = s * 2 * dt;
swap();
}
}
}
else if(c1.dx == 0)
{
float s = od::sign(c1.dy);
c1.dy += s * 2 * dt;
c2.dy -= s * 2 * dt;
if( std::fabs(c1.dy) >= 32 )
{
swapJewels();
if( _swapped || (eat(swap1, 3) | eat(swap2, 3)) )
{
c1.dy = c2.dy = 0;
swap1 = swap2 = Posu();
_valid1 = _valid2 = false;
}
else
{
c2.dy =-s * 2 * dt;
c1.dy = s * 2 * dt;
swap();
}
}
}
return true;
}
void Game::selectGems(float dt)
{
Posu p;
if(!stable() || !mouseCell(p)) {
return;
}
if(!od::mousePressed(od::mb_left)
&& !(od::mouseReleased(od::mb_left)
&& !_valid1)) {
return;
}
if(!_valid1)
{
swap1 = p;
_valid1 = true;
_swapped = false;
}
else
{
if(aligned(od::Posi(p), od::Posi(swap1))
&& distance(od::Posi(p), od::Posi(swap1)) == 1)
{
od::Posi d = od::Posi(swap1) - od::Posi(p);
swap2 = p;
_valid2 = true;
at(swap1).dx = -2 * dt * od::sign(d.x);
at(swap1).dy = -2 * dt * od::sign(d.y);
}
else
{
swap1 = p;
_valid1 = true;
}
}
}
bool Game::aligned(od::Posi p1, od::Posi p2)
{
od::Posi d = p2 - p1;
return d.x == 0 || d.y == 0;
}
float Game::distance(od::Posi p1, od::Posi p2)
{
return sqrtf( (p2.x-p1.x)*(p2.x-p1.x) + (p2.y-p1.y)*(p2.y-p1.y) );
}
void Game::step(od::Posi mouse, float dt)
{
_mouse = mouse;
_stable = true;
updateSoftDieEffect(dt);
if(!swapSelectedGems(dt))
{
updateGamePhysics(dt);
spawnGems();
selectGems(dt);
}
}
void Game::predraw(const od::View& view)
{
_renderer.setDefaultCamera2d(getCameraMatrix(view, true));
_renderer.clear();
{
od::RenderState rs(_renderer, 0, "simple", "sprites");
for(unsigned i=0; i<_size.wid; ++i)
for(unsigned j=0; j<_size.hei; ++j)
{
rs.draw("tiles", 0, od::Pos(i*CellSize, j*CellSize));
}
for(unsigned i=0; i<_size.wid; ++i)
for(unsigned j=0; j<_size.hei; ++j)
{
Cell c = _cells[i + j*_size.wid];
if(!c.jewel.empty()) {
rs.drawScaled(
"jewels", c.jewel.color,
od::Pos(i*CellSize + c.dx + 16, j*CellSize + c.dy + 16),
od::Vec2(c.scale, c.scale));
}
}
if(_valid1)
{
if(_swapped) {
rs.draw("cursor", 0, od::Pos(swap2) * CellSize);
} else {
rs.draw("cursor", 0, od::Pos(swap1) * CellSize);
}
}
}
_renderer.complete();
}
void Game::draw() const
{
_renderer.render(0);
}
}