version 0.4
[zeRace] / zeRace.c
index 7bc2e33..6f787e0 100644 (file)
--- a/zeRace.c
+++ b/zeRace.c
-#include <dirent.h>
+/*
+ * zeRace 0.4, a funny retro racing game
+ * http://royale.zerezo.com/zerace/
+ * 
+ * Copyright (C) 2004  Antoine Jacquet <royale@zerezo.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#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_mixer.h>
-#include <math.h>
+#include <SDL_rotozoom.h>
 #include "sdl.h"
+#include "car.h"
+#include "tracklist.h"
+#include "network.h"
+#include <sys/stat.h>
 
-#define MAXLINELENGTH 1000
-#define VERSION "0.1"
+/* configuration constants */
+#define COEFF 1
+#define DELAY 7
 #define WIDTH 1024
 #define HEIGHT 768
+#define MAXRECORDKEYS 9999
+
+/* tracklist */
+struct _tracklist *tracklist;
 
-/* global variables */
+/* 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;
+  SDLKey boss;
+} config = {"anonymous","",0,0,1,SDLK_UP,SDLK_DOWN,SDLK_LEFT,SDLK_RIGHT,6,SDLK_b};
 
-struct track
+/* full script for a lap */
+struct _record
 {
-  char *name;
-  char *title;
-  char *author;
-  char *version;
-  char *full;
-  char *function;
-  int x,y,a;
-  struct track *prev;
-  struct track *next;
-} *tracklist=NULL;
-
-int fullscreen=0;
-int sound=0;
-char pseudo[MAXLINELENGTH]="anonymous";
-char url[MAXLINELENGTH]="";
+  float x,y,angle,speed;
+  char keys[MAXRECORDKEYS];
+  int time;
+};
+
+/* display and all directions for the car */
 SDL_Surface *screen;
-SDL_Surface *cars[256];
+SDL_Surface *cars[12][256];
+
+/* network stuff */
+UDPsocket udpsock=NULL;
+UDPpacket *packet;
+int network_speed=1;
 
+
+/* read the user configuration file */
+void zeRace_read_config()
+{
+  FILE *fic;
+  if ((fic=fopen("zeRace.cfg","rt"))==NULL)
+  {
+    fprintf(stderr,"can't open config file \"zeRace.cfg\"\n");
+    return;
+  }
+  fread(&config,sizeof(struct _config),1,fic);
+  fclose(fic);
+}
+
+
+/* save the user configuration file */
+void zeRace_save_config()
+{
+  FILE *fic;
+  if ((fic=fopen("zeRace.cfg","wt"))==NULL)
+  {
+    fprintf(stderr,"can't create config file \"zeRace.cfg\"\n");
+    return;
+  }
+  fwrite(&config,sizeof(struct _config),1,fic);
+  fclose(fic);
+}
+
+
+/* exit the game and clean */
 void zeRace_exit()
 {
   printf("quit\n");
-  if (sound) Mix_CloseAudio();
+  if (config.sound) Mix_CloseAudio();
   SDLNet_Quit();
   SDL_Quit();
-  /*
-  save_config();
-  zeRace_send_ghosts();
-  close_sdl();
-  */
+  zeRace_save_config();
   exit(0);
 }
 
 
