/* * ident input: identify the game state described in a given image */ static int cmd_ident(int argc, char *argv[]) { img_t *image; kv_screen_t info; if (argc < 1) return (EXIT_USAGE); if (kv_init(dirname((char *)kv_arg0)) != 0) { warnx("failed to initialize masks"); return (EXIT_FAILURE); } image = img_read(argv[0]); if (image == NULL) { warnx("failed to read %s", argv[0]); return (EXIT_FAILURE); } kv_ident(image, &info, KV_IDENT_ALL); kv_screen_json(argv[0], 0, 0, &info, NULL, stdout); return (EXIT_SUCCESS); }
static int check_start_frame(video_frame_t *vp, void *rawarg) { kv_screen_t ks; int *lastp = rawarg; if (*lastp > 0 && vp->vf_frametime - *lastp < 3000) return (0); kv_ident(&vp->vf_image, &ks, KV_IDENT_START); if (ks.ks_events & KVE_RACE_START) { *lastp = vp->vf_frametime; (void) printf("%d\n", (int) (*lastp / 1000)); (void) fflush(stdout); } return (0); }
void kv_vidctx_frame(const char *framename, int i, int timems, img_t *image, kv_vidctx_t *kvp) { int j; kv_screen_t *ksp, *pksp, *raceksp; kv_screen_t ipks; boolean_t itemsdiff, invalid; ksp = &kvp->kv_frame; pksp = &kvp->kv_pframe; raceksp = &kvp->kv_raceframe; /* * As we process video frames, we go through a simple state machine: * * (1) We start out waiting for the first RACE_START frame. We're in * this state while last_start == -1. When we see RACE_START, we * set last_frame to this frame number. * * (2) We ignore the first KV_MIN_RACE_FRAMES after a RACE_START frame * to avoid catching what may look like multiple start frames right * next to each other. This also avoids pointless changes in player * position in the first few seconds. * * (3) While the race is ongoing, we track player positions until we see * a RACE_DONE frame (indicating the race was completed) or another * RACE_START frame (indicating that the race was aborted and * another race was started). If we see a normal RACE_DONE frame, * we go back to the first state, waiting for another RACE_START * frame. */ if (kvp->kv_last_start != -1 && i - kvp->kv_last_start < KV_MIN_RACE_FRAMES) /* Skip the first frames after a start. See above. */ return; bcopy(ksp, &ipks, sizeof (ipks)); if (kv_debug > 0) (void) printf("%s\n", framename); /* XXX why would this include characters? */ kv_ident(image, ksp, KV_IDENT_NOTRACK); if (ksp->ks_events & KVE_RACE_START) { if (kvp->kv_last_start != -1) { (void) fprintf(stderr, "%s (time %dm:%02ds): " "new race begun (previous one aborted)", framename, (int)((double)timems / MILLISEC) / 60, timems % 60); } kv_ident(image, ksp, KV_IDENT_ALL); bcopy(ksp, &kvp->kv_startbuffer[i % KV_STARTFRAMES], sizeof (ksp)); kv_vidctx_chars(kvp, ksp, i); kvp->kv_last_start = i; *pksp = *ksp; *raceksp = *ksp; kv_vidctx_frame_emit(kvp, framename, i, timems, image, ksp, NULL, stdout); bzero(&kvp->kv_startbuffer[0], sizeof (kvp->kv_startbuffer)); return; } /* * Skip frames if we're not currently inside a race. */ if (kvp->kv_last_start == -1) { bcopy(ksp, &kvp->kv_startbuffer[i % KV_STARTFRAMES], sizeof (*ksp)); return; } /* * kv_screen_invalid() ignores screens that have a different number of * players than the initial race screen. This is rare, since on most * tracks we use the rank numerals in each square to reliably report the * number of players. On such tracks, a wrong number of players * indicates a numeral in transition, in which case the frame can just * be ignored. However, on Yoshi Valley, we only have numerals for * players who have finished the race, so the number of players can * easily be wrong until the race is over (even after some players have * finished). In order to get the correct race times, we must not * ignore such frames. We fix this by simply bumping up the number of * players on that track. That's sufficient, since the later player * fields will be initialized to the "unknown" values. Of course, * consumers need to be able to handle them. */ if (ksp->ks_nplayers > 1 && ksp->ks_nplayers < raceksp->ks_nplayers && raceksp->ks_track[0] == 'y') ksp->ks_nplayers = raceksp->ks_nplayers; /* * Update item box state. This always operates on the immediately * previous frame (saved at the start of this function), rather than the * representative frame for the current game state, because there are * item changes with each frame that don't represent logically different * game states. That's also why we do this every frame, not just those * that we save. */ for (j = 0; j < ksp->ks_nplayers; j++) kv_vidctx_items(ksp, &ipks, j); itemsdiff = kv_screen_compare_items(ksp, pksp, kvp->kv_flags) != 0; invalid = kv_screen_invalid(ksp, pksp, raceksp) != 0; /* * Normally we would omit invalid frames, but item detection is * sensitive to dropping individual frames, and we want to emit state * changes as soon as they happen, even if the rest of the frame would * have been invalid. But we still don't want to emit an invalid frame, * so for this very specific case, we fake up the state based on the * last one we saw, but ignoring any events (which are generally * one-frame-only). */ if (itemsdiff && invalid) { ksp->ks_events = 0; ksp->ks_nplayers = pksp->ks_nplayers; for (j = 0; j < ksp->ks_nplayers; j++) { ksp->ks_players[j].kp_place = pksp->ks_players[j].kp_place; ksp->ks_players[j].kp_lapnum = pksp->ks_players[j].kp_lapnum; } invalid = B_FALSE; } if (invalid) return; if (itemsdiff == 0 && kv_screen_compare(ksp, pksp, raceksp, kvp->kv_flags) == 0) return; /* * In Yoshi Valley (and only this rare case), we must explicitly fill in * the last place finisher, since we usually won't have detected it by * itself. */ if (raceksp->ks_track[0] == 'y' && ksp->ks_events & KVE_RACE_DONE) { for (j = 0; j < ksp->ks_nplayers; j++) { if (ksp->ks_players[j].kp_place == 0) { ksp->ks_players[j].kp_place = ksp->ks_nplayers; ksp->ks_players[j].kp_placescore = 0.0001; break; } } } kv_vidctx_frame_emit(kvp, framename, i, timems, image, ksp, raceksp, stdout); *pksp = *ksp; if (ksp->ks_events & KVE_RACE_DONE) kvp->kv_last_start = -1; }