version 0.3 v0.3
authorAntoine Jacquet <royale@zerezo.com>
Sun, 26 Sep 2004 22:00:00 +0000 (00:00 +0200)
committerAntoine Jacquet <royale@zerezo.com>
Sun, 26 Sep 2004 22:00:00 +0000 (00:00 +0200)
* added network/internet mode
* code is now split into generic modules
* angle limited between 0 and 2*pi to avoid bugs

12 files changed:
CHANGELOG
Makefile
README
car.c [new file with mode: 0644]
car.h [new file with mode: 0644]
network.h [new file with mode: 0644]
sdl.c
server.c [new file with mode: 0644]
tracklist.c [new file with mode: 0644]
tracklist.h [new file with mode: 0644]
tracks/list.txt
zeRace.c

index ae96a29..528d161 100644 (file)
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,5 +1,10 @@
 Change log file for zeRace
 
+version 0.3    (2004-09-27)
+       * added network/internet mode
+       * code is now split into generic modules
+       * angle limited between 0 and 2*pi to avoid bugs
+
 version 0.2    (2004-09-14)
        * code has been commented :)
        * game is now a bit slower (easier to play)
index d999c0b..0b67908 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -1,8 +1,21 @@
-zeRace:zeRace.c sdl.o
-       gcc -ansi -Wall -o zeRace zeRace.c sdl.o `sdl-config --cflags --libs` -lSDL_image -lSDL_gfx -lSDL_net -lSDL_mixer
+CC=gcc
+
+all:zeRace server
+
+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
+
+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
 
 sdl.o:sdl.c sdl.h
-       gcc -ansi -Wall -c sdl.c `sdl-config --cflags`
+       $(CC) -ansi -Wall -c sdl.c `sdl-config --cflags`
+
+car.o:car.c car.h
+       $(CC) -ansi -Wall -c car.c `sdl-config --cflags`
+
+tracklist.o:tracklist.c tracklist.h
+       $(CC) -ansi -Wall -c tracklist.c
 
 clean:
-       rm -f zeRace *.o *.cfg
+       rm -f zeRace server *.o *.cfg
diff --git a/README b/README
index 43fdabe..1c0a1e6 100644 (file)
--- a/README
+++ b/README
@@ -1,3 +1,3 @@
-zeRace 0.2
+zeRace 0.3
 site: http://royale.zerezo.com/zerace/
 mail: royale@zerezo.com
diff --git a/car.c b/car.c
new file mode 100644 (file)
index 0000000..e769567
--- /dev/null
+++ b/car.c
@@ -0,0 +1,84 @@
+#include "car.h"
+
+#define COEFF 1
+
+void move_car(struct _car *car,int keys,SDL_Surface *fun)
+{
+  int c,r,g,b;
+  
+  /* reset flags */
+  car->lapflag=0;
+  car->crashflag=0;
+  
+  /* 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;
+  /* green layer (road quality) */
+  g=(c>>8 )&0xff;
+  /* blue layer (unused) */
+  b=(c>>16)&0xff;
+
+  if (keys & 8) /* up */
+    car->speed+=0.01*2*COEFF;
+  if (keys & 4) /* down */
+    car->speed-=0.01*COEFF;
+  if (keys & 2) /* left */
+  {
+    if (car->speed<0)
+      car->angle+=0.01*(255-b)/255*COEFF;
+    else
+      car->angle-=0.01*(255-b)/255*COEFF;
+  }
+  if (keys & 1) /* right */
+  {
+    if (car->speed<0)
+      car->angle-=0.01*(255-b)/255*COEFF;
+    else
+      car->angle+=0.01*(255-b)/255*COEFF;
+  }
+  
+  /* limit angle between 0 and 2*pi */
+  if (car->angle<0) car->angle+=2*M_PI;
+  if (car->angle>2*M_PI) car->angle-=2*M_PI;
+  
+  /* update the speed depending on the road quality */
+  car->speed-=car->speed*(255-g)/1000;
+  /* if it is a wall we move back to the last position */
+  if (g==0)
+  {
+    car->x=car->ox;
+    car->y=car->oy;
+    car->crashflag=1;
+  }
+  
+  /* save the old position and compute the new one */
+  car->ox=car->x;
+  car->oy=car->y;
+  car->speed*=0.995;
+  car->x-=cos(car->angle)*car->speed;
+  car->y-=sin(car->angle)*car->speed;
+  
+  /* collision with the border of the screen */
+  if (car->x<car->w/2 || car->x>fun->w-car->w/2 || car->y<car->h/2 || car->y>fun->h-car->h/2)
+  {
+    car->x=car->ox;
+    car->y=car->oy;
+    car->speed=0;
+    car->crashflag=1;
+  }
+  
+  /* if we are on the next checkpoint, validate it (no missing allowed) */
+  if (r/8==car->lastcheck+1) car->lastcheck++;
+  /* if we validate all and start over, we complete a turn */
+  if (r/8==0 && car->lastcheck==31)
+  {
+    /* reset turn variables */
+    car->lastcheck=0;
+    car->lap++;
+    car->lapflag=1;
+  }
+  
+  return;
+}
diff --git a/car.h b/car.h
new file mode 100644 (file)
index 0000000..cb9e511
--- /dev/null
+++ b/car.h
@@ -0,0 +1,18 @@
+/* manages car object and its movement */
+
+#include <math.h>
+#include "sdl.h"
+
+#ifndef M_PI
+#define M_PI 3.141592654
+#endif
+
+struct _car
+{
+  float x,y,ox,oy,angle,speed;
+  int w,h,lastcheck,lap;
+  int lapflag,crashflag;
+  int color;
+};
+
+void move_car(struct _car *car,int keys,SDL_Surface *function);
diff --git a/network.h b/network.h
new file mode 100644 (file)
index 0000000..f7cb706
--- /dev/null
+++ b/network.h
@@ -0,0 +1,7 @@
+/* some network constants */
+
+#define MAXLINELENGTH 80
+#define MAX_LAG 500
+#define MAX_CLIENTS 32
+#define PORT "3600"
+#define VERSION "0.3"
diff --git a/sdl.c b/sdl.c
index 16bd1c6..99891f0 100644 (file)
--- a/sdl.c
+++ b/sdl.c
@@ -1,5 +1,3 @@
-/* some useful functions for zeRace game */
-
 #include "sdl.h"
 
 SDL_Surface *font=NULL;
