% Listing 2: init_nim.c

/* init_nim.c */
/* Andrew Davison, June 1996 (ad@cs.mu.oz.au) */

/* The initialisation form for the Nim game, init.html,
   passes the initial stick configuration and the user's
   name to init_nim. init_nim generates a GIF
   representing the configuration and refers to it in a page
   returned to the user. The page also includes text entry
   fields for accepting the player's game move, and a hidden 
   field holding the stick configuration. The form is set
   to invoke NIM_STEP to process the player's move.
/* Makes use of the gd graphics library, by
   Thomas Boutell and the Quest Protein Database Center
   at Cold Spring Harbor Labs.
/* compilation:
     \gcc -Wall -o init_nim init_nim.c -L/home/staff/ad/gd1.2 -lgd -lm

#include <stdio.h>
#include <string.h>
#include <stdlib.h>     /* for atoi() */
#include <ctype.h>      /* for isdigit() */
#include <math.h>       /* for pow() */
#include <unistd.h>     /* for read(), lseek(), write(), close(), lockf() */
#include <fcntl.h>      /* for open() */
#include "/home/staff/ad/gd1.2/gd.h"
#include "/home/staff/ad/gd1.2/gdfontg.h"

#define ROWS 5          /* number of rows */
#define COLS 4          /* max sticks in a row must be <= (2^COLS)-1 */
#define MAXLEN 120      /* max length of a string */
#define ROWLBL 4        /* max length of a row label in the GIF */
#define DIGLEN 3        /* max length of a digit string */ 

#define XOFFSET 4         /* X distance between sticks */
#define YOFFSET 8         /* Y distance between sticks */
#define TEXT_START 15     /* Start of Row ?: text */

#define DELNO 8           /* offset to delete old gifs */
#define STICK_GIF  "/home/staff/ad/www_public/code/nim/stick.gif"
#define COUNT_NM   "/home/staff/ad/www_public/code/nim/count"
#define NIMTBL     "/home/staff/ad/www_public/code/nim/nimtbl"
#define WWW_NIMTBL "http://www.cs.mu.oz.au/~ad/code/nim/nimtbl"
#define NIM_STEP   "http://www.cs.mu.oz.au/cgi-bin/step_nim"
#define BKGROUND   "http://www.cs.mu.oz.au/~ad/chalk.jpg"

#define MAX_ENTRIES 100         /* max number of input fields */
#define NOVAL "$$no-value$$"    /* when no value is found */

typedef struct {                /* for form nameString=valueString pairs */
    char *name;
    char *val;
} entry;

int init_game(entry entries[], int etnum, int sticks[], char **name);
void get_stcknum(char ln[], int rposn, int max, int *no, int *ok);
void display_game(int sticks[]);
void make_names(char gifnm[], char wgifnm[]);
int get_count(char *nm);
void display_pic(int sticks[], char *wgifnm);
void display_form(int sticks[], char *name);
void single_text(char *nm);

/* GIF creation from view_stcks.c */
void visualise_nim(int sticks[], char *gifnm);
void load_stick(gdImagePtr *stick);
void make_canvas(gdImagePtr *canvas, gdImagePtr stick, int max,
                              int *fst_offset);
void draw_row(gdImagePtr canvas, int fst_offset, int r, int black,
                              int numsticks, gdImagePtr stick);
void save_canvas(gdImagePtr canvas, char *gifnm);

/* Standard Form Functions */
void start_reply(char *title);
void cgi_errs();
int build_entries(entry entries[]);
char *find_value(entry entries[], int etnum, char *name);

/* Debugging Utilities */
int input_entries(entry entries[]);
void show_entries(entry entries[], int etnum);

/* CGI utilities written by Rob McCool */
char *makeword(char *line, char stop);
char *fmakeword(FILE *f, char stop, int *len);
void unescape_url(char *url);
char x2c(char *what);
void plustospace(char *str);

