added my simple simulation implementation with trivial graphics

This commit is contained in:
Andreas Ortmann 2016-09-27 18:23:58 +02:00
parent ea0aa01b2d
commit 183a26412a
28 changed files with 950 additions and 36 deletions

22
game/state/commands.cpp Normal file
View file

@ -0,0 +1,22 @@
#include "commands.hpp"
namespace game {
void ShootCommand::apply(Player *player, State *state) const
{
(void) state;
// TODO spawn missile if alive and enough energy
//std::cout<<"apply command " << name() << std::endl;
player->energy -= m_speed;
player->missiles.push_back(new Missile(player->id, player->ship->position, m_angle, m_speed));
}
bool ShootCommand::allowed(const Player *player, const State *state) const
{
(void) state;
// TODO
return player->alive && player->energy >= m_speed;
//return player->alive;
}
}

56
game/state/commands.hpp Normal file
View file

@ -0,0 +1,56 @@
#pragma once
#include "state.hpp"
#include "player.hpp"
#include "ship.hpp"
#include "planet.hpp"
#include "missile.hpp"
#include <iostream>
namespace game {
class Command {
public:
Command()
{
}
virtual ~Command()
{
}
virtual bool allowed(const Player *player, const State *state) const
{
(void) player;
(void) state;
return true;
}
virtual void apply(Player *player, State *state) const
{
(void) state;
(void) player;
std::cout<<"Command '" << name() << "' not yet implemented!" << std::endl;
}
virtual std::string name() const { return "<unnamed>"; }
};
class ShootCommand : public Command {
public:
ShootCommand(float angle, float speed) : m_angle(angle), m_speed(speed)
{
}
std::string name() const { return "<shoot>"; }
void apply( Player *player, State *state) const;
bool allowed(const Player *player, const State *state) const;
private:
float m_angle;
float m_speed;
};
}

0
game/state/explosion.cpp Normal file
View file

17
game/state/explosion.hpp Normal file
View file

@ -0,0 +1,17 @@
#pragma once
#if 0
class Explosion {
public:
enum class Kind {
Normal,
MissileWithShip
};
glm::vec2 position;
Explosion(Kind kind);
public:
};
#endif

82
game/state/missile.cpp Normal file
View file

@ -0,0 +1,82 @@
#include "missile.hpp"
#include <cmath>
#include "state.hpp"
#include "player.hpp"
#include <glm/gtx/norm.hpp>
namespace game {
Missile::Missile(int playerId, const glm::vec2 &pos, float angle, float speed)
: playerId(playerId)
, position(pos)
{
velocity = speed * glm::vec2(std::sin(angle), std::cos(angle));
//std::cout << "spawned missile for player " << playerId << " @ ("
// << pos.x << ", " << pos.y << ") speed = " << speed << std::endl;
}
Missile::~Missile()
{
//std::cout<<"destroyed missile" << std::endl;
}
Missile::Event Missile::advance(const game::State *state, float dt)
{
glm::vec2 gravityForce = glm::vec2(0.0, 0.0);
for (const Player *other : state->players) {
if (other->ship != nullptr && other->id != playerId) {
glm::vec2 diff = other->ship->position - position;
float dist = glm::length(diff);
if (dist <= other->ship->radius) {
// TODO: collect all hits and return the first one only
// TODO: find exact hit position!
return Missile::Event(position, playerId, other->id);
}
}
}
// get forces
for (const Planet *planet : state->planets) {
glm::vec2 diff = planet->position - position;
float dist = glm::length(diff);
if (dist <= planet->radius) {
// TODO: collect all hits and return the first one only
// TODO: find exact hit position!
return Missile::Event(position, Missile::HitObject::HitPlanet);
}
dist *= 20.0;
if (dist > 0.001) {
float G = 0.1;
gravityForce += G * diff / (1.0f + dist*dist);
}
}
// simple (and bad) euler integration.
position += 0.5f * gravityForce*dt*dt + velocity*dt;
velocity += gravityForce * dt;
//const float thr = 0.01;
//float s = glm::length(velocity);
//if (s > thr) {
// velocity = glm::normalize(velocity) * thr;
//}
glm::vec2 nextPos = position + velocity;
// TODO: collision checks
position = nextPos;
// check if distance to center of the universe is getting too big
float distToCenter = glm::length(position);
if (distToCenter > state->maxMissileDistance()) {
return Missile::Event(position, Missile::HitObject::LeftUniverse);
}
return Missile::Event(position);
}
}