diff --git a/server.c b/server.c
new file mode 100644 (file)
index 0000000..c6da730
--- /dev/null
+++ b/server.c
@@ -0,0 +1,366 @@
+/* zeRace dedicated server */
+
+#include <SDL_net.h>
+#include "network.h"
+#include "car.h"
+#include "tracklist.h"
+
+/* each client has his data */
+struct _clients
+{
+  char pseudo[MAXLINELENGTH];
+  int connected;
+  int lasttime;
+  struct _car car;
+  IPaddress address;
+} clients [MAX_CLIENTS];
+
+/* UDP stuff */
+UDPsocket udpsock;
+UDPpacket *packet;
+
+struct _tracklist *tracklist;
+SDL_Surface *fun;
+
+
+/* return the id of a connected address */
+int lookup(IPaddress address)
+{
+  int i;
+  for (i=0;i<MAX_CLIENTS;i++) if (clients[i].connected && clients[i].address.host==address.host && clients[i].address.port==address.port) return i;
+  return -1;
+}
+
+
+/* announce the server on internet */
+void announce(char *name,int clients)
+{
+  IPaddress ip;
+  TCPsocket tcpsock;
+  char *temp;
+  char *msg1=
+    "POST /zerace/announce.php HTTP/1.0\n"
+    "Host: royale.zerezo.com\n"
+    "User-Agent: zeRace dedicated server " VERSION "\n"
+    "Content-Type: application/x-www-form-urlencoded\n"
+    "Content-Length: 99999\n"
+    "\n"
+    "version=" VERSION
+    "&port=" PORT
+    "&name=";
+  char *msg2="&clients=";
+  int len,result;
+  
+  printf("announcing server... ");
+  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;
+  }
+
+  temp=(char *)malloc(strlen(msg1)+strlen(name)+strlen(msg2)+10);
+  sprintf(temp,"%s%s%s%d\n",msg1,name,msg2,clients);
+  
+  len=strlen(temp);
+  result=SDLNet_TCP_Send(tcpsock,temp,len);
+  if(result<len)
+    fprintf(stderr,"SDLNet_TCP_Send: %s\n", SDLNet_GetError());
+  else
+    printf("done\n");
+
+  SDLNet_TCP_Close(tcpsock);
+}
+
+
+/* main program */
+int main(int argc,char *argv[])
+{
+  int id,i,j,time=0,nb;
+  char *tmp;
+  unsigned char ip[4];
+  int nb_laps,network_speed,pub;
+  
+  if (argc!=5)
+  {
+    fprintf(stderr,
+      "usage: %s 'server_name' nb_laps network_speed (public|private)\n"
+      "  server_name : the name of the server\n"
+      "  nb_laps : the number of laps to complete for each race\n"
+      "  network_speed : frequency of network messages (1 for fast network, 10 for slow network...)\n"
+      "  private : this server will not be listed in the 'internet games'\n"
+      ,argv[0]
+    );
+    exit(1);
+  }
+  printf("server_name : %s\n",argv[1]);
+  printf("nb_laps : %d\n",nb_laps=atoi(argv[2]));
+  printf("network_speed : %d\n",network_speed=atoi(argv[3]));
+  printf("public : %d\n",pub=strcmp("private",argv[4]));
+  
+  if (!zeRace_get_tracks(&tracklist)) exit(1);
+
+  if (SDL_Init(0)==-1)
+  {
+    fprintf(stderr,"SDL_Init: %s\n",SDL_GetError());
+    exit(1);
+  };
+  if (SDLNet_Init()==-1)
+  {
+    fprintf(stderr,"SDLNet_Init: %s\n",SDLNet_GetError());
+    exit(2);
+  }
+  
+  udpsock=SDLNet_UDP_Open(atoi(PORT));
+  if (!udpsock)
+  {
+    fprintf(stderr,"SDLNet_UDP_Open: %s\n",SDLNet_GetError());
+    exit(2);
+  }
+
+  packet=SDLNet_AllocPacket(1024);
+  if (!packet)
+  {
+    fprintf(stderr,"SDLNet_AllocPacket: %s\n",SDLNet_GetError());
+    exit(2);
+  }
+
+  for (;;)
+  {
+    /* announce the server on internet if wanted */
+    nb=0;
+    for (i=0;i<MAX_CLIENTS;i++) if (clients[i].connected) nb++;
+    if (pub) announce(argv[1],nb);
+    
+    /* load new track */
+    printf("loading track \"%s\"\n",tracklist->name);
+    fun=IMG_Load(tracklist->function);
+    
+    /* reset clients variables */
+    for (i=0;i<MAX_CLIENTS;i++)
+    {
+      clients[i].lasttime=time;
+      clients[i].car.x=tracklist->x;
+      clients[i].car.y=tracklist->y;
+      clients[i].car.w=30;
+      clients[i].car.h=30;
+      clients[i].car.angle=tracklist->a*2*M_PI/360;
+      clients[i].car.speed=0;
+      clients[i].car.lap=0;
+      clients[i].car.lastcheck=0;
+    }
+    
+    /* tell the clients */
+    tmp=packet->data;
+    strcpy(tmp,"track");
+    tmp+=strlen(tmp)+1;
+    *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);
+    packet->len=(void *)tmp-(void *)packet->data;
+    for (i=0;i<MAX_CLIENTS;i++) if (clients[i].connected)
+    {
+      packet->address=clients[i].address;
+      SDLNet_UDP_Send(udpsock,-1,packet);
+    }
+    
+    /* wait for everybody startup */
+    for (i=0;i<MAX_CLIENTS;i++) if (clients[i].connected) break;
+    if (i!=MAX_CLIENTS) SDL_Delay(5000);
+    
+    printf("go\n");
+    
+    /* main race loop */
+    for (;;)
+    {
+      int finish=0;
+      
+      /* read all available packets */
+      while (SDLNet_UDP_Recv(udpsock,packet))
+      {
+        /* return the local id based on the address */
+        id=lookup(packet->address);
+        
+        /* look for type of message */
+        tmp=packet->data;
+        /*printf("%s\n",tmp);*/
+        
+        /* new connection ? */
+        if (strcmp(tmp,"connect")==0)
+        {
+          /* allready connected ? */
+          if (id!=-1 && clients[id].connected)
+          {
+            /* should not happen */
+            printf("client %d allready connected\n",id);
+          }
+          else
+          for (i=0;i<MAX_CLIENTS;i++) if (!clients[i].connected)
+          {
+            clients[i].address=packet->address;
+            clients[i].connected=1;
+            clients[i].lasttime=time;
+            memset(&clients[i].car,0,sizeof(struct _car));
+            tmp+=strlen(tmp)+1;
+            strcpy(clients[i].pseudo,tmp);
+            tmp+=strlen(tmp)+1;
+            memcpy(&clients[i].car.color,tmp,sizeof(int));
+            clients[i].car.x=tracklist->x;
+            clients[i].car.y=tracklist->y;
+            clients[i].car.w=30;
+            clients[i].car.h=30;
+            clients[i].car.angle=tracklist->a*2*M_PI/360;
+            clients[i].car.speed=0;
+            clients[i].car.lap=0;
+            clients[i].car.lastcheck=0;
+            memcpy(ip,&packet->address.host,4);
+            printf("client %d connected at %d : %d.%d.%d.%d:%d (pseudo : %s, color : %d)\n",i,time,ip[0],ip[1],ip[2],ip[3],packet->address.port,clients[i].pseudo,clients[i].car.color);
+            tmp=packet->data;
+            strcpy(tmp,"track");
+            tmp+=strlen(tmp)+1;
+            *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);
+            packet->len=(void *)tmp-(void *)packet->data;
+            SDLNet_UDP_Send(udpsock,-1,packet);
+            break;
+          }
+        }
+        else
+        
+        /* disconnection ? */
+        if (strcmp(tmp,"disconnect")==0 && id!=-1)
+        {
+          clients[id].connected=0;
+          printf("client %d disconnected\n",id);
+        }
+        else
+        
+        /* keys message ? */
+        if (strcmp(tmp,"keys")==0)
+        {
+          if (id==-1 || !clients[id].connected)
+          {
+            /* should not happen */
+            printf("discarded \"keys\" message\n");
+          }
+          else
+          {
+            int temp;
+            tmp+=strlen(tmp)+1;
+            memcpy(&temp,tmp,sizeof(int));
+            tmp+=sizeof(int);
+            if (clients[id].lasttime==temp)
+            {
+              /* 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)
+              {
+                move_car(&clients[id].car,*tmp-'A',fun);
+                /*printf("%d = %f\n",id,clients[id].car.angle);*/
+                clients[id].lasttime++;
+                tmp++;
+              }
+            }
+          }
+        }
+      }
+      
+      /* check for timeouts */
+      for (i=0;i<MAX_CLIENTS;i++) if (clients[i].connected && clients[i].lasttime+MAX_LAG<time)
+      {
+        printf("client %d timeout at %d\n",i,time);
+        packet->address=clients[i].address;
+        strcpy(packet->data,"disconnected");
+        SDLNet_UDP_Send(udpsock,-1,packet);
+        clients[i].connected=0;
+      }
+      
+      /* send update to clients */
+      if (time%network_speed==0) for (i=0;i<MAX_CLIENTS;i++) if (clients[i].connected)
+      {
+        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 */
+        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);
+          nb++;
+        }
+        memcpy(packet->data+strlen("positions")+1+sizeof(int)+sizeof(int),&nb,sizeof(int));
+        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);
+      }
+      
+      /* did someone finish the track ? */
+      for (i=0;i<MAX_CLIENTS;i++) if (clients[i].connected && clients[i].car.lap==nb_laps) finish=1;
+      if (finish) break;
+        
+      /* wait like clients */
+      SDL_Delay(7);
+      time++;
+    }
+    
+    /* send the top 10 screen */
+    tmp=packet->data;
+    strcpy(tmp,"finish");
+    tmp+=strlen(tmp)+1;
+    tmp+=sizeof(int); /* space for number */
+    nb=0;
+    for (i=0;i<10;i++)
+    {
+      int best_sc=-1,best_id;
+      for (j=0;j<MAX_CLIENTS;j++) if (clients[j].connected && clients[j].car.lap*32+clients[j].car.lastcheck>best_sc)
+      {
+        best_sc=clients[j].car.lap*32+clients[j].car.lastcheck;
+        best_id=j;
+      }
+      if (best_sc!=-1)
+      {
+        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);
+        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));
+    packet->len=(void *)tmp-(void *)packet->data;
+    for (i=0;i<MAX_CLIENTS;i++) if (clients[i].connected)
+    {
+      packet->address=clients[i].address;
+      SDLNet_UDP_Send(udpsock,-1,packet);
+    }
+    SDL_Delay(5000);
+  
+    tracklist=tracklist->next;
+  }
+
+  return 0;
+}
diff --git a/tracklist.c b/tracklist.c
new file mode 100644 (file)
index 0000000..fdab66d
--- /dev/null
@@ -0,0 +1,62 @@
+#include "tracklist.h"
+
+#define MAXTRACKLENGTH 80
+
+/* get available local tracks */
+int zeRace_get_tracks(struct _tracklist **tracklist)
+{
+  FILE *fic;
+  char line[MAXTRACKLENGTH];
+  struct _tracklist *tmp=NULL,*first=NULL;
+
+  *tracklist=NULL;
+  if ((fic=fopen("tracks/list.txt","rt"))==NULL)
+  {
+    fprintf(stderr,"can't open track list\n");
+    return 0;
+  }
+  while (!feof(fic))
+  {
+    tmp=(struct _tracklist *)malloc(sizeof(struct _tracklist));
+    if (first==NULL) first=tmp;
+    fgets(line,MAXTRACKLENGTH,fic);
+    tmp->name=(char *)malloc(strlen(line)+1);
+    strcpy(tmp->name,line);
+    tmp->name[strlen(tmp->name)-1]='\0';
+    fgets(line,MAXTRACKLENGTH,fic);
+    tmp->title=(char *)malloc(strlen(line)+1);
+    strcpy(tmp->title,line);
+    fgets(line,MAXTRACKLENGTH,fic);
+    tmp->author=(char *)malloc(strlen(line)+1);
+    strcpy(tmp->author,line);
+    fgets(line,MAXTRACKLENGTH,fic);
+    tmp->version=(char *)malloc(strlen(line)+1);
+    strcpy(tmp->version,line);
+    fgets(line,MAXTRACKLENGTH,fic);
+    tmp->x=atoi(line);
+    fgets(line,MAXTRACKLENGTH,fic);
+    tmp->y=atoi(line);
+    fgets(line,MAXTRACKLENGTH,fic);
+    tmp->a=atoi(line);
+    fgets(line,MAXTRACKLENGTH,fic);
+    tmp->best_time=atoi(line);
+    fgets(line,MAXTRACKLENGTH,fic);
+    tmp->best_pseudo=(char *)malloc(strlen(line)+1);
+    strcpy(tmp->best_pseudo,line);
+    tmp->full=(char *)malloc(strlen(line)+20);
+    sprintf(tmp->full,"tracks/%s.png",tmp->name);
+    tmp->function=(char *)malloc(strlen(line)+30);
+    sprintf(tmp->function,"tracks/%s_function.png",tmp->name);
+    tmp->prev=*tracklist;
+    if (tmp->prev) tmp->prev->next=tmp;
+    *tracklist=tmp;
+    /* skip one line */
+    fgets(line,MAXTRACKLENGTH,fic);
+  }
+  fclose(fic);
+  if (!tmp) { fprintf(stderr,"no circuits found !\n"); return 0; }
+  while (tmp->prev) tmp=tmp->prev;
+  tmp->prev=*tracklist;
+  (*tracklist)->next=tmp;
+  return 1;
+}
diff --git a/tracklist.h b/tracklist.h
new file mode 100644 (file)
index 0000000..34ee2a8
--- /dev/null
@@ -0,0 +1,22 @@
+/* load the track list */
+
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+struct _tracklist
+{
+  char *name;
+  char *title;
+  char *author;
+  char *version;
+  char *full;
+  char *function;
+  int x,y,a;
+  int best_time;
+  char *best_pseudo;
+  struct _tracklist *prev;
+  struct _tracklist *next;
+};
+
+int zeRace_get_tracks(struct _tracklist **tracklist);
index 6f6f0ac..a352376 100644 (file)
@@ -2,48 +2,48 @@ car
 Car
 ICFP Programming Contest
 0.1