int main()
  entry entries[MAX_ENTRIES];   /* HTML nameString=valueString pairs */
  int etnum = 0;
  int sticks[ROWS];
  char *user_name;

  start_reply("Nim Initialisation Outcome");
  etnum = build_entries(entries);
  etnum = input_entries(entries);
  show_entries(entries, etnum);
  if (init_game(entries, etnum, sticks, &user_name)) {
    printf("\n<P>Hello, <strong>%s</strong></P>\n", user_name);
    display_form(sticks, user_name);
    printf("<P>Some errors have occured, please start again</P>\n");
  return 0;

int init_game(entry entries[], int etnum, int sticks[], char **user_name)
/* Read the init.html form values. There should be 
   one name/value pair for each row, named rK, where K
   goes from 0 upto ROWS-1. The value associated with rK
   is the number of sticks in that row. There should also be
   a name/value pair called name which holds the user name.
  char row_label[ROWLBL];
  char *rowval;
  int maxsticks, r, ok = 1;

  maxsticks = pow(2, COLS) - 1;

  for (r = 0; r < ROWS; r++) {
    sprintf(row_label, "r%d", r);
    rowval = find_value(entries, etnum, row_label);
    get_stcknum(rowval, r, maxsticks, &sticks[r], &ok);
  *user_name = find_value(entries, etnum, "name");
  return ok;

void get_stcknum(char ln[], int rposn, int max, int *no, int *ok)
/* The number of sticks for row number rowposn is extracted
   from the string stored in ln[]. The number is returned
   if no error has occurred. An error is signalled by ok
   being set to 0.
  char num[DIGLEN];
  int neg = 0, i = 0, j = 0;

  if (ln[0] == '-') {
    neg = 1;
    i = 1;    /* skip the first character */
  while ((isdigit(ln[i])) && (j < DIGLEN))
    num[j++] = ln[i++];
  num[j] = '\0';

  if ((i == 0) || (neg && (i == 1))) {
    printf("<P>Error: Row %d does not contain an integer</P>\n", rposn);
    *ok = 0;  
  else if (j == DIGLEN) {
    printf("<P>Error: Row %d number is too large (maximum is %d)</P>\n", 
                                                        rposn, max);
    *ok = 0;
  else {
    *no = atoi(num);
    if (neg)
      *no = -1 * *no;
    if (*no > max) {
      printf("<P>Error: Row %d number is too big (maximum is %d)</P>\n", 
                                                        rposn, max);
      *ok = 0;
    else if (*no < 0) {
      printf("<P>Error: Row %d number is negative</P>\n", rposn);
      *ok = 0;

void display_game(int sticks[])
/* Generate a GIF for the current stick configuration, and 
   store it in gifnm. The URL of this gif file, web_gifnm, 
   is used to access the picture on a Web page.
  char gifnm[MAXLEN], web_gifnm[MAXLEN];

  make_names(gifnm, web_gifnm);
  visualise_nim(sticks, gifnm);
  display_pic(sticks, web_gifnm);

void make_names(char gifnm[], char wgifnm[])
/* A GIF filename is a concatenation of NIMTBL with a count 
   and .gif. The URL of the file is a concatenation of WWW_NIMTBL, 
   the same count, and .gif. The count is held in COUNT_NM.
   make_names() also deletes old GIF files.
  char oldgif[MAXLEN];
  int count;

  count = get_count(COUNT_NM);
  sprintf(gifnm, "%s%d.gif", NIMTBL, count);
  sprintf(wgifnm, "%s%d.gif", WWW_NIMTBL, count);

  /* Remove old Nim picture */
  sprintf(oldgif, "%s%d.gif", NIMTBL, count-DELNO);

int get_count(char *nm)
/* The count is extracted from COUNT_NM, and the value in 
   the file is incremented. The use of lockf() means that 
   lower-level UNIX file manipulation (e.g. open(), read()) 
   must be utilised.
   Some versions of UNIX use flock() rather than lockf().
   The correct calls for flock() are commented out. If flock()
   is used the <sys/file.h> must usually be included.
  char ln[MAXLEN];
  int fd, cnt, size;

  if ((fd = open(nm, O_RDWR)) == -1) {   /* both read and write */
    printf("<P><b>Error</b>: Cannot open %s for changing</P>\n", nm);

  lockf(fd, F_LOCK, 0L);      /* (wait for) exclusive lock */
                              /* or flock(fd, LOCK_EX); */
  size = read(fd, ln, MAXLEN);
  ln[size] = '\0';
  sscanf(ln, "%d\n", &cnt);
  lseek(fd, 0L, 0);           /* rewind to start of file */
  sprintf(ln, "%d\n", cnt+1);
  size = strlen(ln);
  write(fd, ln, size);
  lockf(fd, F_ULOCK, 0L);     /* unlock */
                              /* or flock(fd, LOCK_UN);  */
  return cnt; 

void display_pic(int sticks[], char *wgifnm)
/* Display the image by accessing its URL via wgifnm.
   Debugging code is also included for listing the
   values in the sticks array.
/* int r;

  printf("\n<P>The stick configuration is now:<br>\n");
  for (r = 0; r < ROWS; r++)
    printf("Row %d: %d sticks<br>\n", r, sticks[r]);
  printf("\n<P>The stick configuration is now:</P>\n");
  printf("<P><img src=%s></P>\n", wgifnm);

void display_form(int sticks[], char *user_name)
/* The input form for the user's turn is output. It contains
   two text entry fields (for the row to be modified, and
   the number of sticks to be removed). There is also a
   hidden field, called nimstate, which holds the sticks
   array values and user name. nimstate has the form:
   The action field of the form refers to NIM_STEP, the CGI
   script for processing a player's move.
  int r;

  printf("<P>Now its time for you to remove some sticks.</P>\n\n");

  printf("<FORM METHOD=\"POST\"\n");
  printf("ACTION=\"%s\">\n", NIM_STEP);
  printf("\n<P>Enter a row (0 to %d): \n", ROWS-1);
  printf("\n<P>Enter sticks to remove: \n");
  printf("<P><INPUT TYPE=\"submit\" VALUE=\"Make Move\">\n");
  printf("<INPUT TYPE=\"reset\"  VALUE=\"Reset\"></P>\n");

  printf("<INPUT TYPE=\"hidden\" NAME=\"nimstate\" VALUE=\"%d", sticks[0]);
  for (r = 1; r < ROWS; r++)
    printf("-%d", sticks[r]);
  printf("-%s\">\n", user_name);


void single_text(char *nm)
/* Output the form syntax for a text entry field
   labelled with nm.
  printf("<INPUT TYPE=\"text\" NAME=\"%s\"", nm);
  printf(" SIZE=\"5\" MAXLENGTH=\"5\" VALUE=\"\"></P>\n");

/* GIF Creation from view_stks.c */
/* printf()s have been changed to output HTML, and some have
   been commented out. */

void visualise_nim(int sticks[], char *gifnm)
/* A white canvas is created, consisting of rows of
   sticks. The width of the canvas is based on max, the
   maximum number of sticks in the sticks array.
   The final image is stored in gifnm.
  int black;
  int r, max = 0, fst_offset;
  gdImagePtr canvas, stick;

  for (r = 0; r < ROWS; r++)
    if (max < sticks[r])
      max = sticks[r];

  make_canvas(&canvas, stick, max, &fst_offset);
  black = gdImageColorAllocate(canvas, 0, 0, 0);
  for (r = 0; r < ROWS; r++)
    draw_row(canvas, fst_offset, r, black, sticks[r], stick);

  save_canvas(canvas, gifnm);

void load_stick(gdImagePtr *stick)
/* Load the image representing a stick, which is
   assumed to be in the STICK_GIF file.
  FILE *fp;

  if ((fp = fopen(STICK_GIF, "rb")) == NULL) {
    printf("<P>Cannot read gif image from %s</P>\n", STICK_GIF);
  /* printf("<P>Reading gif image from %s</P>\n", STICK_GIF); */
  *stick = gdImageCreateFromGif(fp);

void make_canvas(gdImagePtr *canvas, gdImagePtr stick, int max,
                              int *fst_offset)
/* Create a blank, white canvas, which is wide enough to
   hold the words Row <ROWS-1>: and max sticks. Its depth
   must be enough to contain ROWS rows.

   The offsets between sticks in the X and Y directions are
   obtained from XOFFSET and YOFFSET. TEXT_START contains 
   the offset from the left edge to the text. fst_offset 
   holds the offset from the left edge to the first stick.
  char row_title[MAXLEN];
  int xlen, ylen, white;

  sprintf(row_title, "Row %d: ", ROWS-1);
  *fst_offset = TEXT_START +
                (strlen(row_title) * gdFontGiant->w) + XOFFSET;

  xlen = *fst_offset + ((stick->sx + XOFFSET) * max);
  ylen = YOFFSET + ((stick->sy + YOFFSET) * ROWS);

  *canvas = gdImageCreate(xlen, ylen);
  /* first colour created becomes the background */
  white = gdImageColorAllocate(*canvas, 255, 255, 255);

void draw_row(gdImagePtr canvas, int fst_offset, int r, int black,
                              int numsticks, gdImagePtr stick)
/* Draw a row of sticks onto the canvas. The row number is r, 
   the number of sticks is numsticks. The text before the
   sticks is drawn in a black Giant font.
  char row_title[MAXLEN];
  int i, xposn, yposn;

  yposn = YOFFSET + ((stick->sy + YOFFSET) * r);
  sprintf(row_title, "Row %d: ", r);
  gdImageString(canvas, gdFontGiant, TEXT_START, yposn, row_title, black);

  xposn = fst_offset;          /* start of sticks on canvas */
  for (i = 0; i < numsticks; i++) {
    gdImageCopy(canvas, stick, xposn, yposn, 0, 0, stick->sx, stick->sy);
    xposn = xposn + (stick->sx + XOFFSET);
    /* debugging printf() */
    /* printf("<P>r=%d; xposn=%d yposn=%d</P>\n", r, xposn, yposn); */

void save_canvas(gdImagePtr canvas, char *gifnm)
/* Save the canvas image in the GIF file gifnm */
  FILE *fp;

  if ((fp = fopen(gifnm, "wb")) == NULL) {
    printf("<P>Cannot write gif image to %s</P>\n", gifnm);
  /* printf("<P>Writing gif image to %s</P>\n", gifnm); */
  gdImageGif(canvas, fp);

/* Standard Form Functions */

void start_reply(char *title)
/* Output the HTML header, including the title and H1 container */
  printf("Content-type: text/html\n\n");
  printf("<html><head><title>%s</title></head>\n", title);
  printf("<body background=\"%s\">", BKGROUND);
  printf("<H1 align=center>%s</H1>\n", title);
  fflush(NULL);   /* force output to occur now */

void cgi_errs()
/* Check for errors in the REQUEST_METHOD and CONTENT_TYPE
   environment variables.
  if(strcmp(getenv("REQUEST_METHOD"),"POST")) {
    printf("<P>This script should be referenced with a METHOD of POST.\n");
    printf("If you don't understand this, read ");
    printf("<A HREF=\"http://www.ncsa.uiuc.edu/SDG/Software/Mosaic/Docs/fill-out-forms/overview.html\">forms overview</A>.</P>\n");
  if(strcmp(getenv("CONTENT_TYPE"), "application/x-www-form-urlencoded")) {
    printf("<P>This script can only be used to decode form results.</P>\n");

int build_entries(entry entries[])
/* Fill in the entries array with nameString=valueString 
   pairs from the form. 
  int len, x;

  len = atoi(getenv("CONTENT_LENGTH"));
  for(x=0; len && (!feof(stdin)) && (x < MAX_ENTRIES); x++) {
    entries[x].val = fmakeword(stdin,'&',&len);
    entries[x].name = makeword(entries[x].val,'=');
  return x;  

char *find_value(entry entries[], int etnum, char *name)
/* Search through the entries array for a name string 
   which matches name and return its associated value 
   string. Otherwise return NOVAL.
  int x;
  char *value;

  for(x=0; x < etnum; x++)
    if (strcmp(entries[x].name, name) == 0) {
      value = (char *)malloc(sizeof(char)*(strlen(entries[x].val)+1));
      strcpy(value, entries[x].val);
  if ((x == etnum) || (value[0] == '\0')) {
    value = (char *)malloc(sizeof(char)*(strlen(NOVAL)+1));
    strcpy(value, NOVAL);
  return value;

/* Debugging Utilities */

int input_entries(entry entries[])
/* Fill in the entries array with nameString=valueString pairs 
   entered by the user at the keyboard.
   Each pair should be entered in the format:
   Terminate the input with <ctrl>d
  int num = 0, namelen, linelen;
  char line[MAXLEN];

  while (gets(line) && (num < MAX_ENTRIES)) {
    linelen = strlen(line);
    namelen = strcspn(line, "=");
    entries[num].name =
      (char *)malloc(sizeof(char)*(namelen+1));
    strncpy(entries[num].name, line, namelen);
    entries[num].name[namelen] = '\0';
    entries[num].val =
      (char *)malloc(sizeof(char)*(linelen-namelen));
    strcpy(entries[num].val, &line[namelen+1]);
  return num;

void show_entries(entry entries[], int etnum)
/* Output the nameString=valueString pairs as an HTML 
   unnumbered list */
  int x;

  printf("<P>You submitted these nameString=valueString pairs:</P>\n");
  for(x=0; x < etnum; x++)
    printf("<li><code>%s = %s</code>\n", entries[x].name, entries[x].val);

/* CGI Utilities by Rob McCool; 
   comments by Andrew Davison */

char *makeword(char *line, char stop) 
/* Builds a word by extracting characters from line up to the
   stopping character in stop (or until line is exhausted).
  int x = 0, y = 0;
  char *word = (char *) malloc(sizeof(char) * (strlen(line) + 1));

  for(x=0; ((line[x]) && (line[x] != stop)); x++)
     word[x] = line[x];
  word[x] = '\0';
  while((line[y++] = line[x++]));
  return word;

char *fmakeword(FILE *f, char stop, int *len) 
/* Similar to makeword(): builds a word by extracting characters 
   from the file f up to the stopping character in stop (or
   until f is exhausted). The function is also supplied with
   the length of the string (len) left unread in f.
  int wsize = 102400, ll = 0;
  char *word = (char *) malloc(sizeof(char) * (wsize + 1));

  while(1) {
    word[ll] = (char)fgetc(f);
    if(ll == wsize) {
       word[ll+1] = '\0';
       wsize += 102400;
       word = (char *)realloc(word,sizeof(char)*(wsize+1));
    if((word[ll] == stop) || (feof(f)) || (!(*len))) {
       if(word[ll] != stop) ll++;
       word[ll] = '\0';
       return word;

void unescape_url(char *url) 
/* Convert the hexadecimal characters in url into ASCII.
   They are detected by starting with a '%'.
  register int x, y;

  for(x=0,y=0; url[y]; ++x,++y) {
    if((url[x] = url[y]) == '%') {
      url[x] = x2c(&url[y+1]);
  url[x] = '\0';

char x2c(char *what) 
/* Convert the hexadecimal (consisting of two characters) into
   a single ASCII character.
  register char digit;

  digit = (what[0] >= 'A' ? ((what[0] & 0xdf) - 'A')+10 : (what[0] - '0'));
  digit *= 16;
  digit += (what[1] >= 'A' ? ((what[1] & 0xdf) - 'A')+10 : (what[1] - '0'));

void plustospace(char *str) 
/* Replace '+'s by spaces in str */
  register int x;

  for(x=0; str[x]; x++) 
    if(str[x] == '+') str[x] = ' ';