diff --git a/.gitignore b/.gitignore index ce47879..d5c648c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,10 @@ *~ *# *.o +*.swp round-*.html +results-*.txt next-round points forftanks @@ -14,3 +16,5 @@ summary.html designer.html intro.html procs.html +chord.html +killmatrix.js diff --git a/Makefile b/Makefile index fded3c4..0b24b5d 100644 --- a/Makefile +++ b/Makefile @@ -1,32 +1,55 @@ BINARIES = forftanks designer.cgi -HTML = forf.html procs.html intro.html designer.html +HTML = forf.html procs.html intro.html designer.html chord.html WWW = style.css grunge.png designer.js figures.js tanks.js nav.html.inc +SCRIPTS = killmatrix.pl rank.awk summary.awk winner.awk -CFLAGS = -Wall +CFLAGS = -Wall -std=gnu90 -g all: $(BINARIES) $(HTML) -install: - install -d $(DESTDIR)/usr/bin - install run-tanks $(DESTDIR)/usr/bin - install forftanks $(DESTDIR)/usr/bin +forftanks: forftanks.o ctanks.o forf.o tankdir.o tankjson.o +forftanks: LDLIBS = -lm -ljansson - install -d $(DESTDIR)/usr/lib/tanks - install designer.cgi $(DESTDIR)/usr/lib/tanks - install $(HTML) $(DESTDIR)/usr/lib/tanks - install $(WWW) $(DESTDIR)/usr/lib/tanks - cp -r examples $(DESTDIR)/usr/lib/tanks/examples - -forftanks: forftanks.o ctanks.o forf.o -forftanks: LDFLAGS = -lm - -forftanks.o: forf.h ctanks.h +forftanks.o: forf.h ctanks.h tankdef.h tankdir.h tankjson.h forf.o: forf.c forf.h ctanks.o: ctanks.h +tankdir.o: tankdef.h tankdir.h +tankjson.o: tankdef.h tankjson.h %.html: %.html.m4 m4 $< > $@ +.PHONY: install clean check-env +install: check-env + install -d $(DESTDIR)/bin + install run-tanks $(DESTDIR)/bin + install forftanks $(DESTDIR)/bin + install $(SCRIPTS) $(DESTDIR)/bin + install -d $(DOCROOT) + install designer.cgi $(CGIBIN) + install $(HTML) $(DOCROOT) + install $(WWW) $(DOCROOT) + cp -r examples $(DOCROOT)/examples + clean: - rm -f *.o next-round round-*.html + rm -f *.o next-round round-*.html round-*.json results-*.txt current.html rm -f $(BINARIES) $(HTML) + +check-env: +ifndef DESTDIR + $(error DESTDIR is undefined) +endif +ifndef DOCROOT +ifndef DESTDIR + $(error DOCROOT is undefined) +else +DOCROOT = $(DESTDIR) +endif +endif +ifndef CGIBIN +ifndef DESTDIR + $(error CGIBIN is undefined) +else +CGIBIN = $(DESTDIR) +endif +endif diff --git a/README.txt b/README.txt index f446fcb..afaf3b5 100644 --- a/README.txt +++ b/README.txt @@ -18,7 +18,7 @@ hodgepodge of C, Bourne shell, and awk, but at least each piece is fairly simple to audit. -### round.sh tank1 tank2 ... +### run-tanks tank1 tank2 ... Runs a single round, awards points with rank.awk, and creates a new summary.html with summary.awk. This is the main interface that you want diff --git a/chord.html.m4 b/chord.html.m4 new file mode 100644 index 0000000..9118a5c --- /dev/null +++ b/chord.html.m4 @@ -0,0 +1,113 @@ + + + + + + + + + + +

Killer Chart