-435
-640
+450
+655
 180
-10000
-?
+923
+Voisin
 
 first
 First circuit for this game...
 Royale
 0.1
-420
-200
+435
+215
 180
-10000
-?
+999
+Voisin
 
 icy
 Same as "First", but in winter !
 Royale
 0.1
-420
-200
+435
+215
 180
-10000
-?
+1175
+Royale
 
 hairpins
 Hairpins
 ICFP Programming Contest
 0.1
-490
-650
+505
+665
 0
-10000
-?
+3158
+Voisin
 
 simple
 Simple
 ICFP Programming Contest
 0.1
-570
-550
+585
+565
 0
-10000
-?
+1784
+Voisin
index ce90ba1..c88d6cf 100644 (file)
--- a/zeRace.c
+++ b/zeRace.c
@@ -1,5 +1,5 @@
 /*
- * zeRace 0.2, a funny retro racing game
+ * zeRace 0.3, a funny retro racing game
  * http://royale.zerezo.com/zerace/
  * 
  * Copyright (C) 2004  Antoine Jacquet <royale@zerezo.com>
 #include <SDL_mixer.h>
 #include <SDL_rotozoom.h>
 #include "sdl.h"
+#include "car.h"
+#include "tracklist.h"
+#include "network.h"
 
 /* configuration constants */
 #define COEFF 1
 #define DELAY 7
