ref: 87bb47145bb65c9fc656a71fdcbed62d6a7a6f81
dir: /spd.c/
#include <u.h> #include <libc.h> #include <bio.h> #include <draw.h> #include <thread.h> #include <keyboard.h> #include <mouse.h> #include <cursor.h> enum{ KMAX = 24 * UTFmax, DICTLEN = 32<<10, TMAX = 32, }; char dict[DICTLEN][KMAX]; int ndic; char *df = "/lib/words"; typedef struct Txt Txt; struct Txt{ Point; char s[KMAX]; char *m; int d; Image *c; Txt *p; Txt *n; }; Txt t0, *tl, *cur; vlong tics; int nt, words, miss, mistyp; enum{ NSEC = 1000000000 }; vlong div, div0 = NSEC/2; /* tic duration (ns) */ int dage = 16; int tddd = 64; /* tic duration decrease delay (tics) */ int newdt = 3; /* target spawn delay (tics) */ int missmax = 10; /* max targets let go */ long tm0; int done; enum{ CBACK, CTXT, CSTALE, COLD, CCUR, CEND }; Image *fb, *col[CEND]; Point fs, wc; int dy; Mousectl *mc; Keyboardctl *kc; Channel *tc; 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(tc, nil) < 0) break; } δ = div; dt = (t + 2*δ - nsec()) / 1000000; t += δ; } } void pop(Txt *t) { t->p->n = t->n; t->n->p = t->p; nt--; free(t); if(cur == t) cur = nil; } void nuke(void) { while(tl->n != tl) pop(tl->n); } void drw(Txt *t, Image *c) { if(c == nil) c = t->c; string(fb, *t, c, ZP, font, t->s); } void drwcur(void) { char c; c = *cur->m; *cur->m = 0; drw(cur, col[CCUR]); *cur->m = c; } void spawn(void) { Txt *t; t = mallocz(sizeof *t, 1); if(t == nil) sysfatal("talloc: %r"); t->p = tl->p; t->n = tl; tl->p->n = t; tl->p = t; nt++; sprint(t->s, dict[nrand(ndic)]); t->c = col[CTXT]; t->y = nrand(dy); drw(t, nil); } void plaster(void) { draw(screen, screen->r, fb, nil, ZP); flushimage(display, 1); } void score(void) { Point o; long t; char s[72]; /* FIXME */ /* this wpm figure is bullshit */ t = (time(nil) - tm0) / 60; sprint(s, "words %d misses %d mistypes %d in %lds", words, miss, mistyp, t); o = subpt(wc, divpt(stringsize(font, s), 2)); string(fb, o, col[CCUR], ZP, font, s); plaster(); } void adv(void) { Txt *t; tics++; if(tics % tddd == 0) div -= div0 / 16; for(t=tl->n; t != tl; t=t->n){ t->d++; drw(t, col[CBACK]); t->x += fs.x; if(t->d % dage == 0){ switch(t->d / dage){ case 1: t->c = col[CSTALE]; break; case 2: t->c = col[COLD]; break; case 3: miss++; if(miss >= missmax){ done++; score(); } pop(t); continue; } } drw(t, nil); } if(cur != nil) drwcur(); if(tics % newdt == 0 && nt < TMAX) spawn(); if(tl->n == tl){ spawn(); tics += newdt - tics % newdt; } } int check(Rune k) { Txt *t; if(cur == nil){ for(t=tl->n; t != tl; t=t->n) if(utfrune(t->s, k) == t->s){ t->m = t->s + runelen(k); cur = t; drwcur(); return 1; } }else if(utfrune(cur->m, k) == cur->m){ cur->m += runelen(k); if(cur->m >= cur->s + strlen(cur->s)){ words++; drw(cur, col[CBACK]); pop(cur); cur = nil; }else drwcur(); return 1; } mistyp++; return 0; } void dreset(void) { Txt *t; dy = Dy(screen->r) - fs.y; if(dy < 4 * fs.y || Dx(screen->r) < KMAX * fs.x / 8) sysfatal("window too small"); wc = Pt(Dx(screen->r)/2, Dy(screen->r)/2); 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=tl->n; t != tl; t=t->n) drw(t, nil); if(done) score(); plaster(); } void init(void) { div = div0; tics = 0; words = miss = mistyp = 0; tm0 = time(nil); done = 0; spawn(); plaster(); } void menu(void) { enum{NEW, QUIT}; static char *item[] = { [NEW] "new", [QUIT] "quit", nil }; static Menu m = { item, nil, 0 }; switch(menuhit(3, mc, &m, nil)){ case NEW: nuke(); init(); draw(fb, fb->r, col[CBACK], nil, ZP); plaster(); break; case QUIT: nuke(); threadexitsall(nil); } } void load(char *f) { int n; char *s, (*p)[KMAX]; Biobuf *bf; bf = Bopen(f, OREAD); if(bf == nil) sysfatal("load: %r"); p = dict; while(s = Brdline(bf, '\n'), s != nil && (char*)p < dict[nelem(dict)]){ n = Blinelen(bf); if(n > sizeof *p) n = sizeof *p; memcpy(*p, s, n-1); ndic++; p++; } Bterm(bf); } void usage(void) { print("%s [-d n] [-h hz] [-m n] [-o tics] [-r tics] [dict]\n", argv0); threadexitsall("usage"); } void threadmain(int argc, char *argv[]) { double d; Rectangle r; Rune k; ARGBEGIN{ case 'd': newdt = strtol(EARGF(usage()), nil, 0); break; case 'h': d = strtod(EARGF(usage()), nil); if(d <= 0.0) sysfatal("invalid divisor %f", d); div0 = NSEC / d; break; case 'm': missmax = strtol(EARGF(usage()), nil, 0); break; case 'o': dage = strtol(EARGF(usage()), nil, 0); break; case 'r': tddd = strtol(EARGF(usage()), nil, 0); break; default: usage(); }ARGEND if(argc > 0) df = *argv; load(df); tl = &t0; tl->n = tl->p = tl; if(initdraw(nil, nil, "spd") < 0) sysfatal("initdraw: %r"); fs = stringsize(font, "A"); r = Rect(0,0,1,1); col[CBACK] = display->black; col[CTXT] = eallocim(r, 1, 0xbb4400ff); col[CSTALE] = eallocim(r, 1, 0x990000ff); col[COLD] = eallocim(r, 1, 0x440000ff); col[CCUR] = eallocim(r, 1, 0xdd9300ff); dreset(); mc = initmouse(nil, screen); if(mc == nil) sysfatal("initmouse: %r"); kc = initkeyboard(nil); if(kc == nil) sysfatal("initkeyboard: %r"); tc = chancreate(sizeof(int), 0); if(tc == 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] {tc, nil, CHANRCV}, [AMOUSE] {mc->c, &mc->Mouse, CHANRCV}, [ARESIZE] {mc->resizec, nil, CHANRCV}, [AKEY] {kc->c, &k, CHANRCV}, [AEND] {nil, nil, CHANEND} }; for(;;) switch(alt(a)){ case ATIC: if(done) break; adv(); plaster(); break; case AMOUSE: if(mc->buttons & 4) menu(); break; case ARESIZE: if(getwindow(display, Refnone) < 0) sysfatal("getwindow: %r"); dreset(); break; case AKEY: if(check(k)) plaster(); break; } }