version 0.4 v0.4
authorAntoine Jacquet <royale@zerezo.com>
Sat, 27 Nov 2004 23:00:00 +0000 (00:00 +0100)
committerAntoine Jacquet <royale@zerezo.com>
Sat, 27 Nov 2004 23:00:00 +0000 (00:00 +0100)
* added "loop" track
* fixed "car" track
* fixed "hairpins" track
* added boss key (thank you Christophe Sauthier)
* tracks server (client can now download new tracks when they are available)
* improved network protocol
* added a framework to program bots
* first bot for the game : "Anticip"
* fixed memory management (server and client release memory when changing track)
* generic Makefile (easier to compile Linux/Windows binaries)
* network startup was fixed to avoid to much delay between clients and server
* fixed a bug when sending keys for record validation
* also send records in network mode
* game and network protocol are now endian safe (thank you Markus W. Weissmann)

17 files changed:
CHANGELOG
Makefile
README
bot.c [new file with mode: 0644]
bot.h [new file with mode: 0644]
bot_anticip.c [new file with mode: 0644]
car.c
network.h
sdl.h
server.c
sprites/boss.png [new file with mode: 0644]
tracks/car_function.png
tracks/hairpins_function.png
tracks/list.txt
tracks/loop.png [new file with mode: 0644]
tracks/loop_function.png [new file with mode: 0644]
zeRace.c

index 528d161..421f75c 100644 (file)
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,5 +1,21 @@
 Change log file for zeRace
 
+version 0.4    (2004-11-28)
+       * added "loop" track
+       * fixed "car" track
+       * fixed "hairpins" track
+       * added boss key (thank you Christophe Sauthier)
+       * tracks server (client can now download new tracks when they are available)
+       * improved network protocol
+       * added a framework to program bots
+       * first bot for the game : "Anticip"
+       * fixed memory management (server and client release memory when changing track)
+       * generic Makefile (easier to compile Linux/Windows binaries)
+       * network startup was fixed to avoid to much delay between clients and server
+       * fixed a bug when sending keys for record validation
+       * also send records in network mode
+       * game and network protocol are now endian safe (thank you Markus W. Weissmann)
+
 version 0.3    (2004-09-27)
        * added network/internet mode
        * code is now split into generic modules
index 0b67908..a359dba 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -1,21 +1,31 @@
 CC=gcc
+SDLCONFIG=sdl-config
 
-all:zeRace server
+all:zeRace server bot_anticip
 
-zeRace:zeRace.c network.h sdl.o car.o tracklist.o
-       $(CC) -ansi -Wall -o zeRace zeRace.c sdl.o car.o tracklist.o `sdl-config --cflags --libs` -lSDL_net -lSDL_mixer -lSDL_image -lSDL_gfx
+zeRace:zeRace.c network.h sdl.o car.o tracklist.o $(ICON)
+       $(CC) -ansi -Wall -o zeRace$(EXT) zeRace.c sdl.o car.o tracklist.o $(ICON) `$(SDLCONFIG) --cflags --libs` -lSDL_net -lSDL_mixer -lSDL_image -lSDL_gfx -L.
 
 server:server.c network.h sdl.o car.o tracklist.o
-       $(CC) -ansi -Wall -o server server.c sdl.o car.o tracklist.o `sdl-config --cflags --libs` -lSDL_net -lSDL_image
+       $(CC) -ansi -Wall -o server$(EXT) server.c sdl.o car.o tracklist.o `$(SDLCONFIG) --cflags --libs` -lSDL_net -lSDL_image -L.
+
+bot_anticip:bot_anticip.c bot.o sdl.o car.o tracklist.o
+       $(CC) -ansi -Wall -o bot_anticip$(EXT) bot_anticip.c bot.o sdl.o car.o tracklist.o `$(SDLCONFIG) --cflags --libs` -lSDL_net -lSDL_image -L.
 
 sdl.o:sdl.c sdl.h
-       $(CC) -ansi -Wall -c sdl.c `sdl-config --cflags`
+       $(CC) -ansi -Wall -c sdl.c `$(SDLCONFIG) --cflags`
 
-car.o:car.c car.h
-       $(CC) -ansi -Wall -c car.c `sdl-config --cflags`
+car.o:car.c car.h sdl.h
+       $(CC) -ansi -Wall -c car.c `$(SDLCONFIG) --cflags`
 
 tracklist.o:tracklist.c tracklist.h
        $(CC) -ansi -Wall -c tracklist.c
 
+bot.o:bot.c sdl.o car.o tracklist.o
+       $(CC) -ansi -Wall -c bot.c `$(SDLCONFIG) --cflags`
+
+icon.o:icon.rc
+       $(WINDRES) -i icon.rc -o icon.o
+
 clean:
-       rm -f zeRace server *.o *.cfg
+       rm -f zeRace server bot_anticip *.exe *.o *.cfg
diff --git a/README b/README
index 1c0a1e6..ff68211 100644 (file)
--- a/README
+++ b/README
@@ -1,3 +1,19 @@
-zeRace 0.3
+zeRace 0.4
 site: http://royale.zerezo.com/zerace/
 mail: royale@zerezo.com
