]> git.ozlabs.org Git - ponghero.git/blobdiff - prpong.c
Calibration images: now usable!
[ponghero.git] / prpong.c
index c62d0fcced8827bfc3881fe0e0ee2b03a17257ac..da584f99ef92560d229ec2d462da50dbcdc89f9e 100644 (file)
--- a/prpong.c
+++ b/prpong.c
 #include <stdlib.h>
 #include <stdio.h>
 #include <stdbool.h>
+#include <sys/time.h>
+#include <time.h>
+#include <limits.h>
 #include "stdrusty.h"
+#include "list/list.h"
+#include "talloc/talloc.h"
+#include "container_of/container_of.h"
 #include <SDL/SDL.h>
+#include <SDL/SDL_image.h>
+#include <cwiid.h>
+#include <assert.h>
+#include <unistd.h>
 
-#define MAX_X 512
-#define MAX_Y 512
+#define EXPIRY_SECS 3
 
-/* Obtained from http://tog.acm.org/GraphicsGems/gemsiii/insectc.c */
-/* Faster Line Segment Intersection   */
-/* Franklin Antonio                   */
+#define MAX_X CWIID_IR_X_MAX
+#define MAX_Y CWIID_IR_Y_MAX
 
-/* return values */
-#define DONT_INTERSECT 0
-#define DO_INTERSECT   1
-#define PARALLEL       2
+#define LEFT_PLAYER 1
+#define RIGHT_PLAYER 2
 
-/* The SAME_SIGNS macro assumes arithmetic where the exclusive-or    */
-/* operation will work on sign bits.  This works for twos-complement,*/
-/* and most other machine arithmetic.                                */
-#define SAME_SIGNS( a, b )                                             \
-       (((long) ((unsigned long) a ^ (unsigned long) b)) >= 0 )
+static LIST_HEAD(objects);
 
-
-/* The use of some short working variables allows this code to run   */
-/* faster on 16-bit computers, but is not essential.  It should not  */
-/* affect operation on 32-bit computers.  The short working variables*/
-/* to not restrict the range of valid input values, as these were    */
-/* constrained in any case, due to algorithm restrictions.           */
-static int lines_intersect(long x1, long y1, long x2, long y2, long x3, long y3,
-                          long x4,long y4, long *x, long *y) 
+struct coord
 {
+       double x;
+       double y;
+};
 
