Browse files

Ressurecting CPU Ichor

  • Loading branch information...
1 parent 91ee3b0 commit e25ba6ba70cacf2661659a82f177ba4c09aa48b1 namaste committed May 28, 2008
View
170 soylent/ichor/DuelMode.cc
@@ -0,0 +1,170 @@
+#include "GameMode.h"
+#include "Player.h"
+#include "Explosion.h"
+#include "Particle.h"
+#include "util.h"
+#include "Input.h"
+
+class DuelMode : public GameMode
+{
+private:
+ Player ppos_;
+ Player pneg_;
+
+ float invincible_timer_;
+ FluidDensityGrid* field_;
+
+ Explosion* explosion_;
+
+ std::list<Particle> particles_;
+ float particles_pending_;
+
+ Input* posinput_;
+ Input* neginput_;
+
+ void check_hit() {
+ invincible_timer_ -= DT;
+ if ((ppos_.get_life() <= 0 || pneg_.get_life() <= 0) && invincible_timer_ < INVINCIBLE_TIME - 1) {
+ QUIT = true;
+ }
+ if (invincible_timer_ < 0) {
+ delete explosion_; explosion_ = 0;
+ invincible_timer_ = 0;
+ }
+
+ if (invincible_timer_ == 0) {
+ if (ppos_.check_hit(field_)) {
+ ppos_.die();
+ explosion_ = new InwardVortexExplosion(ppos_.get_color(), ppos_.position(), DEATH_FLUID, INVINCIBLE_TIME);
+ invincible_timer_ = INVINCIBLE_TIME;
+ }
+ if (pneg_.check_hit(field_)) {
+ pneg_.die();
+ explosion_ = new InwardVortexExplosion(pneg_.get_color(), pneg_.position(), -DEATH_FLUID, INVINCIBLE_TIME);
+ invincible_timer_ = INVINCIBLE_TIME;
+ }
+ }
+ }
+
+ void frame_input() {
+ posinput_->step();
+ neginput_->step();
+ ppos_.set_blow(posinput_->get_direction());
+ pneg_.set_blow(neginput_->get_direction());
+ }
+
+ void clear_field() {
+ delete field_;
+ field_ = new FluidDensityGrid(W,H,vec(0,0), vec(W,H), DIFFUSION, VISCOSITY);
+ }
+
+ void reset() {
+ clear_field();
+ ppos_ = Player(Color(1, 0.5, 0), 1, vec(CLAMPW, H-CLAMPH-1), ppos_.get_life(), load_texture(ICHOR_DATADIR "/redfirefly.png"));
+ pneg_ = Player(Color(0, 0.5, 1), -1, vec(W-CLAMPW-1, CLAMPH), pneg_.get_life(), load_texture(ICHOR_DATADIR "/bluefirefly.png"));
+
+ invincible_timer_ = INVINCIBLE_TIME;
+ delete explosion_;
+ explosion_ = 0;
+ }
+
+ void check_unstable() {
+ float density = field_->get_density_direct(W/2,H/2);
+ if (!(density > -10 && density < 10)) {
+ reset();
+ }
+ }
+
+ void check_particles() {
+ for (std::list<Particle>::iterator i = particles_.begin(); i != particles_.end();) {
+ i->step(field_);
+
+ bool eaten = false;
+ if ((i->pos - ppos_.position()).norm2() < EATDIST*EATDIST) {
+ ppos_.store(EATENERGY);
+ eaten = true;
+ }
+ if ((i->pos - pneg_.position()).norm2() < EATDIST*EATDIST) {
+ pneg_.store(EATENERGY);
+ eaten = true;
+ }
+ if (i->pos.x < CLAMPW || i->pos.x > W-CLAMPW || i->pos.y < CLAMPH || i->pos.y > H-CLAMPH) {
+ eaten = true;
+ }
+
+ if (!eaten) {
+ ++i;
+ }
+ else {
+ std::list<Particle>::iterator iprime = i;
+ ++iprime;
+ particles_.erase(i);
+ i = iprime;
+ }
+ }
+
+ particles_pending_ += PARTICLE_RATE * DT;
+ while (particles_pending_ > 1) {
+ particles_pending_ -= 1;
+ vec pos(randrange(2,W-3), randrange(2, H-3));
+ field_->set_density(pos, field_->get_density(pos) / 1.2);
+ particles_.push_back(Particle(vec(randrange(2,W-3), randrange(2,H-3))));
+ }
+ }
+
+public:
+ DuelMode(Input* posinput, Input* neginput)
+ : ppos_(Color(1, 0.5, 0), 1, vec(CLAMPW, H-CLAMPH-1), 5, load_texture(ICHOR_DATADIR "/redfirefly.png")),
+ pneg_(Color(0, 0.5, 1), -1,vec(W-CLAMPW-1, CLAMPH), 5, load_texture(ICHOR_DATADIR "/bluefirefly.png")),
+ invincible_timer_(INVINCIBLE_TIME),
+ field_(0), explosion_(0), particles_pending_(0),
+ posinput_(posinput), neginput_(neginput)
+ {
+ clear_field();
+ }
+
+ ~DuelMode() {
+ delete posinput_;
+ delete neginput_;
+ delete field_;
+ delete explosion_;
+ }
+
+ void step() {
+ check_unstable();
+ frame_input();
+
+ check_hit();
+
+ if (explosion_) explosion_->step(field_);
+
+ field_->step_velocity();
+ field_->step_density();
+
+ ppos_.step(field_);
+ pneg_.step(field_);
+
+ check_particles();
+ }
+
+ void draw() const {
+ draw_board(field_, W, H);
+
+ ppos_.draw();
+ pneg_.draw();
+ ppos_.draw_lives(vec(3,H-3), 1);
+ pneg_.draw_lives(vec(W-4,H-3), -1);
+
+ draw_particles(particles_);
+
+ if (explosion_) explosion_->draw();
+ }
+
+ bool events(SDL_Event* e) {
+ return posinput_->events(e) || neginput_->events(e);
+ }
+};
+
+GameMode* make_DuelMode(Input* pos, Input* neg) {
+ return new DuelMode(pos, neg);
+}
View
132 soylent/ichor/Explosion.h
@@ -0,0 +1,132 @@
+#ifndef __EXPLOSION_H__
+#define __EXPLOSION_H__
+
+#include <iostream>
+#include "FluidGrid.h"
+#include "color.h"
+
+class Explosion {
+public:
+ virtual ~Explosion() { }
+
+ virtual void draw() const = 0;
+ virtual void step(FluidDensityGrid* field) = 0;
+ virtual bool done() const = 0;
+};
+
+class InwardVortexExplosion : public Explosion
+{
+public:
+ InwardVortexExplosion(Color color, vec pos, float power, float time, float radius = W)
+ : color_(color), pos_(pos), power_(power), timer_(0), time_(time), radius_(radius)
+ { }
+
+ void step(FluidDensityGrid* field) {
+ timer_ += DT;
+ float radius = radius_ * timer_ / time_;
+ float push = 50 * (radius > 5 ? 5 : radius);
+ for (int i = 0; i < 40; i++) {
+ float theta = 2 * M_PI * i / 40;
+ vec p = radius*vec(cos(theta), sin(theta));
+ vec splode = pos_ + p;
+ if (splode.x > CLAMPW && splode.x < W-CLAMPW-1 && splode.y > CLAMPH & splode.y < H-CLAMPH-1) {
+ field->add_velocity(splode, -push * ~(vec(-p.y,p.x)+p));
+ field->add_density(splode, power_);
+ }
+ }
+ }
+
+ bool done() const {
+ return timer_ >= time_;
+ }
+
+ void draw() const {
+ glPushMatrix();
+ glTranslatef(pos_.x, pos_.y, 0);
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ float r1 = radius_ * timer_ / time_;
+ float r0 = r1 - 30;
+ float r2 = r1 + 1;
+ if (r0 < 0) r0 = 0;
+
+ glBindTexture(GL_TEXTURE_2D, 0);
+ glBegin(GL_QUADS);
+ for (int i = 0; i < 60; i++) {
+ float theta0 = 2 * M_PI * i / 60;
+ float theta1 = 2 * M_PI * (i+1) / 60;
+ color_.set_color_a(0);
+ glVertex2f(r0 * cos(theta0), r0 * sin(theta0));
+ glVertex2f(r0 * cos(theta1), r0 * sin(theta1));
+ color_.set_color_a(1 - timer_ / time_);
+ glVertex2f(r1 * cos(theta1), r1 * sin(theta1));
+ glVertex2f(r1 * cos(theta0), r1 * sin(theta0));
+ color_.set_color_a(1 - timer_ / time_);
+ glVertex2f(r1 * cos(theta0), r1 * sin(theta0));
+ glVertex2f(r1 * cos(theta1), r1 * sin(theta1));
+ color_.set_color_a(0);
+ glVertex2f(r2 * cos(theta1), r2 * sin(theta1));
+ glVertex2f(r2 * cos(theta0), r2 * sin(theta0));
+ }
+ glEnd();
+ glDisable(GL_BLEND);
+ glPopMatrix();
+ }
+private:
+ const vec pos_;
+ const float power_;
+ float timer_;
+ const float time_;
+ const float radius_;
+ const Color color_;
+};
+
+class DensityGlobExplosion : public Explosion
+{
+public:
+ DensityGlobExplosion(vec pos, float time, float power, float radius, int steps = 1)
+ : pos_(pos), time_(time), power_(power), radius_(radius), steps_(steps), timer_(0)
+ { }
+
+ void step(FluidDensityGrid* field) {
+ for (int i = 0; i < steps_; i++) {
+ timer_ += DT / steps_;
+ float radius = radius_ * timer_ / time_;
+ put_circle(field, radius);
+ if (radius / radius_ > 0.5) {
+ put_circle(field, 1-radius);
+ }
+ if (0.7 < radius / radius_ && radius / radius_ < 1) {
+ vec offs(randrange(-radius, radius), randrange(-radius, radius));
+ field->add_density(pos_ + offs, power_);
+ }
+ }
+ }
+
+ void draw() const { }
+
+ bool done() const {
+ return timer_ >= time_;
+ }
+private:
+
+ void put_circle(DensityGrid* field, float radius) {
+ for (int i = 0; i < 80; i++) {
+ float theta = 2 * M_PI * i / 80;
+ vec p = radius*vec(cos(theta), sin(theta));
+ vec splode = pos_ + p;
+ if (splode.x > CLAMPW && splode.x < W-CLAMPW-1 && splode.y > CLAMPH & splode.y < H-CLAMPH-1) {
+ field->add_density(splode, power_ / (80 * time_ * steps_));
+ }
+ }
+ }
+
+ const vec pos_;
+ const float time_;
+ const float power_;
+ const float radius_;
+ const int steps_;
+ float timer_;
+};
+
+#endif
View
380 soylent/ichor/FluidGrid.h
@@ -0,0 +1,380 @@
+#ifndef __FLUIDGRID_H__
+#define __FLUIDGRID_H__
+
+#include "vec.h"
+#include "util.h"
+#include <cstdlib>
+#include <cmath>
+
+inline float clamp(float x, float lo, float hi) {
+ if (x <= lo) return lo;
+ if (x >= hi) return hi;
+ return x;
+}
+
+const int DIFFUSE_RELAXATION = 1;
+const int PROJECT_RELAXATION = 15;
+
+class FluidUtils {
+protected:
+ typedef float** Scr;
+
+ FluidUtils(int w, int h) : W(w), H(h) { }
+ const int W, H;
+
+ Scr make_scr() const {
+ float* chunk = new float[W*H];
+ float** addrs = new float*[W];
+ for (int i = 0; i < W; i++) {
+ addrs[i] = &chunk[i*H];
+ }
+ return addrs;
+ }
+
+ void delete_scr(Scr s) const {
+ delete[] s[0];
+ delete[] s;
+ }
+
+public:
+
+ enum BoundComputation { NONE, UNORM, VNORM };
+
+# define INBOUND_LOOP(i,j) for (int i = 1; i < W-1; i++) for (int j = 1; j < H-1; j++)
+
+ void clear(Scr x) {
+ for (int i = 0; i < W; i++) {
+ for (int j = 0; j < H; j++) {
+ x[i][j] = 0;
+ }
+ }
+ }
+
+ void set_boundary(BoundComputation comp, Scr x) {
+ //return;//XXX
+ for (int i = 1; i < W-1; i++) {
+ // Bounded
+ x[i][0] = comp == VNORM ? -x[i][1] : x[i][1];
+ x[i][H-1] = comp == VNORM ? -x[i][H-2] : x[i][H-2];
+
+ // Toroidal
+ /* float avg = (x[i][1] + x[i][H-2])/2;
+ x[i][0] = x[i][1] = avg;
+ x[i][H-1] = x[i][H-2] = avg; */
+ }
+ for (int j = 1; j < H-1; j++) {
+ // Bounded
+ x[0][j] = comp == UNORM ? -x[1][j] : x[1][j];
+ x[W-1][j] = comp == UNORM ? -x[W-2][j] : x[W-2][j];
+
+ // Toroidal
+ /* float avg = (x[1][j] + x[W-2][j])/2;
+ x[0][j] = x[1][j] = avg;
+ x[W-1][j] = x[W-2][j] = avg; */
+ }
+ x[0][0] = (x[1][0] + x[0][1]) * 0.5;
+ x[W-1][0] = (x[W-2][0] + x[W-1][1]) * 0.5;
+ x[0][H-1] = (x[1][H-1] + x[0][H-2]) * 0.5;
+ x[W-1][H-1] = (x[W-2][H-1] + x[W-1][H-2]) * 0.5;
+ }
+
+ void diffuse(BoundComputation comp, Scr x, Scr x0, float diffusion) {
+ float da = DT * diffusion * (W-2)*(H-2);
+
+ if (diffusion == 0) {
+ INBOUND_LOOP(i,j) {
+ x[i][j] = x0[i][j];
+ }
+ return; // Skip the expensive relaxation procedure
+ }
+
+ float mult = 1 / (1+4*da);
+ for (int k = 0; k < DIFFUSE_RELAXATION; k++) {
+ INBOUND_LOOP(i,j) {
+ x[i][j] = (x0[i][j] + da * (x[i-1][j] + x[i+1][j]
+ + x[i][j-1] + x[i][j+1])) * mult;
+ }
+ set_boundary(comp, x);
+ }
+ }
+
+ void advect(BoundComputation comp, Scr d, Scr d0, Scr u, Scr v) {
+ INBOUND_LOOP(i,j) {
+ float x = i - DT*W*u[i][j];
+ float y = j - DT*H*v[i][j];
+ int i0 = int(clamp(x, 0.5, W-1.5));
+ int i1 = i0+1;
+ int j0 = int(clamp(y, 0.5, H-1.5));
+ int j1 = j0+1;
+ float s1 = x - i0; float s0 = 1 - s1;
+ float t1 = y - j0; float t0 = 1 - t1;
+ d[i][j] = s0*(t0*d0[i0][j0] + t1*d0[i0][j1])
+ + s1*(t0*d0[i1][j0] + t1*d0[i1][j1]);
+ }
+ set_boundary(comp, d);
+ }
+
+ void project(Scr u, Scr v, Scr p, Scr div) {
+ float hx = 1.0/W;
+ float hy = 1.0/H;
+
+ INBOUND_LOOP(i,j) {
+ div[i][j] = -0.5 *( hx * (u[i+1][j] - u[i-1][j])
+ + hy * (v[i][j+1] - v[i][j-1]));
+ p[i][j] = 0;
+ }
+ set_boundary(NONE, div);
+ set_boundary(NONE, p);
+
+ for (int k = 0; k < PROJECT_RELAXATION; k++) {
+ INBOUND_LOOP(i,j) {
+ p[i][j] = (div[i][j] + p[i-1][j] + p[i+1][j]
+ + p[i][j-1] + p[i][j+1]) * 0.25;
+ }
+ set_boundary(NONE, p);
+ }
+
+ INBOUND_LOOP(i,j) {
+ u[i][j] -= 0.5 * (p[i+1][j] - p[i-1][j]) * W;
+ v[i][j] -= 0.5 * (p[i][j+1] - p[i][j-1]) * H;
+ }
+ set_boundary(UNORM, u);
+ set_boundary(VNORM, v);
+ }
+
+# undef INBOUND_LOOP
+};
+
+class FluidGrid : private FluidUtils {
+public:
+ typedef FluidUtils::Scr Scr;
+
+ FluidGrid(int w, int h, vec ll, vec ur, float viscosity)
+ : FluidUtils(w,h), ll_(ll), ur_(ur), viscosity_(viscosity),
+ U_(make_scr()), V_(make_scr()), U_back_(make_scr()), V_back_(make_scr())
+ {
+ clear(U_);
+ clear(V_);
+ clear(U_back_);
+ clear(V_back_);
+ }
+
+ ~FluidGrid() {
+ delete_scr(U_);
+ delete_scr(V_);
+ delete_scr(U_back_);
+ delete_scr(V_back_);
+ }
+
+ bool in_range(vec v) const {
+ return ll_.x <= v.x && v.x < ur_.x
+ && ll_.y <= v.y && v.y < ur_.y;
+ }
+
+ void add_velocity(vec x, vec v) {
+ const int xx = ix_x(x.x), yy = ix_y(x.y);
+ U_[xx][yy] += DT * v.x;
+ V_[xx][yy] += DT * v.y;
+ }
+
+ vec get_velocity(vec x) const {
+ const int xx = ix_x(x.x), yy = ix_y(x.y);
+ return vec(U_[xx][yy], V_[xx][yy]);
+ }
+
+ void step_velocity() {
+ internal_step(U_, V_, U_back_, V_back_);
+ }
+
+ vec get_velocity_direct(int x, int y) const {
+ return vec(U_[x][y],V_[x][y]);
+ }
+
+ void set_velocity_direct(int x, int y, vec amt) {
+ U_[x][y] = amt.x;
+ V_[x][y] = amt.y;
+ }
+
+protected:
+ void internal_step(Scr u, Scr v, Scr u0, Scr v0) {
+ diffuse(FluidUtils::UNORM, u0, u, viscosity_);
+ diffuse(FluidUtils::VNORM, v0, v, viscosity_);
+ project(u0, v0, u, v);
+ advect(FluidUtils::UNORM, u, u0, u0, v0);
+ advect(FluidUtils::VNORM, v, v0, u0, v0);
+ project(u, v, u0, v0);
+ }
+
+ bool in_range_i(int x, int y) const {
+ return 0 <= x && x < W && 0 <= y && y < H;
+ }
+
+ int ix_x(float x) const {
+ int ret = int((x - ll_.x) / (ur_.x - ll_.x) * W);
+ if (ret < 0) return 0;
+ if (ret > W-1) return W-1;
+ return ret;
+ }
+
+ int ix_y(float y) const {
+ int ret = int((y - ll_.y) / (ur_.y - ll_.y) * H);
+ if (ret < 0) return 0;
+ if (ret > H-1) return H-1;
+ return ret;
+ }
+
+ void erase() {
+ clear(U_);
+ clear(V_);
+ clear(U_back_);
+ clear(V_back_);
+ }
+
+ const vec ll_, ur_;
+ float viscosity_;
+
+ Scr U_, V_; // x- and y-components of velocity
+ Scr U_back_, V_back_;
+};
+
+
+class DensityGrid : private FluidUtils {
+public:
+ typedef FluidUtils::Scr Scr;
+
+ DensityGrid(int w, int h, vec ll, vec ur, float diffusion)
+ : FluidUtils(w,h), ll_(ll), ur_(ur), diffusion_(diffusion),
+ total_density_(0), D_(make_scr()), D_back_(make_scr())
+ {
+ clear(D_);
+ clear(D_back_);
+ }
+
+ ~DensityGrid() {
+ delete_scr(D_);
+ delete_scr(D_back_);
+ }
+
+ bool in_range(vec v) const {
+ return ll_.x <= v.x && v.x < ur_.x
+ && ll_.y <= v.y && v.y < ur_.y;
+ }
+
+ void add_density(vec x, float amt) {
+ const int xx = ix_x(x.x), yy = ix_y(x.y);
+ total_density_ += DT * amt;
+ D_[xx][yy] += DT * amt;
+ }
+
+ void set_density(vec x, float amt) {
+ const int xx = ix_x(x.x), yy = ix_y(x.y);
+ total_density_ += amt - D_[xx][yy];
+ D_[xx][yy] = amt;
+ }
+
+ float get_density(vec x) const {
+ const int xx = ix_x(x.x), yy = ix_y(x.y);
+ return D_[xx][yy];
+ }
+
+ void step_density(Scr u, Scr v) {
+ diffuse(FluidUtils::NONE, D_back_, D_, diffusion_);
+ advect(FluidUtils::NONE, D_, D_back_, u, v);
+
+ float postotal = 0;
+ float negtotal = 0;
+ for (int i = 0; i < W; i++) {
+ for (int j = 0; j < H; j++) {
+ if (D_[i][j] > 0) postotal += D_[i][j];
+ else negtotal += D_[i][j];
+ }
+ }
+ float posfact = (total_density_ + sqrt(total_density_*total_density_ - 4*postotal*negtotal)) / (2*postotal);
+ float negfact = 1/posfact;
+ if (is_a_number(negfact) && is_a_number(posfact)) {
+ for (int i = 1; i < W-1; i++) {
+ for (int j = 1; j < H-1; j++) {
+ if (D_[i][j] > 0) {
+ D_[i][j] *= posfact;
+ }
+ else {
+ D_[i][j] *= negfact;
+ }
+ }
+ }
+ }
+ }
+
+ float get_density_direct(int x, int y) const {
+ return D_[x][y];
+ }
+
+ void set_density_direct(int x, int y, float amt) {
+ total_density_ += amt - D_[x][y];
+ D_[x][y] = amt;
+ }
+
+ float get_balance() const {
+ return total_density_;
+ }
+
+ void alter_balance(float amt) {
+ total_density_ += amt;
+ }
+
+ void erase()
+ {
+ clear(D_);
+ clear(D_back_);
+ }
+
+protected:
+ int ix_x(float x) const {
+ int ret = int((x - ll_.x) / (ur_.x - ll_.x) * W);
+ if (ret < 0) return 0;
+ if (ret > W-1) return W-1;
+ return ret;
+ }
+
+ int ix_y(float y) const {
+ int ret = int((y - ll_.y) / (ur_.y - ll_.y) * H);
+ if (ret < 0) return 0;
+ if (ret > H-1) return H-1;
+ return ret;
+ }
+
+ const vec ll_, ur_;
+ float diffusion_;
+ float total_density_;
+
+ Scr D_;
+ Scr D_back_;
+};
+
+class FluidDensityGrid : public FluidGrid, public DensityGrid {
+public:
+ typedef FluidGrid::Scr Scr;
+
+ FluidDensityGrid(int w, int h, vec ll, vec ur, float diffusion, float viscosity)
+ : FluidGrid(w, h, ll, ur, viscosity), DensityGrid(w, h, ll, ur, diffusion)
+ { }
+
+ void step_density() {
+ DensityGrid::step_density(FluidGrid::U_, FluidGrid::V_);
+ }
+
+ void step_velocity() {
+ FluidGrid::step_velocity();
+ }
+
+ bool in_range(const vec& v) {
+ return FluidGrid::in_range(v);
+ }
+
+ void erase() {
+ FluidGrid::erase();
+ DensityGrid::erase();
+ }
+};
+
+#endif
View
995 soylent/ichor/GalagaMode.cc
@@ -0,0 +1,995 @@
+#include "GameMode.h"
+#include "Player.h"
+#include "Explosion.h"
+#include "Texture.h"
+#include "Input.h"
+#include <list>
+#include <sstream>
+#include <string>
+#include <cstdlib>
+#include <cstdio>
+#include "HighScoreSheet.h"
+
+class GalagaMode : public GameMode
+{
+private:
+ Player* player_;
+
+ FluidDensityGrid* field_;
+
+ class Joint;
+ class Enemy;
+ class Level;
+
+ std::list<Enemy*> enemies_;
+ std::list<Explosion*> explosions_;
+ std::list<Joint> joints_;
+
+ HighScoreSheet* scores_;
+
+ float invincible_time_;
+ int score_;
+ float level_delay_;
+ bool level_complete_;
+ bool dead_;
+ int combo_;
+ float combo_time_;
+
+ unsigned int seed_;
+ Input* input_;
+ bool record_;
+ std::string log_file_;
+ bool high_score_;
+
+ float level_display_time_;
+ int level_num_;
+ Level* level_;
+
+ void frame_input() {
+ input_->step();
+ player_->set_blow(input_->get_direction());
+ }
+
+ void draw_score() const {
+ std::stringstream ss;
+ ss << score_;
+ std::string text = ss.str();
+
+ glColor3f(1,1,1);
+ glRasterPos2f(W-21, H-3);
+ for (size_t i = 0; i < text.size(); i++) {
+ glutBitmapCharacter(GLUT_BITMAP_9_BY_15, text[i]);
+ }
+ }
+
+ void display_high_scores() const {
+ scores_->draw();
+ }
+
+ void load_high_scores() {
+ scores_ = new LocalHighScoreSheet;
+ if (record_) {
+#ifdef WIN32
+ HighScoreEntry entry(score_, getenv("USERNAME"), time(NULL), seed_);
+#else
+ HighScoreEntry entry(score_, getenv("USER"), time(NULL), seed_);
+#endif
+ if (entry < scores_->min_entry()) { // '<' means '>' here
+ scores_->insert(entry);
+ high_score_ = true;
+ }
+ }
+ }
+
+ int compute_score(int par_score, float par_time, float lifetime) {
+ return combo_ * par_score;
+ }
+
+ float spawn_position_weight(vec p) const {
+ float dens = field_->get_density(p);
+ float playerdist = (p - player_->position()).norm();
+ float enemydist = HUGE_VAL;
+ for (std::list<Enemy*>::const_iterator i = enemies_.begin(); i != enemies_.end(); ++i) {
+ float dist = ((*i)->position() - p).norm();
+ if (dist < enemydist) enemydist = dist;
+ }
+
+ float weight = 0;
+ // points for being bright in color
+ if (dens < 1/100.0) weight += 1 - 100*dens;
+ // not allowed to be within 50 units of the player
+ if (playerdist < 50) return 0;
+ // points for being far away from the player
+ weight += playerdist/100;
+ // points for being far away from any other enemies
+ if (enemydist < HUGE_VAL) weight += enemydist/200;
+ return weight;
+ }
+
+ vec generate_spawn_position() const {
+ vec best;
+ float sum = 0;
+ for (int x = CLAMPW; x < W - CLAMPW; x++) {
+ for (int y = CLAMPH; y < H - CLAMPH; y++) {
+ float weight = spawn_position_weight(vec(x,y));
+ sum += weight;
+ if (randrange(0, sum) < weight) {
+ best = vec(x,y);
+ }
+ }
+ }
+ return best;
+ }
+
+ void prune_joints(Enemy* enemy) {
+ for (std::list<Joint>::iterator i = joints_.begin(); i != joints_.end(); ) {
+ if (i->a == enemy || i->b == enemy) {
+ std::list<Joint>::iterator j = i;
+ ++j;
+ joints_.erase(i);
+ i = j;
+ }
+ else {
+ ++i;
+ }
+ }
+ }
+
+ bool joint_exists(Enemy* a, Enemy* b) const {
+ for (std::list<Joint>::const_iterator i = joints_.begin(); i != joints_.end(); ++i) {
+ if (i->a == a && i->b == b || i->a == b && i->b == a) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ void init() {
+ player_ = new Player(Color(1, 0.5, 0), 1, vec(W/2, H/2), 1, load_texture(ICHOR_DATADIR "/redfirefly.png"));
+ field_ = new FluidDensityGrid(W, H, vec(0,0), vec(W,H), DIFFUSION, VISCOSITY);
+ scores_ = NULL;
+ invincible_time_ = 0;
+ score_ = 0;
+ level_delay_ = 0;
+ level_complete_ = false;
+ dead_ = false;
+ combo_ = 1;
+ combo_time_ = 0;
+ high_score_ = false;
+ level_num_ = 1;
+ level_ = make_level(level_num_);
+ level_display_time_ = 3;
+ explosions_.push_back(new DensityGlobExplosion(vec(W/2,H/2), 0.2, 2.5, 15, 5));
+ init_color();
+ }
+
+ void draw_level() const {
+ if (level_) {
+ glColor3f(1,1,1);
+ glRasterPos2f(W/2-10, H/3);
+ draw_string(level_->title());
+ }
+ }
+
+ void spawn_animation(vec pos) {
+ float balance = field_->get_balance();
+ float power = -2.5 - 2.5*balance/100;
+ if (power > 0) power = 0;
+ explosions_.push_back(new DensityGlobExplosion(pos, 0.2, power, 15, 5));
+ }
+
+ template <class E>
+ void spawn_enemy(float diff = 1) {
+ vec pos = generate_spawn_position();
+ spawn_animation(pos);
+ enemies_.push_back(new E(pos, 0.3, diff));
+ }
+
+ void maybe_advance_level() {
+ if (!level_complete_) {
+ if (level_ && level_->advance()) {
+ level_complete_ = true;
+ level_delay_ = 0.5;
+ }
+ }
+ else if (level_delay_ <= 0) {
+ delete level_;
+ level_num_++;
+ level_ = make_level(level_num_);
+ if (level_ == NULL) {
+ load_high_scores();
+ }
+ level_complete_ = false;
+ }
+ }
+
+public:
+ GalagaMode(Input* input, unsigned int seed)
+ : seed_(seed), input_(input), record_(true)
+ {
+ srand(seed_);
+ log_file_ = HighScoreEntry(0, "nobody", 0, seed_).replay_name();
+ input_ = new WriterInput(seed_, log_file_, input_);
+ init();
+ }
+
+ GalagaMode(ReaderInput* input)
+ : input_(input), record_(false)
+ {
+ seed_ = input->get_seed();
+ srand(seed_);
+ init();
+ }
+
+ ~GalagaMode() {
+ delete player_;
+ delete input_;
+ if (record_ && !high_score_) {
+ remove(log_file_.c_str());
+ }
+ delete field_;
+ delete scores_;
+ for (std::list<Enemy*>::iterator i = enemies_.begin(); i != enemies_.end(); ++i) {
+ delete *i;
+ }
+ for (std::list<Explosion*>::iterator i = explosions_.begin(); i != explosions_.end(); ++i) {
+ delete *i;
+ }
+ }
+
+ void step() {
+ frame_input();
+
+ field_->step_velocity();
+ field_->step_density();
+
+ if (!dead_) player_->step(field_);
+
+ invincible_time_ -= DT;
+ level_display_time_ -= DT;
+ if (!dead_ && invincible_time_ <= 0 && field_->get_density(player_->position()) < -DEATH_DENSITY) {
+ player_->die();
+ if (player_->get_life() <= 0) {
+ dead_ = true;
+ }
+ invincible_time_ = 3;
+ explosions_.push_back(new InwardVortexExplosion(player_->get_color(), player_->position(), 1, 3, W));
+ }
+ if (invincible_time_ <= 2 && dead_ && !scores_) {
+ load_high_scores();
+ }
+
+ combo_time_ -= DT;
+ for (std::list<Enemy*>::iterator i = enemies_.begin(); i != enemies_.end();) {
+ if ((*i)->dead(this)) {
+ score_ += compute_score((*i)->par_score(), (*i)->par_time(), (*i)->lifetime());
+ if (combo_time_ > 0) { combo_++; }
+ else { combo_ = 1; }
+ combo_time_ = 1.5;
+ if ((*i)->should_explode()) {
+ if (combo_ < 4) {
+ explosions_.push_back(new DensityGlobExplosion((*i)->position(), sqrt(float(combo_))*0.2, combo_*5, sqrt(float(combo_))*15, combo_*5));
+ }
+ else {
+ explosions_.push_back(new InwardVortexExplosion(Color(1, 1, 1), (*i)->position(), 2, 0.833, 60));
+ }
+ }
+ prune_joints(*i);
+ delete *i;
+ std::list<Enemy*>::iterator j = i;
+ ++j;
+ enemies_.erase(i);
+ i = j;
+ continue;
+ }
+
+ for (std::list<Enemy*>::iterator j = enemies_.begin(); j != enemies_.end(); ++j) {
+ if (i == j) continue;
+ if (((*i)->position() - (*j)->position()).norm() < 8) {
+ if (!joint_exists(*i, *j)) {
+ Joint joint(*i, *j);
+ if ((*i)->join(*j, &joint, this) && (*j)->join(*i, &joint, this)) {
+ joints_.push_back(joint);
+ }
+ }
+ }
+ }
+
+ (*i)->step(this);
+ ++i;
+ }
+
+ for (std::list<Joint>::iterator i = joints_.begin(); i != joints_.end(); ++i) {
+ i->step();
+ }
+
+ for (std::list<Explosion*>::iterator i = explosions_.begin(); i != explosions_.end();) {
+ if ((*i)->done()) {
+ delete *i;
+ std::list<Explosion*>::iterator j = i;
+ ++j;
+ explosions_.erase(i);
+ i = j;
+ }
+ else {
+ (*i)->step(field_);
+ ++i;
+ }
+ }
+
+ level_delay_ -= DT;
+ if (!dead_ && level_) {
+ level_->step();
+ }
+ maybe_advance_level();
+ }
+
+ void draw() const {
+ draw_board(field_, W, H);
+ for (std::list<Enemy*>::const_iterator i = enemies_.begin(); i != enemies_.end(); ++i) {
+ (*i)->draw(this);
+ }
+ for (std::list<Explosion*>::const_iterator i = explosions_.begin(); i != explosions_.end(); ++i) {
+ (*i)->draw();
+ }
+ if (!dead_) {
+ player_->draw();
+ player_->draw_lives(vec(3,H-3), 1);
+ }
+ draw_score();
+ if (scores_) {
+ display_high_scores();
+ }
+ if (level_display_time_ > 0) {
+ draw_level();
+ }
+ }
+
+ bool events(SDL_Event* e) {
+ if (scores_) {
+ if (e->type == SDL_KEYDOWN || e->type == SDL_MOUSEBUTTONDOWN || e->type == SDL_JOYBUTTONDOWN) {
+ QUIT = true;
+ return true;
+ }
+ }
+ return input_->events(e);
+ }
+
+private:
+ class Level {
+ public:
+ virtual void step() = 0;
+ virtual bool advance() const = 0;
+ virtual ~Level() {}
+ virtual std::string title() const {return "";}
+ };
+
+ class Wave: public Level {
+ public:
+ Wave(GalagaMode* mode): spawned_(false), mode_(mode) { }
+ void step() {
+ if (!spawned_) {
+ spawn();
+ spawned_ = true;
+ }
+ }
+
+ bool advance() const {
+ return mode_->enemies_.size() == 0;
+ }
+
+ virtual void spawn() = 0;
+
+ protected:
+ bool spawned_;
+ GalagaMode* mode_;
+ };
+
+ class RandomEnemies: public Level {
+ public:
+ RandomEnemies(GalagaMode* mode, int enemies_left, float spawn_rate):
+ mode_(mode), enemies_left_(enemies_left), spawn_rate_(spawn_rate), time_left_(0) { }
+
+ bool advance() const {
+ return enemies_left_ <= 0 && mode_->enemies_.size() == 0;
+ }
+
+ void step() {
+ time_left_ -= DT;
+ if (enemies_left_ > 0 && (time_left_ <= 0 || mode_->enemies_.size() == 0)) {
+ spawn();
+ enemies_left_--;
+ time_left_ = spawn_rate_;
+ }
+ }
+
+ virtual void spawn() = 0;
+
+ protected:
+ int enemies_left_;
+ float spawn_rate_;
+ float time_left_;
+ GalagaMode* mode_;
+ };
+
+#define MAKE_RANDOM_SERIES(NAME, ENEMIES, RATE, CODE) \
+ class NAME : public RandomEnemies { \
+ public: \
+ NAME(GalagaMode* mode) : RandomEnemies(mode, ENEMIES, RATE) { } \
+ void spawn() CODE \
+ };
+
+#define MAKE_WAVE(NAME, CODE) \
+ class NAME : public Wave { \
+ public: \
+ NAME(GalagaMode* mode) : Wave(mode) { } \
+ void spawn() CODE \
+ };
+
+ MAKE_WAVE(Level1, {
+ mode_->spawn_enemy<BasicEnemy>();
+ });
+
+ MAKE_RANDOM_SERIES(Level2, 6, 3.5, {
+ mode_->spawn_enemy<BasicEnemy>();
+ });
+
+ MAKE_WAVE(Level3, {
+ mode_->spawn_enemy<SpinEnemy>();
+ });
+
+ MAKE_WAVE(Level4, {
+ mode_->spawn_enemy<MissileEnemy>();
+ });
+
+ MAKE_RANDOM_SERIES(Level5, 15, 3, {
+ switch (rand()% 3) {
+ case 0:
+ mode_->spawn_enemy<BasicEnemy>();
+ break;
+ case 1:
+ mode_->spawn_enemy<MissileEnemy>();
+ break;
+ case 2:
+ mode_->spawn_enemy<SpinEnemy>();
+ break;
+ }
+ });
+
+ MAKE_WAVE(Level6, {
+ mode_->spawn_enemy<BasicEnemy>();
+ mode_->spawn_PairBomb_pair(mode_->generate_spawn_position());
+ });
+
+ MAKE_WAVE(Level7, {
+ mode_->spawn_enemy<ScorpionEnemy>();
+ mode_->spawn_enemy<MissileEnemy>();
+ });
+
+ MAKE_WAVE(Level8, {
+ mode_->spawn_enemy<VortexEnemy>();
+ });
+
+ MAKE_WAVE(Level9, {
+ mode_->spawn_enemy<BasicEnemy>();
+ mode_->spawn_enemy<BasicEnemy>();
+ mode_->spawn_enemy<BasicEnemy>();
+ mode_->spawn_enemy<BasicEnemy>();
+ mode_->spawn_enemy<BasicEnemy>();
+ });
+
+ MAKE_WAVE(Level10, {
+ mode_->spawn_enemy<MissileEnemy>();
+ mode_->spawn_enemy<MissileEnemy>();
+ mode_->spawn_enemy<MissileEnemy>();
+ mode_->spawn_enemy<SpinEnemy>();
+ });
+
+ MAKE_RANDOM_SERIES(Level11, 100, 3, {
+ if (enemies_left_ % 20 == 0) {
+ mode_->spawn_enemy<VortexEnemy>();
+ return;
+ }
+ switch (rand()% 5) {
+ case 0:
+ mode_->spawn_enemy<BasicEnemy>();
+ break;
+ case 1:
+ mode_->spawn_enemy<MissileEnemy>();
+ break;
+ case 2:
+ mode_->spawn_enemy<SpinEnemy>();
+ break;
+ case 3:
+ mode_->spawn_PairBomb_pair(mode_->generate_spawn_position());
+ break;
+ case 4:
+ mode_->spawn_enemy<ScorpionEnemy>();
+ break;
+ }
+ });
+
+ Level* make_level(int num) {
+ switch (num) {
+ case 1: return new Level1(this);
+ case 2: return new Level2(this);
+ case 3: return new Level3(this);
+ case 4: return new Level4(this);
+ case 5: return new Level5(this);
+ case 6: return new Level6(this);
+ case 7: return new Level7(this);
+ case 8: return new Level8(this);
+ case 9: return new Level9(this);
+ case 10: return new Level10(this);
+ case 11: return new Level11(this);
+
+ default: return 0;
+ }
+ }
+
+ class Enemy {
+ public:
+ virtual ~Enemy() { }
+
+ virtual void draw(const GalagaMode* mode) = 0;
+ virtual void step(GalagaMode* mode) = 0;
+ virtual vec position() const = 0;
+ virtual void set_position(vec pos) = 0;
+ virtual bool vulnerable() const { return true; }
+ virtual bool dead(const GalagaMode* mode) const {
+ return vulnerable() && mode->field_->get_density(position()) > DEATH_DENSITY;
+ }
+ virtual bool should_explode() const { return true; }
+ virtual int par_score() const { return 10; }
+ virtual float par_time() const { return 5; }
+ virtual float lifetime() const = 0;
+ // returns whether to make the joint
+ virtual bool join(Enemy* enemy, Joint* joint, GalagaMode* mode) { return true; }
+ };
+
+ class Joint
+ {
+ public:
+ Joint(Enemy* a, Enemy* b)
+ : a(a), b(b),
+ length((b->position() - a->position()).norm())
+ { }
+
+ void step() {
+ vec center = (a->position() + b->position())/2;
+ a->set_position(center + length/2 * ~(a->position() - center));
+ b->set_position(center + length/2 * ~(b->position() - center));
+ }
+
+ Enemy* a;
+ Enemy* b;
+ float length;
+ };
+
+ class BasicEnemy : public Enemy
+ {
+ public:
+ BasicEnemy(vec pos, float wait, float difficulty)
+ : pos_(pos), wait_(wait), tex_(load_texture(ICHOR_DATADIR "/metalball.png")),
+ lifetime_(0), difficulty_(difficulty)
+ { }
+
+ void draw(const GalagaMode* mode) {
+ if (wait_ > 0) return;
+ glColor3f(1,1,1);
+ TextureBinding b = tex_->bind();
+ draw_rect(pos_ - vec(4,4), pos_ + vec(4,4));
+ }
+
+ void step(GalagaMode* mode) {
+ wait_ -= DT;
+ if (wait_ > 0) return;
+ lifetime_ += DT;
+ pos_.x = clamp(pos_.x, CLAMPW, W - CLAMPW - 1);
+ pos_.y = clamp(pos_.y, CLAMPH, H - CLAMPH - 1);
+ pos_ += mode->field_->get_velocity(pos_) * 70 * DT;
+ vec dir = ~(mode->player_->position() - pos_);
+ mode->field_->add_density(pos_, -difficulty_ * 0.6);
+ mode->field_->add_velocity(pos_ - 5*dir, 30*dir);
+ }
+
+ vec position() const { return pos_; }
+ void set_position(vec pos) { pos_ = pos; }
+
+ bool vulnerable() const { return wait_ <= 0; }
+ float lifetime() const { return lifetime_; }
+ int par_score() const { return 5; }
+
+ private:
+ vec pos_;
+ float wait_;
+ Texture* tex_;
+ float lifetime_;
+ float difficulty_;
+ };
+
+ class MissileEnemy : public Enemy
+ {
+ public:
+ MissileEnemy(vec pos, float wait, float difficulty)
+ : pos_(pos), wait_(wait), theta_(0),
+ tex_(load_texture(ICHOR_DATADIR "/rocketball.png")),
+ inited_(false), lifetime_(0)
+ { }
+
+ void draw(const GalagaMode* mode) {
+ if (wait_ > 0) return;
+ glPushMatrix();
+ glTranslatef(pos_.x, pos_.y, 0);
+ glRotatef(180 / M_PI * theta_, 0, 0, 1);
+ glColor3f(1,1,1);
+ TextureBinding b = tex_->bind();
+ draw_rect(-vec(4,4), vec(4,4));
+ glPopMatrix();
+ }
+
+ void step(GalagaMode* mode) {
+ wait_ -= DT;
+ if (wait_ > 0) return;
+ lifetime_ += DT;
+ pos_.x = clamp(pos_.x, CLAMPW, W - CLAMPW - 1);
+ pos_.y = clamp(pos_.y, CLAMPH, H - CLAMPH - 1);
+
+ vec playerdir = mode->player_->position() - pos_;
+ float target_theta = atan2(playerdir.y, playerdir.x);
+ if (!inited_) {
+ theta_ = target_theta;
+ inited_ = true;
+ }
+
+ if (fabs(theta_ + 2*M_PI - target_theta) < fabs(theta_ - target_theta)) {
+ theta_ += 2*M_PI;
+ }
+ else if (fabs(theta_ - 2*M_PI - target_theta) < fabs(theta_ - target_theta)) {
+ theta_ -= 2*M_PI;
+ }
+ if (fabs(theta_ - target_theta) > 1.75*DT) {
+ theta_ += 1.75*DT * (target_theta - theta_ < 0 ? -1 : 1);
+ }
+ else {
+ theta_ = target_theta;
+ }
+
+ vec dir(cos(theta_), sin(theta_));
+
+ mode->field_->add_density(pos_, -.6);
+ add_wide_velocity(mode->field_, pos_, 60*dir);
+ pos_ += mode->field_->get_velocity(pos_) * 30 * DT;
+ }
+
+ vec position() const { return pos_; }
+ void set_position(vec pos) { pos_ = pos; }
+ bool vulnerable() const { return wait_ <= 0; }
+ float lifetime() const { return lifetime_; }
+
+ private:
+ vec pos_;
+ float wait_;
+ float theta_;
+ Texture* tex_;
+ bool inited_;
+ float lifetime_;
+ };
+
+ class ScorpionEnemy : public Enemy
+ {
+ public:
+ ScorpionEnemy(vec pos, float wait, float difficulty)
+ : pos_(pos), wait_(wait), tex_(load_texture(ICHOR_DATADIR "/hornball.png")),
+ lifetime_(0)
+ { }
+
+ void draw(const GalagaMode* mode) {
+ if (wait_ > 0) return;
+ vec dir = ~(mode->player_->position() - pos_);
+ float angle = atan2(dir.y, dir.x)-M_PI/2;
+ glPushMatrix();
+ glTranslatef(pos_.x, pos_.y, 0);
+ glRotatef(180/M_PI*angle, 0, 0, 1);
+ glColor3f(1,1,1);
+ TextureBinding b = tex_->bind();
+ draw_rect(-vec(4,4), vec(4,4));
+ glPopMatrix();
+ }
+
+ void step(GalagaMode* mode) {
+ wait_ -= DT;
+ if (wait_ > 0) return;
+ lifetime_ += DT;
+ pos_.x = clamp(pos_.x, CLAMPW, W - CLAMPW - 1);
+ pos_.y = clamp(pos_.y, CLAMPH, H - CLAMPH - 1);
+ vec grad(
+ (mode->field_->get_density(pos_ + vec(10,0)) - mode->field_->get_density(pos_ + vec(-10,0))) / 2.0,
+ (mode->field_->get_density(pos_ + vec(0,10)) - mode->field_->get_density(pos_ + vec(0,-10))) / 2.0);
+ vec dir = ~(mode->player_->position() - pos_);
+ mode->field_->add_density(pos_ + 5*dir, -0.6);
+ mode->field_->add_velocity(pos_, 30*dir);
+ pos_ -= 5000 * grad * DT;
+ }
+
+ vec position() const { return pos_; }
+ void set_position(vec pos) { pos_ = pos; }
+ bool vulnerable() const { return wait_ <= 0; }
+ float lifetime() const { return lifetime_; }
+ int par_score() const { return 20; }
+ private:
+ vec pos_;
+ float wait_;
+ Texture* tex_;
+ float lifetime_;
+ };
+
+ class PairBomb : public Enemy
+ {
+ public:
+ PairBomb(vec pos, float wait, float difficulty)
+ : wait_(wait), pos_(pos), other_(NULL), detonated_(false),
+ lifetime_(0)
+ { }
+
+ ~PairBomb() {
+ if (other_) other_->other_ = NULL;
+ }
+
+ void set_other(PairBomb* other) {
+ other_ = other;
+ }
+
+ void draw(const GalagaMode* mode) {
+ if (wait_ > 0) return;
+ glColor3f(1,1,0.3);
+ glLineWidth(1.0);
+ glBegin(GL_LINES);
+ for (int i = 0; i < 16; i++) {
+ float theta = randrange(0, 2*M_PI);
+ glVertex2f(pos_.x + 3*cos(theta), pos_.y + 3*sin(theta));
+ }
+ glEnd();
+ }
+
+ bool join(Enemy* enemy, Joint* joint, GalagaMode* mode) {
+ if (enemy == other_) {
+ mode->explosions_.push_back(new InwardVortexExplosion(Color(1, 1, 1), pos_, -2, 0.833, 60));
+ detonated_ = true;
+ other_->other_ = NULL;
+ other_ = NULL;
+ return false;
+ }
+ else {
+ return true;
+ }
+ }
+
+ void step(GalagaMode* mode) {
+ wait_ -= DT;
+ if (wait_ > 0 || other_ == NULL) return;
+ lifetime_ += DT;
+ vec dir = ~(other_->pos_ - pos_);
+ add_wide_velocity(mode->field_, pos_, 15*dir);
+ pos_ += mode->field_->get_velocity(pos_) * 40 * DT;
+ }
+
+ vec position() const { return pos_; }
+ void set_position(vec pos) { pos_ = pos; }
+ bool vulnerable() const { return wait_ <= 0; }
+ bool dead(const GalagaMode* mode) const {
+ return detonated_ || other_ == NULL || Enemy::dead(mode);
+ }
+
+ bool should_explode() const {
+ // XXX this will keep the other from exploding when you kill one
+ return !detonated_ && other_ != NULL;
+ }
+ float lifetime() const { return lifetime_; }
+ int par_score() const { return should_explode() ? 5 : 0; }
+
+ private:
+ float wait_;
+ vec pos_;
+ PairBomb* other_;
+ bool detonated_;
+ float lifetime_;
+ };
+
+ void spawn_PairBomb_pair(vec apos, float difficulty = 1) {
+ vec bpos;
+ int safety = 0;
+ do {
+ if (safety++ > 50) break;
+ bpos = generate_spawn_position();
+ } while ((apos - bpos).norm() < 150);
+
+ if ((apos - bpos).norm() < 150) return;
+
+ PairBomb* a = new PairBomb(apos, 0.3, difficulty);
+ PairBomb* b = new PairBomb(bpos, 0.3, difficulty);
+ a->set_other(b);
+ b->set_other(a);
+
+ enemies_.push_back(a);
+ enemies_.push_back(b);
+ spawn_animation(apos);
+ spawn_animation(bpos);
+ }
+
+
+ class VortexEnemy : public Enemy
+ {
+ public:
+ VortexEnemy(vec pos, float wait, float difficulty)
+ : pos_(pos), wait_(wait), tex_(load_texture(ICHOR_DATADIR "/vortexball.png")),
+ lifetime_(0), total_density_(0), death_(false)
+ { }
+
+ ~VortexEnemy() {
+ if (mode_) {
+ mode_->field_->alter_balance(total_density_);
+ }
+ }
+
+ void draw(const GalagaMode* mode) {
+ if (wait_ > 0) return;
+ glColor3f(1,1,1);
+ TextureBinding b = tex_->bind();
+ draw_rect(pos_ - vec(4,4), pos_ + vec(4,4));
+ }
+
+ void step(GalagaMode* mode) {
+ wait_ -= DT;
+ if (wait_ > 0) return;
+ mode_ = mode;
+ lifetime_ += DT;
+ float radius = 10*(sin(lifetime_/3)+2);
+ float directionp = -cos((lifetime_ - DT)/3) / fabs(cos((lifetime_ - DT)/3));
+ float direction = -cos(lifetime_/3) / fabs(cos(lifetime_/3));
+ if (directionp >= 0 && direction < 0) {
+ death_ = true;
+ }
+ for (int i = 0; i < 96; i++) {
+ float theta = 2*M_PI*i/96;
+ vec dir = radius * vec(cos(theta), sin(theta));
+ vec src = pos_ + dir;
+ if (src.x < CLAMPW || src.x >= W-CLAMPW
+ || src.y < CLAMPH || src.y >= H-CLAMPH) {
+ continue;
+ }
+ if (direction > 0) { // expanding
+ total_density_ += mode->field_->get_density(src);
+ mode->field_->set_density(src, 0);
+ }
+ else { // contracting
+ total_density_ += 0.5 * DT;
+ mode->field_->add_density(src, -0.5);
+ }
+ mode->field_->add_velocity(src, 20*direction*~dir);
+ }
+ pos_ += mode->field_->get_velocity(pos_) * 70 * DT;
+ pos_.x = clamp(pos_.x, CLAMPW, W - CLAMPW - 1);
+ pos_.y = clamp(pos_.y, CLAMPH, H - CLAMPH - 1);
+ }
+
+ vec position() const { return pos_; }
+ void set_position(vec pos) { pos_ = pos; }
+
+ bool vulnerable() const { return wait_ <= 0; }
+ float lifetime() const { return lifetime_; }
+ int par_score() const { return 20; }
+ bool dead(const GalagaMode* mode) const { return death_ || Enemy::dead(mode); }
+
+ private:
+ vec pos_;
+ float wait_;
+ Texture* tex_;
+ float lifetime_;
+ float total_density_;
+ GalagaMode* mode_;
+ bool death_;
+ };
+
+ class SpinEnemy : public Enemy
+ {
+ public:
+ SpinEnemy(vec pos, float wait, float difficulty)
+ : pos_(pos), wait_(wait), angle_(0), lifetime_(0),
+ real_lifetime_(0), tex_(load_texture(ICHOR_DATADIR "/spinball.png"))
+ { }
+
+ void draw(const GalagaMode* mode) {
+ if (wait_ > 0) return;
+ glPushMatrix();
+ glTranslatef(pos_.x, pos_.y, 0);
+ glRotatef(180/M_PI*angle_, 0, 0, 1);
+ glColor3f(1,1,1);
+ TextureBinding b = tex_->bind();
+ draw_rect(-vec(4,4), vec(4,4));
+ glPopMatrix();
+ }
+
+ void step(GalagaMode* mode) {
+ wait_ -= DT;
+ if (wait_ > 0) return;
+ real_lifetime_ += DT;
+
+ if (pos_.x < CLAMPW) {
+ angle_ = 0;//right
+ }
+ if (pos_.x > W - CLAMPW - 1) {
+ angle_ = M_PI;//left
+ }
+ if (pos_.y < CLAMPH) {
+ angle_ = M_PI/2;//up
+ }
+ if(pos_.y > H - CLAMPH - 1) {
+ angle_ = 3*M_PI/2;//down
+ }
+ pos_.x = clamp(pos_.x, CLAMPW, W - CLAMPW - 1);
+ pos_.y = clamp(pos_.y, CLAMPH, H - CLAMPH - 1);
+
+ float largest = -1;
+ vec largestPos;
+ vec tempOffset = vec(15,0);
+ float temp = mode->field_->get_density(pos_ + tempOffset);
+ if (temp > 0) {
+ if (temp > largest) largest = temp;
+ largestPos += tempOffset * temp;
+ }
+ tempOffset = vec(-15, 0);
+ temp = mode->field_->get_density(pos_ + tempOffset);
+ if (temp > 0) {
+ if (temp > largest) largest = temp;
+ largestPos += tempOffset * temp;
+ }
+ tempOffset = vec(0, 15);
+ temp = mode->field_->get_density(pos_ + tempOffset);
+ if (temp > 0) {
+ if (temp > largest) largest = temp;
+ largestPos += tempOffset * temp;
+ }
+ tempOffset = vec(0, -15);
+ temp = mode->field_->get_density(pos_ + tempOffset);
+ if (temp > 0) {
+ if (temp > largest) largest = temp;
+ largestPos += tempOffset * temp;
+ }
+
+ mode->field_->add_density(pos_, -0.6);
+ if (largest > -1e-4) {
+ angle_ = atan2(largestPos.y, largestPos.x);
+ }
+
+ add_wide_velocity(mode->field_, pos_, 50*vec(cos(angle_),sin(angle_)));
+ pos_ += mode->field_->get_velocity(pos_)* 10 * DT;
+
+ if (lifetime_ > 5) lifetime_ = 5;
+ else lifetime_ += DT/10;
+ angle_ += DT / lifetime_;
+ }
+
+ vec position() const { return pos_; }
+ void set_position(vec pos) { pos_ = pos; }
+ bool vulnerable() const { return wait_ <= 0; }
+ float lifetime() const { return real_lifetime_; }
+ private:
+ vec pos_;
+ float wait_;
+ float angle_;
+ float lifetime_;
+ float real_lifetime_;
+ Texture* tex_;
+ };
+};
+
+GameMode* make_GalagaMode(Input* inp) {
+ return new GalagaMode(inp, time(NULL));
+}
+GameMode* make_GalagaMode(Input* inp, unsigned int seed) {
+ return new GalagaMode(inp, seed);
+}
+GameMode* make_GalagaMode(ReaderInput* inp) {
+ return new GalagaMode(inp);
+}
View
25 soylent/ichor/GameMode.h
@@ -0,0 +1,25 @@
+#ifndef __GAMEMODE_H__
+#define __GAMEMODE_H__
+
+#include "tweak.h"
+#include <SDL.h>
+
+class GameMode
+{
+public:
+ virtual ~GameMode() { }
+
+ virtual void draw() const = 0;
+ virtual void step() = 0;
+ virtual bool events(SDL_Event* e) = 0;
+};
+
+class Input;
+class ReaderInput;
+GameMode* make_GalagaMode(Input*);
+GameMode* make_GalagaMode(Input*, unsigned int seed);
+GameMode* make_GalagaMode(ReaderInput*);
+GameMode* make_DuelMode(Input*, Input*);
+GameMode* make_HighScoreMode();
+
+#endif
View
76 soylent/ichor/HighScoreMode.cc
@@ -0,0 +1,76 @@
+#include "GameMode.h"
+#include "HighScoreSheet.h"
+#include "Input.h"
+
+class HighScoreMode : public GameMode
+{
+public:
+ HighScoreMode(HighScoreSheet* sheet) : sheet_(sheet), index_(0), mode_(0) { }
+
+ ~HighScoreMode() {
+ delete mode_;
+ delete sheet_;
+ }
+
+ void step() {
+ if (mode_) {
+ if (QUIT) {
+ delete mode_;
+ mode_ = 0;
+ QUIT = false;
+ }
+ else {
+ mode_->step();
+ }
+ }
+ }
+
+ void draw() const {
+ if (mode_) {
+ mode_->draw();
+ }
+ else {
+ sheet_->draw(index_);
+ }
+ }
+
+ bool events(SDL_Event* e) {
+ if (mode_) return mode_->events(e);
+
+ if (e->type == SDL_KEYDOWN) {
+ if (e->key.keysym.sym == SDLK_RETURN || e->key.keysym.sym == SDLK_SPACE) {
+ std::set<HighScoreEntry> entries = sheet_->entries();
+ std::set<HighScoreEntry>::const_iterator i = entries.begin();
+ for (int j = 0; j < index_; j++) {
+ ++i;
+ }
+ mode_ = make_GalagaMode(new ReaderInput(i->replay_name()));
+ return true;
+ }
+ else if (e->key.keysym.sym == SDLK_DOWN) {
+ index_++;
+ while (index_ >= int(sheet_->entries().size())) {
+ index_ -= sheet_->entries().size();
+ }
+ return true;
+ }
+ else if (e->key.keysym.sym == SDLK_UP) {
+ index_--;
+ while (index_ < 0) {
+ index_ += sheet_->entries().size();
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+private:
+ HighScoreSheet* sheet_;
+ int index_;
+ GameMode* mode_;
+};
+
+GameMode* make_HighScoreMode()
+{
+ return new HighScoreMode(new LocalHighScoreSheet);
+}
View
173 soylent/ichor/HighScoreSheet.h
@@ -0,0 +1,173 @@
+#ifndef __HIGHSCORESHEET_H__
+#define __HIGHSCORESHEET_H__
+
+#include "tweak.h"
+#include "util.h"
+#include <string>
+#include <set>
+#include <ctime>
+#include <iostream>
+#include <sstream>
+#include <iomanip>
+
+using std::string;
+using std::set;
+
+struct HighScoreEntry {
+ HighScoreEntry(int score, string name, time_t date, unsigned int seed)
+ : score(score), name(name), date(date), seed(seed)
+ { }
+
+ int score;
+ string name;
+ time_t date;
+ unsigned int seed;
+
+ bool operator < (const HighScoreEntry& o) const {
+ return score > o.score
+ || score == o.score && (
+ date < o.date ||
+ date == o.date && (
+ name < o.name));
+ }
+
+ void erase_replay() const {
+ remove(replay_name().c_str());
+ }
+
+ std::string replay_name() const {
+ std::stringstream ss;
+ ss << "scores/replay-" << seed << ".dat";
+ return ss.str();
+ }
+
+};
+
+class HighScoreSheet {
+public:
+ virtual ~HighScoreSheet() { }
+
+ virtual string title() const = 0;
+ virtual set<HighScoreEntry> entries() const = 0;
+ virtual void draw(int selected = -1) const {
+ set<HighScoreEntry> es = entries();
+ int rh = H/2 + 4 * es.size();
+ glColor3f(1,1,1);
+ glRasterPos2f(65, rh);
+ draw_string(title());
+ rh -= 8;
+ int idx = 0;
+ for (set<HighScoreEntry>::iterator i = es.begin(); i != es.end(); ++i) {
+ std::stringstream ss;
+ char* date = ctime(&i->date);
+ date[strlen(date)-1] = '\0'; // kill the \n terminator
+ ss << std::setw(24) << std::left << i->name << " "
+ << std::setw(10) << std::right << i->score << " "
+ << std::setw(30) << std::left << date;
+ if (idx++ == selected) {
+ glColor3f(1,1,0);
+ }
+ else {
+ glColor3f(1,1,1);
+ }
+ glRasterPos2f(65, rh);
+ draw_string(ss.str());
+ rh -= 8;
+ }
+ }
+
+ virtual HighScoreEntry min_entry() const = 0;
+ virtual void insert(const HighScoreEntry& e) = 0;
+};
+
+class LocalHighScoreSheet : public HighScoreSheet {
+public:
+ LocalHighScoreSheet() : loaded_(false) { }
+
+ string title() const {
+ return "Local High Scores";
+ }
+
+ set<HighScoreEntry> entries() const {
+ if (!loaded_) read_entries();
+ return entries_;
+ }
+
+ HighScoreEntry min_entry() const {
+ if (!loaded_) read_entries();
+ if (entries_.size() < 10) return HighScoreEntry(-1, "nobody", 0, 0);
+ set<HighScoreEntry>::iterator back = entries_.end();
+ --back;
+ return *back;
+ }
+
+ void insert(const HighScoreEntry& e) {
+ if (!loaded_) read_entries();
+ bool ret = false;
+ if (entries_.size() < 10) {
+ entries_.insert(e);
+ ret = true;
+ }
+ else {
+ set<HighScoreEntry>::iterator back = entries_.end();
+ --back;
+ if (back->score < e.score) {
+ back->erase_replay();
+ entries_.erase(back);
+ entries_.insert(e);
+ ret = true;
+ }
+ }
+ if (ret) {
+ write_entries();
+ }
+ }
+private:
+ void read_entries() const {
+ SDL_RWops* file = SDL_RWFromFile(ICHOR_SCORESDIR "/scores.dat", "rb");
+ loaded_ = true;
+ if (!file) return;
+
+ int nscores = 0;
+ SDL_RWread(file, &nscores, sizeof(int), 1);
+
+ for (int i = 0; i < nscores; i++) {
+ int score = 0;
+ SDL_RWread(file, &score, sizeof(int), 1);
+ int nchars = 0;
+ SDL_RWread(file, &nchars, sizeof(int), 1);
+ char* name = new char[nchars+1];
+ SDL_RWread(file, name, sizeof(char), nchars);
+ name[nchars] = '\0';
+ time_t date = 0;
+ SDL_RWread(file, &date, sizeof(time_t), 1);
+ unsigned int seed = 0;
+ SDL_RWread(file, &seed, sizeof(unsigned int), 1);
+ entries_.insert(HighScoreEntry(score, string(name), date, seed));
+ delete[] name;
+ }
+ SDL_RWclose(file);
+ }
+
+ void write_entries() const {
+ SDL_RWops* file = SDL_RWFromFile(ICHOR_SCORESDIR "/scores.dat", "wb");
+ if (!file) return;
+ int nscores = int(entries_.size());
+ SDL_RWwrite(file, &nscores, sizeof(int), 1);
+ for (set<HighScoreEntry>::const_iterator i = entries_.begin(); i != entries_.end(); ++i) {
+ SDL_RWwrite(file, &i->score, sizeof(int), 1);
+ int nchars = i->name.size();
+ SDL_RWwrite(file, &nchars, sizeof(int), 1);
+ const char* buf = i->name.c_str();
+ SDL_RWwrite(file, buf, sizeof(char), nchars);
+ SDL_RWwrite(file, &i->date, sizeof(time_t), 1);
+ SDL_RWwrite(file, &i->seed, sizeof(unsigned int), 1);
+ }
+ SDL_RWclose(file);
+ }
+
+ mutable bool loaded_;
+ mutable set<HighScoreEntry> entries_;
+};
+
+#endif
View
284 soylent/ichor/Ichor.vcproj
@@ -0,0 +1,284 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<VisualStudioProject
+ ProjectType="Visual C++"
+ Version="9.00"
+ Name="Ichor"
+ ProjectGUID="{DB1787D2-754B-43D1-989D-01479CE809F0}"
+ Keyword="Win32Proj"
+ TargetFrameworkVersion="0"
+ >
+ <Platforms>
+ <Platform
+ Name="Win32"
+ />
+ </Platforms>
+ <ToolFiles>
+ </ToolFiles>
+ <Configurations>
+ <Configuration
+ Name="Debug|Win32"
+ OutputDirectory="Debug"
+ IntermediateDirectory="Debug"
+ ConfigurationType="1"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ Optimization="0"
+ PreprocessorDefinitions="WIN32;_DEBUG;_WINDOWS;"
+ MinimalRebuild="true"
+ BasicRuntimeChecks="3"
+ RuntimeLibrary="3"
+ UsePrecompiledHeader="0"
+ WarningLevel="3"
+ Detect64BitPortabilityProblems="true"
+ DebugInformationFormat="4"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLinkerTool"
+ AdditionalDependencies="sdl.lib sdlmain.lib sdl_image.lib sdl_ttf.lib"
+ LinkIncremental="2"
+ IgnoreDefaultLibraryNames="msvcrt.lib"
+ GenerateDebugInformation="true"
+ SubSystem="2"
+ TargetMachine="1"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCManifestTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCAppVerifierTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ />
+ </Configuration>
+ <Configuration
+ Name="Release|Win32"
+ OutputDirectory="Release"
+ IntermediateDirectory="Release"
+ ConfigurationType="1"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ Optimization="2"
+ PreprocessorDefinitions="WIN32;NDEBUG;_WINDOWS;"
+ RuntimeLibrary="2"
+ UsePrecompiledHeader="0"
+ WarningLevel="3"
+ Detect64BitPortabilityProblems="true"
+ DebugInformationFormat="3"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLinkerTool"
+ AdditionalDependencies="sdl.lib sdlmain.lib sdl_image.lib sdl_ttf.lib"
+ LinkIncremental="2"
+ GenerateDebugInformation="true"
+ SubSystem="1"
+ OptimizeReferences="2"
+ EnableCOMDATFolding="2"
+ TargetMachine="1"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCManifestTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCAppVerifierTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ />
+ </Configuration>
+ </Configurations>
+ <References>
+ </References>
+ <Files>
+ <Filter
+ Name="Header Files"
+ Filter="h;hpp;hxx;hm;inl;inc;xsd"
+ UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}"
+ >
+ <File
+ RelativePath=".\color.h"
+ >
+ </File>
+ <File
+ RelativePath=".\config.h"
+ >
+ </File>
+ <File
+ RelativePath=".\drawing.h"
+ >
+ </File>
+ <File
+ RelativePath=".\Explosion.h"
+ >
+ </File>
+ <File
+ RelativePath=".\FluidGrid.h"
+ >
+ </File>
+ <File
+ RelativePath=".\GameMode.h"
+ >
+ </File>
+ <File
+ RelativePath=".\HighScoreSheet.h"
+ >
+ </File>
+ <File
+ RelativePath=".\Input.h"
+ >
+ </File>
+ <File
+ RelativePath=".\Menu.h"
+ >
+ </File>
+ <File
+ RelativePath=".\Particle.h"
+ >
+ </File>
+ <File
+ RelativePath=".\Player.h"
+ >
+ </File>
+ <File
+ RelativePath=".\Texture.h"
+ >
+ </File>
+ <File
+ RelativePath=".\Timer.h"
+ >
+ </File>
+ <File
+ RelativePath=".\tweak.h"
+ >
+ </File>
+ <File
+ RelativePath=".\util.h"
+ >
+ </File>
+ <File
+ RelativePath=".\vec.h"
+ >
+ </File>
+ </Filter>
+ <Filter
+ Name="Resource Files"
+ Filter="rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx"
+ UniqueIdentifier="{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}"
+ >
+ </Filter>
+ <Filter
+ Name="Source Files"
+ Filter="cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx"
+ UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}"
+ >
+ <File
+ RelativePath=".\drawing.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\DuelMode.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\GalagaMode.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\HighScoreMode.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\main.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\Menu.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\Texture.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\util.cc"
+ >
+ </File>
+ </Filter>
+ </Files>
+ <Globals>
+ </Globals>
+</VisualStudioProject>
View
235 soylent/ichor/Input.h
@@ -0,0 +1,235 @@
+#ifndef __INPUT_H__
+#define __INPUT_H__
+
+#include <SDL.h>
+#include <list>
+
+class Input
+{
+public:
+ virtual ~Input() { }
+ virtual vec get_direction() const = 0;
+ virtual void step() = 0;
+ virtual bool events(SDL_Event* e) = 0;
+};
+
+class WASDInput : public Input
+{
+public:
+ vec get_direction() const { return dir_; }
+
+ void step() {
+ dir_ = vec();
+ Uint8* keys = SDL_GetKeyState(NULL);
+ if (keys[SDLK_a]) { dir_.x -= DT * FLOWSPEED; }
+ if (keys[SDLK_d]) { dir_.x += DT * FLOWSPEED; }
+ if (keys[SDLK_s]) { dir_.y -= DT * FLOWSPEED; }
+ if (keys[SDLK_w]) { dir_.y += DT * FLOWSPEED; }
+ if (dir_.norm() > FLOWSPEED * DT)
+ dir_ = FLOWSPEED * DT * ~dir_;
+ }
+
+ bool events(SDL_Event* e) { return false; }
+private:
+ vec dir_;
+};
+
+class ArrowsInput : public Input
+{
+public:
+ vec get_direction() const { return dir_; }
+
+ void step() {
+ dir_ = vec();
+ Uint8* keys = SDL_GetKeyState(NULL);
+ if (keys[SDLK_LEFT]) { dir_.x -= DT * FLOWSPEED; }
+ if (keys[SDLK_RIGHT]) { dir_.x += DT * FLOWSPEED; }
+ if (keys[SDLK_DOWN]) { dir_.y -= DT * FLOWSPEED; }
+ if (keys[SDLK_UP]) { dir_.y += DT * FLOWSPEED; }
+ if (dir_.norm() > FLOWSPEED * DT)
+ dir_ = FLOWSPEED * DT * ~dir_;
+ }
+
+ bool events(SDL_Event* e) { return false; }
+private:
+ vec dir_;
+};
+
+class MouseInput : public Input
+{
+public:
+ vec get_direction() const { return dir_; }
+
+ void step() {
+ // This copying is to ensure that the output only changes
+ // once per frame, to secure replay integrity.
+ dir_ = tmpdir_;
+ }
+
+ bool events(SDL_Event* e) {
+ if (e->type == SDL_MOUSEMOTION) {
+ vec dp(e->motion.xrel * 0.1, -e->motion.yrel * 0.1);
+ tmpdir_ += dp;
+ if (tmpdir_.norm() > FLOWSPEED * DT) {
+ tmpdir_ = FLOWSPEED * DT * ~tmpdir_;
+ }
+ return true;
+ }
+ return false;
+ }
+private:
+ vec tmpdir_;
+ vec dir_;
+};
+
+class JoystickInput : public Input
+{
+public:
+ vec get_direction() const { return dir_; }
+
+ void step() {
+ // See MouseInput::step for a description
+ dir_ = tmpdir_;
+ }
+
+ bool events(SDL_Event* e) {
+ if (e->type == SDL_JOYAXISMOTION) {
+ if (e->jaxis.axis == 0) {
+ tmpdir_.x = FLOWSPEED * DT * float(e->jaxis.value) / 32768;
+ return true;
+ }
+ else if (e->jaxis.axis == 1) {
+ tmpdir_.y = FLOWSPEED * DT * float(-e->jaxis.value) / 32768;
+ return true;
+ }
+ }
+ return false;
+ }
+private:
+ vec tmpdir_;
+ vec dir_;
+};
+
+class AddInput : public Input
+{
+ typedef std::list<Input*> inputs_t;
+public:
+ AddInput(inputs_t inputs) : inputs_(inputs) { }
+ ~AddInput() {
+ for (inputs_t::iterator i = inputs_.begin(); i != inputs_.end(); ++i) {
+ delete *i;
+ }
+ }
+
+ vec get_direction() const { return dir_; }
+
+ void step() {
+ dir_ = vec();
+ for (inputs_t::const_iterator i = inputs_.begin(); i != inputs_.end(); ++i) {
+ (*i)->step();
+ dir_ += (*i)->get_direction();
+ }
+ if (dir_.norm() > FLOWSPEED * DT) {
+ dir_ = FLOWSPEED * DT * ~dir_;
+ }
+ }
+
+ bool events(SDL_Event* e) {
+ for (inputs_t::const_iterator i = inputs_.begin(); i != inputs_.end(); ++i) {
+ if ((*i)->events(e)) return true;
+ }
+ return false;
+ }
+
+private:
+ std::list<Input*> inputs_;
+ vec dir_;
+};
+
+class WriterInput : public Input {
+public:
+ WriterInput(unsigned int seed, std::string file, Input* in) : in_(in), frames_(0) {
+ file_ = SDL_RWFromFile(file.c_str(), "wb");
+ if (file_) {
+ int writer = 0;
+ SDL_RWwrite(file_, &seed, sizeof(unsigned int), 1);
+ SDL_RWwrite(file_, &writer, sizeof(int), 1);
+ }
+ }
+
+ ~WriterInput() {
+ if (file_) {
+ SDL_RWseek(file_, sizeof(unsigned int), SEEK_SET);
+ SDL_RWwrite(file_, &frames_, sizeof(int), 1);
+ SDL_RWclose(file_);
+ }
+ delete in_;
+ }
+
+ vec get_direction() const { return dir_; }
+
+ void step() {
+ in_->step();
+ dir_ = in_->get_direction();
+ frames_++;
+ if (file_) {
+ SDL_RWwrite(file_, &dir_.x, sizeof(float), 1);
+ SDL_RWwrite(file_, &dir_.y, sizeof(float), 1);
+ }
+ }
+
+ bool events(SDL_Event* e) {
+ return in_->events(e);
+ }
+private:
+ Input* in_;
+ int frames_;
+ SDL_RWops* file_;
+ vec dir_;
+};
+
+class ReaderInput : public Input
+{
+public:
+ ReaderInput(std::string file) {
+ file_ = SDL_RWFromFile(file.c_str(), "rb");
+ if (file_) {
+ SDL_RWread(file_, &seed_, sizeof(unsigned int), 1);
+ SDL_RWread(file_, &frames_, sizeof(int), 1);
+ }
+ else {
+ frames_ = 0;
+ seed_ = 0;
+ }
+ }
+
+ unsigned int get_seed() const { return seed_; }
+
+ vec get_direction() const { return dir_; }
+
+ void step() {
+ if (file_) {
+ if (frames_--) {
+ SDL_RWread(file_, &dir_.x, sizeof(float), 1);
+ SDL_RWread(file_, &dir_.y, sizeof(float), 1);
+ }
+ else {
+ SDL_RWclose(file_);
+ file_ = NULL;
+ dir_ = vec();
+ }
+ }
+ else {
+ dir_ = vec();
+ }
+ }
+
+ bool events(SDL_Event* e) { return false; }
+private:
+ SDL_RWops* file_;
+ int frames_;
+ unsigned int seed_;
+ vec dir_;
+};
+
+#endif
View
167 soylent/ichor/Menu.cc
@@ -0,0 +1,167 @@
+#include "Menu.h"
+#include "drawing.h"
+#include "util.h"
+#include <iostream>
+#include <cmath>
+#include <SDL.h>
+#include <GL/gl.h>
+#include <GL/glu.h>
+
+static int read_pixel(SDL_Surface* surf, int x, int y) {
+ Uint8* ptr = &((Uint8*)surf->pixels)[y * surf->pitch + surf->format->BytesPerPixel * x];
+ if ((Uint8*)ptr > (Uint8*)surf->pixels + surf->h * surf->pitch
+ ||(Uint8*)ptr < (Uint8*)surf->pixels) return 0;
+ switch (surf->format->BitsPerPixel) {
+ case 8: return *ptr;
+ case 16: return *(Uint16*)ptr;
+ case 32: return *(Uint32*)ptr;
+ default: abort();
+ }
+}
+
+const int MENUBOUNDX = 20;
+const int MENUBOUNDY = 4;
+
+static bool BITCHED = false;
+
+void FontRenderer::step()
+{
+ const float factor = pow(float(0.6), float(DT));
+ for (int x = 0; x < MENUW; x++) {
+ for (int y = 0; y < MENUH; y++) {
+ float den = grid_.get_density_direct(x,y);
+ grid_.set_density_direct(x,y,den * factor);
+ }
+ }
+
+ fallback_ = false;
+
+ SDL_Color white = { 255, 255, 255 };
+ SDL_Color black = { 0, 0, 0 };
+ SDL_Surface* surf = TTF_RenderText_Shaded(font_, text_.c_str(), white, black);
+
+ if (surf == 0) { // Luke's desktop is being a bitch, so we must fallback
+ // if TTF isn't working
+
+ if (!BITCHED) {
+ std::cout << "Couldn't render text: " << TTF_GetError() << "\n";
+ BITCHED = true;
+ }
+ fallback_ = true;
+ return;
+ }
+
+ for (int y = 0; y < MENUH; y++) {
+ for (int x = 0; x < MENUW; x++) {
+ int picx = int(float(surf->w) * (x - MENUBOUNDX) / (MENUW - 2*MENUBOUNDX));
+ int picy = int(float(surf->h) * (MENUH - y) / MENUH);
+ if (picx >= 0 && picx < surf->w && picy >= 0 && picy < surf->h) {
+ int pix = read_pixel(surf,picx,picy);