#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 EXPIRY_SECS 3
+
#define MAX_X CWIID_IR_X_MAX
#define MAX_Y CWIID_IR_Y_MAX
+#define LEFT_PLAYER 1
+#define RIGHT_PLAYER 2
+
+static LIST_HEAD(objects);
+
struct coord
{
double x;
}
}
+/* Bounding box to be updated. */
+static bool needs_update;
+static struct coord start_update, end_update;
+
+struct object
+{
+ /* 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;
+};
+
+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,
}
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)
return true;
}
-static struct line_segment *new_line(unsigned int startx, unsigned int starty,
- unsigned int endx, unsigned int endy,
- const struct coord calib[])
+static void add_update(const struct object *obj)
{
- struct line_segment *l = malloc(sizeof(*l));
+ 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;
- l->ignore = false;
if (!map_coord(startx, starty, calib, &l->start)
|| !map_coord(endx, endy, calib, &l->end)) {
- free(l);
- return NULL;
+ talloc_free(l);
+ return;
}
- return l;
+
+ 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;
- SDL_Surface *screen, *ballsur, *savesur = NULL;
+ SDL_Surface *screen;
Uint8 video_bpp;
Uint32 videoflags;
unsigned int i, time_since_last_ir = INT_MAX;
- SDL_Rect rect;
- struct timeval line_life = { 3, 0 };
cwiid_wiimote_t *wiimote;
struct cwiid_state state;
struct coord calib[4];
bool mouse = false, needs_calibration = true, drawing = false;
LIST_HEAD(lines);
struct cwiid_ir_src *ir, last_ir = { .valid = 0 };
- struct line_segment *n;
+ struct score left, right;
videoflags = SDL_SWSURFACE;
argv++;
}
- if (argc != 5)
- errx(1, "Usage: %s [--fullscreen] [--calib=x,y,x,y,x,y,x,y] [--mouse] ballx bally ballangle ballspeed\n"
- " Where ballangle is 0 for north, 90 for east, etc\n",
+ if (argc != 2)
+ errx(1, "Usage: %s [--fullscreen] [--calib=x,y,x,y,x,y,x,y] [--mouse] speed\n",
argv[0]);
- ball.pos.x = atol(argv[1]);
- ball.pos.y = atol(argv[2]);
- ball.move.x = sin(deg_to_rad(atof(argv[3]))) * atol(argv[4]);
- ball.move.y = cos(deg_to_rad(atof(argv[3]))) * atol(argv[4]);
- printf("ball move = %f,%f\n", ball.move.x, ball.move.y);
-
/* Initialize SDL */
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
fprintf(stderr, "Couldn't initialize SDL: %s\n",SDL_GetError());
errx(1, "Can't set IR repeat mode");
}
- if ( (screen=SDL_SetVideoMode(MAX_X,MAX_Y,video_bpp,videoflags)) == NULL ) {
+ 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());
}
calibrate(screen, wiimote, &calib[3], 0, MAX_Y - 1);
}
- /* Draw borders, put them in list. */
+ /* 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;
- list_add(&lines, &border[i].list);
- thick_line(screen, &border[i], 0);
+ line_object_setup(&lines, &objects, &border[i]);
}
- ballsur = ball_surface(screen);
+ /* 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;
/* Expire lines */
list_for_each_safe(&lines, l, next, list) {
- if (timercmp(&l->expires, &now, <)) {
- printf("Deleting line %p\n", l);
- list_del(&l->list);
- /* FIXME: Undraw properly. */
- thick_line(screen, l, 0xFFFFFFFF);
- free(l);
- }
+ if (timercmp(&l->expires, &now, <))
+ talloc_free(l);
}
again:
/* Don't hit the same line twice. */
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(3000);
+ exit(0);
+ }
+ }
goto again;
}
move.x, move.y, new.x, new.y);
}
- /* 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);
- }
+ /* We also need to redraw under old ball. */
+ add_update(&ball.object);
- /* 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;
+ /* Move ball. */
ball.pos = new;
+ 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)) {
drawing = false;
break;
case SDL_MOUSEMOTION:
- if (!drawing)
+ if (!drawing ||
+ !same_side(&ball, event.motion.x))
break;
- n = new_line(event.motion.x,
- event.motion.y,
- event.motion.x
- + event.motion.xrel,
- event.motion.y
- + event.motion.yrel,
- calib);
-
- if (n) {
- timeradd(&now, &line_life,
- &n->expires);
- list_add_tail(&lines, &n->list);
- thick_line(screen, n, 0);
- }
+ add_line(&lines, event.motion.x,
+ event.motion.y,
+ event.motion.x
+ + event.motion.xrel,
+ event.motion.y
+ + event.motion.yrel,
+ calib);
}
}
}
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) {
- n = new_line(last_ir.pos[0],
- last_ir.pos[1],
- ir->pos[0],
- ir->pos[1],
- calib);
- if (n) {
- timeradd(&now, &line_life, &n->expires);
- list_add_tail(&lines, &n->list);
- thick_line(screen, n, 0);
- }
+ 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;