68
game/state/missile.hpp Normal file
View file

@ -0,0 +1,68 @@
#pragma once
#include <glm/vec2.hpp>
#include <glm/vec3.hpp>
#include "state.hpp"
#include "ship.hpp"
#include "planet.hpp"
namespace game {
class State;
// missile belongs to a player and optionally fills a trace behind it.
// trace then belongs to the player.
class Missile {
public:
enum class HitObject {
Nothing,
HitPlayer,
HitPlanet,
// HitMissile,
LeftUniverse
};
// missile advances to pos. if hit != Nothing, it hits something and
// stops existing afterwards.
class Event {
public:
Event(const glm::vec2 &pos) : Event(pos, HitObject::Nothing)
{
}
Event(const glm::vec2 &pos, HitObject hit)
: hit(hit), pos(pos)
{
}
Event(const glm::vec2 &pos, int playerIdKiller, int playerIdVictim)
: Event(pos, HitObject::HitPlayer)
{
this->playerIdKiller = playerIdKiller;
this->playerIdVictim = playerIdVictim;
}
HitObject hit;
glm::vec2 pos;
int playerIdKiller;
int playerIdVictim;
};
// XXX
int playerId; // owner won't be hit by own missiles
glm::vec2 position;
glm::vec2 velocity;
//Trace *trace;
Missile(int playerId, const glm::vec2 &pos, float angle, float speed);
~Missile();
// try to advance. if something will be hit, return the first hit in
// time.
Missile::Event advance(const game::State *state, float dt);
};
}

0
game/state/object.cpp Normal file
View file

15
game/state/object.hpp Normal file
View file

@ -0,0 +1,15 @@
#pragma once
#include <glm/vec2.hpp>
#include <glm/vec3.hpp>
class Object {
public:
Object(const glm::vec2 &pos, float r) : position(pos), radius(r)
{
}
const glm::vec2 position;
const float radius;
};

0
game/state/planet.cpp Normal file
View file

12
game/state/planet.hpp Normal file
View file

@ -0,0 +1,12 @@
#pragma once
#include "object.hpp"
namespace game {
class Planet : public Object {
public:
Planet(const glm::vec2 &pos, float r) : Object(pos, r)
{
}
};
}

33
game/state/player.cpp Normal file
View file

@ -0,0 +1,33 @@
#include "player.hpp"
#include <iostream>
#include "commands.hpp"
#include "ship.hpp"
namespace game {
void Player::popCommand()
{
//std::cout<<"popCommand()" << std::endl;
Command *cmd = commands.front();
commands.erase(commands.begin());
delete(cmd);
}
Command *Player::peekCommand()
{
return commands.front();
}
bool Player::hasCommandInQueue()
{
return !commands.empty();
}
void Player::addCommand(Command *cmd)
{
commands.push_back(cmd);
}
}

36
game/state/player.hpp Normal file
View file

@ -0,0 +1,36 @@
#pragma once
#include <deque>
#include <vector>
#include <glm/vec2.hpp>
#include <glm/vec3.hpp>
namespace game {
class Ship;
class Command;
class Missile;
class Player {
public:
int id;
bool alive;
float energy;
float deadTimeCounter;
Ship *ship;
std::vector<Missile*> missiles;
Player(int id) : id(id), alive(true), energy(0.0), ship(nullptr)
{
}
void addCommand(Command *cmd);
bool hasCommandInQueue();
Command *peekCommand();
void popCommand();
private:
std::deque<Command*> commands;
};
}

