#include #include #include #include #include #include #include #include #include #include #include // This define runs the game without actually setting the video mode and // checking whether the frame was actually copied to the screen. useful for // debugging the game since you can't see console output. //#define HACK_DONT_CHECK_FB // TODO: Hacks that should belong in libm or something. extern "C" float sqrtf(float x) { float ret; asm ("fsqrt" : "=t" (ret) : "0" (x)); return ret; } extern "C" void sincosf(float x, float* sinval, float* cosval) { asm ("fsincos" : "=t" (*cosval), "=u" (*sinval) : "0" (x)); } const float PI = 3.1415926532f; inline float RandomFloat() { return (float) rand() / 32768.0f; } inline float RandomFloat(float min, float max) { return min + RandomFloat() * (max - min); } inline float DegreeToRadian(float degree) { return degree / 180 * PI; } inline float RandomAngle() { return RandomFloat() * DegreeToRadian(360); } inline uint32_t MakeColor(uint8_t r, uint8_t g, uint8_t b) { return b << 0UL | g << 8UL | r << 16UL; } const size_t STARFIELD_WIDTH = 512UL; const size_t STARFIELD_HEIGHT = 512UL; 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; int randval = rand() % 256; bool isstar = randval == 5 || randval == 42 || randval == 101; if ( isstar ) { color = rand(); } bitmap[i] = MakeColor(color, color, color); } } const size_t MAXKEYNUM = 512UL; bool keysdown[MAXKEYNUM] = { false }; void FetchKeyboardInput() { // Read the keyboard input from the user. const unsigned termmode = TERMMODE_KBKEY | TERMMODE_UNICODE | TERMMODE_SIGNAL | TERMMODE_NONBLOCK; if ( settermmode(0, termmode) ) { error(1, errno, "settermmode"); } uint32_t codepoint; ssize_t numbytes; while ( 0 < (numbytes = read(0, &codepoint, sizeof(codepoint))) ) { int kbkey = KBKEY_DECODE(codepoint); int abskbkey = (kbkey < 0) ? -kbkey : kbkey; if ( MAXKEYNUM <= abskbkey ) { continue; } keysdown[abskbkey] = 0 < kbkey; } } size_t xres; size_t yres; int fb; size_t bpp; size_t linesize; size_t framesize; uint32_t* buf; bool gamerunning; 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 && x0 < xres && 0 <= y0 & 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; } Vector& operator-=(const Vector& rhs) { x -= rhs.x; y -= rhs.y; } Vector& operator*=(float scalar) { x *= scalar; y *= scalar; } 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; } 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 Rotate(float radians) const { float sinr; float cosr; sincosf(radians, &sinr, &cosr); 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; Object* firstobject = NULL; Object* lastobject = NULL; Vector screenoff; 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 OnFrame(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() { } virtual ~Actor() { } public: virtual void OnFrame(float deltatime) { Think(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; }; void Actor::Move(float deltatime) { vel += acc * deltatime; pos += vel * deltatime; } class Asteroid : public Actor { public: Asteroid(Vector pos, Vector vel, float size); 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(); private: static const size_t MIN_POLYS = 5; static const size_t MAX_POLYS = 12; static const float MAX_TURN_SPEED = 50.0f; size_t numpolygons; float slice; float polydists[MAX_POLYS+1]; float size; float angle; float turnspeed; }; Asteroid::Asteroid(Vector pos, Vector vel, float size) { this->pos = pos; this->vel = vel; this->size = size; angle = 0.0f; turnspeed = DegreeToRadian(MAX_TURN_SPEED) * (RandomFloat() * 2.0f - 1.0f); numpolygons = MIN_POLYS + rand() % (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); float slice = DegreeToRadian(360.0f) / (float) numpolygons; 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); } if ( MINIMUM_SIZE <= sizeb ) { Vector astvel = vel.Rotate(RandomFloat(0.0, -MAX_ANGLE)) * 1.2; new Asteroid(pos - axis, astvel, sizeb); } 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, 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(); private: float ttl; }; Missile::Missile(Vector pos, Vector vel, float ttl) { this->pos = pos; this->vel = vel; this->ttl = ttl; } 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 + vel * (MISSILE_LEN / vel.Size()); DrawLine(shipcolor, from.x, from.y, to.x, to.y); } Missile::~Missile() { } class Spaceship : public Actor { 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 firing); private: bool turnleft; bool turnright; bool moveforward; bool movebackward; bool firing; float shipangle; }; Spaceship::Spaceship(float shipangle, Vector pos, Vector vel, Vector acc) { this->shipangle = shipangle; this->pos = pos; this->vel = vel; this->acc = acc; turnleft = turnright = moveforward = movebackward = firing = 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; if ( !ast->InsideMe(pos) ) { continue; } ast->OnHit(); pos.y = 16384 - pos.y; 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; } acc = Vector(shipaccel, 0.0).Rotate(shipangle); float shipspeed = vel.Size(); float maxspeed = 50.0f; if ( maxspeed < shipspeed ) { vel *= maxspeed / shipspeed; } if ( firing ) { 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); new Missile(spawnpos, spawnvel, ttl); } } void Spaceship::Render() { Vector screenpos = pos - screenoff; // TODO: Ideally these should be global constants, but global constructors // are _not_ called upon process initiazation on Sortix yet. 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(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 firing) { this->firing = firing; } uintmax_t lastframeat; void GameLogic() { uintmax_t now; do uptime(&now); while ( now == lastframeat); unsigned long deltausecs = now - lastframeat; lastframeat = now; float deltatime = deltausecs / 1000000.0f; float timescale = 3.0; deltatime *= timescale; Object* first = firstobject; Object* obj; for ( obj = first; obj; obj = obj->NextObj() ) { obj->GCBirth(); } playership->SetThrust(keysdown[KBKEY_UP], keysdown[KBKEY_DOWN]); playership->SetTurn(keysdown[KBKEY_LEFT], keysdown[KBKEY_RIGHT]); playership->SetFiring(keysdown[KBKEY_SPACE]); keysdown[KBKEY_SPACE] = false; for ( obj = first; obj; obj = obj->NextObj() ) { if ( !obj->GCIsBorn() ) { continue; } obj->OnFrame(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 FlushBuffer() { lseek(fb, 0, SEEK_SET); if ( writeall(fb, buf, framesize) < framesize ) { #ifndef HACK_DONT_CHECK_FB error(1, errno, "writing to framebuffer"); #endif } } void RunFrame() { FetchKeyboardInput(); GameLogic(); Render(); FlushBuffer(); } char* GetCurrentVideoMode() { FILE* fp = fopen("/dev/video/mode", "r"); if ( !fp ) { return NULL; } char* mode = NULL; size_t n = 0; getline(&mode, &n, fp); fclose(fp); return mode; } // TODO: This should be in libc and not use libmaxsi. #include #include #include using namespace Maxsi; bool ReadParamString(const char* str, ...) { if ( String::Seek(str, '\n') ) { Error::Set(EINVAL); } const char* keyname; va_list args; while ( *str ) { size_t varlen = String::Reject(str, ","); if ( !varlen ) { str++; continue; } size_t namelen = String::Reject(str, "="); if ( !namelen ) { Error::Set(EINVAL); goto cleanup; } if ( !str[namelen] ) { Error::Set(EINVAL); goto cleanup; } if ( varlen < namelen ) { Error::Set(EINVAL); goto cleanup; } size_t valuelen = varlen - 1 /*=*/ - namelen; char* name = String::Substring(str, 0, namelen); if ( !name ) { goto cleanup; } char* value = String::Substring(str, namelen+1, valuelen); if ( !value ) { delete[] name; goto cleanup; } va_start(args, str); while ( (keyname = va_arg(args, const char*)) ) { char** nameptr = va_arg(args, char**); if ( String::Compare(keyname, name) ) { continue; } *nameptr = value; break; } va_end(args); if ( !keyname ) { delete[] value; } delete[] name; str += varlen; str += String::Accept(str, ","); } return true; cleanup: va_start(args, str); while ( (keyname = va_arg(args, const char*)) ) { char** nameptr = va_arg(args, char**); delete[] *nameptr; *nameptr = NULL; } va_end(args); return false; } int atoi_safe(const char* str) { if ( !str ) { return 0; } return atoi(str); } void InitGame() { uptime(&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[]) { #ifndef HACK_DONT_CHECK_FB char* vidmode = GetCurrentVideoMode(); if ( !vidmode ) { perror("Cannot detect current video mode"); exit(1); } char* widthstr = NULL; char* heightstr = NULL; if ( !ReadParamString(vidmode, "width", &widthstr, "height", &heightstr, NULL) ) { error(1, errno, "Can't parse video mode: %s", vidmode); } xres = atoi_safe(widthstr); delete[] widthstr; widthstr = NULL; yres = atoi_safe(heightstr); delete[] heightstr; heightstr = NULL; if ( !xres || !yres ) { const char* chvideomode = "chvideomode"; execlp(chvideomode, chvideomode, argv[0], NULL); perror(chvideomode); exit(127); } #else xres = 1280; yres = 720; #endif fb = open("/dev/video/fb", O_WRONLY); #ifndef HACK_DONT_CHECK_FB if ( fb < 0 ) { error(1, errno, "open: /dev/video/fb"); } #endif bpp = sizeof(uint32_t); linesize = xres; framesize = yres * linesize * bpp; buf = new uint32_t[framesize / sizeof(uint32_t)]; InitGame(); gamerunning = true; for ( framenum = 0; gamerunning; framenum++ ) { RunFrame(); } close(fb); return 0; }