/* * Copyright (c) 2011, 2012, 2013, 2014, 2015, 2016 Jonas 'Sortie' Termansen. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * asteroids.cpp * Single-player space exploration with asteroids! */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include uint32_t WINDOW_ID = 0; uint32_t WINDOW_WIDTH = 800; uint32_t WINDOW_HEIGHT = 512; static inline float RandomFloat() { return arc4random() / (float) UINT32_MAX; } static inline float RandomFloat(float min, float max) { return min + RandomFloat() * (max - min); } static inline float DegreeToRadian(float degree) { return degree / 180 * M_PI; } static inline float RandomAngle() { return RandomFloat() * DegreeToRadian(360); } static inline uint32_t MakeColor(uint8_t r, uint8_t g, uint8_t b) { return b << 0UL | g << 8UL | r << 16UL | 0xFF << 24UL; } static const size_t STARFIELD_WIDTH = 512UL; static const size_t STARFIELD_HEIGHT = 512UL; static uint32_t starfield[STARFIELD_WIDTH * STARFIELD_HEIGHT]; void GenerateStarfield(uint32_t* bitmap, size_t width, size_t height) { size_t numpixels = width * height; for ( size_t i = 0; i < numpixels; i++ ) { uint8_t color = 0; uint8_t randval; arc4random_buf(&randval, sizeof(randval)); if ( randval == 5 || randval == 42 || randval == 101 ) arc4random_buf(&color, sizeof(color)); bitmap[i] = MakeColor(color, color, color); } } static const size_t MAXKEYNUM = 512UL; static bool keysdown[MAXKEYNUM] = { false }; static bool keyspending[MAXKEYNUM] = { false }; static struct timespec key_handled_last[MAXKEYNUM]; bool pop_is_key_just_down(int abskbkey) { assert(0 <= abskbkey); if ( MAXKEYNUM <= (size_t) abskbkey ) return false; if ( keyspending[abskbkey] ) { keyspending[abskbkey] = false; clock_gettime(CLOCK_MONOTONIC, &key_handled_last[abskbkey]); return true; } if ( !keysdown[abskbkey] ) return false; struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); struct timespec elapsed = timespec_sub(now, key_handled_last[abskbkey]); struct timespec repress_delay = timespec_make(0, 100 * 1000 * 1000); if ( timespec_lt(elapsed, repress_delay) ) return false; clock_gettime(CLOCK_MONOTONIC, &key_handled_last[abskbkey]); return true; } static size_t xres; static size_t yres; static size_t bpp; static size_t linesize; static size_t framesize; static uint32_t* buf; static bool gamerunning; static unsigned long framenum; void DrawLine(uint32_t color, long x0, long y0, long x1, long y1) { long dx = labs(x1-x0); long sx = x0 < x1 ? 1 : -1; long dy = labs(y1-y0); long sy = y0 < y1 ? 1 : -1; long err = (dx>dy ? dx : -dy)/2L; long e2; while ( true ) { if ( 0 <= x0 && (size_t) x0 < xres && 0 <= y0 && (size_t) y0 < yres ) { size_t index = y0 * linesize + x0; buf[index] = color; } if ( x0 == x1 && y0 == y1 ) break; e2 = err; if ( e2 > -dx ) { err -= dy; x0 += sx; } if ( e2 < dy ) { err += dx; y0 += sy; } } } class Vector { public: float x; float y; public: Vector(float x = 0.0f, float y = 0.0f) : x(x), y(y) { } Vector& operator=(const Vector& rhs) { if ( this != &rhs ) { x = rhs.x; y = rhs.y; } return *this; } Vector& operator+=(const Vector& rhs) { x += rhs.x; y += rhs.y; return *this; } Vector& operator-=(const Vector& rhs) { x -= rhs.x; y -= rhs.y; return *this; } Vector& operator*=(float scalar) { x *= scalar; y *= scalar; return *this; } Vector& operator/=(float scalar) { x /= scalar; y /= scalar; return *this; } const Vector operator+(const Vector& other) const { Vector ret(*this); ret += other; return ret; } const Vector operator-(const Vector& other) const { Vector ret(*this); ret -= other; return ret; } const Vector operator*(float scalar) const { Vector ret(*this); ret *= scalar; return ret; } const Vector operator/(float scalar) const { Vector ret(*this); ret /= scalar; return ret; } bool operator==(const Vector& other) const { return x == other.x && y == other.y; } bool operator!=(const Vector& other) const { return !(*this == other); } float Dot(const Vector& other) const { return x * other.x + y * other.y; } float SquaredSize() const { return x*x + y*y; } float Size() const { return sqrtf(SquaredSize()); } float DistanceTo(const Vector& other) const { return (other - *this).Size(); } const Vector Normalize() const { float size = Size(); if ( size == 0.0 ) size = 1.0f; return *this / size; } const Vector Rotate(float radians) const { float sinr = sinf(radians); float cosr = cosf(radians); float newx = x * cosr - y * sinr; float newy = x * sinr + y * cosr; return Vector(newx, newy); } const Vector RotateAround(float radians, const Vector& off) const { return Vector(*this - off).Rotate(radians) + off; } }; bool AboveLine(const Vector& a, const Vector& b, const Vector& p) { Vector ba = b - a; Vector bahat = Vector(ba.y, -ba.x); Vector bp = p - a; return 0.0 <= bahat.Dot(bp); } bool InsideTriangle(const Vector& a, const Vector& b, const Vector& c, const Vector& p) { return !AboveLine(a, b, p) && !AboveLine(b, c, p) && !AboveLine(c, a, p); } class Object; class Actor; class Spaceship; static Object* firstobject = NULL; static Object* lastobject = NULL; static Vector screenoff; static Spaceship* playership = NULL; class Object { public: Object() { gcborn = false; gcdead = false; if ( !firstobject ) { firstobject = lastobject = this; prevobj = nextobj = NULL; } else { lastobject->nextobj = this; this->prevobj = lastobject; this->nextobj = NULL; lastobject = this; } }; virtual ~Object() { if ( !prevobj ) firstobject = nextobj; else prevobj->nextobj = nextobj; if ( !nextobj ) lastobject = prevobj; else nextobj->prevobj = prevobj; } public: virtual bool IsA(const char* classname) { return !strcmp(classname, "Object"); } virtual void PreFrame() { } virtual void OnFrame(float /*deltatime*/) { } virtual void PostFrame(float /*deltatime*/) { } virtual void Render() { } private: bool gcborn; bool gcdead; Object* prevobj; Object* nextobj; public: bool GCIsBorn() const { return gcborn; } bool GCIsDead() const { return gcdead; } bool GCIsAlive() const { return GCIsBorn() && !GCIsDead(); } void GCDie() { gcdead = true; } void GCBirth() { gcborn = true; } Object* NextObj() const { return nextobj; } }; class Actor : public Object { public: Actor() { mass = 1.0; } virtual ~Actor() { } public: virtual void PreFrame() { force = Vector(0, 0); otherforce = Vector(0, 0); } virtual void OnFrame(float deltatime) { Think(deltatime); } virtual void PostFrame(float deltatime) { Move(deltatime); } virtual bool IsA(const char* classname) { return !strcmp(classname, "Actor") || Object::IsA(classname); } virtual void Move(float deltatime); virtual void Think(float /*deltatime*/) { } virtual void Render() { } public: Vector pos; Vector vel; Vector acc; Vector force; Vector otherforce; float mass; }; void Actor::Move(float deltatime) { acc = (force + otherforce) / mass; vel += acc * deltatime; pos += vel * deltatime; } enum AsteroidType { TYPE_NORMAL, TYPE_CRYSTAL, TYPE_NOT_CRYSTAL, }; class Asteroid : public Actor { public: Asteroid(Vector pos, Vector vel, float size, AsteroidType type = TYPE_NORMAL); virtual ~Asteroid() { } virtual bool IsA(const char* classname) { return !strcmp(classname, "Asteroid") || Actor::IsA(classname); } virtual void Move(float deltatime); virtual void Render(); private: Vector Point(size_t id); public: bool InsideMe(const Vector& p); void OnHit(); float Size() const { return size; } AsteroidType Type() const { return type; } private: static const size_t MIN_POLYS = 5; static const size_t MAX_POLYS = 12; static constexpr float MAX_TURN_SPEED = 50.0f; size_t numpolygons; float slice; float polydists[MAX_POLYS+1]; float size; float angle; float turnspeed; AsteroidType type; }; Asteroid::Asteroid(Vector pos, Vector vel, float size, AsteroidType type) { this->pos = pos; this->vel = vel; this->size = size; this->type = type; float MASS_PER_UNIT = 1.0; this->mass = MASS_PER_UNIT * 4.0f / 3.0f * size * size * size; if ( type == TYPE_NORMAL ) { const float CRYSTAL_CHANCE = 0.1f; const float MAX_SIZE = 64.0; if ( RandomFloat() < CRYSTAL_CHANCE && size < MAX_SIZE ) this->type = TYPE_CRYSTAL; } angle = 0.0f; turnspeed = DegreeToRadian(MAX_TURN_SPEED) * (RandomFloat() * 2.0f - 1.0f); numpolygons = MIN_POLYS + arc4random_uniform(MAX_POLYS - MIN_POLYS); slice = DegreeToRadian(360.0f) / (float) numpolygons; for ( size_t i = 0; i < numpolygons; i++ ) { polydists[i] = (RandomFloat() + 1.0) * size / 2.0; } polydists[numpolygons] = polydists[0]; } void Asteroid::Move(float deltatime) { Actor::Move(deltatime); angle += turnspeed * deltatime; } Vector Asteroid::Point(size_t i) { float rot = i * slice + angle; return Vector(polydists[i], 0.0).Rotate(rot); } bool Asteroid::InsideMe(const Vector& p) { const Vector& center = pos; for ( size_t i = 0; i < numpolygons; i++ ) { Vector from = Point(i) + pos; Vector to = Point(i+1) + pos; if ( InsideTriangle(from, to, center, p) ) return true; } return false; } void Asteroid::Render() { Vector screenpos = pos - screenoff; uint32_t color = MakeColor(200, 200, 200); if ( type == TYPE_CRYSTAL ) color = MakeColor(100, 100, 255); for ( size_t i = 0; i < numpolygons; i++ ) { Vector from = Point(i) + screenpos; Vector to = Point(i+1) + screenpos; DrawLine(color, from.x, from.y, to.x, to.y); } } void Asteroid::OnHit() { if ( !GCIsAlive() ) return; Vector axis = Vector(size/2.0f, 0.0f).Rotate(RandomAngle()); float sizea = RandomFloat(size*0.3, size*0.7); float sizeb = RandomFloat(size*0.3, size*0.7); const float MINIMUM_SIZE = 6.0; const float MAX_ANGLE = DegreeToRadian(45); if ( MINIMUM_SIZE <= sizea ) { Vector astvel = vel.Rotate(RandomFloat(0.0, MAX_ANGLE)) * 1.2; new Asteroid(pos + axis, astvel, sizea, type); } if ( MINIMUM_SIZE <= sizeb ) { Vector astvel = vel.Rotate(RandomFloat(0.0, -MAX_ANGLE)) * 1.2; new Asteroid(pos - axis, astvel, sizeb, type); } GCDie(); } class AsteroidField : public Actor { public: AsteroidField() { } virtual ~AsteroidField() { } virtual bool IsA(const char* classname) { return !strcmp(classname, "AsteroidField") || Actor::IsA(classname); } public: virtual void Think(float deltatime); }; void AsteroidField::Think(float /*deltatime*/) { float spawndist = 1500.0f; float maxdist = 1.5 * spawndist; size_t minimumasteroids = 200; size_t numasteroids = 0; Vector center = ((Actor*)playership)->pos; for ( Object* obj = firstobject; obj; obj = obj->NextObj() ) { if ( !obj->IsA("Asteroid") ) continue; Asteroid* ast = (Asteroid*) obj; numasteroids++; float dist = ast->pos.DistanceTo(center); if ( spawndist < dist ) { ast->GCDie(); } } for ( ; numasteroids < minimumasteroids; numasteroids++ ) { float dist = RandomFloat(spawndist, maxdist); Vector astpos = Vector(dist, 0.0f).Rotate(RandomAngle()) + center; float minsize = 4.0; float maxsize = 120.0f; float maxspeed = 80.0f; float size = RandomFloat(minsize, maxsize); float speed = RandomFloat() * maxspeed; Vector astvel = Vector(speed, 0.0).Rotate(RandomAngle()); new Asteroid(astpos, astvel, size); } } class Missile : public Actor { public: Missile(Vector pos, Vector vel, Vector direction, float ttl); virtual bool IsA(const char* classname) { return !strcmp(classname, "Missile") || Actor::IsA(classname); } virtual void Think(float deltatime); virtual void Render(); virtual ~Missile(); protected: float ttl; Vector direction; }; Missile::Missile(Vector pos, Vector vel, Vector direction, float ttl) { this->pos = pos; this->vel = vel; this->ttl = ttl; this->direction = direction; } void Missile::Think(float deltatime) { ttl -= deltatime; if ( ttl < 0 ) { GCDie(); } for ( Object* obj = firstobject; obj; obj = obj->NextObj() ) { if ( !obj->GCIsAlive() ) continue; if ( !obj->IsA("Asteroid") ) continue; Asteroid* ast = (Asteroid*) obj; if ( !ast->InsideMe(pos) ) continue; ast->OnHit(); GCDie(); } } void Missile::Render() { Vector screenpos = pos - screenoff; uint32_t shipcolor = MakeColor(31, 255, 31); const float MISSILE_LEN = 5.0f; Vector from = screenpos; Vector to = screenpos + direction * (MISSILE_LEN / direction.Size()); DrawLine(shipcolor, from.x, from.y, to.x, to.y); } Missile::~Missile() { } class Attractor : public Actor { public: Attractor(Vector pos, Vector vel, float growtomass, float rate); virtual ~Attractor() { } virtual bool IsA(const char* classname) { return !strcmp(classname, "Attractor") || Actor::IsA(classname); } virtual void Think(float deltatime); virtual void Render(); public: float size; float growtomass; float rate; float accel; float age; }; Attractor::Attractor(Vector pos, Vector vel, float growtomass, float rate) { this->pos = pos; this->vel = vel; this->growtomass = growtomass; this->rate = rate; this->size = 1.0f; this->mass = 1.0f; this->rate = 0.0; this->accel = 20000.0; this->age = 0.0f; } void Attractor::Think(float deltatime) { growtomass = 20000000.0; rate += deltatime * accel; mass += deltatime * rate; size += 5 * deltatime; age += deltatime; float sofar = 2.5 * age; mass = sofar*sofar*sofar*sofar; size = age*age; if ( 1.5*60 <= age ) { GCDie(); return; } for ( Object* obj = firstobject; obj; obj = obj->NextObj() ) { if ( obj == this ) continue; if ( !obj->GCIsAlive() ) continue; if ( !obj->IsA("Actor") ) continue; if ( obj->IsA("Attractor") ) continue; //if ( obj->IsA("Spaceship") ) // continue; Actor* other = (Actor*) obj; Vector relative = pos - other->pos; float distsq = relative.SquaredSize(); if ( distsq < size ) { distsq = size; } float forcesize = mass * other->mass / distsq; Vector force = relative.Normalize() * forcesize; other->force += force; this->force -= force; if ( distsq < size*size && other->IsA("Asteroid") ) { Asteroid* ast = (Asteroid*) other; if ( RandomFloat() < 0.05f ) ast->OnHit(); } } } void Attractor::Render() { const size_t NUM_SIDES = 128; float slice = DegreeToRadian(360.0f / NUM_SIDES); for ( size_t i = 0; i < NUM_SIDES; i++ ) { Vector screenpos = pos - screenoff; uint32_t color = MakeColor(255, 0, 0); Vector from = screenpos + Vector(size, 0.0f).Rotate((i+0)*slice); Vector to = screenpos + Vector(size, 0.0f).Rotate((i+1)*slice); DrawLine(color, from.x, from.y, to.x, to.y); } } class Firework : public Missile { public: Firework(Vector pos, Vector vel, Vector direction, float ttl); virtual bool IsA(const char* classname) { return !strcmp(classname, "Firework") || Actor::IsA(classname); } virtual void Think(float deltatime); }; class Spaceship : public Actor { protected: Spaceship() { } public: Spaceship(float shipangle, Vector pos = Vector(0, 0), Vector vel = Vector(0, 0), Vector acc = Vector(0, 0)); virtual ~Spaceship(); public: virtual bool IsA(const char* classname) { return !strcmp(classname, "Spaceship") || Actor::IsA(classname); } virtual void Think(float deltatime); virtual void Render(); public: void SetThrust(bool forward, bool backward); void SetTurn(bool turnleft, bool turnright); void SetFiring(bool missile, bool firework, bool attractor); protected: bool turnleft; bool turnright; bool moveforward; bool movebackward; bool missile, firework, attractor; float shipangle; }; Spaceship::Spaceship(float shipangle, Vector pos, Vector vel, Vector acc) { this->shipangle = shipangle; this->pos = pos; this->vel = vel; this->acc = acc; this->mass = 1.0; turnleft = turnright = moveforward = movebackward = missile = \ attractor = firework = false; } Spaceship::~Spaceship() { } void Spaceship::Think(float deltatime) { for ( Object* obj = firstobject; obj; obj = obj->NextObj() ) { if ( !obj->GCIsAlive() ) continue; if ( !obj->IsA("Asteroid") ) continue; Asteroid* ast = (Asteroid*) obj; bool iscrystal = ast->Type() == TYPE_CRYSTAL; bool isinside = false; if ( iscrystal ) { Vector relative = pos - ast->pos; float distsq = relative.SquaredSize(); if ( distsq < 4.0 * 4.0 ) isinside = true; if ( distsq < 100 ) distsq = 100; float constant = 2000.0; float forcesize = constant * mass * ast->mass / distsq; Vector force = relative.Normalize() * forcesize; ast->force += force; } isinside = isinside || ast->InsideMe(pos); if ( isinside && iscrystal ) { ast->GCDie(); continue; } else if ( isinside && IsA("Botship") ) { ast->OnHit(); GCDie(); return; } else if ( isinside ) { ast->OnHit(); pos.y = 16384 - pos.y; vel = Vector(); break; } } const float turnspeed = 100.0; const float turnamount = turnspeed * deltatime; if ( turnleft ) shipangle -= DegreeToRadian(turnamount); if ( turnright ) shipangle += DegreeToRadian(turnamount); float shipaccelamount = 15.0; float shipaccel = 0.0; if ( moveforward ) shipaccel += shipaccelamount; if ( movebackward ) shipaccel -= shipaccelamount; force += Vector(shipaccel, 0.0).Rotate(shipangle) * mass; float shipspeed = vel.Size(); float maxspeed = 50.0f; //if ( maxspeed < shipspeed ) // vel *= maxspeed / shipspeed; if ( maxspeed < shipspeed ) { Vector backforce = vel.Normalize() * -shipaccelamount * mass; force += backforce; } if ( missile || firework || attractor ) { float ttl = 8.0; float speed = 120.0; const Vector P3(16.0f, 0.0f); Vector spawnpos = pos + P3.Rotate(shipangle) * 1.1; Vector spawnvel = Vector(speed, 0.0).Rotate(shipangle); if ( missile ) new Missile(spawnpos, vel + spawnvel, spawnvel, ttl); if ( firework ) new Firework(spawnpos, vel + spawnvel, spawnvel, 0.0); if ( attractor ) new Attractor(spawnpos, vel + spawnvel, 10000.0, 1.001); } } void Spaceship::Render() { Vector screenpos = pos - screenoff; const Vector P1(-8.0f, 8.0f); const Vector P2(-8.0f, -8.0f); const Vector P3(16.0f, 0.0f); Vector p1 = P1.Rotate(shipangle) + screenpos; Vector p2 = P2.Rotate(shipangle) + screenpos; Vector p3 = P3.Rotate(shipangle) + screenpos; uint32_t shipcolor = MakeColor(200, 200, 200); if ( this == playership ) shipcolor = MakeColor(255, 255, 255); DrawLine(shipcolor, p1.x, p1.y, p2.x, p2.y); DrawLine(shipcolor, p2.x, p2.y, p3.x, p3.y); DrawLine(shipcolor, p1.x, p1.y, p3.x, p3.y); } void Spaceship::SetThrust(bool forward, bool backward) { this->moveforward = forward; this->movebackward = backward; } void Spaceship::SetTurn(bool turnleft, bool turnright) { this->turnleft = turnleft; this->turnright = turnright; } void Spaceship::SetFiring(bool missile, bool firework, bool attractor) { this->missile = missile; this->firework = firework; this->attractor = attractor; } class Botship : public Spaceship { public: Botship(float shipangle, Vector pos = Vector(0, 0), Vector vel = Vector(0, 0), Vector acc = Vector(0, 0)); virtual ~Botship(); public: virtual bool IsA(const char* classname) { return !strcmp(classname, "Botship") || Spaceship::IsA(classname); } virtual void Think(float deltatime); private: float firedelay; }; Botship::Botship(float shipangle, Vector pos, Vector vel, Vector acc) { this->shipangle = shipangle; this->pos = pos; this->vel = vel; this->acc = acc; this->mass = 1.0; turnleft = false; turnright = false; moveforward = false; movebackward = false; missile = false; attractor = false; firework = false; firedelay = 0.0f; } Botship::~Botship() { } void Botship::Think(float deltatime) { if ( 0.0f < firedelay ) firedelay -= deltatime; const float PLAYER_MAX_DIST = 512.0f; float playerdist = (playership->pos - pos).Size(); bool needreturn = PLAYER_MAX_DIST < playerdist; Actor* target = NULL; bool movetotarget = false; Asteroid* asttarget = NULL; float targetsafety = 0.0f; for ( Object* obj = firstobject; !needreturn && obj; obj = obj->NextObj() ) { if ( !obj->GCIsAlive() ) continue; if ( !obj->IsA("Asteroid") ) continue; Asteroid* ast = (Asteroid*) obj; if ( ast->Type() == TYPE_CRYSTAL ) continue; Vector mypos = pos + vel * 2.0f; Vector astdir = ast->pos - mypos; float safety = astdir.Size() / (ast->vel.Size() * sqrtf(ast->Size())); if ( !asttarget || safety < targetsafety ) asttarget = ast, targetsafety = safety; } target = asttarget; if ( needreturn ) target = playership, movetotarget = true; moveforward = movebackward = missile = false; if ( (missile = asttarget && !needreturn && firedelay <= 0.0f) ) firedelay = 0.3; if ( target ) { // Estimate the location of the target. Vector targetdir = target->pos - pos; float missile_speed = 120.0; float firetime = targetdir.Size() / missile_speed; Vector projectedpos = target->pos + target->vel * firetime; Vector projecteddir = projectedpos - pos; // Further estimate the location of the target. float firetimeg2 = projecteddir.Size() / missile_speed; Vector projectedposg2 = target->pos + target->vel * firetimeg2; Vector projecteddirg2 = projectedposg2 - pos; // Calculate which direction to look in. Vector forward = Vector(1.0, 0.0).Rotate(shipangle); Vector forwardhat(-forward.y, forward.x); float dotproduct = forwardhat.Dot(projecteddirg2); turnright = 0.0f < dotproduct; turnleft = !turnright; } if ( target && movetotarget ) moveforward = true; Spaceship::Think(deltatime); } Firework::Firework(Vector pos, Vector vel, Vector dir, float ttl) : Missile(pos, vel, dir, ttl) { } void Firework::Think(float deltatime) { ttl -= deltatime; if ( ttl < 0 ) { // Explode in a shower of 8 missiles const float MISSILE_TTL = 3.0; const float MISSILE_SPEED = 8.0; const size_t NUM_MISSILES = 8; const Vector velocity = Vector(MISSILE_SPEED, 0); const float offsetangle = RandomAngle(); const float angle = 2 * M_PI / NUM_MISSILES; for ( size_t i = 0; i < NUM_MISSILES; i++ ) { Vector dir = velocity.Rotate(offsetangle + angle * i); new Missile(pos, vel + dir, dir, MISSILE_TTL); } GCDie(); return; } for ( Object* obj = firstobject; obj; obj = obj->NextObj() ) { if ( !obj->GCIsAlive() ) continue; if ( !obj->IsA("Asteroid") ) continue; Asteroid* ast = (Asteroid*) obj; if ( !ast->InsideMe(pos) ) continue; // Fireworks taken out by asteroids before explosion. GCDie(); } } struct timespec lastframeat; void GameLogic() { struct timespec now; do clock_gettime(CLOCK_MONOTONIC, &now); while ( timespec_eq(now, lastframeat) ); struct timespec delta = timespec_sub(now, lastframeat); float deltatime = delta.tv_sec + delta.tv_nsec / 1E9f; lastframeat = now; float timescale = 3.0; deltatime *= timescale; Object* first = firstobject; Object* obj; for ( obj = first; obj; obj = obj->NextObj() ) obj->GCBirth(); bool key_a = pop_is_key_just_down(KBKEY_A); bool key_b = pop_is_key_just_down(KBKEY_B); bool key_space = pop_is_key_just_down(KBKEY_SPACE); bool key_lctrl = pop_is_key_just_down(KBKEY_LCTRL); playership->SetThrust(keysdown[KBKEY_UP], keysdown[KBKEY_DOWN]); playership->SetTurn(keysdown[KBKEY_LEFT], keysdown[KBKEY_RIGHT]); playership->SetFiring(key_space, key_lctrl, key_a); bool makebot = key_b; if ( makebot ) new Botship(RandomAngle(), playership->pos, playership->vel); for ( obj = first; obj; obj = obj->NextObj() ) { if ( !obj->GCIsBorn() ) continue; obj->PreFrame(); } for ( obj = first; obj; obj = obj->NextObj() ) { if ( !obj->GCIsBorn() ) continue; obj->OnFrame(deltatime); } for ( obj = first; obj; obj = obj->NextObj() ) { if ( !obj->GCIsBorn() ) continue; obj->PostFrame(deltatime); } for ( obj = first; obj; ) { Object* todelete = obj; obj = obj->NextObj(); if ( !todelete->GCIsDead() ) continue; delete todelete; } } void Render() { screenoff = playership->pos - Vector(xres/2.0, yres/2.0); size_t staroffx = (size_t) screenoff.x; size_t staroffy = (size_t) screenoff.y; for ( size_t y = 0; y < yres; y++ ) { uint32_t* line = buf + y * linesize; for ( size_t x = 0; x < xres; x++ ) { size_t fieldx = (x+staroffx) % STARFIELD_WIDTH; size_t fieldy = (y+staroffy) % STARFIELD_HEIGHT; size_t fieldindex = fieldy * STARFIELD_HEIGHT + fieldx; line[x] = starfield[fieldindex]; } } for ( Object* obj = firstobject; obj; obj = obj->NextObj() ) { obj->Render(); } } void on_disconnect(void*) { exit(0); } void on_quit(void*, uint32_t) { exit(0); } void on_resize(void*, uint32_t window_id, uint32_t width, uint32_t height) { if ( window_id != WINDOW_ID ) return; WINDOW_WIDTH = width; WINDOW_HEIGHT = height; } void on_keyboard(void*, uint32_t window_id, uint32_t codepoint) { if ( window_id != WINDOW_ID ) return; int kbkey = KBKEY_DECODE(codepoint); if( !kbkey ) return; int abskbkey = (kbkey < 0) ? -kbkey : kbkey; if ( MAXKEYNUM <= (size_t) abskbkey ) return; bool is_key_down_event = 0 < kbkey; if ( !keysdown[abskbkey] && is_key_down_event ) keyspending[abskbkey] = true; keysdown[abskbkey] = is_key_down_event; } void RunFrame(struct display_connection* connection) { struct display_event_handlers handlers; memset(&handlers, 0, sizeof(handlers)); handlers.disconnect_handler = on_disconnect; handlers.quit_handler = on_quit; handlers.resize_handler = on_resize; handlers.keyboard_handler = on_keyboard; while ( display_poll_event(connection, &handlers) == 0 ); size_t old_framesize = framesize; xres = WINDOW_WIDTH; yres = WINDOW_HEIGHT; bpp = 32; linesize = WINDOW_WIDTH; framesize = WINDOW_WIDTH * sizeof(uint32_t) * WINDOW_HEIGHT; if ( old_framesize != framesize ) { free(buf); buf = (uint32_t*) malloc(framesize); if ( !buf ) err(1, "malloc"); } GameLogic(); Render(); static int fps_counter = 0; fps_counter++; static time_t last_frame_sec = 0; struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); if ( now.tv_sec != last_frame_sec ) { char* title = NULL; asprintf(&title, "Asteroids (fps %i)", fps_counter); display_title_window(connection, WINDOW_ID, title); free(title); fps_counter = 0; last_frame_sec = now.tv_sec; } display_render_window(connection, WINDOW_ID, 0, 0, WINDOW_WIDTH, WINDOW_HEIGHT, buf); display_show_window(connection, WINDOW_ID); } void InitGame() { clock_gettime(CLOCK_MONOTONIC, &lastframeat); GenerateStarfield(starfield, STARFIELD_WIDTH, STARFIELD_HEIGHT); playership = new Spaceship(0.0, Vector(0, 0), Vector(4.0f, 0)); new AsteroidField; } int main(int argc, char* argv[]) { struct display_connection* connection = display_connect_default(); if ( !connection && errno == ECONNREFUSED ) display_spawn(argc, argv); if ( !connection ) error(1, errno, "Could not connect to display server"); display_create_window(connection, WINDOW_ID); display_resize_window(connection, WINDOW_ID, WINDOW_WIDTH, WINDOW_HEIGHT); display_title_window(connection, WINDOW_ID, "Asteroids"); InitGame(); gamerunning = true; for ( framenum = 0; gamerunning; framenum++ ) RunFrame(connection); display_disconnect(connection); return 0; }