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

all occurences of ENTS_ in the code (none / 0) (#18)
by Joe Sixpack on Thu Nov 29, 2007 at 09:58:01 AM EST

are replaced by the number 12. ent ent_l[ENTS_]; --> allocate an array of the size 12*sizeof(ent) on the stack. Tty to use a variable instead of the #define statement and you will get a "variably modified at file scope" error.

---
[ MONKEY STEALS THE PEACH ]
[ Parent ]

NUM_ENTS would have been better (none / 0) (#99)
by ensignyu on Tue Dec 11, 2007 at 02:07:31 AM EST

Also, ent_list instead of ent_l. No point abbreviating to save three letters, especially at the expense of readability.

[ Parent ]
12 Ents (3.00 / 4) (#57)
by b1t r0t on Thu Nov 29, 2007 at 11:59:16 PM EST

It causes 12 large tree-like creatures to appear. They will challenge you, on the chance that you might be one of those nasty dwarves with their axes, chopping at everything in sight. If you can convince them that you will not chop them down, they will let you ride on them.

-- Indymedia: the fanfiction.net of journalism.
[ Parent ]
no no no (none / 0) (#60)
by tetsuwan on Fri Nov 30, 2007 at 04:08:25 AM EST

charge the ents! For Khazad Dür!

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

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?

With pleasure: (none / 1) (#20)
by Joe Sixpack on Thu Nov 29, 2007 at 10:17:51 AM EST

Rogue: The Adventure Game.

---
[ MONKEY STEALS THE PEACH ]
[ Parent ]

See also : (none / 0) (#23)
by some nerd on Thu Nov 29, 2007 at 10:49:24 AM EST

Nethack - Falcon's Eye.

--
Home Sweet Home

[ Parent ]
looks pretty nice (none / 0) (#24)
by tetsuwan on Thu Nov 29, 2007 at 11:39:17 AM EST

although something is missing in this screen shot.

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

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

curs_set(0) (none / 0) (#27)
by Joe Sixpack on Thu Nov 29, 2007 at 12:29:58 PM EST

curs_set controls the visibility of the cursor - you can simply set it to 0 (Invisible).

maybe i should add it to the last source file...

---
[ MONKEY STEALS THE PEACH ]
[ Parent ]

I've put curs_set(0); in the beginning of the main (none / 0) (#30)
by tetsuwan on Thu Nov 29, 2007 at 12:53:11 PM EST

function and curs_set(1); in the end, but nothing happens :(

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

You don't need curs_set(1) (none / 0) (#31)
by Joe Sixpack on Thu Nov 29, 2007 at 12:55:25 PM EST

just try the new code i uploaded.

---
[ MONKEY STEALS THE PEACH ]
[ Parent ]

0, 1 or 2 (none / 0) (#33)
by tetsuwan on Thu Nov 29, 2007 at 01:08:25 PM EST

curs_set does nothing for me in my BSD shell. The cursor, it remains.

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

Maybe your version of curses or your terminal (none / 0) (#35)
by Joe Sixpack on Thu Nov 29, 2007 at 01:15:36 PM EST

isn't quite standard complaint. I don't know of any other way to change the cursor's visibility and the docs says 0 should make it invisible (and indeed it does on my pc).

---
[ MONKEY STEALS THE PEACH ]
[ Parent ]

This is because BSD is dying nt (3.00 / 3) (#53)
by some nerd on Thu Nov 29, 2007 at 07:41:43 PM EST



--
Home Sweet Home

[ Parent ]
Then move it out of the way (none / 0) (#58)
by b1t r0t on Fri Nov 30, 2007 at 12:00:51 AM EST

If your terminal doesn't let you turn off the cursor, then move it to a bottom corner of the screen.

-- Indymedia: the fanfiction.net of journalism.
[ Parent ]
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

I like the current way better, (none / 0) (#36)
by Joe Sixpack on Thu Nov 29, 2007 at 01:19:25 PM EST

but since you have the source code you can make your version do whatever you want!

Also, regarding your compatibility problems - maybe give pdcurses a try, it should be less system dependent that ncurses since it can use X or SDL instead of the terminal.

---
[ MONKEY STEALS THE PEACH ]
[ Parent ]

I'm trying to (none / 0) (#41)
by tetsuwan on Thu Nov 29, 2007 at 01:38:41 PM EST

but I keep getting bus errors. I parse

(0<--(ent_m[*y+dy][*x+dx]->hp))

"Zero is smaller than hp of attacked monster decremented by 1 (and reduce said monster's hp by one while checking)" Is that correct?

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

First you should look at (none / 0) (#42)
by Joe Sixpack on Thu Nov 29, 2007 at 01:54:24 PM EST

the operator precedence.

That line means:
1. get the pointer of the entity at (*y+dy,*x+dx)
2. get the hp
3. decrement hp
4. check if the result is smaller than 0.

Anyway the change you want to do should replace that section with something like:

int hp = --(ent_m[*y+dy][*x+dx]->hp);
//if it's dead remove its reference
if (0>=hp) {
    ent_m[*y+dy][*x+dx]=NULL;
}
dy=0;
dx=0;


---
[ MONKEY STEALS THE PEACH ]
[ Parent ]

sure (none / 0) (#43)
by tetsuwan on Thu Nov 29, 2007 at 02:09:56 PM EST

I don't understand why I can't do a similarly compact variant of my preferred fighting style, that is, why this doesn't work:

if (NULL!=ent_m[*y+dy][*x+dx]) {
dy=0;
dx=0;
if (1>--(ent_m[*y+dy][*x+dx]->hp)) {
ent_m[*y+dy][*x+dx]=NULL;
}
}

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

urm, what I posted is actually shorter than the (none / 0) (#44)
by Joe Sixpack on Thu Nov 29, 2007 at 02:19:21 PM EST

original:

if (0<--(ent_m[*y+dy][*x+dx]->hp)) {
    dy=0;
    dx=0;
//if it's dead remove its reference
} else {
    ent_m[*y+dy][*x+dx]=NULL;
}

Can be replaced by:

int hp = --(ent_m[*y+dy][*x+dx]->hp);
//if it's dead remove its reference
if (0>=hp) {
    ent_m[*y+dy][*x+dx]=NULL;
}
dy=0;
dx=0;


---
[ MONKEY STEALS THE PEACH ]
[ Parent ]

You are zeroing the deltas too early i think.% (none / 0) (#45)
by Joe Sixpack on Thu Nov 29, 2007 at 02:21:18 PM EST


---
[ MONKEY STEALS THE PEACH ]
[ Parent ]

thanks, silly mistake. (none / 0) (#46)
by tetsuwan on Thu Nov 29, 2007 at 02:27:54 PM EST


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

Including this line was a mistake (none / 0) (#75)
by Joe Sixpack on Sat Dec 01, 2007 at 05:26:31 AM EST

it's too difficult to understand.

I've changed it to something less tricky on googlecode, I hope rusty will read the email I've sent him and change that line in the article as well.

---
[ MONKEY STEALS THE PEACH ]
[ Parent ]

I understood it (none / 0) (#78)
by tetsuwan on Sat Dec 01, 2007 at 09:09:50 AM EST

but it was not obvious.

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

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.

I didn't know c before reading this (3.00 / 4) (#49)
by tetsuwan on Thu Nov 29, 2007 at 03:24:36 PM EST

Now my monsters are 'j's that turn into '%'s when killed, and the monsters and characters attack with random blows based on their strength. If the player is killed, he turns into a '%' and you can't move him.

BTW, I use ent_l[0] to refer to the player, I don't know if that's the best way to do it.

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

if it's based on my code that's the only obvious (none / 1) (#50)
by Joe Sixpack on Thu Nov 29, 2007 at 03:33:37 PM EST

way to refer to the player.

---
[ MONKEY STEALS THE PEACH ]
[ Parent ]

n00b update (none / 0) (#63)
by tetsuwan on Fri Nov 30, 2007 at 07:55:28 AM EST

It took quite a while to realize that the "only" way to write current hp to the screen was to use sprintf.

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

The way i'd do it would be something like (none / 0) (#76)
by Joe Sixpack on Sat Dec 01, 2007 at 06:18:19 AM EST

mvaddstr(y,x,"Hit Points:");

//read number of hp into a buffer
char buffer[10];
sprintf(buffer, "%d ", hp);

//display current hp
mvaddstr(y,x+12,buffer );

---
[ MONKEY STEALS THE PEACH ]
[ Parent ]

That's how I do it as well (none / 0) (#77)
by tetsuwan on Sat Dec 01, 2007 at 09:08:06 AM EST

hence the comment about sprintf.

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

Bugs, ye gads! (none / 1) (#80)
by curien on Sun Dec 02, 2007 at 12:29:59 AM EST

Holy buffer overflows, batman!

char buffer[10];
if(sizeof buffer >= snprintf(buffer, sizeof buffer, "%d", hp);)
  * handle event of overflow here
else
  mvaddstr(y, x+sizeof "Hit Points:", buffer);

Or just dynamically allocate.

size_t len = snprintf(NULL, 0, "%d", hp); note: only works with C99 *
char *buffer = malloc(len);
if(buffer) {
  snprintf(buffer, len, "%d", hp);
  mvaddstr(...);
  free(buffer);
  }


--
Murder your babies. -- R Mutt
[ Parent ]

Fscking autoformat (none / 1) (#81)
by curien on Sun Dec 02, 2007 at 12:31:00 AM EST

Holy buffer overflows, batman!

char buffer[10];
if(sizeof buffer >= snprintf(buffer, sizeof buffer, "%d", hp);)
  /* handle event of overflow here */
else
  mvaddstr(y, x+sizeof "Hit Points:", buffer);

Or just dynamically allocate.

size_t len = snprintf(NULL, 0, "%d", hp); /* note: only works with C99 */
char *buffer = malloc(len);
if(buffer) {
  snprintf(buffer, len, "%d", hp);
  mvaddstr(...);
  free(buffer);
  }

--
Murder your babies. -- R Mutt
[ Parent ]

You're right of course, (none / 1) (#84)
by Joe Sixpack on Sun Dec 02, 2007 at 05:55:18 AM EST

although if I wanted to be a lazy asshole about it (which i do), i'd say that in that instance (hitpoints) you as the game's developer can make a pretty good guess as to what the maximum value is going to be like.

To make sure just allocate a couple of chars more. Since every char you add gives you x10 the maximum allowed value, just go wild and add another whooping 6-7 chars to the buffer.

If a player somehow gets 10,000,000 times the hp than you've figured possible, he probably cheated and deserves the buffer overflow.

I should have used the sizeof function, though. This is as simple as i can think of atm:

//display current hp
char buffer[10+sizeof "Hit Points:  "];
sprintf(buffer, "Hit Points: %d ", ent_l[0].hp);
mvaddstr(y,x,buffer );

where 10 >> log(max hp)

---
[ MONKEY STEALS THE PEACH ]
[ Parent ]

Oh, and In that last part the name 'buffer' is (none / 1) (#85)
by Joe Sixpack on Sun Dec 02, 2007 at 06:07:26 AM EST

somewhat inappropriate for how i used that variable.

---
[ MONKEY STEALS THE PEACH ]
[ Parent ]

Yeah, there are other things you can do (3.00 / 2) (#86)
by curien on Sun Dec 02, 2007 at 09:40:53 AM EST

But that could break when you port your code to another platform or even if you just forget to take the negative sign into account (and some bug, somewhere, might allow for negative hitpoints). It's just more fragile code.

tetsuwan and who knows else said they've never written C before using your code -- you need to teach them the right way. Security checks should be obvious (so they don't get removed) and resilient (so changes elsewhere don't render them useless).

C is a sharp tool; wield it carefully.

--
Murder your babies. -- R Mutt
[ Parent ]

well (none / 1) (#88)
by tetsuwan on Sun Dec 02, 2007 at 06:45:27 PM EST

To some extent it's good to write some unsafe code too, just to get to experience how it can f*k things up.

I experienced some array index out of bounds bugs with my code and it was very interesting to see how arrays were corrupted and started to overwrite each other. The program also kept running for a while with the errors, when naively one would think that it should have crashed instantly.

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

Yeah, the bitch about C (none / 0) (#91)
by curien on Sun Dec 02, 2007 at 10:46:32 PM EST

is that much of the time, your code looks like it's working right, but every once in a while it completely fucks up.

That's how security exploits happen. There's an unchecked array overflow somewhere that seems to work, but when a malicious user gives pathological input, it might trash the stack. If it doesn't, it's a DOS attack; if it does, it's a remote code execution exploit.

--
Murder your babies. -- R Mutt
[ Parent ]

don't use mvaddstr for the latter (none / 1) (#90)
by tetsuwan on Sun Dec 02, 2007 at 08:42:15 PM EST

addstr is good enough

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

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.

Well, sir, (none / 1) (#94)
by ksandstr on Mon Dec 03, 2007 at 06:12:06 AM EST

You go ahead and implement this same on the Jacquard loom, and I will raise my hat in congratulation!

Fin.
[ Parent ]
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

Contest (none / 0) (#62)
by tetsuwan on Fri Nov 30, 2007 at 07:52:06 AM EST

write a complete equivalent of 1.4.c with 80 characters in perl.

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

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

How is that easier? (none / 0) (#68)
by Joe Sixpack on Fri Nov 30, 2007 at 01:16:34 PM EST

Installing cygwin and selecting gcc & ncurses is hardly more difficult than installing vmware & than installing Debian on top of that.

---
[ MONKEY STEALS THE PEACH ]
[ Parent ]

dunno (none / 0) (#69)
by raduga on Fri Nov 30, 2007 at 01:55:45 PM EST

I've run into problems with cygwin before, with things not working properly, or at all.
The cygwin environment doesn't always behave as one would expect a "windows" or "gnu" environment to.

If user is not failing it, I suppose cygwin could be simpler, but for failing it folk, vmware + fully-configured-and-ready-to-rumble-debian-in-a-box might be at least comparable.

Mind you, cygwin is Free, and vmware is not, so ymmv.
the linked-to vmware player is free beer, but free beer without freedom makes baby RMS cry.

[ Parent ]

But! (none / 0) (#70)
by Agent1 on Fri Nov 30, 2007 at 04:51:25 PM EST

I hate Cygwin. It doesn't even come with an uninstaller.


-Agent1
"Thats the whole point of the internet, to slander people anonymously." - Anonymous
[ Parent ]
That's the whole point! (none / 1) (#71)
by Joe Sixpack on Fri Nov 30, 2007 at 05:04:11 PM EST

It's so good you don't need one!

---
[ MONKEY STEALS THE PEACH ]
[ Parent ]

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 ]

The files on googlecode have been updated.% (none / 0) (#74)
by Joe Sixpack on Fri Nov 30, 2007 at 09:13:06 PM EST


---
[ MONKEY STEALS THE PEACH ]
[ Parent ]

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.

\datethis (none / 0) (#82)
by yuo on Sun Dec 02, 2007 at 12:31:33 AM EST

@* Introduction. I fucked up and left a typo in the parent comment. I said it was |functional code|, but even though it makes some sense, I really intended to say |functioning code|. This algorithm is probably the next thing you should learn after "hello world", and it also proves that CWEB is awesome for teaching programming.

\smallskip\rightline{--- yuo, December 2007}

@p
#include <stdio.h> /* Basic Input/Output */
#include <stdlib.h>
#include <string.h>

@<Subroutines@>@;
@#
main()
{
 kmp_replace_input("functional", "functioning");
 return(0);
}

@ The subroutine that builds the kmp table.

@<Sub...@>=
void kmp_init(T,w)
 int * T;
 const char const * const w;
{
 int i = 2;
 int j = 0;
 T[0] = -1;
 T[1] = 0;

 while (i < strlen(w))
 {
  if(w[i-1] == j[w])
  {
   T[i++] = j+++1;
  }
  else if (j > 0) j = j[T];
  else T[i++] = 0;
 }
 i=1;
 return;
}

@ Here is the subroutine that prints out the standard input until it matches
the search string, at which point, it prints out the replacement string and
then the rest of the input.

@<Sub...@>=
void kmp_replace_input(w,r)
 const char const * const w; /* the string we're looking to replace */
 const char const * const r; /* the string we're going to replace it with */
{
 int * T = malloc(strlen(w) * sizeof(int));
 char * b = malloc(strlen(w) * sizeof(char));
 int p = 0;
 char * bp = b;
 const char * i = w;
 char * bpt = b;
 char * bptt = bpt;
 char m = 0;
 int diff = 0;
 b[strlen(w)-1] = 0;
 kmp_init(T,w);

 while ((m = getchar()) != EOF)
 {
  if(m == *i)
  {
   i++; *bp++=m; p++;
   if (*i == 0)
   {
    printf("%s", r);
    while((m = getchar()) != EOF) putchar(m);
   }
  }
  else
  {
   if (p > 0)
   {
    diff = p - p[T];
    bpt = b + diff * sizeof(char);
    bptt = b;
    while (bptt < bpt) putchar(*bptt++);
    bptt = b;
    while (bpt < bp){bptt = bpt++; bptt++;}
    bp = bptt;
    bptt = bpt = b;
    p = p[T];
    i = &p[w];
   }
   putchar(m);
  }
 }
}

@* Build. To build this code, simply save it on any real system as |fixshittycomment.w| and save the parent comment as |shittycomment.txt|. |tangle| and |weave| will already be installed, so all you have to do is type |make fixshittycomment; PATH=. fixshittycomment < shittycomment.txt|

@* Conclusion. If you don't understand this code, then you might need to work on your pointer arithmetic.

I wish I had thought of pants pants pants pants pants pants pants pants.
[ Parent ]

yar i fergot to free my mallocs. (none / 0) (#83)
by yuo on Sun Dec 02, 2007 at 12:38:04 AM EST

I guess we'll call that an exercise left for the reader.

I wish I had thought of pants pants pants pants pants pants pants pants.
[ Parent ]

This comment contains nostalgia (none / 1) (#87)
by TDS on Sun Dec 02, 2007 at 09:49:59 AM EST

This is the book I read about writing Rogue-a-likes for kids back in 1984 [cover only here].

There was also a book on writing Adventure games [the actual pdf here].
I can confirm the ZX81 code works in this one because I typed it all in and got it running (on a membrane "keyboard", crippled for life).

Someone should reissue the above (well maybe the Rogue-a-like one, I think today's kids would die of boredom put in front of a text adventure game) with something a little more modern than 8-bit BASIC dialects in ("here is how the GOTO routines work"...ouch).

They were great though.

And when we die, we will die with our hands unbound. This is why we fight.
[ Parent ]

Literate programming is shit (3.00 / 2) (#93)
by ksandstr on Mon Dec 03, 2007 at 06:04:33 AM EST

Well maybe the idea isn't, i.e. regarding code as an inseparable part of documentation, but the methods are. Making functions like sections of a book? Apparently even Knuth isn't immune to the  "everything looks like a nail" effect.

In my opinion program code should be observed like a wild animal, which is to say, in its natural habitat: the editor. In the wild, comments and actual code are equal and presented (hopefully) in a manner of syntax highlighting that's easy on the eyes, so that the different roles of blocks in the source can be seen without getting into their contents.

As for commenting, it's useless to describe code like "allocate enough memory for a struct foo, then fill it in and return a pointer to it, or if allocation fails, return NULL". That much can be seen just with one eyeful of the real thing! Commenting only becomes useful with details that aren't obvious from a first reading of the actual code. If one cannot read code as easily as english or their native language, then they should practice their basics more rather than thinking really hard that they already know everything and trying to patch up their externalized personal issues with comment abuse.

This isn't to say that specifying what a program or a module thereof does wasn't useful. I've used Z in the past, and it's certainly worth trying if only for the changes in thought its use will provoke. That is a method of specifying program behaviour however, not literate programming.

Mixing redundant prose with code just makes for an increased maintenance load to avoid the case where said prose becomes worth less than nothing due to the code having been changed. For those kinds of people who don't understand that their first version is always just the prototype (e.g. some university types), this might be good -- if one never expects to have to maintain their creation, what possible downside could there be from an increased maintenance load? This obviously doesn't hold water outside exercises and academia.

Fin.
[ Parent ]

That's only true if you're not Don Knuth (none / 0) (#96)
by yuo on Tue Dec 04, 2007 at 12:47:19 AM EST

Anyways, a few points:

First, Don Knuth makes so few errors that Literate Programming (LP) would be a natural way to program. ;-)

Second, none of what you said takes away from the fact that it is a truly excellent way to learn to program because you have to understand what you're doing before you do it.

Third, at the nameless company where I used to work, they'd put a prototyping phase in the schedule, but once the prototype was working, they'd just try to use the prototype code rather than spending time rewriting it. Yeah, it's dumb, but if you're going to do that, it can't hurt to program it better the first time.

I wish I had thought of pants pants pants pants pants pants pants pants.
[ Parent ]

I disagree with this. (none / 1) (#97)
by ksandstr on Tue Dec 04, 2007 at 05:47:37 AM EST

Understanding the problem space is what you do before, or during, specification phase. Now, in many cases this happens at the same time as when the prototype is written and indeed that's not very good practice; in fact it's precisely why the first version is termed a prototype in the first place.

This is not to say that one should perform a complete rewrite, from scratch, based on a prototype. That's pointless extra work. What more typically happens is that the prototype is cleaned up, TODOs are done, FIXMEs fixed and ISSUEs resolved, so that substantial portions of either the prototype or an implementation very similar to it remains. OO types would call this "refactoring". After those things are completed, the result can be validated against the specification and then shipped or put into (non-pilot) production or whatever.

Regardless of whether you're Don Knuth (who, according to you, poops rough diamonds), in any non-trivial piece of software the most work on it is done in maintenance. You haven't addressed the issue of the entirely unnecessary extra work produced by having to maintain the prose required by literate programming from the prototype phase all the way into what may turn out to be decades of maintenance.

Perhaps for some, literate programming may be a good way to learn. However exercises should not be mistaken for what one would do "for real", and this kind of learning should not be permitted to confuse participants in a project that is supposed to produce results in the near term.

Not to mention getting it right the first time: you won't, so quit wanking with trivialities and just make it work.

Fin.
[ Parent ]

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

I also did something like that once (none / 1) (#92)
by Joe Sixpack on Mon Dec 03, 2007 at 05:22:37 AM EST

a couple of years ago i tried writing a python roguelike and when i got to the line-of-sight/random map generation/AI part it started getting slow.

One advantage of c is that I can get away with a naive implementation of pretty much everything.

I know i could mix python and c, but honestly for little things like this game (the complete version's size will probably be a single digit multiple of this ~100 loc 1st part) c is good enough for me.

Then again, maybe I just suck at programming in python.

---
[ MONKEY STEALS THE PEACH ]
[ Parent ]

Heh, yeah (none / 1) (#98)
by regeya on Tue Dec 04, 2007 at 09:20:04 PM EST

I thought C was a systems programming language again. :->

[ yokelpunk | kuro5hin diary ]
[ Parent ]

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!