-#define MAXLINELENGTH 80
-#define VERSION "0.2"
 #define WIDTH 1024
 #define HEIGHT 768
+#define MAXRECORDKEYS 9999
 
-/* tracklist : double chained list */
-struct _tracklist
-{
-  char *name;
-  char *title;
-  char *author;
-  char *version;
-  char *full;
-  char *function;
-  int x,y,a;
-  int best_time;
-  char *best_pseudo;
-  struct _tracklist *prev;
-  struct _tracklist *next;
-} *tracklist=NULL;
+/* tracklist */
+struct _tracklist *tracklist;
 
 /* user setup */
 struct _config
@@ -67,12 +56,25 @@ struct _config
   SDLKey down;
   SDLKey left;
   SDLKey right;
-  char color;
+  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;
+};
+
 /* 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 */
@@ -115,64 +117,6 @@ void zeRace_exit()
 }
 
 
-/* get available local tracks */
-void zeRace_get_tracks()
-{
-  FILE *fic;
-  char line[MAXLINELENGTH];
-  struct _tracklist *tmp=NULL,*first=NULL;
-
-  if ((fic=fopen("tracks/list.txt","rt"))==NULL)
-  {
-    fprintf(stderr,"can't open track list\n");
-    zeRace_exit();
-  }
-  while (!feof(fic))
-  {
-    tmp=(struct _tracklist *)malloc(sizeof(struct _tracklist));
-    if (first==NULL) first=tmp;
-    fgets(line,MAXLINELENGTH,fic);
-    tmp->name=(char *)malloc(strlen(line)+1);
-    strcpy(tmp->name,line);
-    tmp->name[strlen(tmp->name)-1]='\0';
-    fgets(line,MAXLINELENGTH,fic);
-    tmp->title=(char *)malloc(strlen(line)+1);
-    strcpy(tmp->title,line);
-    fgets(line,MAXLINELENGTH,fic);
-    tmp->author=(char *)malloc(strlen(line)+1);
-    strcpy(tmp->author,line);
-    fgets(line,MAXLINELENGTH,fic);
-    tmp->version=(char *)malloc(strlen(line)+1);
-    strcpy(tmp->version,line);
-    fgets(line,MAXLINELENGTH,fic);
-    tmp->x=atoi(line);
-    fgets(line,MAXLINELENGTH,fic);
-    tmp->y=atoi(line);
-    fgets(line,MAXLINELENGTH,fic);
-    tmp->a=atoi(line);
-    fgets(line,MAXLINELENGTH,fic);
-    tmp->best_time=atoi(line);
-    fgets(line,MAXLINELENGTH,fic);
-    tmp->best_pseudo=(char *)malloc(strlen(line)+1);
-    strcpy(tmp->best_pseudo,line);
-    tmp->full=(char *)malloc(strlen(line)+20);
-    sprintf(tmp->full,"tracks/%s.png",tmp->name);
-    tmp->function=(char *)malloc(strlen(line)+30);
-    sprintf(tmp->function,"tracks/%s_function.png",tmp->name);
-    tmp->prev=tracklist;
-    if (tmp->prev) tmp->prev->next=tmp;
-    tracklist=tmp;
-    /* skip one line */
-    fgets(line,MAXLINELENGTH,fic);
-  }
-  fclose(fic);
-  if (!tmp) { fprintf(stderr,"no circuits found !\n"); zeRace_exit(); }
-  while (tmp->prev) tmp=tmp->prev;
-  tmp->prev=tracklist;
-  tracklist->next=tmp;
-}
-
-
 /* check for a newer version online to warn the user */
 void zeRace_check_version()
 {
@@ -285,36 +229,44 @@ void zeRace_update_tracks()
 /* load the car sprite and rotate it for every angles */
 void zeRace_generate_cars()
 {
-  int i;
+  int i,j;
   SDL_Surface *car;
   char temp[20]="sprites/carX.png";
-  temp[11]='A'+config.color;
-  /* load the car sprite */
-  car=IMG_Load(temp);
-  /* and rotate it for all available angles */
-  for (i=0;i<256;i++)
-  {
-    float x,y;
-    float tcos,tsin;
-    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++)
+  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++)
     {
-      int x2,y2;
-      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));
+      float x,y;
+      float tcos,tsin;
+      if ((cars[i][j]=SDL_CreateRGBSurface(SDL_SWSURFACE,30,30,32,0x000000ff,0x0000ff00,0x00ff0000,0xff000000))==NULL)
+      {
+        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));
+      }
     }