-       long Ax,Bx,Cx,Ay,By,Cy,d,e,f,num,offset;
-       short x1lo,x1hi,y1lo,y1hi;
+/* Obtained from http://tog.acm.org/GraphicsGems/gemsiii/insectc.c */
+/* Faster Line Segment Intersection   */
+/* Franklin Antonio                   */
+static bool lines_intersect(const struct coord *start1,
+                           const struct coord *start2,
+                           const struct coord *start3,
+                           const struct coord *start4,
+                           struct coord *intersect)
+{
+       double Ax,Bx,Cx,Ay,By,Cy,d,e,f,num;
+       struct coord lo, hi;
 
-       Ax = x2-x1;
-       Bx = x3-x4;
+       Ax = start2->x - start1->x;
+       Bx = start3->x - start4->x;
 
-       if(Ax<0) {                                              /* X bound box test*/
-               x1lo=(short)x2; x1hi=(short)x1;
+       if(Ax<0) {                                      /* X bound box test*/
+               lo.x = start2->x; hi.x = start1->x;
        } else {
-               x1hi=(short)x2; x1lo=(short)x1;
+               hi.x = start2->x; lo.x = start1->x;
        }
        if(Bx>0) {
-               if(x1hi < (short)x4 || (short)x3 < x1lo) return DONT_INTERSECT;
+               if(hi.x < start4->x || start3->x < lo.x) return false;
        } else {
-               if(x1hi < (short)x3 || (short)x4 < x1lo) return DONT_INTERSECT;
+               if(hi.x < start3->x || start4->x < lo.x) return false;
        }
 
-       Ay = y2-y1;
-       By = y3-y4;
+       Ay = start2->y - start1->y;
+       By = start3->y - start4->y;
 
-       if(Ay<0) {                                              /* Y bound box test*/
-               y1lo=(short)y2; y1hi=(short)y1;
+       if(Ay<0) {                                      /* Y bound box test*/
+               lo.y = start2->y; hi.y = start1->y;
        } else {
-               y1hi=(short)y2; y1lo=(short)y1;
+               hi.y = start2->y; lo.y = start1->y;
        }
        if(By>0) {
-               if(y1hi < (short)y4 || (short)y3 < y1lo) return DONT_INTERSECT;
+               if(hi.y < start4->y || start3->y < lo.y) return false;
        } else {
-               if(y1hi < (short)y3 || (short)y4 < y1lo) return DONT_INTERSECT;
+               if(hi.y < start3->y || start4->y < lo.y) return false;
        }
 
 
-       Cx = x1-x3;
-       Cy = y1-y3;
-       d = By*Cx - Bx*Cy;                                      /* alpha numerator*/
-       f = Ay*Bx - Ax*By;                                      /* both denominator*/
-       if(f>0) {                                               /* alpha tests*/
-               if(d<0 || d>f) return DONT_INTERSECT;
+       Cx = start1->x - start3->x;
+       Cy = start1->y - start3->y;
+       d = By*Cx - Bx*Cy;                              /* alpha numerator*/
+       f = Ay*Bx - Ax*By;                              /* both denominator*/
+       if (f>0) {                                      /* alpha tests*/
+               if (d < 0 || d > f) return false;
        } else {
-               if(d>0 || d<f) return DONT_INTERSECT;
+               if (d > 0 || d < f) return false;
        }
 
-       e = Ax*Cy - Ay*Cx;                                      /* beta numerator*/
-       if(f>0) {                                               /* beta tests*/
-               if(e<0 || e>f) return DONT_INTERSECT;
+       e = Ax*Cy - Ay*Cx;                              /* beta numerator*/
+       if (f > 0) {                                    /* beta tests*/
+               if (e < 0 || e > f) return false;
        } else {
-               if(e>0 || e<f) return DONT_INTERSECT;
+               if (e > 0 || e < f) return false;
        }
 
 /*compute intersection coordinates*/
+       /* Don't worry about tiny values of f (< 1/2 pixel across screen) */
+       if (fabs(f) < 1.0 / (2.0 * MAX_X * MAX_Y))
+               return false;
 
-       if(f==0) return PARALLEL;
-       num = d*Ax;                                             /* numerator */
-       offset = SAME_SIGNS(num,f) ? f/2 : -f/2;                /* round direction*/
-       *x = x1 + (num+offset) / f;                             /* intersection x */
+       num = d*Ax;                                     /* numerator */
+       intersect->x = start1->x + num / f;             /* intersection x */
 
        num = d*Ay;
-       offset = SAME_SIGNS(num,f) ? f/2 : -f/2;
-       *y = y1 + (num+offset) / f;                             /* intersection y */
+       intersect->y = start1->y + num / f;             /* intersection y */
 
-       return DO_INTERSECT;
+       return true;
 }
-       
+
 // A set of very useful macros that you will find in most
 // code that I write whether I use them in a program or
 // not.
@@ -478,28 +485,57 @@ static void line(SDL_Surface *s,
   }
 }
 
-#if 1
-struct coord
+/* Bounding box to be updated. */
+static bool needs_update;
+static struct coord start_update, end_update;
+
+struct object
 {
-       long x;
-       long y;
+       /* List of all objects. */
+       struct list_node list;
+       /* Bounding box. */
+       struct coord start, end;
+
+       void (*redraw)(SDL_Surface *s, struct object *me);
 };
 
 struct ball
 {
+       struct object object;
+
        struct coord pos;
        struct coord move;
+       SDL_Surface *image;
 };
 
 struct line_segment
 {
+       struct object object;
+
        struct coord start, end;
+       struct list_node list;
+       bool ignore;
+       struct timeval expires;
+       /* Does someone score if they hit this line ? */
+       struct score *score;
 };
 
-static void thick_line(SDL_Surface *s, const struct line_segment *l)
+struct score
+{
+       struct object object;
+
+       SDL_Surface *image;
+       unsigned int value;
+       SDL_Surface *won;
+};
+
+static void thick_line(SDL_Surface *s, const struct line_segment *l,
+                      unsigned color)
 {
        struct line_segment adj;
-       int x, y;
+       int x, y, minx = MAX_X, miny = MAX_Y, maxx = 0, maxy = 0;
+
+       SDL_LockSurface(s);
 
        /* Cheap hack */
        for (x = -1; x < 2; x++) {
@@ -509,24 +545,49 @@ static void thick_line(SDL_Surface *s, const struct line_segment *l)
                        adj.start.y = l->start.y + y;
                        adj.end.y = l->end.y + y;
 
+                       if (adj.start.x < minx)
+                               minx = adj.start.x;
+                       if (adj.start.x > maxx)
+                               maxx = adj.start.x;
+                       if (adj.start.y < miny)
+                               miny = adj.start.y;
+                       if (adj.start.y > maxy)
+                               maxy = adj.start.y;
+                       if (adj.end.x < minx)
+                               minx = adj.end.x;
+                       if (adj.end.x > maxx)
+                               maxx = adj.end.x;
+                       if (adj.end.y < miny)
+                               miny = adj.end.y;
+                       if (adj.end.y > maxy)
+                               maxy = adj.end.y;
+
                        line(s, adj.start.x, adj.start.y, adj.end.x, adj.end.y,
-                            0);
+                            color);
                }
        }
+
+       if (minx < 0)
+               minx = 0;
+       if (miny < 0)
+               miny = 0;
+       if (maxx >= MAX_X)
+               maxx = MAX_X-1;
+       if (maxy >= MAX_Y)
+               maxy = MAX_Y-1;
+       SDL_UpdateRect(s, minx, miny, maxx-minx, maxy-miny);
+       SDL_UnlockSurface(s);
 }
 
 static bool intersect(const struct coord *c1, const struct coord *c2,
                      const struct line_segment *seg, struct coord *intersect)
 {
-       return lines_intersect(c1->x, c1->y, c2->x, c2->y,
-                              seg->start.x, seg->start.y,
-                              seg->end.x, seg->end.y,
-                              &intersect->x, &intersect->y) == DO_INTERSECT;
+       return lines_intersect(c1, c2, &seg->start, &seg->end, intersect);
 }
 
 static double dist(const struct coord *c1, const struct coord *c2)
 {
-       long x = (c1->x - c2->x), y = (c1->y - c2->y);
+       double x = (c1->x - c2->x), y = (c1->y - c2->y);
        return sqrt(x * x + y * y);
 }
 
@@ -536,23 +597,25 @@ static unsigned rad_to_deg(double rad)
 }
 
 static void bounce(struct ball *ball, const struct line_segment *line,
-                  struct coord *remainder)
+                  struct coord *move, double increase_speed)
 {
        double len, speed, lang, iang, oang;
        struct coord isect, new;
 
-       new.x = ball->pos.x + ball->move.x;
-       new.y = ball->pos.y + ball->move.y;
+       new.x = ball->pos.x + move->x;
+       new.y = ball->pos.y + move->y;
 
        /* How far were we supposed to move ball? */
-       len = sqrt(remainder->x * remainder->x + remainder->y * remainder->y);
+       len = sqrt(move->x * move->x + move->y * move->y);
 
        /* How far is it to intersection? */
-       intersect(&ball->pos, &new, line, &isect);
+       if (!intersect(&ball->pos, &new, line, &isect))
+               errx(1, "No intersection any more?\n");;
        len -= dist(&ball->pos, &isect);
 
        /* Move ball to intersection. */
        ball->pos = isect;
+       printf("ball is now at %f,%f\n", ball->pos.x, ball->pos.y);
 
        /* Outgoing angle = 2 * line angle - incident angle. */
        lang = atan2(line->end.x - line->start.x, line->end.y - line->start.y);
@@ -563,22 +626,23 @@ static void bounce(struct ball *ball, const struct line_segment *line,
               rad_to_deg(iang),
               rad_to_deg(oang));
 
-       /* Set new direction for ball, at same speed */
+       /* Set new direction for ball, at slightly increased speed */
        speed = sqrt(ball->move.x * ball->move.x + ball->move.y * ball->move.y);
-       ball->move.x = round(sin(oang) * speed);
-       ball->move.y = round(cos(oang) * speed);
-
-       /* Set remainder. */
-       remainder->x = round(sin(oang) * len);
-       remainder->y = round(cos(oang) * len);
-       printf("len = %f, remainder = %li,%li\n", len, remainder->x, remainder->y);
+       speed += increase_speed;
+       ball->move.x = sin(oang) * speed;
+       ball->move.y = cos(oang) * speed;
+
+       /* Set move. */
+       move->x = sin(oang) * len;
+       move->y = cos(oang) * len;
+       printf("len = %f, move = %f,%f\n", len, move->x, move->y);
 }
 
 static struct line_segment border[] = {
-       { { 0, 0, }, { MAX_X-1, 0 } },
-       { { MAX_X-1, 0, }, { MAX_X-1, MAX_Y-1 } },
-       { { MAX_X-1, MAX_Y-1, }, { 0, MAX_Y-1 } },
-       { { 0, MAX_Y-1, }, { 0, 0 } },
+       { .start = { 0, 0, }, .end = { MAX_X-1, 0 } },
+       { .start = { MAX_X-1, 0, }, .end = { MAX_X-1, MAX_Y-1 } },
+       { .start = { MAX_X-1, MAX_Y-1, }, .end = { 0, MAX_Y-1 } },
+       { .start = { 0, MAX_Y-1, }, .end = { 0, 0 } },
 };
 
 static inline float deg_to_rad(float degrees)
