Sandworm is a game for the Windows platform inspired by the old arcade game Centipede. It’s another challenge game, like the original version of Inaria. This time, I challenged myself to write a game in a single page of source – a page being defined as 60 rows and 80 columns. I call these One-Page Games.

Lots of heads!

Here I’ve chopped the sandworm up into sushi…I’m probably going to regret that in a few minutes.

To complete the challenge, I did the whole game in text mode. And, of course, the source is available. In fact, here it is right here!


#include <windows.h>
#include <vector>
using namespace std;HANDLE wh;HANDLE rh;CHAR_INFO bb[4000];COORD cBS={80,50};
COORD cPos = {0,0};SMALL_RECT wA = {0,0,79,49};bool running=true;int fc = 0;
struct u{int x;int y;int dx;int dy;int state;};vector<u> c;vector<u> mush;
vector<u> bullets;u s = {20, 49, 20, 49, 1};int score,lives,i,j;void SetChar(
int x, int y, char c, int a){bb[(y*80)+x].Char.AsciiChar = c;bb[(y*80)+x].
Attributes = a;}void ResetC(){c.clear();u head={19,0,1,1,1};c.push_back(head);
for(i=18;i>=0;--i){u b={i,0,0,0,0};c.push_back(b);}}void SetPos(int x,int y)
{COORD c1={x,y};SetConsoleCursorPosition(wh,c1);}void MoveCP(){if(fc%2){for(i=
19;i>-1;--i){int headx, heady;if(c[i].state == 1){int tx = c[i].x + c[i].dx;
bool hm = false;for( j = 0; j< mush.size(); ++j){if( c[i].x+c[i].dx == mush[j]
.x && c[i].y == mush[j].y && mush[j].state )hm=true;}if( tx > 40 || tx < 0 ||
hm){c[i].dx = -c[i].dx;int ty = c[i].y+c[i].dy;if(ty > 49 || ty < 0 || (ty < 39
&& c[i].dy == -1)){c[i].dy = -c[i].dy;}c[i].y += c[i].dy;}else{c[i].x += c[i]
.dx;}}else if(c[i].state != -1){c[i].x = c[i-1].x;c[i].y = c[i-1].y;}}}}void
MoveS(){bool ok = true;for(i = 0; i < mush.size(); ++i){if(s.dx == mush[i].x &&
s.dy == mush[i].y)ok=false;}if(ok){s.x=s.dx;s.y = s.dy;}}void MoveB(){for(j =
0; j < 2; ++j){for(i = 0;i < 20; ++i){if(bullets[j].x == c[i].x && bullets[j].y
== c[i].y && bullets[j].state != 0 && c[i].state != -1){if(c[i].state == 0)
score+=10;else score+=50;u m={c[i].x,c[i].y,0,0,1};mush.push_back(m);c[i].state
= -1;if(i != 19){if(c[i+1].state != -1){c[i+1].state = 1;(c[i+1].x > c[i].x ?
c[i+1].dx = -1 : c[i+1].dx = 1);c[i+1].dy = 1;}}int lc=0;for(int z = 0; z < 20;
++z){if(c[z].state != -1)++lc;}if(lc == 0)ResetC();bullets[j].state = 0;}}for(i
= 0; i < mush.size(); ++i){if(bullets[j].x == mush[i].x && bullets[j].y == mush
[i].y && bullets[j].state != 0 && mush[i].state == 1){bullets[j].state = 0;
mush[i].state = 0;score+=3;}}if(bullets[j].state == 1){--bullets[j].y;if(
bullets[j].y < 0)bullets[j].state=0;}}}void Init(){wh = GetStdHandle(
STD_OUTPUT_HANDLE);rh = GetStdHandle(STD_INPUT_HANDLE);SMALL_RECT w = {0,0,79,
49};SetConsoleWindowInfo(wh, TRUE, &w);COORD b={80,50};
SetConsoleScreenBufferSize(wh, b);}void Input(){DWORD nE=0;DWORD nER=0;
GetNumberOfConsoleInputEvents(rh,&nE);if(nE != 0){INPUT_RECORD *eB=new
INPUT_RECORD[nE];ReadConsoleInput(rh,eB,nE,&nER);for(i = 0; i < nER; ++i){if(
eB[i].EventType == KEY_EVENT){if(eB[i].Event.KeyEvent.wVirtualKeyCode ==
VK_ESCAPE){running=false;}}else if(eB[i].EventType == MOUSE_EVENT){s.dx = eB[i]
.Event.MouseEvent.dwMousePosition.X;if(s.dx > 40)s.dx=40;s.dy=eB[i].Event.
MouseEvent.dwMousePosition.Y;if(s.dy < 40)s.dy = 40;if(eB[i].Event.MouseEvent.
dwButtonState & FROM_LEFT_1ST_BUTTON_PRESSED){for(int z = 0; z < 2; ++z){if(
bullets[z].state == 0){bullets[z].x = s.x;bullets[z].y = s.y-1;bullets[z].state
= 1;break;}}}}}delete eB;}}void ResetM(){mush.clear();for(i = 0;i < 100; ++i)
{u m={rand()%40, (rand()%46)+1, 0, 0, 1};mush.push_back(m);}}int main(int argc,
char* argv[]){Init();ResetC();ResetM();u b1={ -1, -1, 0, 0, 0};bullets.
push_back(b1);bullets.push_back(b1);score=0;lives=3;while(running){ZeroMemory(
bb,4000*sizeof(CHAR_INFO));Input();MoveCP();MoveS();MoveB();for(i = 0; i < 50;
++i)SetChar(41, i, 219, 15);SetChar(s.x, s.y, 127, 15);for(i = 0;i < 20; ++i)
{if(c[i].state == 1){SetChar(c[i].x,c[i].y, 1, 2);}else if(c[i].state == 0){
SetChar(c[i].x, c[i].y, 15, 2);}}for(i = 0; i < mush.size(); ++i){if(mush[i]
.state){SetChar(mush[i].x, mush[i].y, 6, 14);}}for(i = 0; i < 2; ++i){if(
bullets[i].state)SetChar(bullets[i].x, bullets[i].y, 24, 15);}
WriteConsoleOutput(wh, bb, cBS, cPos, &wA);bool ll = false;for(i = 0; i < c.
size(); ++i){if(c[i].state != -1 && c[i].x == s.x && c[i].y == s.y){--lives;
ll=true;ResetC();}}int mc = 0;for(i = 0; i < mush.size(); ++i)if(mush[i].state)
++mc;if(mc<50){int nm = 0;do{nm = rand() % mush.size();} while(mush[nm].state);
mush[nm].state = 1;}SetPos(45,0);printf("SANDWORM!");SetPos(45, 1); printf(
"Score: %d", score);SetPos(45, 2);printf("Lives: %d", lives);if(!lives){SetPos(
16, 20);printf("GAME OVER");Sleep(5000);score=0;lives=3;ResetM();}else if(ll)
Sleep(1000);else Sleep(17);++fc;}return 0;}

Create a new console application project in Visual C++ 2005 or later, paste that into the source file, and compile it. If you have trouble compiling the file, it might be easier to debug if you had a cleaner version – so here’s one.

Or, if that’s too much trouble, you can just download the project and build it yourself.

Or if that’s too much trouble, you can just download the executable!

Control your shooter with the mouse and fire with the left mouse button. Shooting the sandworm creates a new rock and splits the sandworm into two separate parts. Breaking a rock gets you three points, hitting a sandworm segment gets you ten points, and hitting a sandworm head gets you fifty points. You can have up to two shots on the screen at the same time. If you manage to kill the entire sandworm, a new one respawns. If any part of the sandworm hits you, you lose a life. Lose all your lives and it’s game over, but a new game will start in a few seconds automatically. Press ESC on your keyboard to exit the game.