+    SDL_FreeSurface(car);
   }
-  SDL_FreeSurface(car);
 }
 
 
 /* initialize the game */
 void zeRace_init()
 {
-  int flags,i;
+  int flags;
   
   /* do a clean exit in case of emergency */
   signal(SIGINT,zeRace_exit);
@@ -330,7 +282,7 @@ void zeRace_init()
   zeRace_update_tracks();
   
   /* get the list of local tracks */
-  zeRace_get_tracks();
+  if (!zeRace_get_tracks(&tracklist)) zeRace_exit();
   
   srand(time(NULL));
   
@@ -347,6 +299,15 @@ void zeRace_init()
     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 (config.fullscreen) flags|=SDL_FULLSCREEN;
   
@@ -366,18 +327,13 @@ void zeRace_init()
     zeRace_exit();
   }
 
-  /* allocate memory for car sprites */
-  for (i=0;i<256;i++) if ((cars[i]=SDL_CreateRGBSurface(SDL_SWSURFACE,30,30,32,0x000000ff,0x0000ff00,0x00ff0000,0xff000000))==NULL)
-  {
-    fprintf(stderr,"CreateRGBSurface failed: %s\n",SDL_GetError());
-    zeRace_exit();
-  };
+  /* pre-calculate car sprites */
   zeRace_generate_cars();
 }
 
 
 /* send the best time for this race to the web server */
