/*** Dodgem Mark 1 (10 may 1999)
***
*** This file is part of Xgame - an X11 game/animation library,
*** Version 0.99, 10 may 1999.
***
*** Copyright (c) 1998 by B.W. van Schooten. All rights reserved.
*** This software is distributed under a generic version of the Perl Artistic
*** License. No warranty. See file 'artistic.license' for more details.
***
The main objective of this re-engineering attempt is to make the game playable
with multiple enemy cars. Keeping track of multiple dynamical objects
generally results in a much more involved, and also more interesting, task.
Proposed 1st interface didn't work, probably because the keys (7,u,j,m) are
vertical. This means it is hard to get your fingers on all four keys at once.
Horizontal keys (1,2,3,4) might have worked slightly better. Replaced by
crsrup/down, together with a `target lane' indicator, which works fine.
Tried to make the task easier by signalling when the cars are able to switch.
The task remains too hard, but becomes slightly easier yet by more precise
enemy status signalling.
In the first setup, the player starts in the top lane, and each enemy in one
of the three others. Typically, you find yourself cleaning up the top lane
first, because that's the best thing to do. This `initial run' gets boring
very quickly. In the original, the enemy starts in the same lane as your car,
probably because of this. Now, one enemy always starts in the same lane as the
player. This is clearly an improvement in playability.
The wraparound remains confusing: it is hard to estimate the number of gaps
left before the wrap. This is probably why the author chose circular instead
of linear with wrap. Yet, I still think the linear road is (slightly) better.
Another option would be a scrolling road. This was hardly an improvement: it
was easier seeing the cars approach, but it was harder to estimate speeds.
So, non-scrolling linear remains the best choice.
Other attempts to make the task tractable include adding >4 gaps, and having
the same speed in the different lanes.
>4 gaps: slightly easier but still not easy enough. In the limit, adding more
gaps converges to a free-movement race game, with enemies that sometimes
switch lanes.
same speed: the enemies have a fixed horizontal distance from each other, and
their switching moments have a fixed relation to each other. The task changes
slightly in nature, but does not become easier: you don't seem to get the
lower lanes for free anymore.
Why only 4 lanes? In the case of 1 enemy, 3 lanes is not enough to be able to
escape the enemy. With 4 lanes, there is always an outer lane to escape into.
5 lanes is too much: you can lure the enemy away too easily, so you can clear
4 of the 5 lanes safely, instead of 2 of the 4. In case of 2 enemies, adding
more lanes did not seem to make the game easier, apparently because the task
of crossing an approaching block of enemies, which is the hardest task with
multiple enemies wrt the number of lanes, still remains.
In the original setup, when an enemy is ready to switch lanes, its lane, as
well as the lane above and below it, are unsafe. They remain so until the car
has switched. If you're on the same lane as the car, you'd have to either
switch 2 lanes to avoid it, or switch away and then back again. The former
operation often lands you on top of the other enemy, while you often don't
have enough space to do the latter. To make the task easier, I first made the
enemy cars skip their switching turn when they are in the same lane as the
player. This was still too hard. What I did next was make the enemy switch
always, even if it was on the same lane as the player. This last idea made the
player's lane choosing task easier: now, the stretch of lane in front of the
gap where the enemy will switch is always safe. I also found that this makes
the game more fun: it is kind of `freaky' to jump into a lane just as the
enemy car switches away.
***/
#include
#include
#include
#include "xgame.h"
#include "timer.h"
#include "math.h"
#include "xgamet.h"
#include "r250.h"
/*** pixmap sizes ***/
#define WINXSIZE 640
#define WINYSIZE 200
#define SPRXSIZE 32
#define SPRYSIZE 32
#define DOTXSIZE 16
#define DOTYSIZE 16
/*** game parameters ***/
#define NRENEMIES 2
#define NRLANES 4
#define NRGAPS 4
#define ENEMYDELAY 3
#define MAXSWITCH 2
/*** layout parameters ***/
#define GAPSIZE 32
#define WALLSIZE 8
#define LANEOFS (WINYSIZE/NRLANES/2 - SPRXSIZE/2)
#define GAPOFS (WINXSIZE/NRGAPS/2 - GAPSIZE/2)
/*#define GAPOFS (- GAPSIZE/2)*/
#define SWITCHTIME 5
#define BASESPEED 4
#define RELSPEED 1
#define PLAYERACCEL 2
#define NRDOTS (NRLANES*NRGAPS)
#define NROBJ (NRENEMIES+1+15+NRDOTS)
/*** Xgame structs ***/
char *sprnames[] = {
"playercar.xpm",
"enemycar.xpm",
"enemycar2.xpm",
"enemycar3.xpm",
"dot.xpm",
"explosion.xpm",
""
};
char *colornames[] = {
"black",
"magenta",
"blue",
"cyan",
"green",
"yellow",
"red",
""
};
char *fontnames[] = {
""
};
struct XgameWindow *win;
/*** Globals ***/
int targetlane;
int playerlane;
int gametimer;
int frameskip;
int nrdots;
int dying;
/*** Xgamet structs ***/
struct XgametObjList *objlst;
struct XgametBox box[] = {
/*** agents ***/
{0,0, SPRXSIZE,SPRYSIZE},
//{0,0, SPRXSIZE,31},
/*** dots ***/
{0,0, 16,16},
/*** playfield ***/
{-256,-256, 512+WINXSIZE,512+WINYSIZE},
{WINXSIZE,0, WINXSIZE+1,WINYSIZE},
{0,-1, 0,WINYSIZE},
};
struct objdata {
int lanenr; /*** 0..3 ***/
int waittmr; /*** (enemy) # of gaps to wait till it can switch again
*** (points) # of frames left before deleting ***/
int switchtmr; /*** 0=not switching >0=still switching lanes ***/
} objdat[NROBJ+1];
struct objdata defaultdat = {0,0,0};
void
drawsprite(int x, int y, int xstart, int ystart, int xsize, int ysize,
int sprnr) {
/*** draws given part of sprite with horizontal wrap. Only works properly
*** when xpos in the range [0..WINXSIZE[ ***/
XgameDrawSpriteArea(win,x,y,xstart,ystart,xsize,ysize,sprnr);
/*** assumes center = topleft ***/
if (x>=WINXSIZE-xsize)
XgameDrawSpriteArea(win,x-WINXSIZE,y,xstart,ystart,xsize,ysize,sprnr);
}
#define PLAYER_N 1
#define ENEMY_N 2
#define COLLIDE_N 4
#define DOT_N 8
#define GETDOT_N 16
#define BONUS_N 32
#define EXPLO_N 64
void
moveplayer(struct XgametObj *me, struct objdata *medat, int menr,
struct XgametObj *it, struct objdata *itdat, int itnr) {
int xspeed;
xspeed=BASESPEED+RELSPEED*itdat->lanenr;
if (win->keymap[XgameK_Right]==XgameKeyPressed) xspeed*=PLAYERACCEL;
if (gametimer&1 && xspeed&1) xspeed+=2;
xspeed/=2;
it->x+=xspeed;
if (it->x >= WINXSIZE) it->x -= WINXSIZE;
if ( (it->x+(WINXSIZE/NRGAPS)-GAPOFS)%(WINXSIZE/NRGAPS) < xspeed+1
&& itdat->switchtmr==0 )
{
itdat->switchtmr=SWITCHTIME;
if (targetlane > itdat->lanenr+MAXSWITCH) {
itdat->lanenr+=MAXSWITCH;
} else if (targetlane < itdat->lanenr-MAXSWITCH) {
itdat->lanenr-=MAXSWITCH;
} else {
itdat->lanenr=targetlane;
}
}
if (itdat->switchtmr>0) {
itdat->switchtmr--;
it->y = (it->y + LANEOFS + itdat->lanenr*WINYSIZE/NRLANES)/2;
} else {
it->y = LANEOFS + itdat->lanenr*WINYSIZE/NRLANES;
}
playerlane=itdat->lanenr;
if (it->y != LANEOFS + itdat->lanenr*WINYSIZE/NRLANES
|| itdat->lanenr != targetlane)
{
XgameFillArea(win,it->x,LANEOFS+targetlane*WINYSIZE/NRLANES,
SPRXSIZE,SPRYSIZE,2);
}
drawsprite(it->x,it->y, SPRXSIZE*(gametimer&2)/2,0,SPRXSIZE,SPRYSIZE,0);
}
void
moveenemy(struct XgametObj *me, struct objdata *medat, int menr,
struct XgametObj *it, struct objdata *itdat, int itnr) {
int xspeed;
xspeed=BASESPEED+RELSPEED*itdat->lanenr;
if (gametimer&1 && xspeed&1) xspeed+=2;
xspeed/=2;
it->x-=xspeed;
if (it->x < 0) it->x += WINXSIZE;
if ( (it->x+(WINXSIZE/NRGAPS)-GAPOFS)%(WINXSIZE/NRGAPS) < xspeed+1
&& itdat->switchtmr==0 )
{
itdat->switchtmr=SWITCHTIME;
if (itdat->waittmr>0) {
itdat->waittmr--;
} else {
if (playerlane > itdat->lanenr) {
itdat->lanenr++;
itdat->waittmr=ENEMYDELAY;
} else if (playerlane < itdat->lanenr) {
itdat->lanenr--;
itdat->waittmr=ENEMYDELAY;
} else {
if (itdat->lanenr==0) {
itdat->lanenr++;
} else if (itdat->lanenr==NRLANES-1) {
itdat->lanenr--;
} else {
itdat->lanenr+=-1 + 2*r250n(2);
}
itdat->waittmr=ENEMYDELAY;
}
}
}
if (itdat->switchtmr>0) {
itdat->switchtmr--;
it->y = (it->y + LANEOFS + itdat->lanenr*WINYSIZE/NRLANES)/2;
} else {
it->y = LANEOFS + itdat->lanenr*WINYSIZE/NRLANES;
}
drawsprite(it->x,it->y, SPRXSIZE*(gametimer&2)/2,0,
SPRXSIZE,SPRYSIZE,
1+((itdat->waittmr<=1)?2-itdat->waittmr:0));
}
void
showdot(struct XgametObj *me, struct objdata *medat, int menr,
struct XgametObj *it, struct objdata *itdat, int itnr) {
//XgameDrawSprite(win, it->x-32,it->y-32, 1);
}
void
collide(struct XgametObj *me, struct objdata *medat, int menr,
struct XgametObj *it, struct objdata *itdat, int itnr) {
dying=50;
me->id=0;
me->mask=EXPLO_N;
it->id=0;
it->mask=EXPLO_N;
}
void
getdot(struct XgametObj *me, struct objdata *medat, int menr,
struct XgametObj *it, struct objdata *itdat, int itnr) {
int o;
win->drawtobuffer=0;
win->skipdraw=0;
XgameFillArea(win, it->x,it->y, DOTXSIZE,DOTYSIZE,0);
/*** also clear any part of the sprite that wrapped ***/
XgameFillArea(win, it->x-WINXSIZE,it->y, DOTXSIZE,DOTYSIZE,0);
win->skipdraw=frameskip;
win->drawtobuffer=1;
it->exists=0;
nrdots--;
o=XgametAllocObj(objlst,NULL,it->x+DOTXSIZE/2,it->y+DOTYSIZE/2,
-1,0, 0,BONUS_N);
objdat[o].waittmr=80;
}
#define BONUSSZ 8
XgameSegment bonussh[] = {
{-20,-20, -20,20},
{-20,20, 20,20},
{20,20, 20,-20},
{20,-20, -20,-20},
{-10,-10, -10,10},
{-10,10, 10,10},
{10,10, 10,-10},
{10,-10, -10,-10},
};
void
showbonus(struct XgametObj *me, struct objdata *medat, int menr,
struct XgametObj *it, struct objdata *itdat, int itnr) {
XgameDrawVecObj(win,it->x,it->y,bonussh,BONUSSZ,
1+(gametimer>>1)%6, 6, 20.0/(90-itdat->waittmr),0.0);
if (!(itdat->waittmr--)) it->exists=0;
}
int explophase [] = {-8,0,8,0};
void
showexplo(struct XgametObj *me, struct objdata *medat, int menr,
struct XgametObj *it, struct objdata *itdat, int itnr) {
int o;
drawsprite(it->x+explophase[((gametimer+itnr*2)&6)/2],
it->y+explophase[((gametimer+itnr*2+2)&6)/2],
SPRXSIZE*((gametimer+itnr)&1)/2,0,SPRXSIZE,SPRYSIZE,5);
if (!(gametimer&7)) {
o=XgametAllocObj(objlst,NULL,
it->x+-32+r250n(65),it->y-32+r250n(65),
-1,0, 0,EXPLO_N);
objdat[o].waittmr=itdat->waittmr;
}
}
XgametNotifyHit hit[] = {
(XgametNotifyHit)moveplayer,(XgametNotifyHit)moveenemy,
(XgametNotifyHit)collide, (XgametNotifyHit)showdot,
(XgametNotifyHit)getdot, (XgametNotifyHit)showbonus,
(XgametNotifyHit)showexplo,
};
int
main(int argc,char **argv) {
int a,b, o;
int winflags;
float xscale, yscale;
char *xpmdir = "./xpms-dodgem";
r250_init(gettodseconds());
parsestdargs(argc,argv,&xscale,&yscale,&winflags);
if (argc < 5) {
printf("No XPM directory supplied. Using default `%s'.\n",xpmdir);
} else {
xpmdir=argv[4];
}
win=XgameOpenWindow(WINXSIZE,WINYSIZE,0,0,WINXSIZE*xscale,WINYSIZE*yscale,
XgameWinSetAutoRepeat|winflags,
xpmdir,sprnames, colornames, fontnames);
/*** level-loop ***/
while (1) {
win->drawtobuffer=0;
win->skipdraw=0;
/*** create objects ***/
objlst=XgametCreateObjList(NROBJ,objdat,sizeof(struct objdata));
XgametAllocObj(objlst,NULL,0,0, 0,2,
PLAYER_N|ENEMY_N|DOT_N|BONUS_N|EXPLO_N,0);
XgametAllocObj(objlst,&defaultdat,0,LANEOFS, 0,0,
COLLIDE_N|GETDOT_N,PLAYER_N);
for (a=0; a0) objdat[o].lanenr=1+r250n(NRLANES-1);
/*** make sure the enemies are not able to switch immediately,
*** so the player can get his/her bearings ***/
objdat[o].waittmr=2+r250n(ENEMYDELAY);
}
/*** draw background, create dots ***/
nrdots=0;
for (a=-WALLSIZE/2; a<=WINYSIZE; a+=WINYSIZE/NRLANES) {
XgameFillArea(win,0,a,WINXSIZE,WALLSIZE,1);
for (b=GAPOFS; b0) XgameFillArea(win,b,a,GAPSIZE,WALLSIZE,0);
drawsprite(WINXSIZE/NRGAPS+b-GAPOFS-DOTXSIZE/2,
a+WINYSIZE/NRLANES/2-DOTYSIZE/2,
0,0,DOTXSIZE,DOTYSIZE,4);
XgametAllocObj(objlst,NULL,
WINXSIZE/NRGAPS+b-GAPOFS-DOTXSIZE/2,
a+WINYSIZE/NRLANES/2-DOTYSIZE/2,
0,1, 0,GETDOT_N|DOT_N);
nrdots++;
}
}
}
win->drawtobuffer = 1;
frameskip=-1;
targetlane=0;
playerlane=0;
gametimer=0;
dying=0;
/*** game action loop ***/
while (1) {
gametimer++;
/*** Last frame was drawn? display it. ***/
if (!frameskip)
XgameFlushRectList(win,win->bufrectlist);
/*** sync display, get keys ***/
XgameHandleEvents(win);
/*** wait/determine whether to skip this frame ***/
while (!(frameskip=checktimeelapsed(1000000/72,1))) ;
frameskip--;
win->skipdraw=frameskip;
/*** clear buffer ***/
XgameFlushRectList(win,win->bgrectlist);
/*** update objects ***/
XgametSyncObjList(objlst);
XgametCollideObjLists(objlst,box, objlst,box, hit);
if (win->keymap[XgameK_Up]==XgameKeyPressed && targetlane>0) {
targetlane--;
win->keymap[XgameK_Up]=XgameKeyNone;
}
if ( win->keymap[XgameK_Down]==XgameKeyPressed
&& targetlanekeymap[XgameK_Down]=XgameKeyNone;
}
if (nrdots==0) break;
if (dying) {
dying--;
if (!dying) break;
}
if (win->keymap['q']==XgameKeyPressed) break;
}
XgametFreeObjList(objlst);
if (win->keymap['q']==XgameKeyPressed) break;
}
XgameCloseWindow(win);
return(0);
}
               (
geocities.com/timessquare/alley/3583)                   (
geocities.com/timessquare/alley)                   (
geocities.com/timessquare)