+static uint16_t *find_valid_point(struct cwiid_state *state)
+{
+ unsigned int i;
+
+ for (i = 0; i < CWIID_IR_SRC_COUNT; i++) {
+ if (state->ir_src[i].valid)
+ return state->ir_src[i].pos;
+ }
+ return NULL;
+}
+
+static void calibrate(SDL_Surface *screen,
+ const char *filename,
+ cwiid_wiimote_t *wiimote,
+ struct coord *calib, unsigned x, unsigned y)
+{
+ SDL_Surface *img = IMG_Load(filename);
+ uint16_t *pos;
+ struct cwiid_state state;
+ int last_x = MAX_X, last_y = MAX_Y, count = 0;
+ SDL_Rect rect = { .x = screen->w/2 - img->w/2,
+ .y = screen->h/2 - img->h/2 };
+
+ SDL_BlitSurface(img, NULL, screen, &rect);
+ SDL_UpdateRect(screen, 0, 0, 0, 0);
+ SDL_FreeSurface(img);
+
+ /* Must see it for a full half second. */
+ while (count < 20) {
+ SDL_Event event;
+
+ if (cwiid_get_state(wiimote, &state))
+ errx(1, "No wii state");
+
+ pos = find_valid_point(&state);
+ if (!pos) {
+ if (count)
+ count--;
+ } else {
+ /* Allow some jitter */
+ if (abs(pos[0]-last_x) < 3 && abs(pos[1]-last_y) < 3)
+ count++;
+ last_x = pos[0];
+ last_y = pos[1];
+ }
+
+ SDL_Delay(25);
+ if (SDL_PollEvent(&event)
+ && (event.type == SDL_QUIT
+ || (event.type == SDL_KEYDOWN
+ && event.key.keysym.sym == SDLK_ESCAPE))) {
+ SDL_Quit();
+ exit(0);
+ }
+ }
+
+ calib->x = last_x;
+ calib->y = last_y;
+ SDL_Delay(500);
+ printf("Calibration point: %u,%u\n", last_x, last_y);
+}
+
+static bool map_coord(unsigned int x, unsigned int y,
+ const struct coord calib[],
+ struct coord *res)
+{
+ struct coord line_start, line_end;
+ struct coord pos = { x, y }, intersect;
+ double d1, d2;
+
+ /* Calibration layout:
+ *
+ * 0 1
+ *
+ * 3 2
+ *
+ * We figure out the distance to each side, and then map using:
+ *
+ * x = d1 / (d1 + d2) * (rawend - rawstart) + rawstart
+ */
+ line_start.x = 0;
+ line_end.x = MAX_X-1;
+ line_start.y = line_end.y = y;
+
+ if (!lines_intersect(&calib[0], &calib[3], &line_start, &line_end,
+ &intersect))
+ return false;
+ d1 = dist(&pos, &intersect);
+ if (!lines_intersect(&calib[1], &calib[2], &line_start, &line_end,
+ &intersect))
+ return false;
+ d2 = dist(&pos, &intersect);
+ res->x = d1 / (d1 + d2) * MAX_X;
+
+ line_start.y = 0;
+ line_end.y = MAX_Y-1;
+ line_start.x = line_end.x = x;
+
+ if (!lines_intersect(&calib[0], &calib[1], &line_start, &line_end,
+ &intersect))
+ return false;
+ d1 = dist(&pos, &intersect);
+ if (!lines_intersect(&calib[3], &calib[2], &line_start, &line_end,
+ &intersect))
+ return false;
+ d2 = dist(&pos, &intersect);
+ res->y = d1 / (d1 + d2) * MAX_Y;
+
+ return true;
+}
+
+static void add_update(const struct object *obj)
+{
+ if (!needs_update) {
+ start_update = obj->start;
+ end_update = obj->end;
+ needs_update = true;
+ return;
+ }
+ if (obj->start.x < start_update.x)
+ start_update.x = obj->start.x;
+ if (obj->start.y < start_update.y)
+ start_update.y = obj->start.y;
+ if (obj->end.x > end_update.x)
+ end_update.x = obj->end.x;
+ if (obj->end.y > end_update.y)
+ end_update.y = obj->end.y;
+}
+
+static void destroy_object(struct object *object)
+{
+ list_del(&object->list);
+ add_update(object);
+}
+
+static void add_object(struct list_head *list,
+ struct object *obj,
+ unsigned int startx, unsigned int starty,
+ unsigned int width, unsigned int height,
+ void (*redraw)(SDL_Surface *s, struct object *me))
+{
+ list_add_tail(list, &obj->list);
+ obj->start.x = startx;
+ obj->start.y = starty;
+ obj->end.x = startx + width;
+ obj->end.y = starty + height;
+ obj->redraw = redraw;
+ add_update(obj);
+}
+
+static void redraw_line(SDL_Surface *s, struct object *me)
+{
+ struct line_segment *l = container_of(me, struct line_segment, object);
+
+ thick_line(s, l, 0);
+}
+
+static int destroy_line(struct line_segment *l)
+{
+ list_del(&l->list);
+ destroy_object(&l->object);
+ return 0;
+}
+
+static void line_object_setup(struct list_head *lines,
+ struct list_head *objects,
+ struct line_segment *l)
+{
+ unsigned int tmp, startx, endx, starty, endy;
+
+ list_add_tail(lines, &l->list);
+
+ startx = l->start.x;
+ starty = l->start.y;
+ endx = l->end.x;
+ endy = l->end.y;
+
+ /* Find bounding box */
+ if (startx > endx) {
+ tmp = endx;
+ endx = startx;
+ startx = tmp;
+ }
+ if (starty > endy) {
+ tmp = endy;
+ endy = starty;
+ starty = tmp;
+ }
+ /* Padding for thick lines (beware underflow) */
+ if (startx > 0)
+ startx--;
+ if (starty > 0)
+ starty--;
+ endx++;
+ endy++;
+
+ add_object(objects, &l->object, startx, starty,
+ endx - startx, endy - starty, redraw_line);
+}
+
+static void add_line(struct list_head *lines,
+ unsigned int startx, unsigned int starty,
+ unsigned int endx, unsigned int endy,
+ const struct coord calib[])
+{
+ struct line_segment *l = talloc(NULL, struct line_segment);
+ struct timeval now;
+
+ if (!map_coord(startx, starty, calib, &l->start)
+ || !map_coord(endx, endy, calib, &l->end)) {
+ talloc_free(l);
+ return;
+ }
+
+ l->ignore = false;
+ l->score = NULL;
+ gettimeofday(&now, NULL);
+ l->expires = now;
+ l->expires.tv_sec += EXPIRY_SECS;
+
+ line_object_setup(lines, &objects, l);
+ talloc_set_destructor(l, destroy_line);
+}
+
+static void redraw_ball(SDL_Surface *s, struct object *me)
+{
+ struct ball *b = container_of(me, struct ball, object);
+ SDL_Rect rect = { .x = b->pos.x - b->image->w/2,
+ .y = b->pos.y - b->image->h/2 };
+
+ SDL_BlitSurface(b->image, NULL, s, &rect);
+}
+
+static void redraw_score(SDL_Surface *s, struct object *me)
+{
+ struct score *score = container_of(me, struct score, object);
+ SDL_Rect rect = { .x = score->object.start.x,
+ .y = score->object.start.y };
+
+ SDL_BlitSurface(score->image, NULL, s, &rect);
+}
+
+static SDL_Surface *score(struct score *s)
+{
+ char filename[40];
+
+ s->value++;
+ SDL_FreeSurface(s->image);
+ sprintf(filename, "images/%u.png", s->value);
+ s->image = IMG_Load(filename);
+ /* No more images? You won! */
+ if (!s->image)
+ return s->won;
+
+ add_update(&s->object);
+ return NULL;
+}
+
+/* Due to rounding, we exaggerate bounding box by 1 here. */
+static void do_updates(struct list_head *objects, SDL_Surface *screen)
+{
+ struct object *i;
+ unsigned int y, startx, starty, endx, endy;
+
+ /* Clip */
+ if (start_update.x <= 0)
+ startx = 0;
+ else
+ startx = start_update.x - 1;
+ if (start_update.y <= 0)
+ starty = 0;
+ else
+ starty = start_update.y - 1;
+
+ endx = end_update.x+1;
+ endy = end_update.y+1;
+
+ if (endx > screen->w)
+ endx = screen->w;
+ if (endy > screen->h)
+ endy = screen->h;
+
+ SDL_LockSurface(screen);
+ /* First we clear the area to white. */
+ for (y = starty; y < endy; y++) {
+ memset(screen->pixels + y * screen->pitch
+ + screen->format->BytesPerPixel * startx,
+ 0xFF,
+ (endx - startx) * screen->format->BytesPerPixel);
+ }
+ SDL_UnlockSurface(screen);
+
+ /* Now redraw everything which overlaps it */
+ list_for_each(objects, i, list) {
+ if (i->end.x < startx)
+ continue;
+ if (i->end.y < starty)
+ continue;
+ if (i->start.x > endx)
+ continue;
+ if (i->start.y > endy)
+ continue;
+ i->redraw(screen, i);
+ }
+
+ SDL_UpdateRect(screen, startx, starty, endx - startx, endy - starty);
+
+ /* Reset bounding box */
+ needs_update = false;
+}
+
+static bool same_side(const struct ball *ball, unsigned int x)
+{
+ return (ball->pos.x >= MAX_X/2) == (x >= MAX_X/2);
+}
+