@@ -617,81 +681,484 @@ static SDL_Surface *ball_surface(SDL_Surface *screen)
        return ball;
 }
 
+static void clear_ignore(struct list_head *lines)
+{
+       struct line_segment *i;
+       printf("Unignoring...\n");
+       list_for_each(lines, i, list)
+               i->ignore = false;
+}
+
+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);
+}
+
 int main(int argc, char *argv[])
 {
        struct ball ball;
-       struct line_segment lines[ARRAY_SIZE(border) + 1];
-       static int isect_count = 0;
-       SDL_Surface *screen, *ballsur, *savesur = NULL;
+       SDL_Surface *screen;
        Uint8  video_bpp;
        Uint32 videoflags;
-       int i;
-       SDL_Event event;
-       struct line_segment *last_l = NULL;
-       SDL_Rect rect;
-
-       if (argc != 9)
-               errx(1, "Usage: %s ballx bally ballangle ballspeed linestartx linestarty lineendx linendy\n"
-                    "   Where ballangle is 0 for north, 90 for east, etc\n",
-                       argv[0]);
+       unsigned int i, time_since_last_ir = INT_MAX;
+       cwiid_wiimote_t *wiimote;
+       struct cwiid_state state;
+       struct coord calib[4];
+       bdaddr_t addr = *BDADDR_ANY;
+       bool mouse = false, needs_calibration = true, drawing = false;
+       LIST_HEAD(lines);
+       struct cwiid_ir_src *ir, last_ir = { .valid = 0 };
+       struct score left, right;
 
-       ball.pos.x = atol(argv[1]);
-       ball.pos.y = atol(argv[2]);
-       ball.move.x = roundf(sin(deg_to_rad(atof(argv[3]))) * atol(argv[4]));
-       ball.move.y = roundf(cos(deg_to_rad(atof(argv[3]))) * atol(argv[4]));
-       printf("ball move = %li,%li\n", ball.move.x, ball.move.y);
+       videoflags = SDL_SWSURFACE;
 
-       memcpy(lines, border, sizeof(border));
-       lines[ARRAY_SIZE(border)].start.x = atof(argv[5]);
-       lines[ARRAY_SIZE(border)].start.y = atof(argv[6]);
-       lines[ARRAY_SIZE(border)].end.x = atof(argv[7]);
-       lines[ARRAY_SIZE(border)].end.y = atof(argv[8]);
+       if (argv[1] && streq(argv[1], "--fullscreen")) {
+               videoflags |= SDL_FULLSCREEN;
+               argc--;
+               argv++;
+       }
+
+       if (argv[1] && strstarts(argv[1], "--calib=")) {
+               needs_calibration = false;
+               if (sscanf(argv[1], "--calib=%lf,%lf,%lf,%lf,%lf,%lf,%lf,%lf",
+                          &calib[0].x, &calib[0].y,
+                          &calib[1].x, &calib[1].y,
+                          &calib[2].x, &calib[2].y,
+                          &calib[3].x, &calib[3].y) != 8)
+                       errx(1, "Not enough calibration points");
+               argc--;
+               argv++;
+       }
+
+       if (argv[1] && streq(argv[1], "--mouse")) {
+               mouse = true;
+               needs_calibration = false;
+               calib[0].x = calib[0].y = 0;
+               calib[1].x = MAX_X - 1;
+               calib[1].y = 0;
+               calib[2].x = MAX_X - 1;
+               calib[2].y = MAX_Y - 1;
+               calib[3].x = 0;
+               calib[3].y = MAX_Y - 1;
+               argc--;
+               argv++;
+       }
+
+       if (argc != 2)
+               errx(1, "Usage: %s [--fullscreen] [--calib=x,y,x,y,x,y,x,y] [--mouse] speed\n",
+                       argv[0]);
 
        /* Initialize SDL */
-       if ( SDL_Init(SDL_INIT_VIDEO) < 0 ) {
+       if (SDL_Init(SDL_INIT_VIDEO) < 0) {
                fprintf(stderr, "Couldn't initialize SDL: %s\n",SDL_GetError());
                return(1);
        }
 
        video_bpp = 0;
-       videoflags = SDL_SWSURFACE;
 
-       /* Set 640x480 video mode */
-       if ( (screen=SDL_SetVideoMode(MAX_X,MAX_Y,video_bpp,videoflags)) == NULL ) {
+       if (!mouse) {
+               printf("Put Wiimote in discoverable mode (press 1+2)\n");
+               wiimote = cwiid_open(&addr, 0);
+               if (!wiimote)
+                       errx(1, "Can't find the Wiimote");
+
+               if (cwiid_set_rpt_mode(wiimote, CWIID_RPT_IR))
+                       errx(1, "Can't set IR repeat mode");
+       }
+
+       if ((screen=SDL_SetVideoMode(MAX_X,MAX_Y,video_bpp,videoflags)) == NULL) {
                errx(1, "Couldn't set %dx%dx%d video mode: %s",
                     MAX_X, MAX_Y,video_bpp, SDL_GetError());
        }
 
        /* Set the surface pixels and refresh! */
-       if ( SDL_LockSurface(screen) < 0 ) {
+       if (SDL_LockSurface(screen) < 0) {
                errx(1, "Couldn't lock the display surface: %s",
                     SDL_GetError());
        }
        /* White. */
        memset(screen->pixels, 0xFF, screen->pitch * screen->h);
 
-       /* Draw lines. */
-       for (i = 0; i < ARRAY_SIZE(lines); i++)
-               thick_line(screen, &lines[i]);
-
        SDL_UnlockSurface(screen);
        SDL_UpdateRect(screen, 0, 0, 0, 0);
 
-       ballsur = ball_surface(screen);
+       if (needs_calibration) {
+               /* Calibration */
+               calibrate(screen, "images/top-left.png",
+                         wiimote, &calib[0], 0, 0);
+               calibrate(screen, "images/top-right.png",
+                         wiimote, &calib[1], MAX_X - 1, 0);
+               calibrate(screen, "images/bottom-right.png",
+                         wiimote, &calib[2], MAX_X - 1, MAX_Y - 1);
+               calibrate(screen, "images/bottom-left.png",
+                         wiimote, &calib[3], 0, MAX_Y - 1);
+       }
+
+       /* Create borders, put them in list. */
+       for (i = 0; i < ARRAY_SIZE(border); i++) {
+               border[i].ignore = false;
+               border[i].expires.tv_sec = LONG_MAX;
+               border[i].expires.tv_usec = 0;
+               line_object_setup(&lines, &objects, &border[i]);
+       }
 
-       do {
+       /* Set up scores. */
+       left.value = right.value = 0;
+       left.image = IMG_Load("images/0.png");
+       left.won = IMG_Load("images/left-won.png");
+       right.image = IMG_Load("images/0.png");
+       right.won = IMG_Load("images/right-won.png");
+       if (!left.image || !right.image || !left.won || !right.won)
+               err(1, "Opening score images");
+
+       add_object(&objects, &left.object,
+                  MAX_X/6 - left.image->w/2, 10,
+                  left.image->w, left.image->h, redraw_score);
+       add_object(&objects, &right.object,
+                  MAX_X - MAX_X/6 - left.image->w/2, 10,
+                  left.image->w, left.image->h, redraw_score);
+
+       /* Hit right border = score to left, and vice versa. */
+       border[1].score = &left;
+       border[3].score = &right;
+
+       srandom(time(NULL));
+       ball.pos.x = MAX_X/2;
+       ball.pos.y = MAX_Y/2;
+       ball.move.x = (random() % 2 ? -1 : 1) * atoi(argv[1]);
+       ball.move.y = 0;
+       ball.image = ball_surface(screen);
+       add_object(&objects, &ball.object,
+                  ball.pos.x - ball.image->w/2,
+                  ball.pos.y - ball.image->h/2,
+                  ball.image->w, ball.image->h, redraw_ball);
+
+       for (;;) {
                struct coord new, isect, move = ball.move;
-               struct line_segment *best_l;
-               unsigned i;
+               struct line_segment *best_l, *l, *next;
                double best_d;
-#if 0
-               SDL_Rect rect;
+               SDL_Event event;
+               struct timeval now;
+               bool ignored = false;
 
-               rect.x = ball.pos.x - ballsur->w/2;
-               rect.y = ball.pos.y - ballsur->h/2;
-               rect.w = ballsur->w;
-               rect.h = ballsur->h;
-#endif
+               gettimeofday(&now, NULL);
+
+               /* Expire lines */
+               list_for_each_safe(&lines, l, next, list) {
+                       if (timercmp(&l->expires, &now, <))
+                               talloc_free(l);
+               }
 
        again:
                best_d = MAX_X + MAX_Y;
@@ -699,57 +1166,139 @@ int main(int argc, char *argv[])
                new.x = ball.pos.x + move.x;
                new.y = ball.pos.y + move.y;
 
-               printf("Ball at %li,%li (%li,%li)\n", ball.pos.x, ball.pos.y,
-                      move.x, move.y);
-
-               for (i = 0; i < ARRAY_SIZE(lines); i++) {
-                       if (last_l == &lines[i])
-                               continue;
-                       if (intersect(&ball.pos, &new, &lines[i], &isect)) {
+               list_for_each(&lines, l, list) {
+                       if (!l->ignore && intersect(&ball.pos, &new, l, &isect)) {
                                double d;
                                d = dist(&ball.pos, &isect);
                                if (d < best_d) {
-                                       best_l = &lines[i];
+                                       best_l = l;
                                        best_d = d;
                                }
                        }
                }
+
                if (best_l) {
-                       printf("Ball bouncing off (%lu,%lu %lu,%lu)!\n",
+                       printf("Ball bouncing off (%f,%f %f,%f)!\n",
                               best_l->start.x, best_l->start.y,
                               best_l->end.x, best_l->end.y);
-                       bounce(&ball, best_l, &move);
+                       /* Only increase speed on *first* bounce, to avoid
+                        * mega speed increases. */
+                       bounce(&ball, best_l, &move, ignored ? 0.0 : 0.1);
+
+                       /* If we moved, stop ignoring lines. */
+                       if (ignored && (move.x > 0.001 || move.y > 0.001))
+                               clear_ignore(&lines);
+
                        /* Don't hit the same line twice. */
-                       last_l = best_l;
-                       isect_count++;
+                       printf("Ignoring that line\n");
+                       best_l->ignore = ignored = true;
+                       if (best_l->score) {
+                               SDL_Surface *won = score(best_l->score);
+                               if (won) {
+                                       SDL_Rect rect;
+
+                                       rect.x = MAX_X/2 - won->w/2;
+                                       rect.y = MAX_Y/2 - won->h/2;
+                                       rect.w = won->w;
+                                       rect.h = won->h;
+                                       SDL_BlitSurface(won, NULL, screen,
+                                                       &rect);
+                                       SDL_UpdateRect(screen,
+                                                      rect.x, rect.y,
+                                                      rect.w, rect.h);
+                                       SDL_Delay(5000);
+                                       exit(0);
+                               }
+                       }
                        goto again;
                }
 
-               /* Restore what was there before ball. */
-               if (savesur) {
-                       SDL_BlitSurface(savesur, NULL, screen, &rect);
-                       SDL_UpdateRect(screen, rect.x, rect.y, rect.w, rect.h);
-               } else {
-                       savesur = sub_surface(screen, ballsur->w, ballsur->h);
+               if (ignored && (move.x > 0.001 || move.y > 0.001)) {
+                       clear_ignore(&lines);
+                       printf("Moving by %f,%f to %f,%f\n",
+                              move.x, move.y, new.x, new.y);
                }
 
-               /* Save away image under new ball. */
-               rect.w = ballsur->w;
-               rect.h = ballsur->h;
-               rect.x = new.x - ballsur->w/2;
-               rect.y = new.y - ballsur->h/2;
-               SDL_BlitSurface(screen, &rect, savesur, NULL);
-
-               /* Draw new ball */
-               SDL_BlitSurface(ballsur, NULL, screen, &rect);
-               SDL_UpdateRect(screen, rect.x, rect.y, rect.w, rect.h);
-               /* That SDL_BlitSurface can crop rect. */
-               rect.x = new.x - ballsur->w/2;
-               rect.y = new.y - ballsur->h/2;
+               /* We also need to redraw under old ball. */
+               add_update(&ball.object);
+
+               /* Move ball. */
                ball.pos = new;
-               SDL_Delay(5);
-       } while (!SDL_PollEvent(&event) || event.type != SDL_QUIT);
-       SDL_Quit();
-       return 0;
+               ball.object.start.x = ball.pos.x - ball.image->w/2;
+               ball.object.start.y = ball.pos.y - ball.image->h/2;
+               ball.object.end.x = ball.pos.x + ball.image->w/2;
+               ball.object.end.y = ball.pos.y + ball.image->h/2;
+
+               /* Need to draw under new ball. */
+               add_update(&ball.object);
+
+               /* Clears, redraws and resets the updates */
+               do_updates(&objects, screen);
+
+               SDL_Delay(25);
+
+               while (SDL_PollEvent(&event)) {
+                       if (event.type == SDL_QUIT
+                           || (event.type == SDL_KEYDOWN
+                               && event.key.keysym.sym == SDLK_ESCAPE)) {
+                               SDL_Quit();
+                               return 0;
+                       }
+                       if (mouse) {
+                               switch (event.type) {
+                               case SDL_MOUSEBUTTONDOWN:
+                                       drawing = true;
+                                       break;
+                               case SDL_MOUSEBUTTONUP:
+                                       drawing = false;
+                                       break;
+                               case SDL_MOUSEMOTION:
+                                       if (!drawing ||
+                                           !same_side(&ball, event.motion.x))
+                                               break;
+
+                                       add_line(&lines, event.motion.x,
+                                                event.motion.y,
+                                                event.motion.x
+                                                + event.motion.xrel,
+                                                event.motion.y
+                                                + event.motion.yrel,
+                                                calib);
+                               }
+                       }
+               }
+
+               if (mouse)
+                       continue;
+
+               if (cwiid_get_state(wiimote, &state))
+                       errx(1, "No wii state");
+
+               /* Find biggest state. */
+               ir = &state.ir_src[0];
+               for (i = 0; i < CWIID_IR_SRC_COUNT; i++) {
+                       if (!state.ir_src[i].valid)
+                               continue;
+                       /* Only look at dots on same side as ball */
+                       if (!same_side(&ball, state.ir_src[i].pos[0]))
+                               continue;
+                       if (!ir->valid || state.ir_src[i].size > ir->size)
+                               ir = &state.ir_src[0];
+               }
+
+               if (ir->valid) {
+                       /* Give it some slack for missing one or two... */
+                       if (time_since_last_ir <= 5
+                           && same_side(&ball, last_ir.pos[0])) {
+                               add_line(&lines, last_ir.pos[0],
+                                        last_ir.pos[1],
+                                        ir->pos[0],
+                                        ir->pos[1],
+                                        calib);
+                       }
+                       time_since_last_ir = 0;
+                       last_ir = *ir;
+               }
+               time_since_last_ir++;
+       }
 }
-#endif