]> git.ozlabs.org Git - ponghero.git/blob - prpong.c
Akkana Peck takes pity on me and gives me some better GPL'd images.
[ponghero.git] / prpong.c
1 #define _GNU_SOURCE
2 #include <math.h>
3 #include <err.h>
4 #include <stdlib.h>
5 #include <stdio.h>
6 #include <stdbool.h>
7 #include <sys/time.h>
8 #include <time.h>
9 #include <limits.h>
10 #include "stdrusty.h"
11 #include "list/list.h"
12 #include "talloc/talloc.h"
13 #include "container_of/container_of.h"
14 #include <SDL/SDL.h>
15 #include <SDL/SDL_image.h>
16 #include <cwiid.h>
17 #include <assert.h>
18 #include <unistd.h>
19
20 #define EXPIRY_SECS 3
21
22 #define MAX_X CWIID_IR_X_MAX
23 #define MAX_Y CWIID_IR_Y_MAX
24
25 #define LEFT_PLAYER 1
26 #define RIGHT_PLAYER 2
27
28 static LIST_HEAD(objects);
29
30 struct coord
31 {
32         double x;
33         double y;
34 };
35
36 /* Obtained from http://tog.acm.org/GraphicsGems/gemsiii/insectc.c */
37 /* Faster Line Segment Intersection   */
38 /* Franklin Antonio                   */
39 static bool lines_intersect(const struct coord *start1,
40                             const struct coord *start2,
41                             const struct coord *start3,
42                             const struct coord *start4,
43                             struct coord *intersect)
44 {
45         double Ax,Bx,Cx,Ay,By,Cy,d,e,f,num;
46         struct coord lo, hi;
47
48         Ax = start2->x - start1->x;
49         Bx = start3->x - start4->x;
50
51         if(Ax<0) {                                      /* X bound box test*/
52                 lo.x = start2->x; hi.x = start1->x;
53         } else {
54                 hi.x = start2->x; lo.x = start1->x;
55         }
56         if(Bx>0) {
57                 if(hi.x < start4->x || start3->x < lo.x) return false;
58         } else {
59                 if(hi.x < start3->x || start4->x < lo.x) return false;
60         }
61
62         Ay = start2->y - start1->y;
63         By = start3->y - start4->y;
64
65         if(Ay<0) {                                      /* Y bound box test*/
66                 lo.y = start2->y; hi.y = start1->y;
67         } else {
68                 hi.y = start2->y; lo.y = start1->y;
69         }
70         if(By>0) {
71                 if(hi.y < start4->y || start3->y < lo.y) return false;
72         } else {
73                 if(hi.y < start3->y || start4->y < lo.y) return false;
74         }
75
76
77         Cx = start1->x - start3->x;
78         Cy = start1->y - start3->y;
79         d = By*Cx - Bx*Cy;                              /* alpha numerator*/
80         f = Ay*Bx - Ax*By;                              /* both denominator*/
81         if (f>0) {                                      /* alpha tests*/
82                 if (d < 0 || d > f) return false;
83         } else {
84                 if (d > 0 || d < f) return false;
85         }
86
87         e = Ax*Cy - Ay*Cx;                              /* beta numerator*/
88         if (f > 0) {                                    /* beta tests*/
89                 if (e < 0 || e > f) return false;
90         } else {
91                 if (e > 0 || e < f) return false;
92         }
93
94 /*compute intersection coordinates*/
95         /* Don't worry about tiny values of f (< 1/2 pixel across screen) */
96         if (fabs(f) < 1.0 / (2.0 * MAX_X * MAX_Y))
97                 return false;
98
99         num = d*Ax;                                     /* numerator */
100         intersect->x = start1->x + num / f;             /* intersection x */
101
102         num = d*Ay;
103         intersect->y = start1->y + num / f;             /* intersection y */
104
105         return true;
106 }
107
108 // A set of very useful macros that you will find in most
109 // code that I write whether I use them in a program or
110 // not.
111
112 #define abs(a) (((a)<0) ? -(a) : (a))
113 #define sign(a) (((a)<0) ? -1 : (a)>0 ? 1 : 0)
114
115 // The following code implements a Bresenham line drawing
116 // algorithm. There are 4 separate routines each optimized
117 // for one of the four pixel depths supported by SDL. SDL
118 // support many pixel formats, but it only support 8, 16,
119 // 24, and 32 bit pixels.
120
121 //----------------------------------------------------------
122
123 // Draw lines in 8 bit surfaces.
124
125 static void line8(SDL_Surface *s, 
126                   int x1, int y1, 
127                   int x2, int y2, 
128                   Uint32 color)
129 {
130   int d;
131   int x;
132   int y;
133   int ax;
134   int ay;
135   int sx;
136   int sy;
137   int dx;
138   int dy;
139
140   Uint8 *lineAddr;
141   Sint32 yOffset;
142
143   dx = x2 - x1;  
144   ax = abs(dx) << 1;  
145   sx = sign(dx);
146
147   dy = y2 - y1;  
148   ay = abs(dy) << 1;  
149   sy = sign(dy);
150   yOffset = sy * s->pitch;
151
152   x = x1;
153   y = y1;
154
155   lineAddr = ((Uint8 *)(s->pixels)) + (y * s->pitch);
156   if (ax>ay)
157   {                      /* x dominant */
158     d = ay - (ax >> 1);
159     for (;;)
160     {
161       if (y >= 0 && y < MAX_Y && x >= 0 && x < MAX_X)
162         *(lineAddr + x) = (Uint8)color;
163
164       if (x == x2)
165       {
166         return;
167       }
168       if (d>=0)
169       {
170         y += sy;
171         lineAddr += yOffset;
172         d -= ax;
173       }
174       x += sx;
175       d += ay;
176     }
177   }
178   else
179   {                      /* y dominant */
180     d = ax - (ay >> 1);
181     for (;;)
182     {
183       if (y >= 0 && y < MAX_Y && x >= 0 && x < MAX_X)
184         *(lineAddr + x) = (Uint8)color;
185
186       if (y == y2)
187       {
188         return;
189       }
190       if (d>=0) 
191       {
192         x += sx;
193         d -= ay;
194       }
195       y += sy;
196       lineAddr += yOffset;
197       d += ax;
198     }
199   }
200 }
201
202 //----------------------------------------------------------
203
204 // Draw lines in 16 bit surfaces. Note that this code will
205 // also work on 15 bit surfaces.
206
207 static void line16(SDL_Surface *s, 
208                    int x1, int y1, 
209                    int x2, int y2, 
210                    Uint32 color)
211 {
212   int d;
213   int x;
214   int y;
215   int ax;
216   int ay;
217   int sx;
218   int sy;
219   int dx;
220   int dy;
221
222   Uint8 *lineAddr;
223   Sint32 yOffset;
224
225   dx = x2 - x1;  
226   ax = abs(dx) << 1;  
227   sx = sign(dx);
228
229   dy = y2 - y1;  
230   ay = abs(dy) << 1;  
231   sy = sign(dy);
232   yOffset = sy * s->pitch;
233
234   x = x1;
235   y = y1;
236
237   lineAddr = ((Uint8 *)s->pixels) + (y * s->pitch);
238   if (ax>ay)
239   {                      /* x dominant */
240     d = ay - (ax >> 1);
241     for (;;)
242     {
243       if (y >= 0 && y < MAX_Y && x >= 0 && x < MAX_X)
244         *((Uint16 *)(lineAddr + (x << 1))) = (Uint16)color;
245
246       if (x == x2)
247       {
248         return;
249       }
250       if (d>=0)
251       {
252         y += sy;
253         lineAddr += yOffset;
254         d -= ax;
255       }
256       x += sx;
257       d += ay;
258     }
259   }
260   else
261   {                      /* y dominant */
262     d = ax - (ay >> 1);
263     for (;;)
264     {
265       if (y >= 0 && y < MAX_Y && x >= 0 && x < MAX_X)
266         *((Uint16 *)(lineAddr + (x << 1))) = (Uint16)color;
267
268       if (y == y2)
269       {
270         return;
271       }
272       if (d>=0) 
273       {
274         x += sx;
275         d -= ay;
276       }
277       y += sy;
278       lineAddr += yOffset;
279       d += ax;
280     }
281   }
282 }
283
284 //----------------------------------------------------------
285
286 // Draw lines in 24 bit surfaces. 24 bit surfaces require
287 // special handling because the pixels don't fall on even
288 // address boundaries. Instead of being able to store a
289 // single byte, word, or long you have to store 3
290 // individual bytes. As a result 24 bit graphics is slower
291 // than the other pixel sizes.
292
293 static void line24(SDL_Surface *s, 
294                    int x1, int y1, 
295                    int x2, int y2, 
296                    Uint32 color)
297 {
298   int d;
299   int x;
300   int y;
301   int ax;
302   int ay;
303   int sx;
304   int sy;
305   int dx;
306   int dy;
307
308   Uint8 *lineAddr;
309   Sint32 yOffset;
310
311 #if (SDL_BYTEORDER == SDL_BIG_ENDIAN)
312   color <<= 8;
313 #endif
314
315   dx = x2 - x1;  
316   ax = abs(dx) << 1;  
317   sx = sign(dx);
318
319   dy = y2 - y1;  
320   ay = abs(dy) << 1;  
321   sy = sign(dy);
322   yOffset = sy * s->pitch;
323
324   x = x1;
325   y = y1;
326
327   lineAddr = ((Uint8 *)(s->pixels)) + (y * s->pitch);
328   if (ax>ay)
329   {                      /* x dominant */
330     d = ay - (ax >> 1);
331     for (;;)
332     {
333       Uint8 *p = (lineAddr + (x * 3));
334       if (y >= 0 && y < MAX_Y && x >= 0 && x < MAX_X)
335         memcpy(p, &color, 3);
336
337       if (x == x2)
338       {
339         return;
340       }
341       if (d>=0)
342       {
343         y += sy;
344         lineAddr += yOffset;
345         d -= ax;
346       }
347       x += sx;
348       d += ay;
349     }
350   }
351   else
352   {                      /* y dominant */
353     d = ax - (ay >> 1);
354     for (;;)
355     {
356       Uint8 *p = (lineAddr + (x * 3));
357       if (y >= 0 && y < MAX_Y && x >= 0 && x < MAX_X)
358         memcpy(p, &color, 3);
359
360       if (y == y2)
361       {
362         return;
363       }
364       if (d>=0) 
365       {
366         x += sx;
367         d -= ay;
368       }
369       y += sy;
370       lineAddr += yOffset;
371       d += ax;
372     }
373   }
374 }
375
376 //----------------------------------------------------------
377
378 // Draw lines in 32 bit surfaces. Note that this routine
379 // ignores alpha values. It writes them into the surface
380 // if they are included in the pixel, but does nothing
381 // else with them.
382
383 static void line32(SDL_Surface *s, 
384                    int x1, int y1, 
385                    int x2, int y2, 
386                    Uint32 color)
387 {
388   int d;
389   int x;
390   int y;
391   int ax;
392   int ay;
393   int sx;
394   int sy;
395   int dx;
396   int dy;
397
398   Uint8 *lineAddr;
399   Sint32 yOffset;
400
401   dx = x2 - x1;  
402   ax = abs(dx) << 1;  
403   sx = sign(dx);
404
405   dy = y2 - y1;  
406   ay = abs(dy) << 1;  
407   sy = sign(dy);
408   yOffset = sy * s->pitch;
409
410   x = x1;
411   y = y1;
412
413   lineAddr = ((Uint8 *)(s->pixels)) + (y * s->pitch);
414   if (ax>ay)
415   {                      /* x dominant */
416     d = ay - (ax >> 1);
417     for (;;)
418     {
419       if (y >= 0 && y < MAX_Y && x >= 0 && x < MAX_X)
420         *((Uint32 *)(lineAddr + (x << 2))) = (Uint32)color;
421
422       if (x == x2)
423       {
424         return;
425       }
426       if (d>=0)
427       {
428         y += sy;
429         lineAddr += yOffset;
430         d -= ax;
431       }
432       x += sx;
433       d += ay;
434     }
435   }
436   else
437   {                      /* y dominant */
438     d = ax - (ay >> 1);
439     for (;;)
440     {
441       if (y >= 0 && y < MAX_Y && x >= 0 && x < MAX_X)
442         *((Uint32 *)(lineAddr + (x << 2))) = (Uint32)color;
443
444       if (y == y2)
445       {
446         return;
447       }
448       if (d>=0) 
449       {
450         x += sx;
451         d -= ay;
452       }
453       y += sy;
454       lineAddr += yOffset;
455       d += ax;
456     }
457   }
458 }
459
460 //----------------------------------------------------------
461
462 // Examine the depth of a surface and select a line
463 // drawing routine optimized for the bytes/pixel of the
464 // surface.
465
466 static void line(SDL_Surface *s, 
467                  int x1, int y1, 
468                  int x2, int y2, 
469                  Uint32 color)
470 {
471   switch (s->format->BytesPerPixel)
472   {
473   case 1:
474     line8(s, x1, y1, x2, y2, color);
475     break;
476   case 2:
477     line16(s, x1, y1, x2, y2, color);
478     break;
479   case 3:
480     line24(s, x1, y1, x2, y2, color);
481     break;
482   case 4:
483     line32(s, x1, y1, x2, y2, color);
484     break;
485   }
486 }
487
488 /* Bounding box to be updated. */
489 static bool needs_update;
490 static struct coord start_update, end_update;
491
492 struct object
493 {
494         /* List of all objects. */
495         struct list_node list;
496         /* Bounding box. */
497         struct coord start, end;
498
499         void (*redraw)(SDL_Surface *s, struct object *me);
500 };
501
502 struct ball
503 {
504         struct object object;
505
506         struct coord pos;
507         struct coord move;
508         SDL_Surface *image;
509 };
510
511 struct line_segment
512 {
513         struct object object;
514
515         struct coord start, end;
516         struct list_node list;
517         bool ignore;
518         struct timeval expires;
519         /* Does someone score if they hit this line ? */
520         struct score *score;
521 };
522
523 struct score
524 {
525         struct object object;
526
527         SDL_Surface *image;
528         unsigned int value;
529         SDL_Surface *won;
530 };
531
532 static void thick_line(SDL_Surface *s, const struct line_segment *l,
533                        unsigned color)
534 {
535         struct line_segment adj;
536         int x, y, minx = MAX_X, miny = MAX_Y, maxx = 0, maxy = 0;
537
538         SDL_LockSurface(s);
539
540         /* Cheap hack */
541         for (x = -1; x < 2; x++) {
542                 for (y = -1; y < 2; y++) {
543                         adj.start.x = l->start.x + x;
544                         adj.end.x = l->end.x + x;
545                         adj.start.y = l->start.y + y;
546                         adj.end.y = l->end.y + y;
547
548                         if (adj.start.x < minx)
549                                 minx = adj.start.x;
550                         if (adj.start.x > maxx)
551                                 maxx = adj.start.x;
552                         if (adj.start.y < miny)
553                                 miny = adj.start.y;
554                         if (adj.start.y > maxy)
555                                 maxy = adj.start.y;
556                         if (adj.end.x < minx)
557                                 minx = adj.end.x;
558                         if (adj.end.x > maxx)
559                                 maxx = adj.end.x;
560                         if (adj.end.y < miny)
561                                 miny = adj.end.y;
562                         if (adj.end.y > maxy)
563                                 maxy = adj.end.y;
564
565                         line(s, adj.start.x, adj.start.y, adj.end.x, adj.end.y,
566                              color);
567                 }
568         }
569
570         if (minx < 0)
571                 minx = 0;
572         if (miny < 0)
573                 miny = 0;
574         if (maxx >= MAX_X)
575                 maxx = MAX_X-1;
576         if (maxy >= MAX_Y)
577                 maxy = MAX_Y-1;
578         SDL_UpdateRect(s, minx, miny, maxx-minx, maxy-miny);
579         SDL_UnlockSurface(s);
580 }
581
582 static bool intersect(const struct coord *c1, const struct coord *c2,
583                       const struct line_segment *seg, struct coord *intersect)
584 {
585         return lines_intersect(c1, c2, &seg->start, &seg->end, intersect);
586 }
587
588 static double dist(const struct coord *c1, const struct coord *c2)
589 {
590         double x = (c1->x - c2->x), y = (c1->y - c2->y);
591         return sqrt(x * x + y * y);
592 }
593
594 static unsigned rad_to_deg(double rad)
595 {
596         return ((unsigned)(rad / M_PI * 180) + 360) % 360;
597 }
598
599 static void bounce(struct ball *ball, const struct line_segment *line,
600                    struct coord *move, double increase_speed)
601 {
602         double len, speed, lang, iang, oang;
603         struct coord isect, new;
604
605         new.x = ball->pos.x + move->x;
606         new.y = ball->pos.y + move->y;
607
608         /* How far were we supposed to move ball? */
609         len = sqrt(move->x * move->x + move->y * move->y);
610
611         /* How far is it to intersection? */
612         if (!intersect(&ball->pos, &new, line, &isect))
613                 errx(1, "No intersection any more?\n");;
614         len -= dist(&ball->pos, &isect);
615
616         /* Move ball to intersection. */
617         ball->pos = isect;
618         printf("ball is now at %f,%f\n", ball->pos.x, ball->pos.y);
619
620         /* Outgoing angle = 2 * line angle - incident angle. */
621         lang = atan2(line->end.x - line->start.x, line->end.y - line->start.y);
622         iang = atan2(ball->move.x, ball->move.y);
623         oang = 2 * lang - iang, 2*M_PI;
624         printf("lang = %u, iang = %u, oang=%u\n", 
625                rad_to_deg(lang),
626                rad_to_deg(iang),
627                rad_to_deg(oang));
628
629         /* Set new direction for ball, at slightly increased speed */
630         speed = sqrt(ball->move.x * ball->move.x + ball->move.y * ball->move.y);
631         speed += increase_speed;
632         ball->move.x = sin(oang) * speed;
633         ball->move.y = cos(oang) * speed;
634
635         /* Set move. */
636         move->x = sin(oang) * len;
637         move->y = cos(oang) * len;
638         printf("len = %f, move = %f,%f\n", len, move->x, move->y);
639 }
640
641 static struct line_segment border[] = {
642         { .start = { 0, 0, }, .end = { MAX_X-1, 0 } },
643         { .start = { MAX_X-1, 0, }, .end = { MAX_X-1, MAX_Y-1 } },
644         { .start = { MAX_X-1, MAX_Y-1, }, .end = { 0, MAX_Y-1 } },
645         { .start = { 0, MAX_Y-1, }, .end = { 0, 0 } },
646 };
647
648 static inline float deg_to_rad(float degrees)
649 {
650         return degrees * M_PI / 180;
651 }
652
653 static SDL_Surface *sub_surface(SDL_Surface *screen, int w, int h)
654 {
655         return SDL_CreateRGBSurface(SDL_SWSURFACE, w, h,
656                                     screen->format->BitsPerPixel,
657                                     screen->format->Rmask,
658                                     screen->format->Gmask,
659                                     screen->format->Bmask,
660                                     screen->format->Amask);
661 }
662
663 static SDL_Surface *ball_surface(SDL_Surface *screen)
664 {
665         SDL_Surface *ball;
666
667         /* Just like the screen surface. */
668         ball = sub_surface(screen, 5, 5);
669
670         /* Lock the surface */
671         SDL_LockSurface(ball);
672
673         /* Black, white corners. */
674         memset(ball->pixels, 0x00, ball->pitch * 5);
675         line(ball, 0, 0, 0, 0, 0xFFFFFFFF);
676         line(ball, 4, 0, 4, 0, 0xFFFFFFFF);
677         line(ball, 0, 4, 0, 4, 0xFFFFFFFF);
678         line(ball, 4, 4, 4, 4, 0xFFFFFFFF);
679         SDL_UnlockSurface(ball);
680
681         return ball;
682 }
683
684 static void clear_ignore(struct list_head *lines)
685 {
686         struct line_segment *i;
687         printf("Unignoring...\n");
688         list_for_each(lines, i, list)
689                 i->ignore = false;
690 }
691
692 static uint16_t *find_valid_point(struct cwiid_state *state)
693 {
694         unsigned int i;
695
696         for (i = 0; i < CWIID_IR_SRC_COUNT; i++) {
697                 if (state->ir_src[i].valid)
698                         return state->ir_src[i].pos;
699         }
700         return NULL;
701 }
702
703 static void calibrate(SDL_Surface *screen,
704                       const char *filename,
705                       cwiid_wiimote_t *wiimote,
706                       struct coord *calib, unsigned x, unsigned y)
707 {
708         SDL_Surface *img = IMG_Load(filename);
709         uint16_t *pos;
710         struct cwiid_state state;
711         int last_x = MAX_X, last_y = MAX_Y, count = 0;
712         SDL_Rect rect = { .x = screen->w/2 - img->w/2,
713                           .y = screen->h/2 - img->h/2 };
714
715         SDL_BlitSurface(img, NULL, screen, &rect);
716         SDL_UpdateRect(screen, 0, 0, 0, 0);
717         SDL_FreeSurface(img);
718
719         /* Must see it for a full half second. */
720         while (count < 20) {
721                 SDL_Event event;
722
723                 if (cwiid_get_state(wiimote, &state))
724                         errx(1, "No wii state");
725
726                 pos = find_valid_point(&state);
727                 if (!pos) {
728                         if (count)
729                                 count--;
730                 } else {
731                         /* Allow some jitter */
732                         if (abs(pos[0]-last_x) < 3 && abs(pos[1]-last_y) < 3)
733                                 count++;
734                         last_x = pos[0];
735                         last_y = pos[1];
736                 }
737
738                 SDL_Delay(25);
739                 if (SDL_PollEvent(&event)
740                     && (event.type == SDL_QUIT
741                         || (event.type == SDL_KEYDOWN
742                             && event.key.keysym.sym == SDLK_ESCAPE))) {
743                         SDL_Quit();
744                         exit(0);
745                 }
746         }
747
748         calib->x = last_x;
749         calib->y = last_y;
750         SDL_Delay(500);
751         printf("Calibration point: %u,%u\n", last_x, last_y);
752 }
753
754 static bool map_coord(unsigned int x, unsigned int y,
755                       const struct coord calib[],
756                       struct coord *res)
757 {
758         struct coord line_start, line_end;
759         struct coord pos = { x, y }, intersect;
760         double d1, d2;
761
762         /* Calibration layout:
763          *
764          *   0    1
765          *
766          *   3    2
767          *
768          * We figure out the distance to each side, and then map using:
769          *
770          * x = d1 / (d1 + d2) * (rawend - rawstart) + rawstart
771          */
772         line_start.x = 0;
773         line_end.x = MAX_X-1;
774         line_start.y = line_end.y = y;
775
776         if (!lines_intersect(&calib[0], &calib[3], &line_start, &line_end,
777                              &intersect))
778                 return false;
779         d1 = dist(&pos, &intersect);
780         if (!lines_intersect(&calib[1], &calib[2], &line_start, &line_end,
781                              &intersect))
782                 return false;
783         d2 = dist(&pos, &intersect);
784         res->x = d1 / (d1 + d2) * MAX_X;
785
786         line_start.y = 0;
787         line_end.y = MAX_Y-1;
788         line_start.x = line_end.x = x;
789
790         if (!lines_intersect(&calib[0], &calib[1], &line_start, &line_end,
791                              &intersect))
792                 return false;
793         d1 = dist(&pos, &intersect);
794         if (!lines_intersect(&calib[3], &calib[2], &line_start, &line_end,
795                              &intersect))
796                 return false;
797         d2 = dist(&pos, &intersect);
798         res->y = d1 / (d1 + d2) * MAX_Y;
799
800         return true;
801 }
802
803 static void add_update(const struct object *obj)
804 {
805         if (!needs_update) {
806                 start_update = obj->start;
807                 end_update = obj->end;
808                 needs_update = true;
809                 return;
810         }
811         if (obj->start.x < start_update.x)
812                 start_update.x = obj->start.x;
813         if (obj->start.y < start_update.y)
814                 start_update.y = obj->start.y;
815         if (obj->end.x > end_update.x)
816                 end_update.x = obj->end.x;
817         if (obj->end.y > end_update.y)
818                 end_update.y = obj->end.y;
819 }
820
821 static void destroy_object(struct object *object)
822 {
823         list_del(&object->list);
824         add_update(object);
825 }
826
827 static void add_object(struct list_head *list,
828                        struct object *obj,
829                        unsigned int startx, unsigned int starty,
830                        unsigned int width, unsigned int height,
831                        void (*redraw)(SDL_Surface *s, struct object *me))
832 {
833         list_add_tail(list, &obj->list);
834         obj->start.x = startx;
835         obj->start.y = starty;
836         obj->end.x = startx + width;
837         obj->end.y = starty + height;
838         obj->redraw = redraw;
839         add_update(obj);
840 }
841
842 static void redraw_line(SDL_Surface *s, struct object *me)
843 {
844         struct line_segment *l = container_of(me, struct line_segment, object);
845
846         thick_line(s, l, 0);
847 }
848
849 static int destroy_line(struct line_segment *l)
850 {
851         list_del(&l->list);
852         destroy_object(&l->object);
853         return 0;
854 }
855
856 static void line_object_setup(struct list_head *lines,
857                               struct list_head *objects,
858                               struct line_segment *l)
859 {
860         unsigned int tmp, startx, endx, starty, endy;
861
862         list_add_tail(lines, &l->list);
863
864         startx = l->start.x;
865         starty = l->start.y;
866         endx = l->end.x;
867         endy = l->end.y;
868
869         /* Find bounding box */
870         if (startx > endx) {
871                 tmp = endx;
872                 endx = startx;
873                 startx = tmp;
874         }
875         if (starty > endy) {
876                 tmp = endy;
877                 endy = starty;
878                 starty = tmp;
879         }
880         /* Padding for thick lines (beware underflow) */
881         if (startx > 0)
882                 startx--;
883         if (starty > 0)
884                 starty--;
885         endx++;
886         endy++;
887
888         add_object(objects, &l->object, startx, starty,
889                    endx - startx, endy - starty, redraw_line);
890 }
891
892 static void add_line(struct list_head *lines,
893                      unsigned int startx, unsigned int starty,
894                      unsigned int endx, unsigned int endy,
895                      const struct coord calib[])
896 {
897         struct line_segment *l = talloc(NULL, struct line_segment);
898         struct timeval now;
899
900         if (!map_coord(startx, starty, calib, &l->start)
901             || !map_coord(endx, endy, calib, &l->end)) {
902                 talloc_free(l);
903                 return;
904         }
905
906         l->ignore = false;
907         l->score = NULL;
908         gettimeofday(&now, NULL);
909         l->expires = now;
910         l->expires.tv_sec += EXPIRY_SECS;
911
912         line_object_setup(lines, &objects, l);
913         talloc_set_destructor(l, destroy_line);
914 }
915
916 static void redraw_ball(SDL_Surface *s, struct object *me)
917 {
918         struct ball *b = container_of(me, struct ball, object);
919         SDL_Rect rect = { .x = b->pos.x - b->image->w/2,
920                           .y = b->pos.y - b->image->h/2 };
921
922         SDL_BlitSurface(b->image, NULL, s, &rect);
923 }
924
925 static void redraw_score(SDL_Surface *s, struct object *me)
926 {
927         struct score *score = container_of(me, struct score, object);
928         SDL_Rect rect = { .x = score->object.start.x,
929                           .y = score->object.start.y };
930
931         SDL_BlitSurface(score->image, NULL, s, &rect);
932 }
933
934 static SDL_Surface *score(struct score *s)
935 {
936         char filename[40];
937
938         s->value++;
939         SDL_FreeSurface(s->image);
940         sprintf(filename, "images/%u.png", s->value);
941         s->image = IMG_Load(filename);
942         /* No more images?  You won! */
943         if (!s->image)
944                 return s->won;
945
946         add_update(&s->object);
947         return NULL;
948 }
949
950 /* Due to rounding, we exaggerate bounding box by 1 here. */
951 static void do_updates(struct list_head *objects, SDL_Surface *screen)
952 {
953         struct object *i;
954         unsigned int y, startx, starty, endx, endy;
955
956         /* Clip */
957         if (start_update.x <= 0)
958                 startx = 0;
959         else
960                 startx = start_update.x - 1;
961         if (start_update.y <= 0)
962                 starty = 0;
963         else
964                 starty = start_update.y - 1;
965
966         endx = end_update.x+1;
967         endy = end_update.y+1;
968
969         if (endx > screen->w)
970                 endx = screen->w;
971         if (endy > screen->h)
972                 endy = screen->h;
973
974         SDL_LockSurface(screen);
975         /* First we clear the area to white. */
976         for (y = starty; y < endy; y++) {
977                 memset(screen->pixels + y * screen->pitch
978                        + screen->format->BytesPerPixel * startx,
979                        0xFF,
980                        (endx - startx) * screen->format->BytesPerPixel);
981         }
982         SDL_UnlockSurface(screen);
983
984         /* Now redraw everything which overlaps it */
985         list_for_each(objects, i, list) {
986                 if (i->end.x < startx)
987                         continue;
988                 if (i->end.y < starty)
989                         continue;
990                 if (i->start.x > endx)
991                         continue;
992                 if (i->start.y > endy)
993                         continue;
994                 i->redraw(screen, i);
995         }
996
997         SDL_UpdateRect(screen, startx, starty, endx - startx, endy - starty);
998
999         /* Reset bounding box */
1000         needs_update = false;
1001 }
1002
1003 static bool same_side(const struct ball *ball, unsigned int x)
1004 {
1005         return (ball->pos.x >= MAX_X/2) == (x >= MAX_X/2);
1006 }
1007
1008 int main(int argc, char *argv[])
1009 {
1010         struct ball ball;
1011         SDL_Surface *screen;
1012         Uint8  video_bpp;
1013         Uint32 videoflags;
1014         unsigned int i, time_since_last_ir = INT_MAX;
1015         cwiid_wiimote_t *wiimote;
1016         struct cwiid_state state;
1017         struct coord calib[4];
1018         bdaddr_t addr = *BDADDR_ANY;
1019         bool mouse = false, needs_calibration = true, drawing = false;
1020         LIST_HEAD(lines);
1021         struct cwiid_ir_src *ir, last_ir = { .valid = 0 };
1022         struct score left, right;
1023
1024         videoflags = SDL_SWSURFACE;
1025
1026         if (argv[1] && streq(argv[1], "--fullscreen")) {
1027                 videoflags |= SDL_FULLSCREEN;
1028                 argc--;
1029                 argv++;
1030         }
1031
1032         if (argv[1] && strstarts(argv[1], "--calib=")) {
1033                 needs_calibration = false;
1034                 if (sscanf(argv[1], "--calib=%lf,%lf,%lf,%lf,%lf,%lf,%lf,%lf",
1035                            &calib[0].x, &calib[0].y,
1036                            &calib[1].x, &calib[1].y,
1037                            &calib[2].x, &calib[2].y,
1038                            &calib[3].x, &calib[3].y) != 8)
1039                         errx(1, "Not enough calibration points");
1040                 argc--;
1041                 argv++;
1042         }
1043
1044         if (argv[1] && streq(argv[1], "--mouse")) {
1045                 mouse = true;
1046                 needs_calibration = false;
1047                 calib[0].x = calib[0].y = 0;
1048                 calib[1].x = MAX_X - 1;
1049                 calib[1].y = 0;
1050                 calib[2].x = MAX_X - 1;
1051                 calib[2].y = MAX_Y - 1;
1052                 calib[3].x = 0;
1053                 calib[3].y = MAX_Y - 1;
1054                 argc--;
1055                 argv++;
1056         }
1057
1058         if (argc != 2)
1059                 errx(1, "Usage: %s [--fullscreen] [--calib=x,y,x,y,x,y,x,y] [--mouse] speed\n",
1060                         argv[0]);
1061
1062         /* Initialize SDL */
1063         if (SDL_Init(SDL_INIT_VIDEO) < 0) {
1064                 fprintf(stderr, "Couldn't initialize SDL: %s\n",SDL_GetError());
1065                 return(1);
1066         }
1067
1068         video_bpp = 0;
1069
1070         if (!mouse) {
1071                 printf("Put Wiimote in discoverable mode (press 1+2)\n");
1072                 wiimote = cwiid_open(&addr, 0);
1073                 if (!wiimote)
1074                         errx(1, "Can't find the Wiimote");
1075
1076                 if (cwiid_set_rpt_mode(wiimote, CWIID_RPT_IR))
1077                         errx(1, "Can't set IR repeat mode");
1078         }
1079
1080         if ((screen=SDL_SetVideoMode(MAX_X,MAX_Y,video_bpp,videoflags)) == NULL) {
1081                 errx(1, "Couldn't set %dx%dx%d video mode: %s",
1082                      MAX_X, MAX_Y,video_bpp, SDL_GetError());
1083         }
1084
1085         /* Set the surface pixels and refresh! */
1086         if (SDL_LockSurface(screen) < 0) {
1087                 errx(1, "Couldn't lock the display surface: %s",
1088                      SDL_GetError());
1089         }
1090         /* White. */
1091         memset(screen->pixels, 0xFF, screen->pitch * screen->h);
1092
1093         SDL_UnlockSurface(screen);
1094         SDL_UpdateRect(screen, 0, 0, 0, 0);
1095
1096         if (needs_calibration) {
1097                 /* Calibration */
1098                 calibrate(screen, "images/top-left.png",
1099                           wiimote, &calib[0], 0, 0);
1100                 calibrate(screen, "images/top-right.png",
1101                           wiimote, &calib[1], MAX_X - 1, 0);
1102                 calibrate(screen, "images/bottom-right.png",
1103                           wiimote, &calib[2], MAX_X - 1, MAX_Y - 1);
1104                 calibrate(screen, "images/bottom-left.png",
1105                           wiimote, &calib[3], 0, MAX_Y - 1);
1106         }
1107
1108         /* Create borders, put them in list. */
1109         for (i = 0; i < ARRAY_SIZE(border); i++) {
1110                 border[i].ignore = false;
1111                 border[i].expires.tv_sec = LONG_MAX;
1112                 border[i].expires.tv_usec = 0;
1113                 line_object_setup(&lines, &objects, &border[i]);
1114         }
1115
1116         /* Set up scores. */
1117         left.value = right.value = 0;
1118         left.image = IMG_Load("images/0.png");
1119         left.won = IMG_Load("images/left-won.png");
1120         right.image = IMG_Load("images/0.png");
1121         right.won = IMG_Load("images/right-won.png");
1122         if (!left.image || !right.image || !left.won || !right.won)
1123                 err(1, "Opening score images");
1124
1125         add_object(&objects, &left.object,
1126                    MAX_X/6 - left.image->w/2, 10,
1127                    left.image->w, left.image->h, redraw_score);
1128         add_object(&objects, &right.object,
1129                    MAX_X - MAX_X/6 - left.image->w/2, 10,
1130                    left.image->w, left.image->h, redraw_score);
1131
1132         /* Hit right border = score to left, and vice versa. */
1133         border[1].score = &left;
1134         border[3].score = &right;
1135
1136         srandom(time(NULL));
1137         ball.pos.x = MAX_X/2;
1138         ball.pos.y = MAX_Y/2;
1139         ball.move.x = (random() % 2 ? -1 : 1) * atoi(argv[1]);
1140         ball.move.y = 0;
1141         ball.image = ball_surface(screen);
1142         add_object(&objects, &ball.object,
1143                    ball.pos.x - ball.image->w/2,
1144                    ball.pos.y - ball.image->h/2,
1145                    ball.image->w, ball.image->h, redraw_ball);
1146
1147         for (;;) {
1148                 struct coord new, isect, move = ball.move;
1149                 struct line_segment *best_l, *l, *next;
1150                 double best_d;
1151                 SDL_Event event;
1152                 struct timeval now;
1153                 bool ignored = false;
1154
1155                 gettimeofday(&now, NULL);
1156
1157                 /* Expire lines */
1158                 list_for_each_safe(&lines, l, next, list) {
1159                         if (timercmp(&l->expires, &now, <))
1160                                 talloc_free(l);
1161                 }
1162
1163         again:
1164                 best_d = MAX_X + MAX_Y;
1165                 best_l = NULL;
1166                 new.x = ball.pos.x + move.x;
1167                 new.y = ball.pos.y + move.y;
1168
1169                 list_for_each(&lines, l, list) {
1170                         if (!l->ignore && intersect(&ball.pos, &new, l, &isect)) {
1171                                 double d;
1172                                 d = dist(&ball.pos, &isect);
1173                                 if (d < best_d) {
1174                                         best_l = l;
1175                                         best_d = d;
1176                                 }
1177                         }
1178                 }
1179
1180                 if (best_l) {
1181                         printf("Ball bouncing off (%f,%f %f,%f)!\n",
1182                                best_l->start.x, best_l->start.y,
1183                                best_l->end.x, best_l->end.y);
1184                         /* Only increase speed on *first* bounce, to avoid
1185                          * mega speed increases. */
1186                         bounce(&ball, best_l, &move, ignored ? 0.0 : 0.1);
1187
1188                         /* If we moved, stop ignoring lines. */
1189                         if (ignored && (move.x > 0.001 || move.y > 0.001))
1190                                 clear_ignore(&lines);
1191
1192                         /* Don't hit the same line twice. */
1193                         printf("Ignoring that line\n");
1194                         best_l->ignore = ignored = true;
1195                         if (best_l->score) {
1196                                 SDL_Surface *won = score(best_l->score);
1197                                 if (won) {
1198                                         SDL_Rect rect;
1199
1200                                         rect.x = MAX_X/2 - won->w/2;
1201                                         rect.y = MAX_Y/2 - won->h/2;
1202                                         rect.w = won->w;
1203                                         rect.h = won->h;
1204                                         SDL_BlitSurface(won, NULL, screen,
1205                                                         &rect);
1206                                         SDL_UpdateRect(screen,
1207                                                        rect.x, rect.y,
1208                                                        rect.w, rect.h);
1209                                         SDL_Delay(5000);
1210                                         exit(0);
1211                                 }
1212                         }
1213                         goto again;
1214                 }
1215
1216                 if (ignored && (move.x > 0.001 || move.y > 0.001)) {
1217                         clear_ignore(&lines);
1218                         printf("Moving by %f,%f to %f,%f\n",
1219                                move.x, move.y, new.x, new.y);
1220                 }
1221
1222                 /* We also need to redraw under old ball. */
1223                 add_update(&ball.object);
1224
1225                 /* Move ball. */
1226                 ball.pos = new;
1227                 ball.object.start.x = ball.pos.x - ball.image->w/2;
1228                 ball.object.start.y = ball.pos.y - ball.image->h/2;
1229                 ball.object.end.x = ball.pos.x + ball.image->w/2;
1230                 ball.object.end.y = ball.pos.y + ball.image->h/2;
1231
1232                 /* Need to draw under new ball. */
1233                 add_update(&ball.object);
1234
1235                 /* Clears, redraws and resets the updates */
1236                 do_updates(&objects, screen);
1237
1238                 SDL_Delay(25);
1239
1240                 while (SDL_PollEvent(&event)) {
1241                         if (event.type == SDL_QUIT
1242                             || (event.type == SDL_KEYDOWN
1243                                 && event.key.keysym.sym == SDLK_ESCAPE)) {
1244                                 SDL_Quit();
1245                                 return 0;
1246                         }
1247                         if (mouse) {
1248                                 switch (event.type) {
1249                                 case SDL_MOUSEBUTTONDOWN:
1250                                         drawing = true;
1251                                         break;
1252                                 case SDL_MOUSEBUTTONUP:
1253                                         drawing = false;
1254                                         break;
1255                                 case SDL_MOUSEMOTION:
1256                                         if (!drawing ||
1257                                             !same_side(&ball, event.motion.x))
1258                                                 break;
1259
1260                                         add_line(&lines, event.motion.x,
1261                                                  event.motion.y,
1262                                                  event.motion.x
1263                                                  + event.motion.xrel,
1264                                                  event.motion.y
1265                                                  + event.motion.yrel,
1266                                                  calib);
1267                                 }
1268                         }
1269                 }
1270
1271                 if (mouse)
1272                         continue;
1273
1274                 if (cwiid_get_state(wiimote, &state))
1275                         errx(1, "No wii state");
1276
1277                 /* Find biggest state. */
1278                 ir = &state.ir_src[0];
1279                 for (i = 0; i < CWIID_IR_SRC_COUNT; i++) {
1280                         if (!state.ir_src[i].valid)
1281                                 continue;
1282                         /* Only look at dots on same side as ball */
1283                         if (!same_side(&ball, state.ir_src[i].pos[0]))
1284                                 continue;
1285                         if (!ir->valid || state.ir_src[i].size > ir->size)
1286                                 ir = &state.ir_src[0];
1287                 }
1288
1289                 if (ir->valid) {
1290                         /* Give it some slack for missing one or two... */
1291                         if (time_since_last_ir <= 5
1292                             && same_side(&ball, last_ir.pos[0])) {
1293                                 add_line(&lines, last_ir.pos[0],
1294                                          last_ir.pos[1],
1295                                          ir->pos[0],
1296                                          ir->pos[1],
1297                                          calib);
1298                         }
1299                         time_since_last_ir = 0;
1300                         last_ir = *ir;
1301                 }
1302                 time_since_last_ir++;
1303         }
1304 }