-void zeRace_send_time(float x,float y,float speed,float angle,int btime,char *bkeys)
+void zeRace_send_time(struct _record record/*float x,float y,float speed,float angle,int btime,char *bkeys*/)
 {
   IPaddress ip;
   TCPsocket tcpsock;
@@ -416,8 +372,8 @@ void zeRace_send_time(float x,float y,float speed,float angle,int btime,char *bk
     return;
   }
 
-  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(bkeys)+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,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);
@@ -451,32 +407,36 @@ void print_time(int x,int y,int time)
 
 
 /* launch a new race */
-void zeRace_launch()
+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,g,b;
-  char keys[10000];
-  char bkeys[10000];
+  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,alltime=0,lastsound=0;
+  int lastsound_time=-999,lastsound=0;
+  int delay=DELAY;
+  int lastack=alltime;
+  struct _record net;
+  struct _car oldnetpos[MAX_CLIENTS],newnetpos[MAX_CLIENTS];
 
   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 (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")))
   {
@@ -490,10 +450,10 @@ void zeRace_launch()
     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);
@@ -504,47 +464,164 @@ void zeRace_launch()
     }
     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 (;;)
   {
+    /* 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:
+              /* 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 !");
+                strcpy(packet->data,"disconnect");
+                packet->len=strlen(packet->data)+1;
+                SDLNet_UDP_Send(udpsock,-1,packet);
+                SDL_Flip(screen);
+              }
+              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;
+              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;
+      }
+    }
+    
+    /* 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)
+      {
+        /*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));
+        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;
+          /* 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);
+          lastack=clienttime;
+        }
+      } else return;
+      if (strlen(net.keys)!=0)
+      {
+        tmp=packet->data;
+        strcpy(tmp,"keys");
+        tmp+=strlen(tmp)+1;
+        memcpy(tmp,&lastack,sizeof(int));
+        tmp+=sizeof(int);
+        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 */
-    size.w=cars[0]->w;
-    size.h=cars[0]->h;
-    pos.x=x;
-    pos.y=y;
+    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);
     
