ref: f2d78c632d9384ef45c7da914321f9e30f79ff67
dir: /rfx.c/
#include <u.h> #include <libc.h> #include <draw.h> #include <thread.h> #include <keyboard.h> #include <mouse.h> #include <cursor.h> typedef struct Tg Tg; struct Tg{ Point; Point v; int r; Tg *p; Tg *n; }; Tg troot; Tg *tg; vlong tics; int ntg; int hits, misses, misclicks; uint bmask = 1; int oldb; enum{ NSEC = 1000000000 }; /* recommend these are set well outside the confort range, but not so much so * that every game lasts less than a minute */ vlong div, div0 = NSEC/20; /* tic duration (ns) */ int tddd = 220; /* tic duration decrease delay (tics) */ int Δ0 = 100; /* tic duration delta factor */ int mtdt = 5; /* tics/tddd % mtdt: delay to tgmax++ */ int floatdt = 25; /* floating text delay (tics) */ int newdt = 8; /* target spawn delay (tics) */ int dmax, rmax = 24; /* max target radius (px) */ int tgmax = 3; /* initial max number of targets onscreen */ int missmax = 10; /* max targets let go */ int nogrow; /* don't grow and shrink targets */ int done; enum{ CBACK, CTARG, CMISS, CTXT, CEND }; Image *col[CEND]; Image *fb; Rectangle winxy; /* spawning area */ Mousectl *mctl; Keyboardctl *kctl; Channel *ticc; #define trr() (rmax+1 - t->r % (rmax+1)) #define tr() (nogrow ? rmax : t->r <= rmax ? t->r : trr()) Image * eallocim(Rectangle r, int repl, ulong col) { Image *i; i = allocimage(display, r, screen->chan, repl, col); if(i == nil) sysfatal("allocimage: %r"); return i; } void tic(void *) { vlong t, dt, δ; t = nsec(); dt = div/1000000; for(;;){ if(dt >= 0){ sleep(dt); if(nbsend(ticc, nil) < 0) break; } δ = div; dt = (t + 2*δ - nsec()) / 1000000; t += δ; } } Tg * push(void) { Tg *t; t = mallocz(sizeof *t, 1); if(t == nil) sysfatal("talloc: %r"); t->p = tg->p; t->n = tg; tg->p->n = t; tg->p = t; return t; } void pop(Tg *t) { t->p->n = t->n; t->n->p = t->p; free(t); } void nuke(void) { while(tg->n != tg) pop(tg->n); ntg = 0; } void drw(Tg *t) { int r; r = tr(); fillellipse(fb, *t, r, r, col[CTARG], ZP); } void clr(Tg *t) { draw(fb, Rpt(*t, t->v), col[CBACK], nil, ZP); } void txt(char *s, Tg *t, Image *i) { Point o; if(t == nil) o = subpt(divpt(winxy.max, 2), divpt(stringsize(font, s), 2)); else{ o = divpt(stringsize(font, s), 2); t->v = addpt(*t, o); t->x -= o.x; t->y -= o.y; o = *t; } string(fb, o, i, ZP, font, s); } void misclk(Point o) { Tg *t; /* FIXME: often doesn't do anything */ fillellipse(fb, o, 1, 1, col[CTARG], ZP); t = push(); t->v = addpt(o, Pt(2,2)); t->x = o.x - 1; t->y = o.y - 1; t->r = dmax+1; misclicks++; } void hit(Tg *t) { int r; r = tr(); fillellipse(fb, *t, r, r, col[CBACK], ZP); pop(t); ntg--; hits++; } void miss(Tg *t) { int r; r = nogrow ? rmax : 1; fillellipse(fb, *t, r, r, col[CBACK], ZP); txt("miss", t, col[CMISS]); misses++; ntg--; } void grow(Tg *t) { if(nogrow) return; drw(t); } void shrink(Tg *t) { int r; if(nogrow) return; r = trr(); fillellipse(fb, *t, r, r, col[CBACK], ZP); r--; fillellipse(fb, *t, r, r, col[CTARG], ZP); } void spawn(void) { Tg *t; t = push(); ntg++; t->x = winxy.min.x + nrand(winxy.max.x - winxy.min.x); t->y = winxy.min.y + nrand(winxy.max.y - winxy.min.y); if(nogrow){ t->r = rmax; drw(t); } } void check(int b, Point m) { Point o; Tg *t; /* FIXME: implement counters for each button */ USED(b); m = subpt(m, screen->r.min); for(t=tg->n; t != tg; t=t->n){ if(t->r > dmax) continue; o = subpt(*t, m); if(hypot(o.x, o.y) < tr()){ hit(t); return; } } misclk(m); } void plaster(void) { draw(screen, screen->r, fb, nil, ZP); flushimage(display, 1); } void score(void) { char s[64]; sprint(s, "hit %d misses %d misclicks %d", hits, misses, misclicks); txt(s, nil, col[CTXT]); plaster(); } void adv(void) { Tg *t; tics++; if(tics % tddd == 0){ div -= div0 / Δ0; if(tics / tddd % mtdt == 0) tgmax++; } for(t=tg->n; t != tg; t=t->n){ t->r++; if(t->r <= rmax) grow(t); else if(t->r > rmax && t->r <= dmax) shrink(t); else if(t->r == dmax+1){ miss(t); if(misses >= missmax){ done++; score(); } }else if(t->r > dmax+floatdt){ clr(t); pop(t); } } if(tics % newdt == 0 && ntg < tgmax) spawn(); } void dreset(void) { Tg *t; winxy = rectsubpt(insetrect(screen->r, rmax), screen->r.min); if(Dx(winxy) < 3*dmax || Dy(winxy) < 3*dmax) sysfatal("window too small"); freeimage(fb); fb = eallocim(Rect(0,0,Dx(screen->r),Dy(screen->r)), 0, DNofill); draw(fb, fb->r, col[CBACK], nil, ZP); for(t=tg->n; t != tg; t=t->n){ if(t->r > dmax) continue; drw(t); } if(done) score(); plaster(); } void init(void) { div = div0; tics = 0; hits = misses = misclicks = 0; done = 0; } void usage(void) { print("%s [-g] [-d newdt] [-h div0] [-i Δ0] [-m nmiss] [-o rmax] [-r tddd] [-t 1-5]\n", argv0); threadexitsall("usage"); } void threadmain(int argc, char *argv[]) { Rectangle d; Rune r; ARGBEGIN{ case 'd': newdt = strtol(EARGF(usage()), nil, 0); break; case 'g': nogrow++; break; case 'h': div0 = strtoll(EARGF(usage()), nil, 0); if(div0 <= 0) sysfatal("invalid divisor %lld", div0); div0 = NSEC / div0; break; case 'i': Δ0 = strtol(EARGF(usage()), nil, 0); if(Δ0 <= 0) sysfatal("invalid divisor divisor %d", Δ0); break; case 'm': missmax = strtol(EARGF(usage()), nil, 0); break; case 'o': rmax = strtol(EARGF(usage()), nil, 0); break; case 'r': tddd = strtol(EARGF(usage()), nil, 0); break; case 't': bmask = 31 >> 5 - strtol(EARGF(usage()), nil, 0); break; default: usage(); }ARGEND dmax = rmax*2; tg = &troot; tg->n = tg->p = tg; if(initdraw(nil, nil, "rfx") < 0) sysfatal("initdraw: %r"); d = Rect(0,0,1,1); col[CBACK] = display->black; col[CTARG] = eallocim(d, 1, 0x990000ff); col[CMISS] = eallocim(d, 1, 0x440000ff); col[CTXT] = eallocim(d, 1, 0xbb4400ff); dreset(); mctl = initmouse(nil, screen); if(mctl == nil) sysfatal("initmouse: %r"); kctl = initkeyboard(nil); if(kctl == nil) sysfatal("initkeyboard: %r"); ticc = chancreate(sizeof(int), 0); if(ticc == nil) sysfatal("chancreate: %r"); srand(time(nil)); init(); if(proccreate(tic, nil, 8192) < 0) sysfatal("proccreate tproc: %r"); enum{ATIC, AMOUSE, ARESIZE, AKEY, AEND}; Alt a[AEND+1] = { [ATIC] {ticc, nil, CHANRCV}, [AMOUSE] {mctl->c, &mctl->Mouse, CHANRCV}, [ARESIZE] {mctl->resizec, nil, CHANRCV}, [AKEY] {kctl->c, &r, CHANRCV}, [AEND] {nil, nil, CHANEND} }; for(;;){ switch(alt(a)){ case ATIC: if(done) break; adv(); plaster(); break; case AMOUSE: if(!done && mctl->buttons & bmask && mctl->buttons & ~oldb) check(mctl->buttons, mctl->xy); oldb = mctl->buttons; break; case ARESIZE: if(getwindow(display, Refnone) < 0) sysfatal("getwindow: %r"); dreset(); break; case AKEY: switch(r){ case 'n': nuke(); init(); draw(fb, fb->r, col[CBACK], nil, ZP); plaster(); break; case 'q': case Kdel: nuke(); threadexitsall(nil); } break; } } }