Kuro5hin.org: technology and culture, from the trenches
create account | help/FAQ | contact | links | search | IRC | site news
[ Everything | Diaries | Technology | Science | Culture | Politics | Media | News | Internet | Op-Ed | Fiction | Meta | MLP ]
We need your support: buy an ad | premium membership

[P]
RogueLulz, Part I: Drawing the Map, Walking Around, Basic Monsters and Attacking.

By Joe Sixpack in Technology
Fri Nov 30, 2007 at 10:52:27 AM EST
Tags: RogueLulz, roguelike, computer games, the RORing 80's (all tags)

The first of a multi-part series on how to write an 80's style roguelike.

You should be familiar with c to better understand the source code. A C99 compiler (I use gcc) and a curses compatible library (I use ncurses) is required for compiling the code snippets in the article. The easiest way get both running on windows is to install cygwin.


1.1. Drawing the Map
The first thing we need is a map (aka level) to walk around in. To keep things simple we'll use a hard coded map for now (generating random maps will be discussed in a later RogueLulz article):


###############
#      #      #
#             #
#      ### ####
#### ###   #  #
#          #  #
#          #  #
#             #
#          #  #
###############

# - wall

Keeping with the tradition established by Rogue, we will use the at-mark (@) as our protagonist. The following code initializes a curses screen, draws the map and the player character and exits after a key has been pressed:

#include <curses.h>

char *map[]={
    "###############",
    "#      #      #",
    "#             #",
    "#      ### ####",
    "#### ###   #  #",
    "#          #  #",
    "#          #  #",
    "#             #",
    "#          #  #",
    "###############"
};

int main() {
    //initialize curses
    keypad(initscr(),1);
    curs_set(0);
   
    //player's starting coordinates
    int y=1;
    int x=1;
   
    //draw map
    for (int yy=0;yy<10;yy++)
        for (int xx=0;xx<15;xx++)
            mvaddch(yy,xx,map[yy][xx]);
    //draw player
    mvaddch(y,x,'@');

    //wait for key press before leaving
    getch();
    //clean up after we've finished using curses
    return endwin();
}

You can compile the code by running

gcc 1.1.c -std=c99 -lcurses -o roguelulz

1.2. Walking Around
Now that we have our map, let's handle input from the user: the arrow keys will move @ and the escape key will quit the game. We'll also put the drawing (mvaddch) and keyboard input (getch) in a while loop so that the program doesn't exit after one key press:

#define ESC 27 //ASCII for escape

//last key pressed
int c=0;
do {
    //draw map
    for (int yy=0;yy<10;yy++)
        for (int xx=0;xx<15;xx++)
            mvaddch(yy,xx,map[yy][xx]);
       
    //move player if there is no wall on the way
    if (KEY_UP==c && ' '==map[y-1][x])
        y--;
    if (KEY_DOWN==c && ' '==map[y+1][x])
        y++;
    if (KEY_LEFT==c && ' '==map[y][x-1])
        x--;
    if (KEY_RIGHT==c && ' '==map[y][x+1])
        x++;
       
    //draw player
    mvaddch(y,x,'@');
//quit when ESC is pressed
} while((ESC!=(c=getch())));

Compile again:

gcc 1.2.c -std=c99 -lcurses -o roguelulz

And @ can now walk around the map!

1.3. Basic Monsters
It's time to add some monsters - for now they'll just hang around waiting to be slain, we'll leave the AI for later. First let's define a struct to represent a monster. Every monster should have a location on the map and hitpoints- as should the player. The ent structure (short for entity) will be able to represent both:

typedef struct {
    int y,x,hp;
}ent;

We'll have a list of 12 entities in our map- 1 for the player and 11 monsters for him to fight:

#define ENTS_ 12
ent ent_l[ENTS_];

We'll also add a two dimensional array ent_m the size of our map, such that if an entity in our ent_l list is located on tile (x,y) in the map, ent_m[y][x] will point to it (Otherwise it will be NULL):

#define Y_ 10
#define X_ 15
ent *ent_m[Y_][X_];

Previous references to our map's dimensions have also been replaced by Y_ & X_ so we could easily change its size later. The ent_m array would allow us to easily find out if a tile is occupied in the following function. We initialise each of our entities to have 3 hitpoints and start in a random vacant tile (one that contains no wall and no other entity):

void init_ent() {
    for (int e=0;e<ENTS_;e++) {
        ent *ce=&ent_l[e];
        ce->hp=3;
        do {
            ce->y=rand()%Y_;
            ce->x=rand()%X_;
        } while ( '#'==map[ce->y][ce->x] || NULL!=ent_m[ce->y][ce->x] );
        ent_m[ce->y][ce->x]=ce;
    }
}