-    /* save the old position and compute the new one */
-    ox=x;
-    oy=y;
-    speed*=0.995;
-    x=x-cos(angle)*speed;
-    y=y-sin(angle)*speed;
-    
-    /* collision with the border of the screen */
-    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);
     }
     
-    /* display the car at the new position and update display */
-    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);
     
-    /* accelerate, brake and turn depending on the pressed keys */
-    if (kl) { if (speed<0) angle+=0.01*(255-b)/255*COEFF; else angle-=0.01*(255-b)/255*COEFF; }
-    if (kr) { if (speed<0) angle-=0.01*(255-b)/255*COEFF; else angle+=0.01*(255-b)/255*COEFF; }
-    if (ku) speed+=0.01*2*COEFF;
-    if (kd) speed-=0.01*COEFF;
+    /* 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)
@@ -555,7 +632,7 @@ void zeRace_launch()
     }
     
     /* if the car is fast or braking, it slides */
-    if ((kd && speed>0.5) || (speed>2.0 && !ku))
+    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)
@@ -568,116 +645,54 @@ void zeRace_launch()
       /* display tires slide */
       if (config.tire)
       {
-        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);
+        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,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)*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);
         }
       }
     }
    
-    /* get the pixel color under the center of car in the function map */
-    c=getpixel(fun,x+cars[i]->w/2,y+cars[i]->h/2);
-    /* red layer (checkpoints) */
-    r=(c    )&0xff;
-    /* green layer (road quality) */
-    g=(c>>8 )&0xff;
-    /* blue layer (unused) */
-    b=(c>>16)&0xff;
-
-    /* if it is a wall we move back to the last position */
-    if (g==0)
+    /* if we crashed */
+    if (car.crashflag && (lastsound_time+100<alltime || lastsound<2))
     {
-      x=ox;
-      y=oy;
-      /* play the crash sound */
-      if (lastsound_time+100<alltime || lastsound<2)
-      {
-        lastsound=2;
-        lastsound_time=alltime;
-        if (config.sound) Mix_PlayMusic(crash,1)==-1;
-      }
+      lastsound=2;
+      lastsound_time=alltime;
+      if (config.sound) Mix_PlayMusic(crash,1)==-1;
     }
-    /* update the speed depending on the road quality */
-    speed-=speed*(255-g)/1000;
    