+
+install:
+In order to compile the game, you will need SDL, SDL_net, SDL_mixer, SDL_image, SDL_gfx and the related developpement headers.
+Then, just type "make" to build the game.
+The Makefile as some parameters. For example, in order to cross-compile Windows binaries using MinGW you can type :
+make CC=i586-mingw32msvc-gcc SDLCONFIG=/path/to/sdl-config WINDRES=i586-mingw32msvc-windres ICON=icon.o EXT=.exe
+
+bots:
+To play a game against bots, you will first need to launch a local server :
+./server "My private server" 5 1 private
+Then launch as many bots as you want :
+./bot_anticip localhost 3600 0
+./bot_anticip localhost 3600 8
+./bot_anticip localhost 3600 16
+Then you can join your own server in zeRace in the "network game" section.
+If you are under Windows, replace by "server.exe" and "bot_anticip.exe" in MS-DOS commands.
diff --git a/bot.c b/bot.c
new file mode 100644 (file)
index 0000000..ef93b04
--- /dev/null
+++ b/bot.c
@@ -0,0 +1,266 @@
+#include "bot.h"
+
+#define DELAY 7
+#define MAXRECORDKEYS 9999
+
+/* tracklist */
+struct _tracklist *tracklist;
+
+/* user setup */
+struct _config
+{
+  char pseudo[MAXLINELENGTH];
+  char url[MAXLINELENGTH];
+  int fullscreen;
+  int sound;
+  int tire;
+  SDLKey up;
+  SDLKey down;
+  SDLKey left;
+  SDLKey right;
+  int color;
+} config = {"anonymous","",0,0,1,SDLK_UP,SDLK_DOWN,SDLK_LEFT,SDLK_RIGHT,6};
+
+/* full script for a lap */
+struct _record
+{
+  float x,y,angle,speed;
+  char keys[MAXRECORDKEYS];
+  int time;
+};
+
+/* network stuff */
+UDPsocket udpsock=NULL;
+UDPpacket *packet;
+int network_speed=1;
+int aleas;
+
+
+/* exit the game and clean */
+void zeRace_exit()
+{
+  printf("quit\n");
+  SDLNet_Quit();
+  SDL_Quit();
+  exit(0);
+}
+
+
+/* initialize the game */
+void zeRace_init()
+{
+  /* do a clean exit in case of emergency */
+  signal(SIGINT,zeRace_exit);
+  signal(SIGTERM,zeRace_exit);
+
+  /* get the list of local tracks */
+  if (!zeRace_get_tracks(&tracklist)) zeRace_exit();
+  
+  srand(time(NULL));
+  
+  /* robot configuration */
+  sprintf(config.pseudo,"\"%s\" bot(%d)",bot_name(),aleas);
+  config.color=rand()%12;
+  
+  if (SDL_Init(SDL_INIT_VIDEO|SDL_INIT_AUDIO)<0)
+  {
+    fprintf(stderr,"could not initialize SDL : %s\n",SDL_GetError());
+    zeRace_exit();
+  }
+  atexit(SDL_Quit);
+  
+  if (SDLNet_Init()==-1)
+  {
+    fprintf(stderr,"could not initialize SDLNet : %s\n",SDLNet_GetError());
+    zeRace_exit();
+  }
+
+  packet=SDLNet_AllocPacket(1024);
+  if (!packet)
+  {
+    fprintf(stderr,"SDLNet_AllocPacket: %s\n",SDLNet_GetError());
+    zeRace_exit();
+  }
+}
+
+
+/* launch a new race */
+void zeRace_launch(int alltime,int go)
+{
+  SDL_Surface *cir,*fun;
+  int ku=0,kd=0,kl=0,kr=0,i;
+  struct _car car;
+  int delay=DELAY;
+  int lastack=alltime;
+  struct _record net;
+
+  cir=IMG_Load(tracklist->full);
+  fun=IMG_Load(tracklist->function);
+  
+  car.speed=0;
+  car.angle=tracklist->a*2*M_PI/360;
+  car.ox=car.x=tracklist->x;
+  car.oy=car.y=tracklist->y;
+  car.lastcheck=0;
+  car.w=30;
+  car.h=30;
+  net.time=0;
+
+  /* startup countdown */
+  for (i=4;i>=-1;i--)
+  {
+    if (!go) break;
+    if (i!=-1) SDL_Delay(1000);
+  }
+  
+  /* main loop */
+  for (;;)
+  {
+    /* call the IA */
+    bot_ia(tracklist->name,&car,fun,&ku,&kd,&kl,&kr);
+    
+    /* random movement if asked */
+    if (aleas) if (rand()%aleas==0)
+    {
+      ku=rand()%2;
+      kl=rand()%2;
+      kr=rand()%2;
+      kd=rand()%2;
+    }
+    
+    if (udpsock)
+    {
+      net.keys[net.time]=(ku<<3 | kd<<2 | kl<<1 | kr)+'A';
+      net.keys[net.time+1]='\0';
+    }
+
+    delay=DELAY;
+    /* if we are in network mode */
+    if (udpsock!=NULL)
+    {
+      char *tmp;
+      while (SDLNet_UDP_Recv(udpsock,packet)) if (strcmp(packet->data,"positions")==0)
+      {
+        int servertime,clienttime,nb;
+        servertime=SDLNet_Read32(packet->data+strlen("positions")+1);
+        clienttime=SDLNet_Read32(packet->data+strlen("positions")+1+4);
+        nb=SDLNet_Read16(packet->data+strlen("positions")+1+4+4);
+        if (clienttime>lastack)
+        {
+          memcpy(net.keys,net.keys+clienttime-lastack,net.time+1);
+          net.time-=clienttime-lastack;
+          if (clienttime>servertime+5) delay+=DELAY;
+          if (clienttime<servertime-5) delay-=DELAY;
+          if (delay<0) delay=0;
+          lastack=clienttime;
+        }
+      }
+      else /* end of this network race */
+      {
+        SDL_FreeSurface(cir);
+        SDL_FreeSurface(fun);
+        return;
+      }
+      if (strlen(net.keys)!=0)
+      {
+        tmp=packet->data;
+        strcpy(tmp,"keys");
+        tmp+=strlen(tmp)+1;
+        SDLNet_Write32(lastack,tmp);
+        tmp+=4;
+        strcpy(tmp,net.keys);
+        tmp+=strlen(tmp)+1;
+        packet->len=(void *)tmp-(void *)packet->data+10;
+        if (net.time%network_speed==0) if (!SDLNet_UDP_Send(udpsock,-1,packet))
+        {
+          fprintf(stderr,"SDLNet_UDP_Send: %s\n",SDLNet_GetError());
+          exit(2);
+        };
+      }
+    }
+
+    /* move the car */
+    move_car(&car,(ku<<3 | kd<<2 | kl<<1 | kr),fun);
+
+    /* let the system breath */
+    SDL_Delay(delay);
+    
+    /* game time */
+    net.time++;
+    if (udpsock && net.time>MAX_LAG)
+    {
+      fprintf(stderr,"timeout !\n");
+      SDL_FreeSurface(cir);
+      SDL_FreeSurface(fun);
+      return;
+    }
+    alltime++;
+  }
+}
+
+
+/* connect to a server */
+void zeRace_connect(char *host,int port)
+{
+  char *tmp;
+  int lag=0;
+  udpsock=SDLNet_UDP_Open(0);
+  if (udpsock==NULL)
+  {
+    fprintf(stderr,"SDLNet_UDP_Open: %s\n",SDLNet_GetError());
+    zeRace_exit();
+  }
+  SDLNet_ResolveHost(&packet->address,host,port);
+  tmp=packet->data;
+  strcpy(tmp,"connect");
+  tmp+=strlen(tmp)+1;
+  strcpy(tmp,config.pseudo);
+  tmp+=strlen(tmp)+1;
+  SDLNet_Write16(config.color,tmp);
+  tmp+=2;
+  packet->len=(void *)tmp-(void *)packet->data;
+  SDLNet_UDP_Send(udpsock,-1,packet);
+  /* network loop */
+  while (SDLNet_UDP_Recv(udpsock,packet) || lag<MAX_LAG)
+  {
+    tmp=packet->data;
+    if (strcmp(tmp,"track")==0)
+    {
+      struct _tracklist *loopcheck=tracklist;
+      int time;
+      char go;
+      tmp+=strlen(tmp)+1;
+      go=*tmp++;
+      printf("server asked for track : %s\n",tmp);
+      while (tracklist->next!=loopcheck) if (strcmp(tracklist->name,tmp)==0) break; else tracklist=tracklist->next;
+      if (strcmp(tracklist->name,tmp)!=0)
+      {
+        fprintf(stderr,"unknown track : %s\n",tmp);
+        zeRace_exit();
+      }
+      tmp+=strlen(tmp)+1;
+      time=SDLNet_Read32(tmp);
+      tmp+=4;
+      network_speed=SDLNet_Read32(tmp);
+      zeRace_launch(time,go);
+      if (strcmp(packet->data,"finish")==0) SDL_Delay(5000);
+      lag=0;
+    }
+    SDL_Delay(7);
+    lag++;
+  }
+  SDLNet_UDP_Close(udpsock);
+  udpsock=NULL;
+}
+
+
+/* main program */
+int main(int argc,char *argv[])
+{
+  if (argc!=4) { fprintf(stderr,"Usage : %s host port random\n  host   : host or ip of the server to connect to\n  port   : port number of the server to connect to\n  random : frequency of random moves (0 = no random moves, 1 = only random, 1000 = 1/1000 random moves)\n",argv[0]); exit(1); }
+  aleas=atoi(argv[3]);
+  zeRace_init();
+  zeRace_connect(argv[1],atoi(argv[2]));
+  zeRace_exit();
+  return 0;
+}
diff --git a/bot.h b/bot.h
new file mode 100644 (file)
index 0000000..22c3b7f
--- /dev/null
+++ b/bot.h
@@ -0,0 +1,18 @@
+/* generic bot framework */
+
+#include <stdlib.h>
+#include <time.h>
+#include <signal.h>
+#include <math.h>
+#include <string.h>
+#include <SDL.h>
+#include <SDL_image.h>
+#include <SDL_net.h>
+#include "sdl.h"
+#include "car.h"
+#include "tracklist.h"
+#include "network.h"
+
+/* ia functions */
+char *bot_name();
+void bot_ia(char *trackname,struct _car *car,SDL_Surface *fun,int *ku,int *kd,int *kl,int *kr);
diff --git a/bot_anticip.c b/bot_anticip.c
new file mode 100644 (file)
index 0000000..22de8dd
--- /dev/null
@@ -0,0 +1,121 @@
+/*
+ * zeRace "anticip" bot
+ * http://royale.zerezo.com/zerace/
+ * 
+ * Copyright (C) 2004  Antoine Jacquet <royale@zerezo.com>
+ * Licensed under GPL
+ *
+ * This robot uses the idea described on this page :
+ * http://rars.sourceforge.net/selection/felix.html
+ * Thanks to Doug Eleveld
+ */
+
+#include "bot.h"
+
+char *bot_name()
+{
+  return "Anticip";
+}
+
+void bot_ia(char *trackname,struct _car *car,SDL_Surface *fun,int *ku,int *kd,int *kl,int *kr)
+{
+  int a1,a2,c,g,tg,og,l,x1,x2,x3,x4,y1,y2,y3,y4;
+  float o,m,s;
+  
+  /* adjust the properties depending of the track, this was "manually" optimized ;) */
+  if (strcmp(trackname,"car")==0)
+  {
+    a1=190;
+    a2=180;
+    o=0.1;
+    m=0.1;
+    s=car->speed/3.3+0.4;
+    og=130;
+  }
+  else if (strcmp(trackname,"first")==0)
+  {
+    a1=240;
+    a2=230;
+    o=0.1;
+    m=0.1;
+    s=car->speed/3.3+0.3;
+    og=120;
+  }
+  else if (strcmp(trackname,"icy")==0)
+  {
+    a1=250;
+    a2=240;
+    o=0.3;
+    m=0.1;
+    s=car->speed/3.3+0.3;
+    og=120;
+  }
+  else if (strcmp(trackname,"hairpins")==0)
+  {
+    a1=220;
+    a2=210;
+    o=0.6;
+    m=0.1;
+    s=car->speed/3.3+0.4;
+    og=120;
+  }
+  else if (strcmp(trackname,"simple")==0)
+  {
+    a1=190;
+    a2=180;
+    o=0.2;
+    m=0.1;
+    s=car->speed/3.3+0.4;
+    og=1;
+  }
+  else if (strcmp(trackname,"loop")==0)
+  {
+    a1=150;
+    a2=90;
+    o=0.8;
+    m=0.1;
+    s=car->speed/3.3+0.4;
+    og=1;
+  }
+  /* some default values that may work on some tracks */
+  else
+  {
+    a1=190;
+    a2=180;
+    o=0.2;
+    m=0.1;
+    s=car->speed/3.3+0.4;
+    og=1;
+  }
+  
+  /* should we accelerate or brake ? */
+  c=getpixel(fun,car->x,car->y);
+  *ku=1,*kd=0;
+  for (l=0;l<a1;l++)
+  {
+    x1=car->x-cos(car->angle)*l*s;
+    y1=car->y-sin(car->angle)*l*s;
+    if (x1>0 && y1>0 && x1<fun->w && y1<fun->h) c=getpixel(fun,x1,y1); else c=0;
+    g=(c>>GSHIFT)&0xff;
+    if (g<og && car->speed>m) { *ku=0,*kd=1; break; }
+  }
+  
+  /* should we turn ? left or right ? */
+  *kl=0;
+  *kr=0;
+  for (l=0;l<a2;l++)
+  {
+    x2=car->x-cos(car->angle)*l*s;
+    y2=car->y-sin(car->angle)*l*s;
+    x3=car->x-cos(car->angle-o)*l*s;
+    y3=car->y-sin(car->angle-o)*l*s;
+    x4=car->x-cos(car->angle+o)*l*s;
+    y4=car->y-sin(car->angle+o)*l*s;
+    
+    if (x3>0 && y3>0 && x3<fun->w && y3<fun->h) c=getpixel(fun,x3,y3); else c=0;
+    tg=(c>>GSHIFT)&0xff;
+    if (x4>0 && y4>0 && x4<fun->w && y4<fun->h) c=getpixel(fun,x4,y4); else c=0;
+    g=(c>>GSHIFT)&0xff;
+    if (g>tg) { *kr=1; break; } else if (g<tg) { *kl=1; break; }
+  }
+}
diff --git a/car.c b/car.c
index e769567..3c9be9b 100644 (file)
--- a/car.c
+++ b/car.c
@@ -4,7 +4,7 @@
 
 void move_car(struct _car *car,int keys,SDL_Surface *fun)
 {
-  int c,r,g,b;
+  Uint32 c,r,g,b;
   
   /* reset flags */
   car->lapflag=0;
@@ -13,11 +13,11 @@ void move_car(struct _car *car,int keys,SDL_Surface *fun)
   /* get the pixel color under the center of car in the function map */
   c=getpixel(fun,car->x,car->y);
   /* red layer (checkpoints) */
-  r=(c    )&0xff;
+  r=(c>>RSHIFT)&0xff;
   /* green layer (road quality) */
-  g=(c>>)&0xff;
-  /* blue layer (unused) */
-  b=(c>>16)&0xff;
+  g=(c>>GSHIFT)&0xff;
+  /* blue layer (grip) */
+  b=(c>>BSHIFT)&0xff;
 
   if (keys & 8) /* up */
     car->speed+=0.01*2*COEFF;
index f7cb706..206cfe6 100644 (file)
--- a/network.h
+++ b/network.h
@@ -4,4 +4,4 @@
 #define MAX_LAG 500
 #define MAX_CLIENTS 32
 #define PORT "3600"
-#define VERSION "0.3"
+#define VERSION "0.4"
diff --git a/sdl.h b/sdl.h
index 193d175..3eceb70 100644 (file)
--- a/sdl.h
+++ b/sdl.h
@@ -4,6 +4,19 @@
 #include <SDL.h>
 #include <SDL_image.h>
 
+/* endianness setup */
+#if SDL_BYTEORDER == SDL_BIG_ENDIAN
+  #define RSHIFT 24
+  #define GSHIFT 16
+  #define BSHIFT 8
+  #define ASHIFT 0
+#else
+  #define RSHIFT 0
+  #define GSHIFT 8
+  #define BSHIFT 16
+  #define ASHIFT 24
+#endif
+
 void print(SDL_Surface *dst,int x,int y,unsigned char *text);
 void readstring(SDL_Surface *dst,int x,int y,unsigned char *text,int limit);
 Uint32 getpixel(SDL_Surface *surface, int x, int y);
index c6da730..047d985 100644 (file)
--- a/server.c
+++ b/server.c
@@ -54,14 +54,14 @@ void announce(char *name,int clients)
   printf("announcing server... ");
   fflush(stdout);
 
-  if(SDLNet_ResolveHost(&ip,"royale.zerezo.com",80)==-1)
+  if (SDLNet_ResolveHost(&ip,"royale.zerezo.com",80)==-1)
   {
     fprintf(stderr,"SDLNet_ResolveHost: %s\n",SDLNet_GetError());
     return;
   }
   
   tcpsock=SDLNet_TCP_Open(&ip);
-  if(!tcpsock)
+  if (!tcpsock)
   {
     fprintf(stderr,"SDLNet_TCP_Open: %s\n",SDLNet_GetError());
     return;
@@ -72,11 +72,12 @@ void announce(char *name,int clients)
   
   len=strlen(temp);
   result=SDLNet_TCP_Send(tcpsock,temp,len);
-  if(result<len)
+  if (result<len)
     fprintf(stderr,"SDLNet_TCP_Send: %s\n", SDLNet_GetError());
   else
     printf("done\n");
-
+  
+  free(temp);
   SDLNet_TCP_Close(tcpsock);
 }
 
@@ -165,10 +166,10 @@ int main(int argc,char *argv[])
     *tmp++=1; /* startup countdown */
     strcpy(tmp,tracklist->name);
     tmp+=strlen(tmp)+1;
-    memcpy(tmp,&time,sizeof(int));
-    tmp+=sizeof(int);
-    memcpy(tmp,&network_speed,sizeof(int));
-    tmp+=sizeof(int);
+    SDLNet_Write32(time,tmp);
+    tmp+=4;
+    SDLNet_Write32(network_speed,tmp);
+    tmp+=4;
     packet->len=(void *)tmp-(void *)packet->data;
     for (i=0;i<MAX_CLIENTS;i++) if (clients[i].connected)
     {
@@ -178,7 +179,8 @@ int main(int argc,char *argv[])
     
     /* wait for everybody startup */
     for (i=0;i<MAX_CLIENTS;i++) if (clients[i].connected) break;
-    if (i!=MAX_CLIENTS) SDL_Delay(5000);
+    /* 5000ms for countdown, and 500ms for loading time... */
+    if (i!=MAX_CLIENTS) SDL_Delay(5500);
     
     printf("go\n");
     
@@ -216,7 +218,7 @@ int main(int argc,char *argv[])
             tmp+=strlen(tmp)+1;
             strcpy(clients[i].pseudo,tmp);
             tmp+=strlen(tmp)+1;
-            memcpy(&clients[i].car.color,tmp,sizeof(int));
+            clients[i].car.color=SDLNet_Read16(tmp);
             clients[i].car.x=tracklist->x;
             clients[i].car.y=tracklist->y;
             clients[i].car.w=30;
@@ -233,10 +235,10 @@ int main(int argc,char *argv[])
             *tmp++=0; /* no startup countdown */
             strcpy(tmp,tracklist->name);
             tmp+=strlen(tmp)+1;
-            memcpy(tmp,&time,sizeof(int));
-            tmp+=sizeof(int);
-            memcpy(tmp,&network_speed,sizeof(int));
-            tmp+=sizeof(int);
+            SDLNet_Write32(time,tmp);
+            tmp+=4;
+            SDLNet_Write32(network_speed,tmp);
+            tmp+=4;
             packet->len=(void *)tmp-(void *)packet->data;
             SDLNet_UDP_Send(udpsock,-1,packet);
             break;
@@ -264,10 +266,11 @@ int main(int argc,char *argv[])
           {
             int temp;
             tmp+=strlen(tmp)+1;
-            memcpy(&temp,tmp,sizeof(int));
-            tmp+=sizeof(int);
-            if (clients[id].lasttime==temp)
+            temp=SDLNet_Read32(tmp);
+            tmp+=4;
+            if (clients[id].lasttime<=temp)
             {
+              tmp+=temp-clients[id].lasttime;
               /* printf("servertime = %d lasttime = %d temp = %d strlen(tmp) = %d\n",time,clients[id].lasttime,temp,strlen(tmp)); */
               /*printf("keys = %s\n",tmp);*/
               while (*tmp)
@@ -298,20 +301,26 @@ int main(int argc,char *argv[])
         tmp=packet->data;
         strcpy(tmp,"positions");
         tmp+=strlen(tmp)+1;
-        memcpy(tmp,&time,sizeof(int));
-        tmp+=sizeof(int); /* for server time */
-        tmp+=sizeof(int); /* for client time */
-        tmp+=sizeof(int); /* for number of cars */
+        SDLNet_Write32(time,tmp);
+        tmp+=4; /* for server time */
+        tmp+=4; /* for client time */
+        tmp+=2; /* for number of cars */
         nb=0;
         for (j=0;j<MAX_CLIENTS;j++) if (j!=i && clients[j].connected)
         {
-          memcpy(tmp,&clients[j].car,sizeof(struct _car));
-          tmp+=sizeof(struct _car);
+          SDLNet_Write16(clients[j].car.x,tmp);
+          tmp+=2;
+          SDLNet_Write16(clients[j].car.y,tmp);
+          tmp+=2;
+          SDLNet_Write16(clients[j].car.angle*1000,tmp);
+          tmp+=2;
+          SDLNet_Write16(clients[j].car.color,tmp);
+          tmp+=2;
           nb++;
         }
-        memcpy(packet->data+strlen("positions")+1+sizeof(int)+sizeof(int),&nb,sizeof(int));
+        SDLNet_Write16(nb,packet->data+strlen("positions")+1+4+4);
+        SDLNet_Write32(clients[i].lasttime,packet->data+strlen("positions")+1+4);
         packet->len=(void *)tmp-(void *)packet->data;
-        memcpy(packet->data+strlen("positions")+1+sizeof(int),&clients[i].lasttime,sizeof(int));
         packet->address=clients[i].address;
         SDLNet_UDP_Send(udpsock,-1,packet);
       }
@@ -329,7 +338,7 @@ int main(int argc,char *argv[])
     tmp=packet->data;
     strcpy(tmp,"finish");
     tmp+=strlen(tmp)+1;
-    tmp+=sizeof(int); /* space for number */
+    tmp+=2; /* space for number */
     nb=0;
     for (i=0;i<10;i++)
     {
@@ -343,14 +352,14 @@ int main(int argc,char *argv[])
       {
         sprintf(tmp,"%s : %d",clients[best_id].pseudo,best_sc);
         tmp+=strlen(tmp)+1;
-        memcpy(tmp,&clients[best_id].car.color,sizeof(int));
-        tmp+=sizeof(int);
+        SDLNet_Write16(clients[best_id].car.color,tmp);
+        tmp+=2;
         clients[best_id].car.lap=-1;
         nb++;
         printf("top %d : %s - %d\n",nb,clients[best_id].pseudo,best_sc);
       }
     }
-    memcpy(packet->data+strlen("finish")+1,&nb,sizeof(int));
+    SDLNet_Write16(nb,packet->data+strlen("finish")+1);
     packet->len=(void *)tmp-(void *)packet->data;
     for (i=0;i<MAX_CLIENTS;i++) if (clients[i].connected)
     {
@@ -358,7 +367,8 @@ int main(int argc,char *argv[])
       SDLNet_UDP_Send(udpsock,-1,packet);
     }
     SDL_Delay(5000);
-  
+    
+    SDL_FreeSurface(fun);
     tracklist=tracklist->next;
   }
 
diff --git a/sprites/boss.png b/sprites/boss.png
new file mode 100644 (file)
index 0000000..d61eae7
Binary files /dev/null and b/sprites/boss.png differ
index 1fd3062..8b6430a 100644 (file)
Binary files a/tracks/car_function.png and b/tracks/car_function.png differ
index 9d9339b..10cb4d9 100644 (file)
Binary files a/tracks/hairpins_function.png and b/tracks/hairpins_function.png differ
index a352376..d198f48 100644 (file)
@@ -25,8 +25,8 @@ Royale
 435
 215
 180
-1175
-Royale
+1149
+mrhe
 
 hairpins
 Hairpins
@@ -35,8 +35,8 @@ ICFP Programming Contest
 505
 665
 0
-3158
-Voisin
+3030
+Royale
 
 simple
 Simple
@@ -45,5 +45,15 @@ ICFP Programming Contest
 585
 565
 0
-1784
-Voisin
+1766
+Royale
+
+loop
+Loop
+Royale
+0.1
+678
+686
+0
+1614
+Royale
diff --git a/tracks/loop.png b/tracks/loop.png
new file mode 100644 (file)
index 0000000..f9dd0af
Binary files /dev/null and b/tracks/loop.png differ
diff --git a/tracks/loop_function.png b/tracks/loop_function.png
new file mode 100644 (file)
index 0000000..3d486b9
Binary files /dev/null and b/tracks/loop_function.png differ
index c88d6cf..6f787e0 100644 (file)
--- a/zeRace.c
+++ b/zeRace.c
@@ -1,5 +1,5 @@
 /*
- * zeRace 0.3, a funny retro racing game
+ * zeRace 0.4, a funny retro racing game
  * http://royale.zerezo.com/zerace/
  * 
  * Copyright (C) 2004  Antoine Jacquet <royale@zerezo.com>
@@ -33,6 +33,7 @@
 #include "car.h"
 #include "tracklist.h"
 #include "network.h"
+#include <sys/stat.h>
 
 /* configuration constants */
 #define COEFF 1
@@ -57,7 +58,8 @@ struct _config
   SDLKey left;
   SDLKey right;
   int color;
-} config = {"anonymous","",0,0,1,SDLK_UP,SDLK_DOWN,SDLK_LEFT,SDLK_RIGHT,6};
+  SDLKey boss;
+} config = {"anonymous","",0,0,1,SDLK_UP,SDLK_DOWN,SDLK_LEFT,SDLK_RIGHT,6,SDLK_b};
 
 /* full script for a lap */
 struct _record