0
game/state/ship.cpp Normal file
View file

12
game/state/ship.hpp Normal file
View file

@ -0,0 +1,12 @@
#pragma once
#include "object.hpp"
namespace game {
class Ship : public Object {
public:
Ship(const glm::vec2 &pos, float r) : Object(pos, r)
{
}
};
}

262
game/state/state.cpp Normal file
View file

@ -0,0 +1,262 @@
#include "state.hpp"
#include <cmath>
#include <glm/vec2.hpp>
#include <glm/vec3.hpp>
#include <glm/gtx/norm.hpp>
#include "object.hpp"
#include "missile.hpp"
#include "player.hpp"
#include "planet.hpp"
#include "ship.hpp"
#include "commands.hpp"
#include "util.hpp"
namespace game {
void State::init()
{
m_shipRadius = 0.05;
m_maxMissileDistance = 2.0;
m_playerRespawnTime = 2.0;
m_defaultEnergy = 10.0;
bool planetsOnCircle = false;
int numPlanets = 10;
for (int i=0; i<numPlanets; i++) {
float t = i / static_cast<float>(numPlanets);
t *= 2.0*M_PI;
// distribute but not in the center
int tries = 0;
glm::vec2 pos;
do {
float cr = 0.7;
if (planetsOnCircle) {
pos = cr * glm::vec2(std::sin(t), std::cos(t));
} else {
pos = cr * util::randv2_m1_1();
}
} while(glm::length(pos) < 0.2 && tries++ < 1000);
planets.push_back(new Planet(pos, 0.03 + 0.07*util::randf_0_1()));
}
}
bool State::spawnShipForPlayer(Player *player)
{
glm::vec2 spawnPos;
if (!findFreePositionWithRadius(5.0 * m_shipRadius, spawnPos)) {
return false;
}
player->ship = new Ship(spawnPos, m_shipRadius);
ships.push_back(player->ship);
player->energy = m_defaultEnergy;
return true;
}
void State::addPlayer(int playerId)
{
Player *player = new Player(playerId);
players.push_back(player);
}
void State::playerLeft(int playerId)
{
(void) playerId;
}
void State::advancePlayerShipSpawns(float dt)
{
(void) dt;
for (Player *player : players) {
if (!player->alive) {
player->deadTimeCounter += dt;
if (player->deadTimeCounter >= m_playerRespawnTime) {
player->deadTimeCounter = 0;
player->alive = true;
std::cout<<"respawning player " << player->id << " now!" << std::endl;
}
}
if (player->alive && player->ship == NULL) {
if (!spawnShipForPlayer(player)) {
std::cout<<"could not spawn ship for player!" << std::endl;
}
}
}
}
void State::advancePlayerCommands(float dt)
{
(void) dt;
for (Player *player : players) {
if (player->alive) {
player->energy += dt;
}
// try to execute as much queued commands as possible.
while (player->hasCommandInQueue()) {
Command *command = player->peekCommand();
if (!command->allowed(player, this)) {
break;
}
command->apply(player, this);
player->popCommand();
}
}
}
void State::playerKillsPlayer(Player *killer, Player *victim)
{
if (killer == nullptr || victim == nullptr) {
std::cerr <<"error: killer / victim is NULL!" << std::endl;
exit(-1);
return;
}
std::cout<<"player " << killer->id << " killed " << victim->id << std::endl;
// destroy ship
size_t i;
for (i=0; i<ships.size(); i++) {
if (ships[i] == victim->ship) {
ships.erase(ships.begin() + i);
break;
}
}
delete(victim->ship);
victim->ship = nullptr;
victim->alive = false;
victim->deadTimeCounter = 0.0;
// TODO
// add points
//
// TODO
// message
//
// TODO
// respawn timer
}
void State::advancePlayerMissiles(float dt)
{
// advance missiles
for (Player *player : players) {
size_t i = 0;
while (i<player->missiles.size()) {
Missile *missile = player->missiles[i];
//std::cout<<"missile: " << (long unsigned int) missile << std::endl;
const Missile::Event evt = missile->advance(this, dt);
if (evt.hit == Missile::HitObject::Nothing) {
i++;
} else {
switch(evt.hit) {
case Missile::HitObject::HitPlanet:
//std::cout<<"hit Planet" << std::endl;
break;
case Missile::HitObject::HitPlayer:
//std::cout<<"hit Player" << std::endl;
playerKillsPlayer(playerForId(evt.playerIdKiller), playerForId(evt.playerIdVictim));
break;
case Missile::HitObject::LeftUniverse:
//std::cout<<"missile left the universe." << std::endl;
break;
default:
break;
}
player->missiles.erase(player->missiles.begin() + i);
delete(missile);
//std::cout<<std::endl;
}
// TODO
// add trace to the missile
}
}
}
void State::advance(float dt)
{
//std::cout<<"advance ship spawns" << std::endl;
advancePlayerShipSpawns(dt);
//std::cout<<"advance commands" << std::endl;
advancePlayerCommands(dt);
//std::cout<<"advance missiles" << std::endl;
advancePlayerMissiles(dt);
}
Player *State::playerForId(int playerId)
{
for (Player *p : players) {
if (p->id == playerId) {
return p;
}
}
return nullptr;
}
bool State::findFreePositionWithRadius(float radius, glm::vec2 &pos)
{
bool first = ships.size() == 0;
int tries = 0;
while(true) {
bool noCollision = true;
//pos = glm::linearRand2(-1.0, 1.0);
if (first) {
first = false;
pos = glm::vec2(0.0, 0.0);
} else {
pos = glm::vec2(util::randf_0_1(), util::randf_m1_1());
//glm::linearRand(-1.0, 1.0), glm::linearRand(-1.0, 1.0));
}
for (Planet *p : planets) {
if (glm::distance(p->position, pos) <= radius) {
noCollision = false;
break;
}
}
if (noCollision) {
return true;
}
if (tries++ >= 1000) {
pos = glm::vec2(0.0, 0.0);
return false;
}
}
}
void State::commandForPlayer(int playerId, game::Command *cmd)
{
Player *player = playerForId(playerId);
if (player != nullptr) {
player->addCommand(cmd);
}
}
}