-    /* if we are on the next checkpoint, validate it (no missing allowed) */
-    if (r/8==lastcheck+1) lastcheck++;
-    /* if we validate all and start over, we complete a turn */
-    if (r/8==0 && lastcheck==31)
+    /* if we completed a lap */
+    if (car.lapflag)
     {
-      printf("time = %d\"%d\n",time*DELAY/1000,time*DELAY%1000);
+      printf("time = %d\"%d\n",current.time*DELAY/1000,current.time*DELAY%1000);
       print(screen,0,0,"Last lap : ");
-      print_time(110,0,time);
+      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 (btime==-1 || time<btime)
-      {
-        btime=time;
-        bx=sx;
-        by=sy;
-        bangle=sangle;
-        bspeed=sspeed;
-        keys[time]='\0';
-        memcpy(bkeys,keys,btime);
-      }
+      if (best.time==-1 || current.time<best.time)
+        memcpy(&best,&current,sizeof(struct _record));
       /* reset turn variables */
-      lastcheck=0;
-      time=0;
-      sx=x;
-      sy=y;
-      sangle=angle;
-      sspeed=speed;
-    }
-    
-    /* 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:
-              /* 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 (btime<10000) zeRace_send_time(bx,by,bspeed,bangle,btime,bkeys);
-              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;
-              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;
-      }
+      current.time=0;
+      current.x=car.x;
+      current.y=car.y;
+      current.angle=car.angle;
+      current.speed=car.speed;
     }
     
     /* let the system breath */
-    SDL_Delay(DELAY);
-    /* save pressed keys to validate best time */
-    if (time<btime) keys[time]=(ku<<3 | kd<<2 | kl<<1 | kr)+'A';
+    SDL_Delay(delay);
     /* game time */
-    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++;
   }
 }
@@ -704,12 +719,12 @@ 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);*/
 }
 
 
 /* menu loop to select track */
-void zeRace_select_track()
+void zeRace_local()
 {
   SDL_Event event;
   
@@ -760,7 +775,7 @@ void zeRace_select_track()
               return;
             case SDLK_RETURN:
             case SDLK_SPACE:
-              zeRace_launch();
+              zeRace_launch(0,1);
               update();
               break;
             case SDLK_LEFT:
@@ -782,6 +797,279 @@ void zeRace_select_track()
 }
 
 
+/* top 10 screen */
+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);
+  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);
+    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;
+  memcpy(tmp,&config.color,sizeof(int));
+  tmp+=sizeof(int);
+  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;
+      memcpy(&time,tmp,sizeof(int));
+      tmp+=sizeof(int);
+      memcpy(&network_speed,tmp,sizeof(int));
+      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;
+    printf("%s\n",tmp);
+    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()
 {
@@ -816,7 +1104,7 @@ void zeRace_config()
     print(screen,40,HEIGHT/(CONFIG_OPTIONS+4)*12,"Color : ");
     pos.x=123;
     pos.y=HEIGHT/(CONFIG_OPTIONS+4)*12-7;
-    SDL_BlitSurface(cars[0],NULL,screen,&pos);
+    SDL_BlitSurface(cars[config.color][0],NULL,screen,&pos);
     print(screen,40,HEIGHT/(CONFIG_OPTIONS+4)*(CONFIG_OPTIONS+2),"Back to main menu");
     SDL_Flip(screen);
   }
@@ -834,7 +1122,6 @@ void zeRace_config()
     }
   }
   
-  SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY,SDL_DEFAULT_REPEAT_INTERVAL);
   update();
   for (;;)
   {
@@ -849,7 +1136,6 @@ void zeRace_config()
           switch (event.key.keysym.sym)
           {
             case SDLK_ESCAPE:
-              SDL_EnableKeyRepeat(0,0);
               return;
             case SDLK_RETURN:
             case SDLK_SPACE:
@@ -870,10 +1156,8 @@ void zeRace_config()
                   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;
-                  zeRace_generate_cars();
                   break;
                 case 10:
-                  SDL_EnableKeyRepeat(0,0);
                   return;
               }
               update();
@@ -903,7 +1187,7 @@ void zeRace_menu()
   SDL_Event event;
   int active=0;
   SDL_Surface *logo;
-  #define MENU_OPTIONS 3
+  #define MENU_OPTIONS 5
   
   void update()
   {
@@ -912,17 +1196,17 @@ void zeRace_menu()
     pos.x=WIDTH/2-logo->w/2;
     pos.y=HEIGHT/6-logo->h/2;
     SDL_BlitSurface(logo,NULL,screen,&pos);
-    print(screen,650,HEIGHT/(MENU_OPTIONS+4)*2,"version " VERSION);
+    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)*4,"Configuration");
-    print(screen,440,HEIGHT/(MENU_OPTIONS+4)*5,"Exit 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");
-  SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY,SDL_DEFAULT_REPEAT_INTERVAL);
   update();
   for (;;)
   {
@@ -937,7 +1221,6 @@ void zeRace_menu()
           switch (event.key.keysym.sym)
           {
             case SDLK_ESCAPE:
-              SDL_EnableKeyRepeat(0,0);
               return;
             case SDLK_RETURN:
             case SDLK_SPACE:
@@ -945,9 +1228,11 @@ void zeRace_menu()
             case SDLK_RIGHT:
               switch (active)
               {
-                case 0: zeRace_select_track(); break;
-                case 1: zeRace_config(); break;
-                case 2: return;
+                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;