@@ -133,14 +135,14 @@ void zeRace_check_version()
   printf("checking version... ");
   fflush(stdout);
 
-  if(SDLNet_ResolveHost(&ip,"royale.zerezo.com",80)==-1)
+  if (SDLNet_ResolveHost(&ip,"royale.zerezo.com",80)==-1)
   {
     fprintf(stderr,"SDLNet_ResolveHost: %s\n",SDLNet_GetError());
     return;
   }
   
   tcpsock=SDLNet_TCP_Open(&ip);
-  if(!tcpsock)
+  if (!tcpsock)
   {
     fprintf(stderr,"SDLNet_TCP_Open: %s\n",SDLNet_GetError());
     return;
@@ -148,7 +150,7 @@ void zeRace_check_version()
 
   len=strlen(request);
   result=SDLNet_TCP_Send(tcpsock,request,len);
-  if(result<len)
+  if (result<len)
     fprintf(stderr,"SDLNet_TCP_Send: %s\n",SDLNet_GetError());
   else
     printf("done\n");
@@ -189,14 +191,14 @@ void zeRace_update_tracks()
   printf("checking version and updating tracks... ");
   fflush(stdout);
 
-  if(SDLNet_ResolveHost(&ip,"royale.zerezo.com",80)==-1)
+  if (SDLNet_ResolveHost(&ip,"royale.zerezo.com",80)==-1)
   {
     fprintf(stderr,"SDLNet_ResolveHost: %s\n",SDLNet_GetError());
     return;
   }
   
   tcpsock=SDLNet_TCP_Open(&ip);
-  if(!tcpsock)
+  if (!tcpsock)
   {
     fprintf(stderr,"SDLNet_TCP_Open: %s\n",SDLNet_GetError());
     return;
@@ -204,7 +206,7 @@ void zeRace_update_tracks()
 
   len=strlen(request);
   result=SDLNet_TCP_Send(tcpsock,request,len);
-  if(result<len)
+  if (result<len)
     fprintf(stderr,"SDLNet_TCP_Send: %s\n",SDLNet_GetError());
   else
   {
@@ -226,6 +228,66 @@ void zeRace_update_tracks()
 }
 
 
+/* download the file if it is missing */
+void zeRace_download_file(char *file)
+{
+  IPaddress ip;
+  TCPsocket tcpsock;
+  char request[1024];
+  char response[10240],*tmp;
+  int len,result;
+  FILE *fic;
+  struct stat buf;
+
+  if (stat(file,&buf)<0)
+  {
+    printf("downloading file \"%s\" : ",file);
+    fflush(stdout);
+    
+    if (SDLNet_ResolveHost(&ip,"royale.zerezo.com",80)==-1)
+    {
+      fprintf(stderr,"SDLNet_ResolveHost: %s\n",SDLNet_GetError());
+      return;
+    }
+    
+    tcpsock=SDLNet_TCP_Open(&ip);
+    if (!tcpsock)
+    {
+      fprintf(stderr,"SDLNet_TCP_Open: %s\n",SDLNet_GetError());
+      return;
+    }
+  
+    sprintf(request,
+      "GET /zerace/%s HTTP/1.0\n"
+      "Host: royale.zerezo.com\n"
+      "User-Agent: zeRace " VERSION "\n"
+      "\n",file);
+    len=strlen(request);
+    result=SDLNet_TCP_Send(tcpsock,request,len);
+    if (result<len)
+      fprintf(stderr,"SDLNet_TCP_Send: %s\n",SDLNet_GetError());
+    else
+    {
+      if ((fic=fopen(file,"wt"))==NULL)
+      {
+        fprintf(stderr,"can't create \"%s\" file\n",file);
+        zeRace_exit();
+      }
+      len=SDLNet_TCP_Recv(tcpsock,response,10240);
+      tmp=response;
+      while (*tmp!='\n' || *(tmp+1)!='\r') tmp++;
+      tmp+=3;
+      fwrite(tmp,1,len+response-tmp,fic);
+      while ((len=SDLNet_TCP_Recv(tcpsock,response,10240))) fwrite(response,1,len,fic);
+      fclose(fic);
+      printf("done\n");
+    }
+    
+    SDLNet_TCP_Close(tcpsock);
+  }
+}
+
+
 /* load the car sprite and rotate it for every angles */
 void zeRace_generate_cars()
 {
@@ -242,7 +304,7 @@ void zeRace_generate_cars()
     {
       float x,y;
       float tcos,tsin;
-      if ((cars[i][j]=SDL_CreateRGBSurface(SDL_SWSURFACE,30,30,32,0x000000ff,0x0000ff00,0x00ff0000,0xff000000))==NULL)
+      if ((cars[i][j]=SDL_CreateRGBSurface(SDL_SWSURFACE,30,30,32,0xff<<RSHIFT,0xff<<GSHIFT,0xff<<BSHIFT,0xff<<ASHIFT))==NULL)
       {
         fprintf(stderr,"CreateRGBSurface failed: %s\n",SDL_GetError());
         zeRace_exit();
@@ -267,6 +329,7 @@ void zeRace_generate_cars()
 void zeRace_init()
 {
   int flags;
+  struct _tracklist *loopcheck;
   
   /* do a clean exit in case of emergency */
   signal(SIGINT,zeRace_exit);
@@ -284,6 +347,15 @@ void zeRace_init()
   /* get the list of local tracks */
   if (!zeRace_get_tracks(&tracklist)) zeRace_exit();
   
+  /* download missing files */
+  loopcheck=tracklist;
+  while (tracklist->next!=loopcheck)
+  {
+    zeRace_download_file(tracklist->full);
+    zeRace_download_file(tracklist->function);
+    tracklist=tracklist->next;
+  }
+  
   srand(time(NULL));
   
   if (SDL_Init(SDL_INIT_VIDEO|SDL_INIT_AUDIO)<0)
@@ -293,7 +365,7 @@ void zeRace_init()
   }
   atexit(SDL_Quit);
   
-  if(SDLNet_Init()==-1)
+  if (SDLNet_Init()==-1)
   {
     fprintf(stderr,"could not initialize SDLNet : %s\n",SDLNet_GetError());
     zeRace_exit();
@@ -333,7 +405,7 @@ void zeRace_init()
 
 
 /* send the best time for this race to the web server */
-void zeRace_send_time(struct _record record/*float x,float y,float speed,float angle,int btime,char *bkeys*/)
+void zeRace_send_time(struct _record record)
 {
   IPaddress ip;
   TCPsocket tcpsock;
@@ -356,17 +428,20 @@ void zeRace_send_time(struct _record record/*float x,float y,float speed,float a
   char *msg9="&bkeys=";
   int len,result;
   
+  /* if the best time is small enought to save all keys, send it */
+  if (record.time>=MAXRECORDKEYS) return;
+
   printf("sending time... ");
   fflush(stdout);
 
-  if(SDLNet_ResolveHost(&ip,"royale.zerezo.com",80)==-1)
+  if (SDLNet_ResolveHost(&ip,"royale.zerezo.com",80)==-1)
   {
     fprintf(stderr,"SDLNet_ResolveHost: %s\n",SDLNet_GetError());
     return;
   }
   
   tcpsock=SDLNet_TCP_Open(&ip);
-  if(!tcpsock)
+  if (!tcpsock)
   {
     fprintf(stderr,"SDLNet_TCP_Open: %s\n",SDLNet_GetError());
     return;
@@ -377,7 +452,7 @@ void zeRace_send_time(struct _record record/*float x,float y,float speed,float a
   
   len=strlen(temp);
   result=SDLNet_TCP_Send(tcpsock,temp,len);
-  if(result<len)
+  if (result<len)
     fprintf(stderr,"SDLNet_TCP_Send: %s\n", SDLNet_GetError());
   else
     printf("done\n");
@@ -421,6 +496,21 @@ void zeRace_launch(int alltime,int go)
   int lastack=alltime;
   struct _record net;
   struct _car oldnetpos[MAX_CLIENTS],newnetpos[MAX_CLIENTS];
+    
+  /* free memory */
+  void free_mem()
+  {
+    SDL_FreeSurface(cir);
+    SDL_FreeSurface(fun);
+    if (config.sound)
+    {
+      Mix_FreeMusic(light);
+      Mix_FreeMusic(engine);
+      Mix_FreeMusic(crash);
+      Mix_FreeMusic(slide);
+    }
+    return;
+  }
 
   cir=IMG_Load(tracklist->full);
   fun=IMG_Load(tracklist->function);
@@ -483,13 +573,6 @@ void zeRace_launch(int alltime,int go)
           switch (event.key.keysym.sym)
           {
             case SDLK_ESCAPE:
-              /* free memory */
-              Mix_FreeMusic(light);
-              Mix_FreeMusic(engine);
-              Mix_FreeMusic(crash);
-              Mix_FreeMusic(slide);
-              /* if the best time is small enought to save all keys, send it */
-              if (best.time<MAXRECORDKEYS) zeRace_send_time(best);
               if (udpsock)
               {
                 print(screen,WIDTH/2-strlen("Disconnecting !")*5,HEIGHT/2-10,"Disconnecting !");
@@ -498,6 +581,8 @@ void zeRace_launch(int alltime,int go)
                 SDLNet_UDP_Send(udpsock,-1,packet);
                 SDL_Flip(screen);
               }
+              zeRace_send_time(best);
+              free_mem();
               return;  
             default:
               i=event.key.keysym.sym;
@@ -505,6 +590,19 @@ void zeRace_launch(int alltime,int go)
               if (i==config.down) kd=1;
               if (i==config.left) kl=1;
               if (i==config.right) kr=1;
+              if (i==config.boss)
+              {
+                /* display the boss screen */
+                SDL_Surface *boss;
+                boss=IMG_Load("sprites/boss.png");
+                SDL_BlitSurface(boss,NULL,screen,NULL);
+                SDL_FreeSurface(boss);
+                SDL_Flip(screen);
+                /* and wait until the user press another key */
+                for (;;) if (SDL_PollEvent(&event)) { if (event.type==SDL_KEYDOWN) break; } else SDL_Delay(10);
+                SDL_BlitSurface(cir,NULL,screen,NULL);
+                SDL_Flip(screen);
+              }
               break;
           }
           break;
@@ -535,11 +633,10 @@ void zeRace_launch(int alltime,int go)
       char *tmp;
       while (SDLNet_UDP_Recv(udpsock,packet)) if (strcmp(packet->data,"positions")==0)
       {
-        /*struct _car netcar;*/
         int servertime,clienttime,nb;
-        memcpy(&servertime,packet->data+strlen("positions")+1,sizeof(int));
-        memcpy(&clienttime,packet->data+strlen("positions")+1+sizeof(int),sizeof(int));
-        memcpy(&nb,packet->data+strlen("positions")+1+sizeof(int)+sizeof(int),sizeof(int));
+        servertime=SDLNet_Read32(packet->data+strlen("positions")+1);
+        clienttime=SDLNet_Read32(packet->data+strlen("positions")+1+4);
+        nb=SDLNet_Read16(packet->data+strlen("positions")+1+4+4);
         if (clienttime>lastack)
         {
           memcpy(net.keys,net.keys+clienttime-lastack,net.time+1);
@@ -547,19 +644,31 @@ void zeRace_launch(int alltime,int go)
           if (clienttime>servertime+5) delay+=DELAY;
           if (clienttime<servertime-5) delay-=DELAY;
           if (delay<0) delay=0;
-          /* printf("servertime = %d clienttime = %d lastack = %d nb = %d delay = %d net.time = %d\n",servertime,clienttime,lastack,nb,delay,net.time); */
           for (i=0;i<MAX_CLIENTS;i++) newnetpos[i].w=0;
-          memcpy(newnetpos,packet->data+strlen("positions")+1+sizeof(int)*3,sizeof(struct _car)*nb);
+          for (i=0;i<nb;i++)
+          {
+            newnetpos[i].w=newnetpos[i].h=30;
+            newnetpos[i].x=SDLNet_Read16(packet->data+strlen("positions")+1+4+4+2+i*8);
+            newnetpos[i].y=SDLNet_Read16(packet->data+strlen("positions")+1+4+4+2+i*8+2);
+            newnetpos[i].angle=(float)SDLNet_Read16(packet->data+strlen("positions")+1+4+4+2+i*8+2+2)/1000;
+            newnetpos[i].color=SDLNet_Read16(packet->data+strlen("positions")+1+4+4+2+i*8+2+2+2);
+          }
           lastack=clienttime;
         }
-      } else return;
+      }
+      else /* end of this network race */
+      {
+        zeRace_send_time(best);
+        free_mem();
+        return;
+      }
       if (strlen(net.keys)!=0)
       {
         tmp=packet->data;
         strcpy(tmp,"keys");
         tmp+=strlen(tmp)+1;
-        memcpy(tmp,&lastack,sizeof(int));
-        tmp+=sizeof(int);
+        SDLNet_Write32(lastack,tmp);
+        tmp+=4;
         strcpy(tmp,net.keys);
         tmp+=strlen(tmp)+1;
         packet->len=(void *)tmp-(void *)packet->data+10;
@@ -664,6 +773,19 @@ void zeRace_launch(int alltime,int go)
       if (config.sound) Mix_PlayMusic(crash,1)==-1;
     }
    
+    /* game time */
+    current.time++;
+    net.time++;
+    if (udpsock && net.time>MAX_LAG)
+    {
+      print(screen,WIDTH/2-strlen("Timeout !")*5,HEIGHT/2-10,"Timeout !");
+      SDL_Flip(screen);
+      zeRace_send_time(best);
+      free_mem();
+      return;
+    }
+    alltime++;
+
     /* if we completed a lap */
     if (car.lapflag)
     {
@@ -684,16 +806,6 @@ void zeRace_launch(int alltime,int go)
     
     /* let the system breath */
     SDL_Delay(delay);
-    /* game time */
-    current.time++;
-    net.time++;
-    if (udpsock && net.time>MAX_LAG)
-    {
-      print(screen,WIDTH/2-strlen("Timeout !")*5,HEIGHT/2-10,"Timeout !");
-      SDL_Flip(screen);
-      return;
-    }
-    alltime++;
   }
 }
 
@@ -719,7 +831,7 @@ void zeRace_splash()
   print(screen,screen->w/2-strlen("zeRace " VERSION)*5,screen->h/2-splash->h/2-20,"zeRace " VERSION);
   SDL_FreeSurface(splash);
   SDL_Flip(screen);
-  /*SDL_Delay(2000);*/
+  SDL_Delay(2000);
 }
 
 
@@ -803,15 +915,15 @@ void zeRace_top10(char *buf)
   int i,nb,tmp;
   SDL_Rect pos;
   SDL_FillRect(screen,NULL,0x000000);
-  memcpy(&nb,buf,sizeof(int));
-  buf+=sizeof(int);
+  nb=SDLNet_Read16(buf);
+  buf+=2;
   print(screen,WIDTH/2-16*5,HEIGHT/14,"* Race results *");
   for (i=0;i<nb;i++)
   {
     print(screen,150,(i+3)*HEIGHT/14,buf);
     buf+=strlen(buf)+1;
-    memcpy(&tmp,buf,sizeof(int));
-    buf+=sizeof(int);
+    tmp=SDLNet_Read16(buf);
+    buf+=2;
     pos.x=110;
     pos.y=(i+3)*HEIGHT/14-8;
     SDL_BlitSurface(cars[tmp][0],NULL,screen,&pos);
@@ -838,8 +950,8 @@ void zeRace_connect(char *host,int port)
   tmp+=strlen(tmp)+1;
   strcpy(tmp,config.pseudo);
   tmp+=strlen(tmp)+1;
-  memcpy(tmp,&config.color,sizeof(int));
-  tmp+=sizeof(int);
+  SDLNet_Write16(config.color,tmp);
+  tmp+=2;
   packet->len=(void *)tmp-(void *)packet->data;
   SDLNet_UDP_Send(udpsock,-1,packet);
   /* network loop */
@@ -861,9 +973,9 @@ void zeRace_connect(char *host,int port)
         zeRace_exit();
       }
       tmp+=strlen(tmp)+1;
-      memcpy(&time,tmp,sizeof(int));
-      tmp+=sizeof(int);
-      memcpy(&network_speed,tmp,sizeof(int));
+      time=SDLNet_Read32(tmp);
+      tmp+=4;
+      network_speed=SDLNet_Read32(tmp);
       zeRace_launch(time,go);
       if (strcmp(packet->data,"finish")==0) zeRace_top10(packet->data+strlen(packet->data)+1);
       lag=0;
@@ -974,14 +1086,14 @@ void zeRace_internet()
   printf("dowloading list of servers... ");
   fflush(stdout);
 
-  if(SDLNet_ResolveHost(&ip,"royale.zerezo.com",80)==-1)
+  if (SDLNet_ResolveHost(&ip,"royale.zerezo.com",80)==-1)
   {
     fprintf(stderr,"SDLNet_ResolveHost: %s\n",SDLNet_GetError());
     return;
   }
   
   tcpsock=SDLNet_TCP_Open(&ip);
-  if(!tcpsock)
+  if (!tcpsock)
   {
     fprintf(stderr,"SDLNet_TCP_Open: %s\n",SDLNet_GetError());
     return;
@@ -989,7 +1101,7 @@ void zeRace_internet()
 
   len=strlen(request);
   result=SDLNet_TCP_Send(tcpsock,request,len);
-  if(result<len)
+  if (result<len)
     fprintf(stderr,"SDLNet_TCP_Send: %s\n",SDLNet_GetError());
   else
   {
@@ -999,7 +1111,6 @@ void zeRace_internet()
     tmp=response;
     while (*tmp!='\0' || *(tmp+1)!='\r') tmp++;
     tmp+=3;
-    printf("%s\n",tmp);
     for (i=0;i<10;i++)
     {
       strcpy(servers[i].name,tmp);
@@ -1075,7 +1186,7 @@ void zeRace_config()
 {
   SDL_Event event;
   int active=0;
-  #define CONFIG_OPTIONS 11
+  #define CONFIG_OPTIONS 12
   
   void update()
   {
@@ -1105,6 +1216,8 @@ void zeRace_config()
     pos.x=123;
     pos.y=HEIGHT/(CONFIG_OPTIONS+4)*12-7;
     SDL_BlitSurface(cars[config.color][0],NULL,screen,&pos);
+    print(screen,40,HEIGHT/(CONFIG_OPTIONS+4)*13,"Boss key : ");
+    print(screen,40+10*strlen("Boss key : "),HEIGHT/(CONFIG_OPTIONS+4)*13,config.boss?SDL_GetKeyName(config.boss):"<press key>");
     print(screen,40,HEIGHT/(CONFIG_OPTIONS+4)*(CONFIG_OPTIONS+2),"Back to main menu");
     SDL_Flip(screen);
   }
@@ -1157,7 +1270,8 @@ void zeRace_config()
                   if (config.color<0) config.color=11;
                   if (config.color>11) config.color=0;
                   break;
-                case 10:
+                case 10: config.boss=0; update(); config.boss=read_key(); break;
+                case 11:
                   return;
               }
               update();