Gish Source

Gish Screenshot

Now that the Gish source code has been posted, it's time to actually look at this thing. Here are some of my notes while chugging through this thing quickly:

  1. Firstly, this project is in C, and not C++. C tends to be faster and more portable, but large game project tend to go with C++, so this surprised me, but in a good way. :)
  2. Secondly, in order to compile for Apple computers, you need to compile with "THINKSTUPID" defined. I lol'ed heartily about that.
  3. Thus far, I have not found any useful comments. They're all either licencing info or commented-out code. Too bad. :/
  4. This code uses OpenGL all over the place, not just in one neat file. This sucks, because in order for someone to port this to the Pandora, they'll have to go file-by-file through out this project, converting the glBegin()/glEnd() code to OpenGL ES's vertex buffers.
  5. Lots of the code REEKS of copy-and-paste, and magic numbers are littered throughout the code. This results in functions that are hundreds or thousands of lines long, with HUGE if-blocks that span nearly the entire function. Plus, many of the other functions could have been written WAY nicer. Consider loadstorylevel() from setup.c:
    void loadstorylevel(int levelnum)
    {
    #ifndef DEMO
    if (levelnum==1)
    loadlevel("sewer1.lvl");
    if (levelnum==2)
    loadlevel("sewer2.lvl");
    if (levelnum==3)
    loadlevel("sewer3.lvl");
    if (levelnum==4)
    loadlevel("sewer4.lvl");
    if (levelnum==5)
    loadlevel("sewer5.lvl");
    if (levelnum==6)
    loadlevel("sewer6.lvl");
    if (levelnum==7)
    loadlevel("sewer7.lvl");

    if (levelnum==8)
    loadlevel("cave1.lvl");
    if (levelnum==9)
    loadlevel("cave2.lvl");
    if (levelnum==10)
    loadlevel("cave3.lvl");
    if (levelnum==11)
    loadlevel("cave4.lvl");
    if (levelnum==12)
    loadlevel("cave5.lvl");
    if (levelnum==13)
    loadlevel("cave6.lvl");
    if (levelnum==14)
    loadlevel("cave7.lvl");

    if (levelnum==15)
    loadlevel("hell1.lvl");
    if (levelnum==16)
    loadlevel("hell2.lvl");
    if (levelnum==17)
    loadlevel("hell3.lvl");
    if (levelnum==18)
    loadlevel("hell4.lvl");
    if (levelnum==19)
    loadlevel("hell5.lvl");
    if (levelnum==20)
    loadlevel("hell6.lvl");
    if (levelnum==21)
    loadlevel("hell7.lvl");

    if (levelnum==22)
    loadlevel("egypt1.lvl");
    if (levelnum==23)
    loadlevel("egypt2.lvl");
    if (levelnum==24)
    loadlevel("egypt3.lvl");
    if (levelnum==25)
    loadlevel("egypt4.lvl");
    if (levelnum==26)
    loadlevel("egypt5.lvl");
    if (levelnum==27)
    loadlevel("egypt6.lvl");
    if (levelnum==28)
    loadlevel("egypt7.lvl");

    if (levelnum==29)
    loadlevel("church1.lvl");
    if (levelnum==30)
    loadlevel("church2.lvl");
    if (levelnum==31)
    loadlevel("church3.lvl");
    if (levelnum==32)
    loadlevel("church4.lvl");
    if (levelnum==33)
    loadlevel("church5.lvl");
    if (levelnum==34)
    loadlevel("church6.lvl");
    if (levelnum==64)
    loadlevel("sewer8.lvl");
    if (levelnum==65)
    loadlevel("cave8.lvl");
    if (levelnum==66)
    loadlevel("egypt8.lvl");
    if (levelnum==67)
    loadlevel("death.lvl");
    if (levelnum==68)
    loadlevel("death2.lvl");
    #else
    if (levelnum==1)
    loadlevel("demo1.lvl");
    if (levelnum==2)
    loadlevel("demo2.lvl");
    if (levelnum==3)
    loadlevel("demo3.lvl");
    if (levelnum==4)
    loadlevel("demo4.lvl");
    if (levelnum==5)
    loadlevel("demo5.lvl");
    #endif
    }
    That very well could have been just:
    void loadstorylevel(int levelnum) {
    char buf[16];

    assert(levelnum>0);

    #ifndef DEMO
    if (levelnum<=7 ) sprintf(buf, "sewer%d.lvl", levelnum);
    else if(levelnum<=14) sprintf(buf, "cave%d.lvl", levelnum-7);
    else if(levelnum<=21) sprintf(buf, "hell%d.lvl", levelnum-14);
    else if(levelnum<=28) sprintf(buf, "egypt%d.lvl", levelnum-21);
    else if(levelnum<=34) sprintf(buf, "church%d.lvl",levelnum-28);

    // All of the super-secret bonus levels are special cases:
    else if(levelnum==64) sprintf(buf, "sewer8.lvl");
    else if(levelnum==65) sprintf(buf, "cave8.lvl");
    else if(levelnum==66) sprintf(buf, "egypt8.lvl");
    else if(levelnum==67) sprintf(buf, "death.lvl");
    else if(levelnum==68) sprintf(buf, "death2.lvl");

    else assert(0);

    #else
    if (levelnum<=5) sprintf(buf, "demo%d.lvl", levelnum);
    else assert(0);

    #endif

    loadlevel(buf);
    }
    I know it really doesn't matter; the code does the same thing, but the code on the bottom is shorter and easier to read.
  6. I'm seeing all kinds of code that relies on chdir()'s, and other functions that force the program to be run out of the same directory as the executable. Plus, the high-scores, and game saves go into that same directory as well, and replays get saved into a replay directory. That type of code may be cool on Windows, but it's going to need some rewriting if we ever want to see a Gish *.deb, or *.rpm.
  7. Check out animation.c if you dare. 1 function. 1337 lines of code. That's a cool number for it to be, but O.O at the actual function...
  8. Finally, the reason I dove into this code in the first place... the physics code. The physics code is surprisingly simple. Gish himself is merely a bundle of unbreakable bonds and springs. (See: createtarboy() in object.c), as are all of the dynamic blocks and such. From what I can tell, the internals of their physics engine is actually incredibly simple. It will simply go through all dynamic objects subject for collision, and will then compare that block to every other dynamic block for collision handling. Each block will compare the level separately from the blocks, separately from the particles, and so on. With this engine, each physics update has a O(n2) running time, where n is the number of dynamic objects in the level. This simple approach seems to have worked well for Gish, but probably won't work for other projects, so don't try to use the Gish physics engine in other projects... It would probably be best to just stick with more advanced 2d physics libraries, like Box2d for C++, and Chipmunk for C. (I'm personally a huge fan of Chipmunk, and I used it in that Tetris Hell game I posted a short while back.)

One final note: this list may seem a little harsh at points, but I don't want it to be. I know that I have coded some pretty horrible things when I've been under pressure, and I don't want to act like I'm above committing my own personal coding sins. I am completely thrilled that the Gish code is now free under the GPL, and I hope more projects do the same!

That said, rewriting a few bits couldn't hurt... ;)


2 Responses to Gish Source

  1. 179 Pathogen David 2010-06-01 08:59:33

    Yeah, both Lugaru and Gish's source are appallingly bad. (Gish being slightly better.) I was thinking about porting Lugaru to the Nintendo DS as homebrew, but it has the same issue as Gish where OpenGL is deeply woven in to the main code instead of abstracted.

  2. 180 ray 2010-06-02 14:16:52

    @David

    Man, you're not kidding. I've finally gotten around to looking at Lugaru's source, and that stuff is pretty jaw-dropping. The functions are long, and unnecessarily nested. The code is poorly formatted, and has hundreds of 'extern' statements in the cpp files, and OpenGL is littered throughout the source. At least there are occasional comments...

    I mean, I understand that game developers just want to get the product out there, and may not entirely care about the quality of their repositories, but there HAS to be some sort of limit...

    Penumbra is a good example. Their source, while devoid of useful comments, is still very well done. It follows fairly strict object-oriented principles, and utilizes inheritance heavily. Their code is easy-to-follow, and understandable. Here's to hope that Aquaria, when it comes out, is more like Penumbra and less like Gish or Lugaru.

Leave a Reply



About

User