X-Git-Url: http://git.ozlabs.org/?a=blobdiff_plain;f=prpong.c;h=da584f99ef92560d229ec2d462da50dbcdc89f9e;hb=c8bec7224a7fa4403a570e0f690f09496e815f97;hp=36b297e600d02c3c8c774b922d28a64e8d01e079;hpb=b2d2e0a475b2831604b11986ef342ba749eb4222;p=ponghero.git diff --git a/prpong.c b/prpong.c index 36b297e..da584f9 100644 --- a/prpong.c +++ b/prpong.c @@ -9,13 +9,24 @@ #include #include "stdrusty.h" #include "list/list.h" +#include "talloc/talloc.h" +#include "container_of/container_of.h" #include +#include #include #include +#include + +#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; @@ -474,18 +485,48 @@ static void line(SDL_Surface *s, } } +/* 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, @@ -598,10 +639,10 @@ static void bounce(struct ball *ball, const struct line_segment *line, } 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) @@ -648,63 +689,401 @@ static void clear_ignore(struct list_head *lines) 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; - 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]; 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; videoflags = SDL_SWSURFACE; if (argv[1] && streq(argv[1], "--fullscreen")) { - videoflags = SDL_SWSURFACE; + videoflags |= SDL_FULLSCREEN; argc--; argv++; } - if (argc != 5) - errx(1, "Usage: %s [--fullscreen] ballx bally ballangle ballspeed\n" - " Where ballangle is 0 for north, 90 for east, etc\n", - argv[0]); + 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++; + } - 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); + 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; - 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 (!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 (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 ) { + 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()); } @@ -714,16 +1093,56 @@ int main(int argc, char *argv[]) SDL_UnlockSurface(screen); SDL_UpdateRect(screen, 0, 0, 0, 0); - /* Draw borders, put them in list. */ + 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; - 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; @@ -737,13 +1156,8 @@ int main(int argc, char *argv[]) /* 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: @@ -778,6 +1192,24 @@ int main(int argc, char *argv[]) /* 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(5000); + exit(0); + } + } goto again; } @@ -787,38 +1219,58 @@ int main(int argc, char *argv[]) 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); - if (SDL_PollEvent(&event) - && (event.type == SDL_QUIT - || (event.type == SDL_KEYDOWN - && event.key.keysym.sym == SDLK_ESCAPE))) { - SDL_Quit(); - return 0; + 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"); @@ -827,23 +1279,22 @@ int main(int argc, char *argv[]) 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) { - struct line_segment *n = malloc(sizeof(*n)); - /* Wiimote coordinates are backwards for us. */ - n->start.x = last_ir.pos[0]; - n->start.y = MAX_Y - last_ir.pos[1]; - n->end.x = ir->pos[0]; - n->end.y = MAX_Y - ir->pos[1]; - n->ignore = false; - 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;