At this point we can change the x & y variables inside the main() function to pointers to the first entity, which is none other than our hero, @:

//initialize entities
srand(time(0));
init_ent();
   
//player's starting coordinates
int *y=&ent_l[0].y;
int *x=&ent_l[0].x;

Now @ starts at a pseudo-random position every time we load the game. Let's also change the line responsible for drawing the player to also draw the monsters:

//draw entities
for(int e=0;e<ENTS_;e++){
    mvaddch(ent_l[e].y,ent_l[e].x, 0==e?'@':'m');
}

Compile and check out the changes:

gcc 1.3.c -std=c99 -lcurses -o roguelulz

1.4. Attacking
As you may have noticed, although the monsters are placed and drawn, @ still passes through them as if they aren't. In this final section of RogueLulz, Part I we will implement a simple attack. Every time @ walks into a monster, the monster's hitpoints will be reduced by 1. When a monster reaches 0 hitpoints it dies. First we modify the drawing code to only draw entities with more than 0 hitpoints:

//draw entities
for(int e=0;e<ENTS_;e++){
    if (ent_l[e].hp>0)
        mvaddch(ent_l[e].y,ent_l[e].x, 0==e?'@':'m');

Then, we modify the movement code to have @ attack monsters he walks into, instead of just walking through them:

//move player if there is no living entity on the way
void move_to(int *y,int *x,int dy,int dx) {
    //remove reference to the player's old position
    ent_m[*y][*x]=NULL;
   
    //if the destination tile has an entity in it
    if (NULL!=ent_m[*y+dy][*x+dx]) {
        //decrement hitpoints of the entity at the destination tile
        ent *de=ent_m[*y+dy][*x+dx];
        de->hp--;
        //if it's still alive don't move into its place
        if (0 < de->hp) {
            dy=0;
            dx=0;
        //if it's dead remove its reference
        } else {
            ent_m[*y+dy][*x+dx]=NULL;
        }
    }
    //update player's position
    *y+=dy;
    *x+=dx;

    //add reference to the player's new position
    ent_m[*y][*x]=&ent_l[0];
}

and in the main loop:

//move player if there is no wall on the way
if (KEY_UP==c && ' '==map[*y-1][*x])
    move_to(y,x,-1,0);
if (KEY_DOWN==c && ' '==map[*y+1][*x])
    move_to(y,x,1,0);
if (KEY_LEFT==c && ' '==map[*y][*x-1])
    move_to(y,x,0,-1);
if (KEY_RIGHT==c && ' '==map[*y][*x+1])
    move_to(y,x,0,1);

At last, compile RogueLulz, Part I's final result:

gcc 1.4.c -std=c99 -lcurses -o roguelulz

1.5. Conclusion and Future Installments
Thus far we have learned how to draw the map, walk around it, add some dumb monsters and even attack them. In the next installments we'll add random map generation, multilevel dungeons, field-of-view/line-of-sight, AI and winning/losing conditions.

1.6. The Final Source Code

#include <curses.h>
#include <stdlib.h>
#include <time.h>

#define ESC 27// ASCII for escape

typedef struct {
    int y,x,hp;
}ent;

#define ENTS_ 12
ent ent_l[ENTS_];

#define Y_ 10
#define X_ 15
ent *ent_m[Y_][X_];

char *map[]={
    "###############",
    "#      #      #",
    "#             #",
    "#      ### ####",
    "#### ###   #  #",
    "#          #  #",
    "#          #  #",
    "#             #",
    "#          #  #",
    "###############"
};

void init_ent() {
    for (int e=0;e<ENTS_;e++) {
        ent *ce=&ent_l[e];
        ce->hp=3;
        do {
            ce->y=rand()%Y_;
            ce->x=rand()%X_;
        } while ( '#'==map[ce->y][ce->x] || NULL!=ent_m[ce->y][ce->x] );
        ent_m[ce->y][ce->x]=ce;
    }
}

//move player if there is no living entity on the way
void move_to(int *y,int *x,int dy,int dx) {
    //remove reference to the player's old position
    ent_m[*y][*x]=NULL;
   
    //if the destination tile has an entity in it
    if (NULL!=ent_m[*y+dy][*x+dx]) {
        //decrement hitpoints of the entity at the destination tile
        ent *de=ent_m[*y+dy][*x+dx];
        de->hp--;
        //if it's still alive don't move into its place
        if (0 < de->hp) {
            dy=0;
            dx=0;
        //if it's dead remove its reference
        } else {
            ent_m[*y+dy][*x+dx]=NULL;
        }
    }
    //update player's position
    *y+=dy;
    *x+=dx;

    //add reference to the player's new position
    ent_m[*y][*x]=&ent_l[0];
}

int main() {
    //initialize curses
    keypad(initscr(),1);
    curs_set(0);

    //initialize entities
    srand(time(0));
    init_ent();

    //player's starting coordinates
    int *y=&ent_l[0].y;
    int *x=&ent_l[0].x;

    //last key pressed
    int c=0;
    do {
        //draw map
        for (int yy=0;yy<Y_;yy++)
            for (int xx=0;xx<X_;xx++)
                mvaddch(yy,xx,map[yy][xx]);

        //move player if there is no wall on the way
        if (KEY_UP==c && ' '==map[*y-1][*x])
            move_to(y,x,-1,0);
        if (KEY_DOWN==c && ' '==map[*y+1][*x])
            move_to(y,x,1,0);
        if (KEY_LEFT==c && ' '==map[*y][*x-1])
            move_to(y,x,0,-1);
        if (KEY_RIGHT==c && ' '==map[*y][*x+1])
            move_to(y,x,0,1);

        //draw entities
        for (int e=0;e<ENTS_;e++) {
            if (ent_l[e].hp>0)
                mvaddch(ent_l[e].y,ent_l[e].x, 0==e?'@':'m');
        }
    //quit when ESC is pressed
    } while ((ESC!=(c=getch())));

    //clean up after we've finished using curses
    endwin();
}

1.7. Further Reading

Sponsors

Voxel dot net
o Managed Hosting
o VoxCAST Content Delivery
o Raw Infrastructure

Login

Related Links
o Google
o roguelike
o curses
o cygwin
o Rogue
o 1.1.c
o mvaddch
o getch
o 1.2.c
o struct
o srand
o 1.3.c
o 1.4.c
o Google Groups
o Roguelike Dev FAQ
o RogueBasin
o Also by Joe Sixpack


Display: Sort:
RogueLulz, Part I: Drawing the Map, Walking Around, Basic Monsters and Attacking. | 99 comments (69 topical, 30 editorial, 0 hidden)
great job, will try at home. (none / 0) (#4)
by tetsuwan on Thu Nov 29, 2007 at 06:50:10 AM EST


Njal's Saga: Just like Romeo & Juliet without the romance

I haven't programmed in C for years (none / 0) (#5)
by mybostinks on Thu Nov 29, 2007 at 07:59:20 AM EST

so it was fun reading through this.

I used to play Rogue-like games for hours and still do from time to time. They were some of the best games written IMHO. I believe Rogue games spawned a lot of game developers over the years.

+1 FP

Totally gay (3.00 / 5) (#10)
by Vampire Zombie Abu Musab al Zarqawi on Thu Nov 29, 2007 at 09:25:45 AM EST

This type of nerd wankery shouldn't interest anyone. +1 FP.

help a n00b (none / 0) (#17)
by tetsuwan on Thu Nov 29, 2007 at 09:51:09 AM EST

what does this do:

#define ENTS_ 12
ent ent_l[ENTS_];

?

Njal's Saga: Just like Romeo & Juliet without the romance

does this have a gui? (1.42 / 7) (#19)
by chlorus on Thu Nov 29, 2007 at 10:10:11 AM EST

please link to the version with the point and click interface.

Peahippo: Coked-up internet tough guy or creepy pedophile?

next n00b question (none / 1) (#25)
by tetsuwan on Thu Nov 29, 2007 at 12:11:17 PM EST

How do I get rid of the cursor? It appears after the last monster drawn and is struck there.

Njal's Saga: Just like Romeo & Juliet without the romance

I don't like the kill/move maneuver (none / 0) (#34)
by tetsuwan on Thu Nov 29, 2007 at 01:11:24 PM EST

I'd rewrite the extremely compact "if hp-1 = 0 then trample monster and remove it" to "if hp-1 = 0 then remove monster, but don't move"

Njal's Saga: Just like Romeo & Juliet without the romance

PLEASE PROVIDE CODE IN VISUAL BASIC 3.0 (1.50 / 2) (#48)
by circletimessquare on Thu Nov 29, 2007 at 03:11:12 PM EST

+1 fp

The tigers of wrath are wiser than the horses of instruction.

C is such an ugly language (2.33 / 3) (#52)
by I Hate Yanks on Thu Nov 29, 2007 at 06:45:00 PM EST

...and it's so 20th century.


Reasons to hate Americans (No. 812): Circletimessquare lives there.

Skip showering for a week, then do this in perl. (3.00 / 6) (#61)
by noogee on Fri Nov 30, 2007 at 06:09:03 AM EST


--
still here

About videogames +1 (none / 0) (#64)
by Redcatblack on Fri Nov 30, 2007 at 09:39:53 AM EST



Your vote (1) was recorded (3.00 / 2) (#66)
by Phssthpok on Fri Nov 30, 2007 at 10:53:58 AM EST

This story currently has a total score of 40.

You're the straw that broke the camel's back!
Your vote put this story over the threshold, and it should now appear on the front page. Enjoy!

Finally I hit one. Now I can stop reading k5 forever.
____________

affective flattening has caused me to kill 11,357 people

easier way to get both running on Windows (none / 0) (#67)
by raduga on Fri Nov 30, 2007 at 01:12:40 PM EST

part one    part two

Good FP AND Sectional Articles posted... (none / 0) (#72)
by HyperMediocrity on Fri Nov 30, 2007 at 08:00:18 PM EST

WTF is going on?!

From the input i've received (none / 0) (#73)
by Joe Sixpack on Fri Nov 30, 2007 at 09:02:28 PM EST

the line:

//decrement entity's hitpoints & if it's
//still alive don't move into its place
if (0<--(ent_m[*y+dy][*x+dx]->hp)) {

is too difficult to understand and should be replaced, perhaps by:

//decrement hitpoints of the entity at the destination tile
ent *de=ent_m[*y+dy][*x+dx];
de->hp--;
//if it's still alive don't move into its place
if (0 < de->hp) {

I'll write rusty an email and hopefully this change will make the code easier to understand.

---
[ MONKEY STEALS THE PEACH ]

Adventure game for the kids (none / 1) (#79)
by yuo on Sat Dec 01, 2007 at 09:31:17 AM EST

This type of article is an excellent way to introduce people to programming. In fact, Don Knuth recommends that people program (i.e. create programs) this way, by interweaving prose with functional source code. This style is called Literate Programming.

Literate Programming is an effective way to improve your programming style and (perhaps ironically) Don Knuth says that it helps you program more quickly because you have to explain your code in plain English before you write it. As far as I know, Knuth uses this system when he writes software.

Interestingly, on the afore-linked website, one of the examples used to demonstrate Literate Programming is "Adventure", an early computer RPG, though not a Roguelike. You can find the game and other examples of Literate Programming on the CWEB examples page. Here's a direct link to the PDF of Don Knuth's version of Adventure.

I wish I had thought of pants pants pants pants pants pants pants pants.

I made similar thing in Python (none / 1) (#89)
by Rainy on Sun Dec 02, 2007 at 07:30:27 PM EST

I think this would fly much better in Python, or Perl or Ruby. This is not photorealistic doom3 engine, it makes no sense to do this in C now. I made a playable game with very little code, probably 1/10th of what it would take in C. I'm not a great coder by any means, I'm more of a hobbyist and even then I don't code much. So, a lot of things there are ugly but it is very easy to understand and change, Python makes code very clear, the structure of the game is very minimal, part of it was that I wanted to make an example, a sort of a working roguelike engine in Python. To play it, you need either linux with curses and python, or cygwin with curses & python. There may still be bugs, not only in the dungeons but in the code.

http://www.silmarill.org/  in menu on the left, go to programs/I, monster game.
--
Rainy "Collect all zero" Day

In SUSE Linux (none / 0) (#95)
by tetsuwan on Mon Dec 03, 2007 at 08:30:04 AM EST

"-lcurses" doesn't work, you have to use "-lncurses" whne compiling.

Njal's Saga: Just like Romeo & Juliet without the romance

RogueLulz, Part I: Drawing the Map, Walking Around, Basic Monsters and Attacking. | 99 comments (69 topical, 30 editorial, 0 hidden)
Display: Sort:

kuro5hin.org

[XML]
All trademarks and copyrights on this page are owned by their respective companies. The Rest © 2000 - Present Kuro5hin.org Inc.
See our legalese page for copyright policies. Please also read our Privacy Policy.
Kuro5hin.org is powered by Free Software, including Apache, Perl, and Linux, The Scoop Engine that runs this site is freely available, under the terms of the GPL.
Need some help? Email help@kuro5hin.org.
My heart's the long stairs.

Powered by Scoop create account | help/FAQ | mission | links | search | IRC | YOU choose the stories!