/* * pinewood.c * * Pinewood derby timer. * * The basic idea is to measure times of pinewood race cars. * Give both a relative ranking as well as absolute time * good to milliseconds. * * * There are some elaborations that are nice. * Roster of all contestants with relative ranking. * After each heat, generate new list of contestants. * Generate reports (on demand) of the current state of the race. * Load the current state from disk at any time. * Allow test runs which aren't logged. * * The basic idea is that at the start, each car is checked in * and entered into the list of contestants. The first heat is * grouped roughly by age (rank). * * After all cars are checked in, generate the race schedule. * This will have room to hand write results. This schedule is * printed out and posted. * * Check out the hardware by running test races. * * For each race, present the names of the contestants and start * monitoring the interface. Time the entries. Once the race is * complete (all cars finish or a keyboard command indicates that * the race is over), ask for a confirmation that the race is valid. * If not valid, run the race over. * * After all the cars have been run once, generate new race pairings * based upon current time. Start with slow cars and go up to the * faster cars. Print out this new report. Repeat the race. * * Once all heats have been run, generate a last report using the * final race pairings. * * When scheduling tracks, use a different track for each run. * If the number of tracks equals the number of heats, then * every car will run on every track. * * For simplicity, assume that the printer and the track are * operating off of the same port. This means that somebody * is fiddling with an A/B switch or plugging cables in, * but only requires a single port. */ /* * This program was written by Dave Regan for Pack 163 in * Corvallis Oregon. This program is placed in the public domain. * Do what you want with the program. * Dave Regan * regan@ao.com * 20 December 1994 */ /* * Bugs: * ----- * * I saw a random trashing in the edit screen. I don't know why. * * Zero entries in the database is rough on the editor. */ /* * Future directions: * ------------------ * * Have the "process_table" routine do a screen oriented presentation * and allow the cursor keys to do their thing. Right now it is a * trivial menu. * * The help file can be displayed by an internal pager, or use * a better external pager. * * There are a lot of fixed lengthed fields and database sizes and * such. The only reason for this is to make the program easy to * hack together. They probably should be dynamic. * * Search in editor. * * The find_contestant routine needs to handle the case where * NHEATS > NLANES and produce reasonable choices. Right now * it will probably loop. * * Allow certain results to be invalidated. */ /*** *** Include files ***/ #include #include #include #include #include #include #include "pragmas.h" #define MAIN #include "pinewood.h" /*lint -e509 Yes it is a syntatic violation, but required of Borland */ extern unsigned _stklen = 8192; /*lint +e509 */ /*** *** Command tables ***/ struct cmd Main_menu[] = { { "Information", show_information }, { "Edit database", edit_data }, { "Clear all results", clear_results }, { "Run the races", run_heat }, { "Test run", test_run }, { "Print reports", print_standings }, { "Quit", exit_program }, { "Initialize database", delete_database }, { NULL, NULL } }; /*** *** Function definitions ***/ int main(void) { FILE *fp; /* Tell the printer to print from left to right */ fp = xfopen(PRINTER, "a"); xfprintf(fp, "\033&k0W"); xfclose(fp); calib_pmon(); big_modify(); Nusers = 0; read_database(); read_ranks(); for (;;) { process_table(Main_menu); if (Database_dirty) write_database(); } } /* * clear_results * * Clear the results. * Make sure the user knows what they are doing. * Even then, don't trust them. */ void clear_results(void) { char buffer[100]; int user; printf("Do you really want to clear all results? "); fflush(stdout); fgets(buffer, sizeof(buffer), stdin); if (tolower(buffer[0]) == 'y') { save_database(); for (user = 0; user < Nusers; user++) init_db(user, FALSE); } else { printf("No change made\n"); sleep(5); } } /* * delete_database * * Delete the database. */ void delete_database(void) { char buffer[100]; printf("Do you really want to delete the database? "); fflush(stdout); fgets(buffer, sizeof(buffer), stdin); if (tolower(buffer[0]) == 'y') { save_database(); Nusers = 0; Database_dirty = TRUE; } else { printf("No change made\n"); sleep(5); } } /* * exit_program * * The user has requested to quit. * Do so. */ void exit_program(void) { exit(0); } /* * get_track_sensors * * Get the track sensors, and do what is necessary to make them * easily useful. */ unsigned int get_track_sensors(void) { #ifdef KB_INTERFACE return (Kb_interface); #else unsigned int bits; bits = (inportb(IO_PORT + 1) ^ 0xFFFF ^ (0x80)) & (BIT_LANE_1 | BIT_LANE_2 | BIT_LANE_3 | BIT_LANE_4 | BIT_START); return (bits); #endif /* KB_INTERFACE */ } /* * print_standings * * The user calls this up by hand. Let him print any * of the possible forms. */ void print_standings(void) { print_results(); print_roster(); print_race_schedule(); } /* * process_keyboard * * A special routine used for testing to process keyboard * presses as if they were switch changes. * * F1 to F4 for the four lanes, F5 for the start. * These operate as toggles. * * Return TRUE if it was one of the appropriate characters. */ #ifdef KB_INTERFACE bool_t process_keyboard(unsigned int ch) { bool_t retval = TRUE; if (ch == 0) Kb_interface = 0; /* Initialize */ else if (ch == K_F1) Kb_interface ^= BIT_LANE_1; else if (ch == K_F2) Kb_interface ^= BIT_LANE_2; else if (ch == K_F3) Kb_interface ^= BIT_LANE_3; else if (ch == K_F4) Kb_interface ^= BIT_LANE_4; else if (ch == K_F5) Kb_interface ^= BIT_START; else retval = FALSE; return (retval); } #endif /* KB_INTERFACE */ /* * process_table * * Present a menu. Let the user select an appropriate entry * and then execute the desired function. */ void process_table(struct cmd *cmd) { char buffer[100]; int maxent; int num; clrscr(); for (;;) { for (maxent = 0; cmd[maxent].title != NULL; maxent++) printf("%d %s\n", maxent + 1, cmd[maxent].title); printf("Enter item number: "); fflush(stdout); fgets(buffer, sizeof(buffer), stdin); num = atoi(buffer); if (isdigit(buffer[0]) && num <= maxent && num >= 1) { cmd[num - 1].fn(); return; } else printf("Please enter a number between 1 and %d\n", maxent); } } /* * run_race * * Run one race. * * The basic idea is to watch for the start signal. When it starts, * clear the screen. Monitor changes of the completion lines. When * all cars have completed, or a keyboard key is hit, or upon timeout, * the run times are displayed. * * While waiting, have an area of the screen for each track which is * the current state of the completion switches. * * Return FALSE if the race is aborted. */ bool_t run_race(char *labels[NLANES], int rank[NLANES], long int *times, int racenum) { static long int hertz = 0; unsigned int bits; char buffer[30]; int car; unsigned int ch; long int delta; unsigned int end; US_TIME e_time; long int lane[NLANES]; /*lint -esym(771,lane) */ unsigned int last_bits; long int loops; int minor; US_TIME s_time; int xrank[NLANES]; /*lint -esym(771,rank) */ if (rank == NULL) rank = xrank; restart: /* Oh no!!! A goto */ gotoxy(1, 1); // cprintf("Exit with an ESCape key, restart with F9, any other key stops"); cprintf("ESC - Exit, F9 - Reset start, F8 - Print last, F7 term race"); gotoxy(70, 1); cprintf("Hz %ld ", hertz); for (car = 0; car < NLANES; car++) { gotoxy(15, 3 + car * 6 + 2); cputs(labels[car]); } /* Wait for a race to start */ for (car = 0; car < NLANES; car++) lane[car] = 0; process_keyboard(0x0000); gotoxy(1, 2); cprintf("Waiting for race to start "); if (racenum != -1) cprintf(" race %d ", racenum); do { bits = get_track_sensors(); #define PROC_BIT(pos, bit) \ gotoxy(3, pos); \ cputs( ((bits & bit) == 0) ? "." : "X"); PROC_BIT(6, BIT_LANE_1); PROC_BIT(12, BIT_LANE_2); PROC_BIT(18, BIT_LANE_3); PROC_BIT(24, BIT_LANE_4); #undef PROC_BIT if (kbhit()) { ch = getxch(); if (ch == K_ESC) return (FALSE); else if (ch == K_F8) xreprint(); else process_keyboard(ch); } } while ((bits & BIT_START) == 0); /* Wait for the race to end */ process_keyboard(0x0000); get_time(&s_time); gotoxy(1, 2); cprintf("Waiting for race to end "); end = 0; if (labels[0][0] != '\0') end |= BIT_LANE_1; if (labels[1][0] != '\0') end |= BIT_LANE_2; if (labels[2][0] != '\0') end |= BIT_LANE_3; if (labels[3][0] != '\0') end |= BIT_LANE_4; loops = 0; minor = 0; last_bits = 0xFF; delta = 1; do { ++loops; ++minor; bits = get_track_sensors(); #define PROC_BIT(pos, bit, ord) \ gotoxy(3, pos); \ if ((bits & bit) == 0) \ cputs("."); \ else \ { \ cputs("X"); \ end &= ~bit; \ if (lane[ord] == 0) \ lane[ord] = delta; \ } if ((bits != last_bits) || (minor > 250)) { minor = 0; get_time(&e_time); delta = delta_us(&s_time, &e_time); if (delta > MAXTIME * 1000000) end = 0; #ifndef KB_INTERFACE if (kbhit()) { ch = getxch(); if (ch == K_ESC) return (FALSE); else if (ch == K_F9) goto restart; end = 0; } #endif /* KB_INTERFACE */ if (bits != last_bits) { last_bits = bits; PROC_BIT(6, BIT_LANE_1, 0); PROC_BIT(12, BIT_LANE_2, 1); PROC_BIT(18, BIT_LANE_3, 2); PROC_BIT(24, BIT_LANE_4, 3); } } #undef PROC_BIT #ifdef KB_INTERFACE if (kbhit()) { ch = getxch(); if (ch == K_ESC) return (FALSE); else if (ch == K_F9) goto restart; if (!process_keyboard(ch)) end = 0; } #endif /* KB_INTERFACE */ } while (end != 0); /* Display results */ clrscr(); if (delta < 1000) delta = 1000; /* divide by zero protection */ gotoxy(72, 2); hertz = (loops * 1000) / (delta / 1000); for (car = 0; car < NLANES; car++) rank[car] = find_rank(lane, car); for (car = 0; car < NLANES; car++) { gotoxy(15, 3 + car * 6 + 2); cputs(labels[car]); lane[car] = (lane[car] + 500) / 1000; if (times != NULL) times[car] = lane[car]; if (lane[car] == 0) strcpy(buffer, "-- ---"); else { sprintf(buffer, "%2ld.%03ld %d", lane[car] / 1000, lane[car] % 1000, rank[car]); } big_display(30, 3 + car * 6, buffer); if (racenum > 0) { strncpy(buffer, labels[car], 10); buffer[3] = '\0'; big_display(5, 3 + car * 6, buffer); } } return (TRUE); } /* * show_information * * Show an external file as a help file. * Just use the standard "MORE" program to present the help file. */ void show_information(void) { char buffer[100]; sprintf(buffer, "more <%s", HELPFILE); (void) system(buffer); printf("\n\nPress ENTER when done\n"); fflush(stdout); fgets(buffer, sizeof(buffer), stdin); } /* * test_run * * Allow running unscored tests. * * Continue running tests until the user "ESCapes". */ void test_run(void) { int car; char *labels[NLANES]; char lbl[NLANES][20]; for (car = 0; car < NLANES; car++) { labels[car] = lbl[car]; sprintf(labels[car], "LANE %d", car + 1); } clrscr(); while (run_race(labels, NULL, NULL, -1)) ; }