+include(nav.html.inc) + + + diff --git a/ctanks.c b/ctanks.c index f7d1049..be7a165 100644 --- a/ctanks.c +++ b/ctanks.c @@ -105,17 +105,13 @@ rotate_point(float angle, float point[2]) point[1] = new[1]; } - static void -tanks_fire_cannon(struct tanks_game *game, - struct tank *this, - struct tank *that, - float vector[2], - float dist2) +tanks_collision_detect(struct tanks_game *game, + struct tank *this, + struct tank *that, + float vector[2], + float dist2) { - float theta = this->angle + this->turret.current; - float rpos[2]; - /* If someone's a crater, this is easy */ if (this->killer || that->killer) { return; @@ -128,18 +124,32 @@ tanks_fire_cannon(struct tanks_game *game, that->killer = this; that->cause_death = "collision"; + } +} - return; +static int +tanks_fire_cannon(struct tanks_game *game, + struct tank *this, + struct tank *that, + float vector[2], + float dist2) +{ + float theta = this->angle + this->turret.current; + float rpos[2]; + + /* If someone's a crater, this is easy */ + if (this->killer || that->killer) { + return 0; } /* No need to check if it's not even firing */ if (! this->turret.firing) { - return; + return 0; } /* Also no need to check if it's outside cannon range */ if (dist2 > TANK_CANNON_ADJ2) { - return; + return 0; } /* Did this shoot that? Rotate point by turret degrees, and if |y| < @@ -147,10 +157,7 @@ tanks_fire_cannon(struct tanks_game *game, rpos[0] = vector[0]; rpos[1] = vector[1]; rotate_point(-theta, rpos); - if ((rpos[0] > 0) && (fabsf(rpos[1]) < TANK_RADIUS)) { - that->killer = this; - that->cause_death = "shot"; - } + return ((rpos[0] > 0) && (fabsf(rpos[1]) < TANK_RADIUS)); } static void @@ -428,10 +435,21 @@ tanks_run_turn(struct tanks_game *game, struct tank *tanks, int ntanks) struct tank *that = &tanks[j]; compute_vector(game, vector, &dist2, this, that); - tanks_fire_cannon(game, this, that, vector, dist2); + tanks_collision_detect(game, that, this, vector, dist2); + int a,b; + a = tanks_fire_cannon(game, this, that, vector, dist2); vector[0] = -vector[0]; vector[1] = -vector[1]; - tanks_fire_cannon(game, that, this, vector, dist2); + b = tanks_fire_cannon(game, that, this, vector, dist2); + + if(a){ + that->killer = this; + that->cause_death = "shot"; + } + if(b){ + this->killer = that; + this->cause_death = "shot"; + } } } } diff --git a/forftanks.c b/forftanks.c index 65205fc..497bc27 100644 --- a/forftanks.c +++ b/forftanks.c @@ -20,14 +20,20 @@ #include #include #include +#include #include "ctanks.h" #include "forf.h" #include "dump.h" +#include "tankdef.h" +#include "tankdir.h" +#include "tankjson.h" #define MAX_TANKS 50 #define ROUNDS 500 #define SPACING 150 +#define MAX_JSON_SIZE 10000 + #define LENV_SIZE 100 #define DSTACK_SIZE 200 @@ -194,34 +200,6 @@ struct forf_lexical_env tanks_lenv_addons[] = { * Filesystem stuff * */ - -int -ft_read_file(char *ptr, size_t size, char *dir, char *fn) -{ - char path[256]; - FILE *f = NULL; - int ret; - int success = 0; - - do { - snprintf(path, sizeof(path), "%s/%s", dir, fn); - f = fopen(path, "r"); - if (! f) break; - - ret = fread(ptr, 1, size - 1, f); - ptr[ret] = '\0'; - if (! ret) break; - - success = 1; - } while (0); - - if (f) fclose(f); - if (! success) { - return 0; - } - return 1; -} - void ft_bricked_tank(struct tank *tank, void *ignored) { @@ -244,33 +222,17 @@ ft_run_tank(struct tank *tank, struct forftank *ftank) } } -int +int //&L Added function ft_read_program(struct forftank *ftank, - struct tank *tank, - struct forf_lexical_env *lenv, - char *path) + struct tank *tank, + struct tankdef *tank_def) { - char progpath[256]; - FILE *f; - - /* Open program */ - snprintf(progpath, sizeof(progpath), "%s/program", path); - f = fopen(progpath, "r"); - if (! f) return 0; - - /* Parse program */ - ftank->error_pos = forf_parse_file(&ftank->env, f); - fclose(f); + ftank->error_pos = forf_parse_string(&ftank->env, tank_def->program); if (ftank->error_pos) { return 0; } - - /* Back up the program so we can run it over and over without - needing to re-parse */ forf_stack_copy(&ftank->_prog, &ftank->_cmd); - tank_init(tank, (tank_run_func *)ft_run_tank, ftank); - return 1; } @@ -292,71 +254,71 @@ ft_tank_init(struct forftank *ftank, tank); } -void -ft_read_sensors(struct tank *tank, - char *path) +void //&L Added function +ft_read_sensors(struct tank *tank, + struct tankdef *tankdef) { int i; for (i = 0; i < TANK_MAX_SENSORS; i += 1) { - int ret; - char fn[10]; - char s[20]; - char *p = s; long range; - long angle; - long width; + double angle; + double width; long turret; - snprintf(fn, sizeof(fn), "sensor%d", i); - ret = ft_read_file(s, sizeof(s), path, fn); - if (! ret) { - s[0] = 0; - } - range = strtol(p, &p, 0); - angle = strtol(p, &p, 0); - width = strtol(p, &p, 0); - turret = strtol(p, &p, 0); + range = tankdef->sensors[i].range; + angle = tankdef->sensors[i].angle; + width = tankdef->sensors[i].width; + turret = tankdef->sensors[i].turret; tank->sensors[i].range = min(range, TANK_SENSOR_RANGE); - tank->sensors[i].angle = deg2rad(angle % 360); - tank->sensors[i].width = deg2rad(width % 360); + tank->sensors[i].angle = deg2rad((long)angle % 360); + tank->sensors[i].width = deg2rad((long)width % 360); tank->sensors[i].turret = (0 != turret); } } -int +char* //&L Added function. +temp_parse_path(char* tankName) +{ + size_t inTankName; + size_t inPath; + char* retVal = (char*)malloc(strlen(tankName)*sizeof(char)); + for(inTankName=0, inPath=0; inTankNamepath = path; - + ftank->path = temp_parse_path(tank_def->name); // No path anymore. /* What is your name? */ - ret = ft_read_file(ftank->name, sizeof(ftank->name), path, "name"); - if (! ret) { - strncpy(ftank->name, path, sizeof(ftank->name)); - } - + strncpy(ftank->name, tank_def->name, sizeof(ftank->name)); /* What is your quest? */ ft_tank_init(ftank, tank, lenv); - ret = ft_read_program(ftank, tank, lenv, path); + ret = ft_read_program(ftank, tank, tank_def); if (ret) { - ft_read_sensors(tank, path); + ft_read_sensors(tank, tank_def); } else { /* Brick the tank */ tank_init(tank, ft_bricked_tank, NULL); } - /* What is your favorite color? */ - ret = ft_read_file(ftank->color, sizeof(ftank->color), path, "color"); - if (! ret) { - strncpy(ftank->color, "#808080", sizeof(ftank->color)); - } - + strncpy(ftank->color, tank_def->color, sizeof(ftank->color)); return 1; } @@ -469,10 +431,24 @@ print_standings(FILE *f, } } +void +delete_tank(struct forftank theTank) { + free(theTank.path); +} + +void +delete_tanks(struct forftank* myftanks, const int ntanks) { + int i; + for(i=0; i 1){ + /* Every argument is a tank directory */ + ntanks = argc-1; // argc[0] is program name, not a tank. + if(ntanks > MAX_TANKS) { + fprintf(stderr, "Too many tanks! Tried to load: %d, Max: %d", + ntanks, MAX_TANKS); + return 1; } + for (i = 0; i < ntanks; i++) { //&L changed loop. + mytankdefs[i] = readTankFromDir(argv[i+1]); + } + } else { // Expecting JSON on stdin. + char* jsonString = malloc(sizeof(char)*MAX_JSON_SIZE); + char c; + long size = 0; + while( (c=fgetc(stdin)) != EOF ) { + jsonString[size] = c; + size++; + if(size>=(MAX_JSON_SIZE-1)) { + fprintf(stderr,"Error: JSON text too large.\n"); + return 1; + } + } + jsonString[size] = '\0'; + ntanks = jsonArraySize(jsonString); // argc[0] is program name, not a tank. + if(ntanks > MAX_TANKS) { + fprintf(stderr, "Too many tanks! Tried to load: %d, Max: %d", + ntanks, MAX_TANKS); + return 1; + } + readTanksFromJSON(mytankdefs, ntanks, jsonString); } - if (0 == ntanks) { fprintf(stderr, "No usable tanks!\n"); return 1; } + for(i=0; i < ntanks; i++) { + ft_read_tank(&myftanks[i], + &mytanks[i], + lenv, + &mytankdefs[i]); + } /* Calculate the size of the game board */ { @@ -580,6 +584,8 @@ main(int argc, char *argv[]) print_standings(standings, myftanks, mytanks, ntanks); } } + + delete_tanks(myftanks, ntanks); return 0; } diff --git a/killmatrix.pl b/killmatrix.pl new file mode 100755 index 0000000..1f51db1 --- /dev/null +++ b/killmatrix.pl @@ -0,0 +1,55 @@ +#!/usr/bin/perl + +foreach $file (@ARGV) { + $tanks = {}; + open FH, "<$file"; + foreach () { + chomp; + @tank{"id","path","cause","killer","errorpos","error"} = split; + $tanks->{$tank{"id"}} = {%tank}; + $paths->{$tank{"path"}}++}; + foreach (values %$tanks) { + next if $_->{"killer"} eq "(nil)"; + next unless $_->{"cause"} eq "shot"; + $data->{$tanks->{$_->{"killer"}}->{"path"}}->{$_->{"path"}}++ + } + }; + @paths = keys %$paths; + print + "var names = [", + (join + ",", + map { + open NF, $_."/name"; + $name = ; + chomp $name;"\"$name\"" + } + @paths + ), + "];\n"; + + print "var colors = [", + (join + ",", + map { + open CF, $_."/color"; + $color = ; + $color ||= "#c0c0c0"; + chomp $color; + "\"$color\"" + } @paths + ), + "];\n"; + + print + "var matrix = [\n [", + (join + ("],\n [", + map { + join( + ",", + map{$_||=0} @{$data->{$_}}{@paths} + ) + } + @paths) + ),"]\n];\n"; diff --git a/rank.awk b/rank.awk index 1ad320a..f7e44ad 100755 --- a/rank.awk +++ b/rank.awk @@ -5,9 +5,9 @@ BEGIN { } function esc(s) { - gsub(/&/, "&", s); - gsub(//, ">", s); + gsub(/&/, "\\&", s); + gsub(//, "\\>", s); return s; } diff --git a/run-tanks b/run-tanks index 854aec9..edc5a20 100755 --- a/run-tanks +++ b/run-tanks @@ -22,10 +22,13 @@ fi expr $next + 1 > next-round fn=$(printf "round-%04d.html" $next) -rfn=results$$.txt - +rfn=$(printf "results-%04d.txt" $next) +jfn=$(printf "round-%04d.json" $next) +cfn="current$$.html"; echo -n "Running round $next... " +$TANKS_GAME $tanks >>$jfn 3>$rfn + cat <$fn @@ -38,7 +41,7 @@ cat <$fn start("battlefield", // Start JSON data EOF -$TANKS_GAME $tanks >>$fn 3>$rfn +cat $jfn >> $fn cat <>$fn // end JSON data ); @@ -52,15 +55,56 @@ window.onload = go;

0 fps

EOF rank.awk $rfn >>$fn -rm -f $rfn cat $NAV_HTML_INC >>$fn cat <>$fn EOF +cat <$cfn + + + + Tanks Round $next + + + + + + +
+EOF +cat <>$cfn + + +EOF + +mv "current$$.html" "current.html" + summary.awk $tanks > summary.html.$$ mv summary.html.$$ summary.html +./killmatrix.pl results-*.txt > killmatrix$$.js + +mv killmatrix$$.js killmatrix.js + echo "done." diff --git a/summary.awk b/summary.awk index fadcc3d..b8486f3 100755 --- a/summary.awk +++ b/summary.awk @@ -1,9 +1,9 @@ #! /usr/bin/awk -f function esc(s) { - gsub(/&/, "&", s); - gsub(//, ">", s); + gsub(/&/, "\\&", s); + gsub(//, "\\>", s); return s; } diff --git a/tankdef.h b/tankdef.h new file mode 100644 index 0000000..e4b6679 --- /dev/null +++ b/tankdef.h @@ -0,0 +1,27 @@ +#ifndef __TANKDEF_H__ +#define __TANKDEF_H__ + +#include "ctanks.h" + +#define MAX_PROGRAM_SIZE 10000 +#define MAX_NAME_SIZE 100 +#define MAX_AUTHOR_SIZE 200 +#define MAX_COLOR_SIZE 7 + +#define max_size(p) MAX_ ## p ## _SIZE + +struct tankdef { + struct sensor sensors[TANK_MAX_SENSORS]; /* Sensor array */ + char program[MAX_PROGRAM_SIZE]; + char name[MAX_NAME_SIZE]; + char author[MAX_AUTHOR_SIZE]; + char color[MAX_COLOR_SIZE]; +}; + +void +print_tank(struct tankdef); + +void +print_sensor(struct sensor); + +#endif /* __TANKDEF_H__ */ diff --git a/tankdir.c b/tankdir.c new file mode 100644 index 0000000..60c12d2 --- /dev/null +++ b/tankdir.c @@ -0,0 +1,189 @@ +#include +#include +#include +#include +#include +#include +#include "tankdef.h" +#include "tankdir.h" + +#define MATCH 0 + +int isSensor(char* string) { + char* s = "sensor"; + size_t len = strlen(s); + return ( strncmp(s, string, len) == MATCH ) && (strlen(string) > len); +} + +int isNotRealDir(struct dirent* file) { + return strcmp(file->d_name,".") == MATCH || + strcmp(file->d_name,"..") == MATCH; +} + +char* readFile(char* file, const int MAX_SIZE) { + char* buffer = NULL; + long length; + FILE* f = fopen(file, "r"); + if(f == NULL) { + perror(file); + exit(1); + } + fseek(f, 0, SEEK_END); + length = ftell(f); + if(length>MAX_SIZE) { + fprintf(stderr, "File %s to large for variable.\n", file); + exit(1); + } + fseek(f, 0, SEEK_SET); + buffer = (char*)malloc(length+1); // +1 for null terminator. + if(buffer == NULL) { + perror(file); + exit(1); + } + fread(buffer, sizeof(char), length, f); + buffer[length] = (char)'\0'; + fclose(f); + return buffer; +} + +void +sensorReadError(char* file) { + fprintf(stderr, "Malformed sensor file: %s\n", file); + exit(1); +} + +struct sensor +parseSensor(char* file) { + struct sensor theSensor; + char* content = readFile(file, 50); + char* token = strtok(content, " "); + if(token == NULL) { + sensorReadError(file); + } + theSensor.angle = atof(token); + token = strtok(NULL, " "); + if(token == NULL) { + sensorReadError(file); + } + theSensor.width = atof(token); + token = strtok(NULL, " "); + if(token == NULL) { + sensorReadError(file); + } + theSensor.range = atoi(token); + token = strtok(NULL, " "); + if(token == NULL) { + sensorReadError(file); + } + theSensor.turret = atoi(token); + free(content); + return theSensor; +} + +void +loadFileContent(char* dest, char* file, const int MAX_SIZE) { + char* content = readFile(file, MAX_SIZE); + strcpy(dest, content); + free(content); +} + +void +initSensor(struct sensor* theSensor) { + theSensor->turret = 0; + theSensor->width = 0.0; + theSensor->range = 0; + theSensor->angle = 0.0; + theSensor->triggered = 0; +} + +void +initTank(struct tankdef* theTank) { + int i; + strcpy(theTank->name, "noname"); + strcpy(theTank->author, "noauthor"); + strcpy(theTank->color, "#808080"); + strcpy(theTank->program, ""); + for(i=0; isensors[i]); + } +} + +struct tankdef +readTankFromDir(char* path) { + DIR* dp; + if( (dp=opendir(path))==NULL ) { + perror(path); + exit(1); + } + + struct tankdef theTank; + initTank(&theTank); + struct dirent* files; + while( (files=readdir(dp)) != NULL ) { + if( isNotRealDir(files) ) { + continue; + } + char file[1000]; + sprintf(file, "%s/%s", path, files->d_name); + if( isSensor(files->d_name) ) { + int sensorId = files->d_name[strlen("sensor")]-'0'; + theTank.sensors[sensorId] = parseSensor(file); + } else if( strcmp(files->d_name, "name") == MATCH ) { + loadFileContent( theTank.name, file, max_size(NAME) ); + } else if( strcmp(files->d_name, "author") == MATCH ) { + loadFileContent( theTank.author, file, max_size(AUTHOR) ); + } else if( strcmp(files->d_name, "program") == MATCH ) { + loadFileContent( theTank.program, file, max_size(PROGRAM) ); + } else if( strcmp(files->d_name, "color") == MATCH ) { + loadFileContent( theTank.color, file, max_size(COLOR) ); + } + } + closedir(dp); + return theTank; +} + +void +writeFile(char* path, char* fileName, char* text) { + char file[100] = ""; + strcat(file, path); + if(file[strlen(file)-1] != '/') { + strcat(file, "/"); + } + strcat(file, fileName); + FILE* pfile = fopen(file, "w"); + if(pfile == NULL) { + char errorString[100]; + sprintf(errorString, "Error opening file %s/%s\n", path, fileName); + perror(errorString); + exit(1); + } + fprintf(pfile, "%s", text); + fclose(pfile); +} + +void +writeSensorFile(char* path, int sensorIndex, struct sensor theSensor) { + char file[100] = ""; + strcat(file, path); + if(file[strlen(file)-1] != '/') { + strcat(file, "/"); + } + char fileName[] = "sensor0"; + fileName[6] = sensorIndex + '0'; + strcat(file, fileName); + FILE* pfile = fopen(file, "w"); + fprintf(pfile, "%.0f %.0f %d %d", theSensor.angle, theSensor.width, theSensor.range, theSensor.turret); + fclose(pfile); +} + +void +writeTankToDir(char* dir, struct tankdef theTank) { + int i = 0; + writeFile(dir, "name", theTank.name); + writeFile(dir, "color", theTank.color); + writeFile(dir, "author", theTank.author); + writeFile(dir, "program", theTank.program); + for(i=0; i +#include +#include +#include +#include "tankdef.h" +#include "tankjson.h" + +json_t* +tankToJSON(struct tankdef theTank); + +char* +writeTankToJSON(struct tankdef theTank) { + json_t* root = tankToJSON(theTank); + char* ret_string = json_dumps(root,0); + json_decref(root); + + return ret_string; +} + +json_t* +tankToJSON(struct tankdef theTank) { + json_t* root = json_object(); + json_t* sensors = json_array(); + int i; + for(i=0; i (size_t)MAX_LEN) ) { + fprintf(stderr, "%s field too large in JSON object.\n", name); + exit(1); + } else { + strcpy(dest, string); + } +} + +struct sensor +sensorFromJSON(json_t* sensor) { + struct sensor theSensor; + json_t* tmp = json_object_get(sensor, "angle"); + theSensor.angle = json_real_value(tmp); + tmp = json_object_get(sensor, "width"); + theSensor.width = json_real_value(tmp); + tmp = json_object_get(sensor, "range"); + theSensor.range = json_integer_value(tmp); + tmp = json_object_get(sensor, "turret"); + theSensor.turret = json_integer_value(tmp); + return theSensor; +} + +struct tankdef +tankFromJSON(json_t* root) { + size_t i; + if( !json_is_object(root) ) { + fprintf(stderr, "Error: root is not an object!\n"); + json_decref(root); + exit(1); + } + + struct tankdef theTank; + loadJSONString(theTank.name, max_size(NAME), root, "name"); + loadJSONString(theTank.author, max_size(AUTHOR), root, "author"); + loadJSONString(theTank.program, max_size(PROGRAM), root, "program"); + loadJSONString(theTank.color, max_size(COLOR), root, "color"); + + json_t* sensors = json_object_get(root, "sensors"); + if(!json_is_array(sensors)) { + fprintf(stderr, "Error: sensors object is not an array!\n"); + exit(1); + } + for(i=0; i (size_t)MAX_TANKS) { + fprintf(stderr,"Error: Too many tanks! Tried to load %d, Max: %d\n", + (int)json_array_size(root), MAX_TANKS); + } + for(i=0; i