-void zeRace_read_config()
+/* check for a newer version online to warn the user */
+void zeRace_check_version()
 {
+  IPaddress ip;
+  TCPsocket tcpsock;
+  char *request=
+    "GET /zerace/version.php HTTP/1.0\n"
+    "Host: royale.zerezo.com\n"
+    "User-Agent: zeRace " VERSION "\n"
+    "\n";
+  char response[1024],*tmp,*version;
+  int len,result;
+  
+  printf("checking version... ");
+  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;
+  }
+
+  len=strlen(request);
+  result=SDLNet_TCP_Send(tcpsock,request,len);
+  if (result<len)
+    fprintf(stderr,"SDLNet_TCP_Send: %s\n",SDLNet_GetError());
+  else
+    printf("done\n");
+  
+  len=SDLNet_TCP_Recv(tcpsock,response,1024);
+  if (len<0)
+    fprintf(stderr,"SDLNet_TCP_Recv: %s\n",SDLNet_GetError());
+  else
+  {
+    tmp=response;
+    while (*tmp++!='\0') if (strncmp(tmp,"version=",8)==0)
+    {
+      version=tmp+8;
+      while (*tmp!='\n') tmp++;
+      *tmp='\0';
+      if (strcmp(version,VERSION)>0) printf("new version available !\nhttp://royale.zerezo.com/zerace/\n");
+    }
+  }
+
+  SDLNet_TCP_Close(tcpsock);
+}
+
+
+/* get remote list of tracks */
+void zeRace_update_tracks()
+{
+  IPaddress ip;
+  TCPsocket tcpsock;
+  char *request=
+    "GET /zerace/tracks.php HTTP/1.0\n"
+    "Host: royale.zerezo.com\n"
+    "User-Agent: zeRace " VERSION "\n"
+    "\n";
+  char response[10240],*tmp;
+  int len,result;
   FILE *fic;
-  char line[MAXLINELENGTH];
-  char keyword[MAXLINELENGTH];
-  char value[MAXLINELENGTH];
-  int configlines=0;
   
-  if ((fic=fopen("zeRace.cfg","rt"))==NULL)
+  printf("checking version and updating tracks... ");
+  fflush(stdout);
+
+  if (SDLNet_ResolveHost(&ip,"royale.zerezo.com",80)==-1)
   {
-    fprintf(stderr,"can't open config file \"zeRace.cfg\"\n");
-    exit(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;
   }
 
-  while (fgets(line,MAXLINELENGTH,fic))
+  len=strlen(request);
+  result=SDLNet_TCP_Send(tcpsock,request,len);
+  if (result<len)
+    fprintf(stderr,"SDLNet_TCP_Send: %s\n",SDLNet_GetError());
+  else
   {
-    configlines++;
-    if (*line!=';' && *line!='#' && *line!='/' && *line!='-' && *line!='\n')
+    if ((fic=fopen("tracks/list.txt","wt"))==NULL)
     {
-      if ((sscanf(line,"%s : %s\n",(char *)&keyword,(char *)&value))!=2) { fprintf(stderr,"error in config file : each line must have the format \"keyword : value\" (line %d)\n",configlines); exit(1); }
-      
-      if (strcmp("pseudo",keyword)==0)
-        strcpy(pseudo,value);
-      else
-      
-      if (strcmp("url",keyword)==0)
-        strcpy(url,value);
-      else
-      
-      if (strcmp("fullscreen",keyword)==0)
-      {
-        if (strcmp("no",value)==0) fullscreen=0;
-        else if (strcmp("yes",value)==0) fullscreen=1;
-        else { fprintf(stderr,"unknown value for \"fullscreen\" option, must be \"yes\" or \"no\"\n"); exit(1); }
-      }
-      else
-      
-      if (strcmp("sound",keyword)==0)
-      {
-        if (strcmp("no",value)==0) sound=0;
-        else if (strcmp("yes",value)==0) sound=1;
-        else { fprintf(stderr,"unknown value for \"sound\" option, must be \"yes\" or \"no\"\n"); exit(1); }
-      }
-      
-      else { fprintf(stderr,"error in config file : \"%s\" is an unknown keyword (line %d)\n",keyword,configlines); exit(1); }        
+      fprintf(stderr,"can't create track list\n");
+      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);
+    fclose(fic);
+    printf("done\n");
   }
+  
+  SDLNet_TCP_Close(tcpsock);
 }
 
 
-void zeRace_get_tracks()
+/* download the file if it is missing */
+void zeRace_download_file(char *file)
 {
-  struct dirent **namelist;
-  int i;
-  unsigned char ext[5];
+  IPaddress ip;
+  TCPsocket tcpsock;
+  char request[1024];
+  char response[10240],*tmp;
+  int len,result;
   FILE *fic;
-  char *shortname=NULL;
-  char line[MAXLINELENGTH];
-  int configlines;
-  struct track *tmp=NULL,*first=NULL;
-  DIR *dirp;
-  struct dirent *dp;
-
-  if ((dirp=opendir("tracks"))==NULL) { fprintf(stderr,"can't open \"tracks\" directory or no tracks\n"); return; }
-  
-  while (dp=readdir(dirp)) if (strlen(dp->d_name)>4)
-  {
-    shortname=(char *)realloc(shortname,strlen(dp->d_name)-3);
-    strncpy(shortname,dp->d_name,strlen(dp->d_name)-4);
-    shortname[strlen(dp->d_name)-4]='\0';
-    for (i=0;i<5;i++) ext[i]=tolower(dp->d_name[strlen(dp->d_name)-4+i]);
-    if (strcmp(".txt",ext)==0)
+  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)
     {
-      tmp=(struct track *)malloc(sizeof(struct track));
-      if (first==NULL) first=tmp;
-      tmp->name=(char *)malloc(strlen(shortname)+1);
-      strcpy(tmp->name,shortname);
-      tmp->title="";
-      tmp->author="";
-      tmp->version="";
-      tmp->x=10;
-      tmp->y=10;
-      tmp->a=0;
-      configlines=0;
-      sprintf(line,"tracks/%s.txt",shortname);
-      if ((fic=fopen(line,"rt"))==NULL)
+      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 open track file \"%s\"\n",line);
+        fprintf(stderr,"can't create \"%s\" file\n",file);
         zeRace_exit();
       }
-      while (fgets(line,MAXLINELENGTH,fic))
+      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()
+{
+  int i,j;
+  SDL_Surface *car;
+  char temp[20]="sprites/carX.png";
+  for (i=0;i<12;i++)
+  {
+    temp[11]='A'+i;
+    /* load the car sprite */
+    car=IMG_Load(temp);
+    /* and rotate it for all available angles */
+    for (j=0;j<256;j++)
+    {
+      float x,y;
+      float tcos,tsin;
+      if ((cars[i][j]=SDL_CreateRGBSurface(SDL_SWSURFACE,30,30,32,0xff<<RSHIFT,0xff<<GSHIFT,0xff<<BSHIFT,0xff<<ASHIFT))==NULL)
       {
-        configlines++;
-        if (*line!=';' && *line!='#' && *line!='/' && *line!='-' && *line!='\n')
-        {
-          for (i=0;i<strlen(line)-2;i++) if (line[i]==' ' && line[i+1]==':' && line[i+2]==' ') break;
-          if (line[i]!=' ' || line[i+1]!=':' || line[i+2]!=' ')
-          {
-            fprintf(stderr,"error in track file \"%s\" : each line must have the format \"keyword : value\" (line %d)\n",shortname,configlines);
-            exit(1);
-          }
-          line[i]='\0';
-          
-          if (strcmp("title",line)==0)
-          {
-            tmp->title=(char *)malloc(strlen(line+i+3)+1);
-            strcpy(tmp->title,line+i+3);
-          }
-          else
-          
-          if (strcmp("author",line)==0)
-          {
-            tmp->author=(char *)malloc(strlen(line+i+3)+1);
-            strcpy(tmp->author,line+i+3);
-          }
-          else
-          
-          if (strcmp("x",line)==0)
-            tmp->x=atoi(line+i+3);
-          else
-          
-          if (strcmp("y",line)==0)
-            tmp->y=atoi(line+i+3);
-          else
-          
-          if (strcmp("a",line)==0)
-            tmp->a=atoi(line+i+3);
-          else
-          
-          if (strcmp("version",line)==0)
-          {
-            tmp->version=(char *)malloc(strlen(line+i+3)+1);
-            strcpy(tmp->version,line+i+3);
-          }
-          
-          else { fprintf(stderr,"error in track file \"%s\" : \"%s\" is an unknown keyword (line %d)\n",shortname,line,configlines); exit(1); }        
-        }
+        fprintf(stderr,"CreateRGBSurface failed: %s\n",SDL_GetError());
+        zeRace_exit();
+      };
+      tcos=cos(2*M_PI*j/256);
+      tsin=sin(2*M_PI*j/256);
+      for (x=0;x<cars[i][j]->w;x++) for (y=0;y<cars[i][j]->h;y++)
+      {
+        int x2,y2;
+        x2=(x-cars[i][j]->w/2.0)*tcos+(y-cars[i][j]->h/2.0)*tsin+car->w/2.0;
+        y2=(x-cars[i][j]->w/2.0)*tsin-(y-cars[i][j]->h/2.0)*tcos+car->h/2.0;
+        if (x2>0 && x2<car->w && y2>0 && y2<car->h)
+          putpixel(cars[i][j],x,y,getpixel(car,x2,y2));
       }
-      fclose(fic);
-      tmp->full=(char *)malloc(strlen(line)+20);
-      sprintf(tmp->full,"tracks/%s.png",shortname);
-      tmp->function=(char *)malloc(strlen(line)+30);
-      sprintf(tmp->function,"tracks/%s_function.png",shortname);
-      tmp->prev=tracklist;
-      if (tmp->prev) tmp->prev->next=tmp;
-      tracklist=tmp;
     }
+    SDL_FreeSurface(car);
   }
-  if (!tmp) { fprintf(stderr,"no circuits found !\n"); zeRace_exit(); }
-  while (tmp->prev) tmp=tmp->prev;
-  tmp->prev=tracklist;
-  tracklist->next=tmp;
 }
 
 
+/* initialize the game */
 void zeRace_init()
 {
-  SDL_Surface *car;
-  int i,flags;
+  int flags;
+  struct _tracklist *loopcheck;
   
+  /* do a clean exit in case of emergency */
   signal(SIGINT,zeRace_exit);
   signal(SIGTERM,zeRace_exit);
+
+  /* read the user configuration file */
   zeRace_read_config();
-  //zeRace_check_version();
-  zeRace_get_tracks();
-  //zeRace_get_ghosts();
+
+  /* check for a newer available version */
+  zeRace_check_version();
+  
+  /* update the list of tracks */
+  zeRace_update_tracks();
+  
+  /* 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));
   
@@ -225,14 +365,23 @@ 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();
   }
 
+  packet=SDLNet_AllocPacket(1024);
+  if (!packet)
+  {
+    fprintf(stderr,"SDLNet_AllocPacket: %s\n",SDLNet_GetError());
+    zeRace_exit();
+  }
+
+  SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY,SDL_DEFAULT_REPEAT_INTERVAL);
+
   flags=SDL_HWSURFACE|SDL_ANYFORMAT;
-  if (fullscreen) flags|=SDL_FULLSCREEN;
+  if (config.fullscreen) flags|=SDL_FULLSCREEN;
   
   if ((screen=SDL_SetVideoMode(WIDTH,HEIGHT,32,flags))==NULL)
   {
@@ -244,45 +393,25 @@ void zeRace_init()
   SDL_WM_SetCaption("zeRace " VERSION,"zeRace " VERSION);
   SDL_ShowCursor(SDL_DISABLE);
   
-  if (sound) if (Mix_OpenAudio(44100,MIX_DEFAULT_FORMAT,2,512)<0)
+  if (config.sound) if (Mix_OpenAudio(44100,MIX_DEFAULT_FORMAT,2,512)<0)
   {
     fprintf(stderr,"Mix_OpenAudio error\n");
     zeRace_exit();
   }
 
-  car=IMG_Load("sprites/car.png");
-  for (i=0;i<256;i++)
-  {
-    float x,y;
-    float tcos,tsin;
-    cars[i]=SDL_CreateRGBSurface(SDL_SWSURFACE,car->h*2,car->h*2,32,0x000000ff,0x0000ff00,0x00ff0000,0xff000000);
-    if(cars[i]==NULL)
-    {
-      fprintf(stderr,"CreateRGBSurface failed: %s\n",SDL_GetError());
-      exit(1);
-    }
-    tcos=cos(2*M_PI*i/256);
-    tsin=sin(2*M_PI*i/256);
-    for (x=0;x<cars[i]->w;x++) for (y=0;y<cars[i]->h;y++)
-    {
-      int x2,y2;
-      Uint32 col;
-      x2=(x-cars[i]->w/2.0)*tcos+(y-cars[i]->h/2.0)*tsin+car->w/2.0;
-      y2=(x-cars[i]->w/2.0)*tsin-(y-cars[i]->h/2.0)*tcos+car->h/2.0;
-      if (x2>0 && x2<car->w && y2>0 && y2<car->h)
-        putpixel(cars[i],x,y,getpixel(car,x2,y2));
-    }
-  }
+  /* pre-calculate car sprites */
+  zeRace_generate_cars();
 }
 
 
-void zeRace_send_time(float x,float y,float speed,float angle,int btime,char *bkeys)
+/* send the best time for this race to the web server */
+void zeRace_send_time(struct _record record)
 {
   IPaddress ip;
   TCPsocket tcpsock;
   char *temp;
   char *msg1=
-    "POST /zerace/time.php HTTP/1.1\n"
+    "POST /zerace/time.php HTTP/1.0\n"
     "Host: royale.zerezo.com\n"
     "User-Agent: zeRace " VERSION "\n"
     "Content-Type: application/x-www-form-urlencoded\n"
@@ -299,80 +428,122 @@ void zeRace_send_time(float x,float y,float speed,float angle,int btime,char *bk
   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;
   }
 
-  temp=(char *)malloc(strlen(msg1)+strlen(pseudo)+strlen(msg2)+strlen(url)+strlen(msg3)+strlen(tracklist->name)+strlen(msg4)+10+strlen(msg5)+10+strlen(msg6)+10+strlen(msg7)+10+strlen(msg8)+10+strlen(msg9)+strlen(bkeys)+100);
-  sprintf(temp,"%s%s%s%s%s%s%s%d%s%f%s%f%s%f%s%f%s%s\n",msg1,pseudo,msg2,url,msg3,tracklist->name,msg4,btime,msg5,x,msg6,y,msg7,speed,msg8,angle,msg9,bkeys);
+  temp=(char *)malloc(strlen(msg1)+strlen(config.pseudo)+strlen(msg2)+strlen(config.url)+strlen(msg3)+strlen(tracklist->name)+strlen(msg4)+10+strlen(msg5)+10+strlen(msg6)+10+strlen(msg7)+10+strlen(msg8)+10+strlen(msg9)+strlen(record.keys)+100);
+  sprintf(temp,"%s%s%s%s%s%s%s%d%s%f%s%f%s%f%s%f%s%s\n",msg1,config.pseudo,msg2,config.url,msg3,tracklist->name,msg4,record.time,msg5,record.x,msg6,record.y,msg7,record.speed,msg8,record.angle,msg9,record.keys);
   
   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");
+    printf("done\n");
 
   SDLNet_TCP_Close(tcpsock);
 }
 
 
-void zeRace_launch()
+/* print a time for a lap */
+void print_time(int x,int y,int time)
+{
+  char temp[20];
+  int q,r;
+  time*=DELAY;
+  q=(time/1000);
+  r=(time%1000);
+  if (q>100) return;
+  temp[0]=q/10+'0';
+  temp[1]=q%10+'0';
+  temp[2]='"';
+  temp[3]=r/100+'0';
+  temp[4]=r%100/10+'0';
+  temp[5]=r%10+'0';
+  temp[6]='\0';
+  print(screen,x,y,temp);
+}
+
+
+/* launch a new race */
+void zeRace_launch(int alltime,int go)
 {
   SDL_Surface *cir,*fun;
-  SDL_Rect pos,size;
+  SDL_Rect pos;
   SDL_Event event;
-  int ku=0,kd=0,kl=0,kr=0;
-  int i,time=0,lastcheck=0,btime=10000;
-  float ox,oy;
-  float x,y,angle,speed;
-  float sx,sy,sangle,sspeed;
-  float bx,by,bangle,bspeed;
-  int c,r,v,b;
-  int temp;
-  char keys[10000];
-  char bkeys[10000];
-  char text[10];
-       Mix_Music *light,*engine,*crash,*slide;
-  int lastsound_time=-999,alltime=0,lastsound=0;
+  int ku=0,kd=0,kl=0,kr=0,i;
+  struct _car car;
+  struct _record current,best;
+  Mix_Music *light,*engine,*crash,*slide;
+  int lastsound_time=-999,lastsound=0;
+  int delay=DELAY;
+  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);
   
-  sspeed=speed=0;
-  sangle=angle=(tracklist->a*2*M_PI/360);
-  sx=x=tracklist->x;
-  sy=y=tracklist->y;
-  lastcheck=0;
-  time=0;
+  current.speed=car.speed=0;
+  current.angle=car.angle=tracklist->a*2*M_PI/360;
+  current.x=car.ox=car.x=tracklist->x;
+  current.y=car.oy=car.y=tracklist->y;
+  car.lastcheck=0;
+  car.w=cars[0][0]->w;
+  car.h=cars[0][0]->h;
+  current.time=0;
+  best.time=MAXRECORDKEYS;
+  net.time=0;
+  memset(oldnetpos,0,MAX_CLIENTS*sizeof(struct _car));
+  memset(newnetpos,0,MAX_CLIENTS*sizeof(struct _car));
 
-  if (sound) if (!(light=Mix_LoadMUS("sounds/light.wav")) || !(engine=Mix_LoadMUS("sounds/engine.wav")) || !(crash=Mix_LoadMUS("sounds/crash.wav")) || !(slide=Mix_LoadMUS("sounds/slide.wav")))
+  if (config.sound) if (!(light=Mix_LoadMUS("sounds/light.wav")) || !(engine=Mix_LoadMUS("sounds/engine.wav")) || !(crash=Mix_LoadMUS("sounds/crash.wav")) || !(slide=Mix_LoadMUS("sounds/slide.wav")))
   {
     fprintf(stderr,"Mix_LoadMUS error\n");
     zeRace_exit();
   }
   
+  /* startup countdown */
   for (i=4;i>=-1;i--)
   {
     char startup[15]="sprites/?.png";
     SDL_Surface *temp;
     startup[8]='0'+i;
-    pos.x=x;
-    pos.y=y;
+    pos.x=car.x-car.w/2;
+    pos.y=car.y-car.h/2;
     SDL_BlitSurface(cir,NULL,screen,NULL);
-    SDL_BlitSurface(cars[(unsigned char)(256*angle/2.0/M_PI)%256],NULL,screen,&pos);
+    SDL_BlitSurface(cars[config.color][(unsigned char)(256*car.angle/2.0/M_PI)%256],NULL,screen,&pos);
     if (i!=4 && i!=-1)
     {
       temp=IMG_Load(startup);
@@ -381,173 +552,269 @@ void zeRace_launch()
       SDL_BlitSurface(temp,NULL,screen,&pos);
       SDL_FreeSurface(temp);
     }
-    if (sound) if (i!=4) Mix_PlayMusic(light,1);
+    if (config.sound) if (i!=4) Mix_PlayMusic(light,1);
     SDL_Flip(screen);
+    if (!go) break;
     if (i!=-1) SDL_Delay(1000);
   }
   
+  /* main loop */
   for (;;)
-  {    
-    size.w=cars[0]->w;
-    size.h=cars[0]->h;
-    pos.x=x;
-    pos.y=y;
-    SDL_BlitSurface(cir,&pos,screen,&pos);
+  {
+    /* look for user interaction */
+    while (SDL_PollEvent(&event))
+    {
+      switch (event.type)
+      {
+        case SDL_QUIT:
+          zeRace_exit();
+          break;
+        case SDL_KEYDOWN:
+          switch (event.key.keysym.sym)
+          {
+            case SDLK_ESCAPE:
+              if (udpsock)
+              {
+                print(screen,WIDTH/2-strlen("Disconnecting !")*5,HEIGHT/2-10,"Disconnecting !");
+                strcpy(packet->data,"disconnect");
+                packet->len=strlen(packet->data)+1;
+                SDLNet_UDP_Send(udpsock,-1,packet);
+                SDL_Flip(screen);
+              }
+              zeRace_send_time(best);
+              free_mem();
+              return;  
+            default:
+              i=event.key.keysym.sym;
+              if (i==config.up) ku=1;
+              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;
+        case SDL_KEYUP:
+          i=event.key.keysym.sym;
+          if (i==config.up) ku=0;
+          if (i==config.down) kd=0;
+          if (i==config.left) kl=0;
+          if (i==config.right) kr=0;
+          break;
+      }
+    }
     
-    ox=x;
-    oy=y;
-    speed*=0.995;
-    x=x-cos(angle)*speed;
-    y=y-sin(angle)*speed;
+    /* save pressed keys to validate best time */
+    if (current.time<MAXRECORDKEYS) current.keys[current.time]=(ku<<3 | kd<<2 | kl<<1 | kr)+'A';
+    current.keys[current.time+1]='\0';
+    /* and to send to server if needed */
+    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;
+          for (i=0;i<MAX_CLIENTS;i++) newnetpos[i].w=0;
+          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 /* 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;
+        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);
+        };
+      }
+    }
+
+    /* clear the old network position */
+    if (udpsock) for (i=0;i<MAX_CLIENTS;i++) if (oldnetpos[i].w)
+    {
+      pos.x=oldnetpos[i].x-car.w/2;
+      pos.y=oldnetpos[i].y-car.h/2;
+      pos.w=car.w;
+      pos.h=car.h;
+      SDL_BlitSurface(cir,&pos,screen,&pos);
+    }
+    
+    /* clear the old position */
+    pos.x=car.ox-car.w/2;
+    pos.y=car.oy-car.h/2;
+    pos.w=car.w;
+    pos.h=car.h;
+    SDL_BlitSurface(cir,&pos,screen,&pos);
     
-    if (x<0 || x>screen->w-cars[0]->w || y<0 || y>screen->h-cars[0]->h)
+    /* display the network car at the new position */
+    if (udpsock) for (i=0;i<MAX_CLIENTS;i++) if (newnetpos[i].w)
     {
-      x=ox;
-      y=oy;
-      speed=0;
+      pos.x=newnetpos[i].x-car.w/2;
+      pos.y=newnetpos[i].y-car.h/2;
+      pos.w=car.w;
+      pos.h=car.h;
+      SDL_BlitSurface(cars[newnetpos[i].color][(unsigned char)(256*newnetpos[i].angle/2.0/M_PI)%256],NULL,screen,&pos);
     }
     
-    pos.x=x;
-    pos.y=y;
-    i=(unsigned char)(256*angle/2.0/M_PI)%256;
-    SDL_BlitSurface(cars[i],NULL,screen,&pos);
-    SDL_UpdateRect(screen,ox,oy,cars[i]->w,cars[i]->h);
-    SDL_UpdateRect(screen,x,y,cars[i]->w,cars[i]->h);
+    /* display the car at the new position */
+    pos.x=car.x-car.w/2;
+    pos.y=car.y-car.h/2;
+    pos.w=car.w;
+    pos.h=car.h;
+    SDL_BlitSurface(cars[config.color][(unsigned char)(256*car.angle/2.0/M_PI)%256],NULL,screen,&pos);
     
-    if (kl) angle-=0.01;
-    if (kr) angle+=0.01;
-    if (ku) speed+=0.01*2;
-    if (kd) speed-=0.01;
+    /* update display */
+    if (udpsock)
+    {
+      for (i=0;i<MAX_CLIENTS;i++)
+      {
+        if (oldnetpos[i].w) SDL_UpdateRect(screen,oldnetpos[i].x-car.w/2,oldnetpos[i].y-car.h/2,car.w,car.h);
+        if (newnetpos[i].w) SDL_UpdateRect(screen,newnetpos[i].x-car.w/2,newnetpos[i].y-car.h/2,car.w,car.h);
+      }
+      memcpy(oldnetpos,newnetpos,MAX_CLIENTS*sizeof(struct _car));
+    }
+    SDL_UpdateRect(screen,car.ox-car.w/2,car.oy-car.h/2,car.w,car.h);
+    SDL_UpdateRect(screen,car.x-car.w/2,car.y-car.h/2,car.w,car.h);
 
+    memcpy(oldnetpos,newnetpos,MAX_CLIENTS*sizeof(struct _car));
+    
+    /* move the car */
+    move_car(&car,(ku<<3 | kd<<2 | kl<<1 | kr),fun);
+
+    /* play engine sound if no sound is currently playing */
     if (lastsound_time+100<alltime)
     {
       lastsound=0;
       lastsound_time=alltime;
-      if (sound) Mix_PlayMusic(engine,1);
+      if (config.sound) Mix_PlayMusic(engine,1);
     }
     
-    if (kd && speed>0.5 || speed>2.0 && !ku)
+    /* if the car is fast or braking, it slides */
+    if ((kd && car.speed>0.5) || (car.speed>2.0 && !ku))
     {
-
+      /* if the only sound is the engine, play the slide sound */
       if (lastsound_time+100<alltime || lastsound<1)
       {
         lastsound=1;
         lastsound_time=alltime;
-        if (sound) Mix_PlayMusic(slide,1)==-1;
+        if (config.sound) Mix_PlayMusic(slide,1)==-1;
       }
 
-      putpixel(cir,x+cars[i]->w/2+cos(angle)*cars[i]->w/3-sin(angle)*2,y+cars[i]->h/2+sin(angle)*cars[i]->h/3+cos(angle)*2,0);
-      putpixel(cir,x+cars[i]->w/2+cos(angle)*cars[i]->w/3+sin(angle)*5,y+cars[i]->h/2+sin(angle)*cars[i]->h/3-cos(angle)*5,0);
-      if (kd)
+      /* display tires slide */
+      if (config.tire)
       {
-        putpixel(cir,x+cars[i]->w/2+cos(angle)*cars[i]->w/3-sin(angle)*3,y+cars[i]->h/2+sin(angle)*cars[i]->h/3+cos(angle)*3,0);
-        putpixel(cir,x+cars[i]->w/2+cos(angle)*cars[i]->w/3+sin(angle)*4,y+cars[i]->h/2+sin(angle)*cars[i]->h/3-cos(angle)*4,0);
+        putpixel(cir,car.x+cos(car.angle)*car.w/3-sin(car.angle)*4,car.y+sin(car.angle)*car.h/3+cos(car.angle)*4,0);
+        putpixel(cir,car.x+cos(car.angle)*car.w/3+sin(car.angle)*4,car.y+sin(car.angle)*car.h/3-cos(car.angle)*4,0);
+        /* if we are braking the slide is larger */
+        if (kd)
+        {
+          putpixel(cir,car.x+cos(car.angle)*car.w/3-sin(car.angle)*3,car.y+sin(car.angle)*car.h/3+cos(car.angle)*3,0);
+          putpixel(cir,car.x+cos(car.angle)*car.w/3+sin(car.angle)*3,car.y+sin(car.angle)*car.h/3-cos(car.angle)*3,0);
+        }
       }
     }
-    
-    c=getpixel(fun,x+cars[i]->w/2,y+cars[i]->h/2);
-    r=c&0x000000ff;
-    v=(c&0x0000ff00)>>8;
-
-    if (v==0)
+   
+    /* if we crashed */
+    if (car.crashflag && (lastsound_time+100<alltime || lastsound<2))
     {
-      x=ox;
-      y=oy;
-      if (lastsound_time+100<alltime || lastsound<2)
-      {
-        lastsound=2;
-        lastsound_time=alltime;
-        if (sound) Mix_PlayMusic(crash,1)==-1;
-      }
+      lastsound=2;
+      lastsound_time=alltime;
+      if (config.sound) Mix_PlayMusic(crash,1)==-1;
     }
-    speed-=speed*(255-v)/1000;
    
-    if (r/8==lastcheck+1) lastcheck++;
-    if (r/8==0 && lastcheck==31)
+    /* game time */
+    current.time++;
+    net.time++;
+    if (udpsock && net.time>MAX_LAG)
     {
-      printf("time = %d\"%d\n",time*5/1000,time*5%1000);
-      if (btime==-1 || time<btime)
-      {
-        btime=time;
-        bx=sx;
-        by=sy;
-        bangle=sangle;
-        bspeed=sspeed;
-        keys[time]='\0';
-        memcpy(bkeys,keys,btime);
-      }
-      lastcheck=0;
-      time=0;
-      sx=x;
-      sy=y;
-      sangle=angle;
-      sspeed=speed;
+      print(screen,WIDTH/2-strlen("Timeout !")*5,HEIGHT/2-10,"Timeout !");
+      SDL_Flip(screen);
+      zeRace_send_time(best);
+      free_mem();
+      return;
     }
-    
-    while (SDL_PollEvent(&event))
+    alltime++;
+
+    /* if we completed a lap */
+    if (car.lapflag)
     {
-      switch (event.type)
-      {
-        case SDL_QUIT:
-          zeRace_exit();
-          break;
-        case SDL_KEYDOWN:
-          switch (event.key.keysym.sym)
-          {
-            case SDLK_ESCAPE: // escape
-              Mix_FreeMusic(light);
-              Mix_FreeMusic(engine);
-              Mix_FreeMusic(crash);
-              Mix_FreeMusic(slide);
-              if (btime<10000) zeRace_send_time(bx,by,bspeed,bangle,btime,bkeys);
-              return;  
-            case SDLK_UP: //up
-              ku=1;
-              break;
-            case SDLK_DOWN: //down
-              kd=1;
-              break;
-            case SDLK_LEFT: //left
-              kl=1;
-              break;
-            case SDLK_RIGHT: //right
-              kr=1;
-              break;
-          }
-          break;
-        case SDL_KEYUP:
-          switch (event.key.keysym.sym)
-          {
-            case SDLK_UP: //up
-              ku=0;
-              break;
-            case SDLK_DOWN: //down
-              kd=0;
-              break;
-            case SDLK_LEFT: //left
-              kl=0;
-              break;
-            case SDLK_RIGHT: //right
-              kr=0;
-              break;
-          }
-          break;
-      }
+      printf("time = %d\"%d\n",current.time*DELAY/1000,current.time*DELAY%1000);
+      print(screen,0,0,"Last lap : ");
+      print_time(110,0,current.time);
+      SDL_UpdateRect(screen,0,0,170,19);
+      /* if it is the first turn of the best turn, save it */
+      if (best.time==-1 || current.time<best.time)
+        memcpy(&best,&current,sizeof(struct _record));
+      /* reset turn variables */
+      current.time=0;
+      current.x=car.x;
+      current.y=car.y;
+      current.angle=car.angle;
+      current.speed=car.speed;
     }
     
-    SDL_Delay(5);
-    if (time<btime) keys[time]=(ku<<3 | kd<<2 | kl<<1 | kr)+'A';
-    time++;
-    alltime++;
+    /* let the system breath */
+    SDL_Delay(delay);
   }
 }
 
 
+/* display a random splash screen at startup */
 void zeRace_splash()
 {
   SDL_Surface *splash;
   SDL_Rect pos;
-  int i;
   char temp[20]="splashs/0.jpg";
   
   SDL_FillRect(screen,NULL,0x000000);
@@ -568,7 +835,8 @@ void zeRace_splash()
 }
 
 
-void zeRace_menu()
+/* menu loop to select track */
+void zeRace_local()
 {
   SDL_Event event;
   
@@ -581,8 +849,12 @@ void zeRace_menu()
     print(screen,WIDTH/2-strlen(tracklist->title)*5,5*HEIGHT/6-20,tracklist->title);
     print(screen,WIDTH/2-(strlen(tracklist->author)+strlen("Author : "))*5,5*HEIGHT/6+0,"Author : ");
     print(screen,WIDTH/2-(strlen(tracklist->author)-strlen("Author : "))*5,5*HEIGHT/6+0,tracklist->author);
-    print(screen,WIDTH/2-(strlen(tracklist->version)+strlen("Version : "))*5,5*HEIGHT/6+20,"Version : ");
-    print(screen,WIDTH/2-(strlen(tracklist->version)-strlen("Version : "))*5,5*HEIGHT/6+20,tracklist->version);
+    print(screen,WIDTH/2-( strlen("Version : ")+strlen(tracklist->version))*5,5*HEIGHT/6+20,"Version : ");
+    print(screen,WIDTH/2-(-strlen("Version : ")+strlen(tracklist->version))*5,5*HEIGHT/6+20,tracklist->version);
+    print(screen,WIDTH/2-( strlen("Best time : ")+6+strlen(" by ")+strlen(tracklist->best_pseudo))*5,5*HEIGHT/6+40,"Best time : ");
+    print_time  (WIDTH/2-(-strlen("Best time : ")+6+strlen(" by ")+strlen(tracklist->best_pseudo))*5,5*HEIGHT/6+40,tracklist->best_time);
+    print(screen,WIDTH/2-(-strlen("Best time : ")-6+strlen(" by ")+strlen(tracklist->best_pseudo))*5,5*HEIGHT/6+40," by ");
+    print(screen,WIDTH/2-(-strlen("Best time : ")-6-strlen(" by ")+strlen(tracklist->best_pseudo))*5,5*HEIGHT/6+40,tracklist->best_pseudo);
     full=IMG_Load(tracklist->full);
     preview=(SDL_Surface *)zoomSurface(full,0.5,0.5,1);
     SDL_FreeSurface(full);
@@ -611,22 +883,483 @@ void zeRace_menu()
         case SDL_KEYDOWN:
           switch (event.key.keysym.sym)
           {
-            case SDLK_ESCAPE: // escape
-              zeRace_exit();
-              break;
-            case SDLK_RETURN: //enter
-            case SDLK_SPACE: //space
-              zeRace_launch();
+            case SDLK_ESCAPE:
+              return;
+            case SDLK_RETURN:
+            case SDLK_SPACE:
+              zeRace_launch(0,1);
               update();
               break;
-            case SDLK_LEFT: //left
+            case SDLK_LEFT:
               tracklist=tracklist->next;
               update();
               break;
-            case SDLK_RIGHT: //right
+            case SDLK_RIGHT:
               tracklist=tracklist->prev;
               update();
               break;
+            default:
+              break;
+          }
+          break;
+      }
+    }
+    SDL_Delay(10);
+  }
+}
+
+
+/* top 10 screen */
+void zeRace_top10(char *buf)
+{
+  int i,nb,tmp;
+  SDL_Rect pos;
+  SDL_FillRect(screen,NULL,0x000000);
+  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;
+    tmp=SDLNet_Read16(buf);
+    buf+=2;
+    pos.x=110;
+    pos.y=(i+3)*HEIGHT/14-8;
+    SDL_BlitSurface(cars[tmp][0],NULL,screen,&pos);
+  }
+  SDL_Flip(screen);
+  SDL_Delay(5000);
+}
+
+
+/* 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) zeRace_top10(packet->data+strlen(packet->data)+1);
+      lag=0;
+    }
+    SDL_Delay(7);
+    lag++;
+  }
+  SDLNet_UDP_Close(udpsock);
+  udpsock=NULL;
+}
+
+
+/* network game */
+void zeRace_network()
+{
+  SDL_Event event;
+  int active=0;
+  char server[MAXLINELENGTH]="localhost";
+  char port[6]=PORT;
+  #define NETWORK_OPTIONS 4
+
+  void update()
+  {
+    SDL_FillRect(screen,NULL,0x000000);
+    print(screen,380,HEIGHT/(NETWORK_OPTIONS+4)*(3+active),">");
+    print(screen,WIDTH/2-18*5,HEIGHT/(NETWORK_OPTIONS+4),"* Network screen *");
+    print(screen,400,HEIGHT/(NETWORK_OPTIONS+4)*3,"Server : ");
+    print(screen,400+10*strlen("Server : "),HEIGHT/(NETWORK_OPTIONS+4)*3,server);
+    print(screen,400,HEIGHT/(NETWORK_OPTIONS+4)*4,"Port : ");
+    print(screen,400+10*strlen("Port : "),HEIGHT/(NETWORK_OPTIONS+4)*4,port);
+    print(screen,400,HEIGHT/(NETWORK_OPTIONS+4)*(NETWORK_OPTIONS+1),"Connect");
+    print(screen,400,HEIGHT/(NETWORK_OPTIONS+4)*(NETWORK_OPTIONS+2),"Back to main menu");
+    SDL_Flip(screen);
+  }
+
+  update();
+  for (;;)
+  {
+    while (SDL_PollEvent(&event))
+    {
+      switch (event.type)
+      {
+        case SDL_QUIT:
+          zeRace_exit();
+          break;
+        case SDL_KEYDOWN:
+          switch (event.key.keysym.sym)
+          {
+            case SDLK_ESCAPE:
+              return;
+            case SDLK_RETURN:
+            case SDLK_SPACE:
+            case SDLK_LEFT:
+            case SDLK_RIGHT:
+              switch (active)
+              {
+                case 0: readstring(screen,400+10*strlen("Server : "),HEIGHT/(NETWORK_OPTIONS+4)*3,server,MAXLINELENGTH); break;
+                case 1: readstring(screen,400+10*strlen("Port : "),HEIGHT/(NETWORK_OPTIONS+4)*4,port,5); break;;
+                case 2:
+                  zeRace_connect(server,atoi(port));
+                  break;
+                case 3:
+                  return;
+              }
+              update();
+              break;
+            case SDLK_UP:
+              active--; if (active<0) active=NETWORK_OPTIONS-1;
+              update();
+              break;
+            case SDLK_DOWN:
+              active++; if (active>NETWORK_OPTIONS-1) active=0;
+              update();
+              break;
+            default:
+              break;
+          }
+          break;
+      }
+    }
+    SDL_Delay(10);
+  }
+}
+
+
+/* internet game */
+void zeRace_internet()
+{
+  IPaddress ip;
+  TCPsocket tcpsock;
+  char *request=
+    "GET /zerace/servers.php HTTP/1.0\n"
+    "Host: royale.zerezo.com\n"
+    "User-Agent: zeRace " VERSION "\n"
+    "\n";
+  char response[10240],*tmp;
+  int len,result,i;
+  struct _server
+  {
+    char name[MAXLINELENGTH];
+    char ip[16];
+    char port[6];
+  } servers[10];
+  SDL_Event event;
+  int active=0;
+  #define INTERNET_OPTIONS 11
+  
+  printf("dowloading list of servers... ");
+  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;
+  }
+
+  len=strlen(request);
+  result=SDLNet_TCP_Send(tcpsock,request,len);
+  if (result<len)
+    fprintf(stderr,"SDLNet_TCP_Send: %s\n",SDLNet_GetError());
+  else
+  {
+    len=SDLNet_TCP_Recv(tcpsock,response,10240);
+    tmp=response;
+    for (tmp=response;tmp<response+10240;tmp++) if (*tmp=='\n') *tmp='\0';
+    tmp=response;
+    while (*tmp!='\0' || *(tmp+1)!='\r') tmp++;
+    tmp+=3;
+    for (i=0;i<10;i++)
+    {
+      strcpy(servers[i].name,tmp);
+      tmp+=strlen(tmp)+1;
+      strcpy(servers[i].ip,tmp);
+      tmp+=strlen(tmp)+1;
+      strcpy(servers[i].port,tmp);
+      tmp+=strlen(tmp)+1;
+    }
+    printf("done\n");
+  }
+  
+  SDLNet_TCP_Close(tcpsock);
+  
+  void update()
+  {
+    int i;
+    SDL_FillRect(screen,NULL,0x000000);
+    print(screen,380,HEIGHT/(INTERNET_OPTIONS+4)*(3+active),">");
+    print(screen,WIDTH/2-19*5,HEIGHT/(INTERNET_OPTIONS+4),"* Internet screen *");
+    for (i=0;i<10;i++)
+      print(screen,400,HEIGHT/(INTERNET_OPTIONS+4)*(i+3),servers[i].name);
+    print(screen,400,HEIGHT/(INTERNET_OPTIONS+4)*(INTERNET_OPTIONS+2),"Back to main menu");
+    SDL_Flip(screen);
+  }
+
+  update();
+  for (;;)
+  {
+    while (SDL_PollEvent(&event))
+    {
+      switch (event.type)
+      {
+        case SDL_QUIT:
+          zeRace_exit();
+          break;
+        case SDL_KEYDOWN:
+          switch (event.key.keysym.sym)
+          {
+            case SDLK_ESCAPE:
+              return;
+            case SDLK_RETURN:
+            case SDLK_SPACE:
+            case SDLK_LEFT:
+            case SDLK_RIGHT:
+              if (active==INTERNET_OPTIONS-1)
+                return;
+              else
+                zeRace_connect(servers[active].ip,atoi(servers[active].port));
+              update();
+              break;
+            case SDLK_UP:
+              active--; if (active<0) active=INTERNET_OPTIONS-1;
+              update();
+              break;
+            case SDLK_DOWN:
+              active++; if (active>INTERNET_OPTIONS-1) active=0;
+              update();
+              break;
+            default:
+              break;
+          }
+          break;
+      }
+    }
+    SDL_Delay(10);
+  }
+}
+
+
+/* configuration screen */
+void zeRace_config()
+{
+  SDL_Event event;
+  int active=0;
+  #define CONFIG_OPTIONS 12
+  
+  void update()
+  {
+    SDL_Rect pos;
+    SDL_FillRect(screen,NULL,0x000000);
+    print(screen,20,HEIGHT/(CONFIG_OPTIONS+4)*(3+active),">");
+    print(screen,WIDTH/2-24*5,HEIGHT/(CONFIG_OPTIONS+4),"* Configuration screen *");
+    print(screen,40,HEIGHT/(CONFIG_OPTIONS+4)*3,"Pseudo : ");
+    print(screen,40+10*strlen("Pseudo : "),HEIGHT/(CONFIG_OPTIONS+4)*3,config.pseudo);
+    print(screen,40,HEIGHT/(CONFIG_OPTIONS+4)*4,"Url : ");
+    print(screen,40+10*strlen("Url : "),HEIGHT/(CONFIG_OPTIONS+4)*4,config.url);
+    print(screen,40,HEIGHT/(CONFIG_OPTIONS+4)*5,"Fullscreen : ");
+    print(screen,40+10*strlen("Fullscreen : "),HEIGHT/(CONFIG_OPTIONS+4)*5,config.fullscreen?"Yes":"No");
+    print(screen,40,HEIGHT/(CONFIG_OPTIONS+4)*6,"Sound : ");
+    print(screen,40+10*strlen("Sound : "),HEIGHT/(CONFIG_OPTIONS+4)*6,config.sound?"Yes":"No");
+    print(screen,40,HEIGHT/(CONFIG_OPTIONS+4)*7,"Tire : ");
+    print(screen,40+10*strlen("Tire : "),HEIGHT/(CONFIG_OPTIONS+4)*7,config.tire?"Yes":"No");
+    print(screen,40,HEIGHT/(CONFIG_OPTIONS+4)*8,"Accelerate key : ");
+    print(screen,40+10*strlen("Accelerate key : "),HEIGHT/(CONFIG_OPTIONS+4)*8,config.up?SDL_GetKeyName(config.up):"<press key>");
+    print(screen,40,HEIGHT/(CONFIG_OPTIONS+4)*9,"Brake key : ");
+    print(screen,40+10*strlen("Brake key : "),HEIGHT/(CONFIG_OPTIONS+4)*9,config.down?SDL_GetKeyName(config.down):"<press key>");
+    print(screen,40,HEIGHT/(CONFIG_OPTIONS+4)*10,"Turn left key : ");
+    print(screen,40+10*strlen("Turn left key : "),HEIGHT/(CONFIG_OPTIONS+4)*10,config.left?SDL_GetKeyName(config.left):"<press key>");
+    print(screen,40,HEIGHT/(CONFIG_OPTIONS+4)*11,"Turn right key : ");
+    print(screen,40+10*strlen("Turn right key : "),HEIGHT/(CONFIG_OPTIONS+4)*11,config.right?SDL_GetKeyName(config.right):"<press key>");
+    print(screen,40,HEIGHT/(CONFIG_OPTIONS+4)*12,"Color : ");
+    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);
+  }
+
+  int read_key()
+  {
+    for (;;)
+    {
+      while (SDL_PollEvent(&event)) switch (event.type)
+      {
+        case SDL_KEYDOWN:
+          return event.key.keysym.sym;
+      }
+      SDL_Delay(10);
+    }
+  }
+  
+  update();
+  for (;;)
+  {
+    while (SDL_PollEvent(&event))
+    {
+      switch (event.type)
+      {
+        case SDL_QUIT:
+          zeRace_exit();
+          break;
+        case SDL_KEYDOWN:
+          switch (event.key.keysym.sym)
+          {
+            case SDLK_ESCAPE:
+              return;
+            case SDLK_RETURN:
+            case SDLK_SPACE:
+            case SDLK_LEFT:
+            case SDLK_RIGHT:
+              switch (active)
+              {
+                case 0: readstring(screen,40+10*strlen("Pseudo : "),HEIGHT/(CONFIG_OPTIONS+4)*3,config.pseudo,MAXLINELENGTH); break;
+                case 1: readstring(screen,40+10*strlen("Url : "),HEIGHT/(CONFIG_OPTIONS+4)*4,config.url,MAXLINELENGTH); break;;
+                case 2: config.fullscreen=!config.fullscreen; break;
+                case 3: config.sound=!config.sound; break;
+                case 4: config.tire=!config.tire; break;
+                case 5: config.up=0; update(); config.up=read_key(); break;
+                case 6: config.down=0; update(); config.down=read_key(); break;
+                case 7: config.left=0; update(); config.left=read_key(); break;
+                case 8: config.right=0; update(); config.right=read_key(); break;
+                case 9:
+                  if (event.key.keysym.sym==SDLK_LEFT) config.color--; else config.color++;
+                  if (config.color<0) config.color=11;
+                  if (config.color>11) config.color=0;
+                  break;
+                case 10: config.boss=0; update(); config.boss=read_key(); break;
+                case 11:
+                  return;
+              }
+              update();
+              break;
+            case SDLK_UP:
+              active--; if (active<0) active=CONFIG_OPTIONS-1;
+              update();
+              break;
+            case SDLK_DOWN:
+              active++; if (active>CONFIG_OPTIONS-1) active=0;
+              update();
+              break;
+            default:
+              break;
+          }
+          break;
+      }
+    }
+    SDL_Delay(10);
+  }
+}
+
+
+/* main menu */
+void zeRace_menu()
+{
+  SDL_Event event;
+  int active=0;
+  SDL_Surface *logo;
+  #define MENU_OPTIONS 5
+  
+  void update()
+  {
+    SDL_Rect pos;
+    SDL_FillRect(screen,NULL,0x000000);
+    pos.x=WIDTH/2-logo->w/2;
+    pos.y=HEIGHT/6-logo->h/2;
+    SDL_BlitSurface(logo,NULL,screen,&pos);
+    print(screen,650,HEIGHT/6+logo->h/3,"version " VERSION);
+    print(screen,420,HEIGHT/(MENU_OPTIONS+4)*(3+active),">");
+    print(screen,440,HEIGHT/(MENU_OPTIONS+4)*3,"Local game");
+    print(screen,440,HEIGHT/(MENU_OPTIONS+4)*4,"Network game");
+    print(screen,440,HEIGHT/(MENU_OPTIONS+4)*5,"Internet game");
+    print(screen,440,HEIGHT/(MENU_OPTIONS+4)*6,"Configuration");
+    print(screen,440,HEIGHT/(MENU_OPTIONS+4)*7,"Exit game");
+    SDL_Flip(screen);
+  }
+
+  logo=IMG_Load("sprites/logo.jpg");
+  update();
+  for (;;)
+  {
+    while (SDL_PollEvent(&event))
+    {
+      switch (event.type)
+      {
+        case SDL_QUIT:
+          zeRace_exit();
+          break;
+        case SDL_KEYDOWN:
+          switch (event.key.keysym.sym)
+          {
+            case SDLK_ESCAPE:
+              return;
+            case SDLK_RETURN:
+            case SDLK_SPACE:
+            case SDLK_LEFT:
+            case SDLK_RIGHT:
+              switch (active)
+              {
+                case 0: zeRace_local(); break;
+                case 1: zeRace_network(); break;
+                case 2: zeRace_internet(); break;
+                case 3: zeRace_config(); break;
+                case 4: return;
+              }
+              update();
+              break;
+            case SDLK_UP:
+              active--; if (active<0) active=MENU_OPTIONS-1;
+              update();
+              break;
+            case SDLK_DOWN:
+              active++; if (active>MENU_OPTIONS-1) active=0;
+              update();
+              break;
+            default:
+              break;
           }
           break;
       }
@@ -636,6 +1369,7 @@ void zeRace_menu()
 }
 
 
+/* main program */
 int main(int argc,char *argv[])
 {
   zeRace_init();