64
game/state/state.hpp Normal file
View file

@ -0,0 +1,64 @@
#pragma once
#include <vector>
#include <queue>
#include <iostream>
#include <glm/vec2.hpp>
namespace game {
class Command;
class Missile;
class Player;
class Planet;
class Ship;
// trace of a missile. exists without a missile at player.
//class Trace {
//public:
// std::vector<glm::vec2> points;
//};
class State {
public:
void init();
void advance(float dt);
// the (network) layer calling these three functions should keep id's
// unique and give one (network) input an id.
void addPlayer(int playerId);
void playerLeft(int playerId);
void commandForPlayer(int playerId, Command *cmd);
// lookup. return nullptr on invalid playerId
Player *playerForId(int playerId);
float maxMissileDistance() const { return m_maxMissileDistance; }
float shipRadius() const { return m_shipRadius; }
std::vector<Planet*> planets;
std::vector<Ship*> ships;
std::vector<Player*> players;
private:
void playerKillsPlayer(Player *killer, Player *victim);
void advancePlayerShipSpawns(float dt);
void advancePlayerCommands(float dt);
void advancePlayerMissiles(float dt);
// try to spawn a ship for this player.
// return true on success, false on failure to find a spot.
bool spawnShipForPlayer(Player *player);
// find some place where nothing is placed nearby (ships/planets).
// usefule for spanwing things
bool findFreePositionWithRadius(float r, glm::vec2 &pos);
float m_maxMissileDistance;
float m_playerRespawnTime;
float m_shipRadius;
float m_defaultEnergy;
};
};