ref: 4821c261c4cea895ddcbd7c70bc71103c9a2763b
parent: baf20a548b903bb76f94045645f9b679383bb728
author: aiju <devnull@localhost>
date: Thu Jun 23 17:12:06 EDT 2016
added games/timmy
--- /dev/null
+++ b/sys/man/1/timmy
@@ -1,0 +1,51 @@
+.TH TIMMY 1
+.SH NAME
+timmy \- physics sandbox
+.SH SYNOPSIS
+.B games/timmy
+[
+.B -s
+.I steps-per-frame
+]
+.SH DESCRIPTION
+.I Timmy
+is a simple 2D physics sandbox.
+.PP
+To pick up an object click on it with the LMB.
+New objects can be created by picking up their archetypes in the gray area on the bottom (the "tray").
+To place an object in the working area click at the desired position with the LMB;
+.I timmy
+will refuse to place the object if it would collide with an existing one.
+To abort the process \(em deleting the carried object \(em click anywhere with the RMB.
+Picking up an object in the working area with the RMB will duplicate the object.
+.PP
+The following operations can be performed with the keyboard.
+.IP w
+Rotate carried object by 15° to the left.
+.IP e
+Rotate carried object by 15° to the right.
+.IP space
+Start or stop the simulation.
+.IP del
+Exit timmy.
+.PP
+The small circles on some objects are "hinges".
+Two hinges can be connected by placing them on top of each other.
+Their relative position will not change during the simulation; objects are however free to rotate around them.
+To undo a hinge, pick up either of the objects.
+.PP
+The
+.B -s
+option adjusts the speed of the simulation; only integer values are permitted.
+It does not compromise accuracy.
+.SH SOURCE
+.B /sys/src/games/timmy
+.SH BUGS
+.IR Timmy 's
+physics may occasionally appear to originate from another universe.
+.PP
+.B -s
+is a hack.
+.SH HISTORY
+.I Timmy
+first appeared in 9front (June, 2016).
--- /dev/null
+++ b/sys/src/games/timmy/dat.h
@@ -1,0 +1,56 @@
+typedef struct Hinge Hinge;
+typedef struct ObjT ObjT;
+typedef struct Obj Obj;
+typedef struct Poly Poly;
+typedef struct Vec Vec;
+
+struct Vec {
+ double x, y;
+};
+
+struct Poly {
+ int nvert;
+ Vec *vert;
+ double invI;
+};
+
+struct Hinge {
+ Vec p;
+ Vec p0;
+ Obj *o;
+ Hinge *onext;
+ Hinge *cnext, *cprev;
+ Hinge *anext;
+};
+
+struct ObjT {
+ int flags;
+ Poly *poly;
+ Hinge *hinge;
+ Image *line, *fill;
+ double imass;
+ void (*draw)(Obj *, Image *);
+ void (*move)(Obj *, double, double, double);
+ void (*init)(Obj *);
+};
+
+struct Obj {
+ ObjT *tab;
+ Vec p, v;
+ double θ, ω;
+ Rectangle bbox;
+ Poly *poly;
+ Hinge *hinge;
+ Obj *next, *prev;
+ int idx;
+};
+
+enum {
+ TrayH = 100,
+ TraySpc = 20,
+};
+
+#define DEG 0.01745329251994330
+#define Slop 0.5
+
+#define HingeSep 4.0
--- /dev/null
+++ b/sys/src/games/timmy/fns.h
@@ -1,0 +1,25 @@
+void *emalloc(ulong);
+Image *rgb(u32int);
+Poly *mkpoly(int, ...);
+Poly *polydup(Poly *);
+void polytrans(Poly *, Poly *, double, double, double);
+void polydraw(Poly *, Image *, Image *, Image *);
+void polybbox(Poly *, Rectangle *);
+void polyfix(Poly *);
+Obj *mkobj(ObjT *);
+Obj *objdup(Obj *);
+void objcat(Obj *, Obj *);
+Vec vecadd(Vec, Vec);
+Vec vecsub(Vec, Vec);
+Vec vecmul(Vec, double);
+Vec vecnorm(Vec);
+double vecdist(Vec, Vec);
+double vecdot(Vec, Vec);
+Vec vecnormal(Vec);
+int objcoll(Obj *, Obj *);
+void freeobj(Obj *);
+void objexcise(Obj *);
+void addtray(ObjT *, ...);
+void physstep(void);
+int hinged(Obj *, Obj *);
+void copyhinges(Obj *, Obj *);
--- /dev/null
+++ b/sys/src/games/timmy/mkfile
@@ -1,0 +1,14 @@
+</$objtype/mkfile
+
+BIN=/$objtype/bin/games
+TARG=timmy
+OFILES=\
+ timmy.$O\
+ simple.$O\
+ poly.$O\
+ util.$O\
+ phys.$O\
+
+HFILES=dat.h fns.h
+
+</sys/src/cmd/mkone
--- /dev/null
+++ b/sys/src/games/timmy/phys.c
@@ -1,0 +1,219 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include "dat.h"
+#include "fns.h"
+
+extern Obj runo;
+#define Grav 10
+#define Dt 0.01
+#define Beta 0.5
+int Steps = 4;
+
+typedef struct Contact Contact;
+struct Contact {
+ Obj *vo, *eo;
+ Vec v, n;
+ double pen;
+ double impn, impt;
+ double targun;
+};
+enum { CTSBLOCK = 64 };
+
+Contact *cts;
+int icts, ncts;
+
+static void
+colldect(Obj *a, Obj *b)
+{
+ int i, j;
+ Vec *x, *y, *z;
+ double d, m;
+ Poly *ap, *bp;
+
+ ap = a->poly;
+ bp = b->poly;
+ for(i = 0; i < ap->nvert; i++){
+ z = &ap->vert[i];
+ m = Inf(1);
+ for(j = 0; j < bp->nvert; j++){
+ x = &bp->vert[j];
+ y = x + 1;
+ d = -(z->x - x->x) * (y->y - x->y) + (z->y - x->y) * (y->x - x->x);
+ d /= vecdist(*x, *y);
+ if(d < -Slop) goto next;
+ if(d < m){
+ if(m < 0) goto next;
+ m = d;
+ if(icts == ncts)
+ cts = realloc(cts, sizeof(Contact) * (ncts += CTSBLOCK));
+ cts[icts] = (Contact){a, b, *z, vecnormal(vecsub(*x, *y)), d, 0, 0, 0};
+ }else if(d < 0) goto next;
+ }
+ icts++;
+ next: ;
+ }
+}
+
+static void
+collresp(Contact *p)
+{
+ double μs, μd;
+ Vec n, t, u, r0, r1, Δp;
+ double ut, un, α, γ, γt, γn, pt, mt, mn;
+ double r0n, r0t, r1n, r1t;
+ double mv, me, Iv, Ie;
+
+ n = p->n;
+ t = (Vec){n.y, -n.x};
+ mv = p->vo->tab->imass;
+ me = p->eo->tab->imass;
+ Iv = mv * p->vo->poly->invI;
+ Ie = me * p->eo->poly->invI;
+ r0 = vecsub(p->v, p->vo->p);
+ r1 = vecsub(p->v, p->eo->p);
+ Δp.x = -(t.x * p->impt + n.x * p->impn);
+ Δp.y = -(t.y * p->impt + n.y * p->impn);
+ p->vo->v = vecadd(p->vo->v, vecmul(Δp, mv));
+ p->vo->ω -= (Δp.x * r0.y - Δp.y * r0.x) * Iv;
+ p->eo->v = vecadd(p->eo->v, vecmul(Δp, -me));
+ p->eo->ω += (Δp.x * r1.y - Δp.y * r1.x) * Ie;
+ u.x = p->vo->v.x - p->vo->ω * r0.y - p->eo->v.x + p->eo->ω * r1.y;
+ u.y = p->vo->v.y + p->vo->ω * r0.x - p->eo->v.y - p->eo->ω * r1.x;
+ ut = vecdot(u, t);
+ un = vecdot(u, n);
+ r0t = vecdot(r0, t);
+ r0n = vecdot(r0, n);
+ r1t = vecdot(r1, t);
+ r1n = vecdot(r1, n);
+ γ = 0; /* accumulated normal impulse */
+ pt = 0; /* accumulated transverse impulse */
+ μs = 0.5;
+ μd = 0.3;
+
+ un += p->targun;
+ if(un >= 0 && p->pen <= 0){
+ p->impt = 0;
+ p->impn = 0;
+ return;
+ }
+ if(p->pen > 0){
+ un -= Beta * p->pen / Dt;
+ if(un >= 0){
+ mn = mv + r0t * r0t * Iv;
+ mn += me + r1t * r1t * Ie;
+ γ = -un/mn;
+ un = 0;
+ }
+ }
+ while(un < 0){
+ /* calculate α, the effective coefficient of friction */
+ if(ut == 0){
+ α = r0t * r0n * Iv + r1t * r1n * Ie;
+ α /= mv + r0n * r0n * Iv + me + r1n * r1n * Ie;
+ if(α > μs) α = μd;
+ else if(α < -μs) α = -μd;
+ }else
+ α = ut < 0 ? μd : -μd;
+
+ mt = α * mv + (r0n * r0n * α - r0t * r0n) * Iv;
+ mt += α * me + (r1n * r1n * α - r1t * r1n) * Ie;
+ mn = mv + (r0t * r0t - r0n * r0t * α) * Iv;
+ mn += me + (r1t * r1t - r1n * r1t * α) * Ie;
+ /* determine events which would change α */
+ if(ut == 0) γt = Inf(1);
+ else{
+ γt = γ - ut / mt;
+ if(γt < γ) γt = Inf(1);
+ }
+ γn = γ - un / mn;
+ if(γn < γ) γn = Inf(1);
+ /* choose earlier one */
+ if(γt < γn){
+ ut = 0;
+ un += mn * (γt - γ);
+ pt += (γt - γ) * α;
+ γ = γt;
+ }else{
+ assert(γn < Inf(1));
+ un = 0;
+ ut += mt * (γn - γ);
+ pt += (γn - γ) * α;
+ γ = γn;
+ }
+ }
+
+ p->impt = pt;
+ p->impn = γ;
+ Δp.x = t.x * pt + n.x * γ;
+ Δp.y = t.y * pt + n.y * γ;
+ p->vo->v = vecadd(p->vo->v, vecmul(Δp, mv));
+ p->vo->ω -= (Δp.x * r0.y - Δp.y * r0.x) * Iv;
+ p->eo->v = vecadd(p->eo->v, vecmul(Δp, -me));
+ p->eo->ω += (Δp.x * r1.y - Δp.y * r1.x) * Ie;
+}
+
+extern Hinge *hinges;
+
+static void
+hingeresp(Hinge *h)
+{
+ Obj *a, *b;
+ Vec u, Δp, r0, r1;
+ double ma, mb, Ia, Ib;
+ double mxx, mxy, myy, det;
+
+ a = h->o;
+ b = h->cnext->o;
+ ma = a->tab->imass;
+ mb = b->tab->imass;
+ Ia = ma * a->poly->invI;
+ Ib = mb * b->poly->invI;
+ r0 = vecsub(h->p, a->p);
+ r1 = vecsub(h->cnext->p, b->p);
+ u.x = a->v.x - a->ω * r0.y - b->v.x + b->ω * r1.y;
+ u.y = a->v.y + a->ω * r0.x - b->v.y - b->ω * r1.x;
+ u.x += Beta * (h->p.x - h->cnext->p.x) / Dt;
+ u.y += Beta * (h->p.y - h->cnext->p.y) / Dt;
+ mxx = ma + Ia * r0.x * r0.x + mb + Ib * r1.x * r1.x;
+ mxy = Ia * r0.x * r0.y + Ib * r1.x * r1.y;
+ myy = ma + Ia * r0.y * r0.y + mb + Ib * r1.y * r1.y;
+ det = mxx * myy - mxy * mxy;
+ Δp.x = (mxx * u.x + mxy * u.y) / det;
+ Δp.y = (myy * u.y + mxy * u.x) / det;
+ a->v = vecadd(a->v, vecmul(Δp, -ma));
+ a->ω += (Δp.x * r0.y - Δp.y * r0.x) * Ia;
+ b->v = vecadd(b->v, vecmul(Δp, mb));
+ b->ω -= (Δp.x * r1.y - Δp.y * r1.x) * Ib;
+ u.x = a->v.x - a->ω * r0.y - b->v.x + b->ω * r1.y;
+ u.y = a->v.y + a->ω * r0.x - b->v.y - b->ω * r1.x;
+}
+
+void
+physstep(void)
+{
+ Obj *o, *a, *b;
+ int i, j, k;
+ Hinge *p;
+
+ for(k = 0; k < Steps; k++){
+ for(o = runo.next; o != &runo; o = o->next)
+ if(o->tab->imass != 0)
+ o->v.y += Grav * Dt;
+ icts = 0;
+ for(a = runo.next; a != &runo; a = a->next)
+ for(b = a->next; b != &runo; b = b->next){
+ if(!rectXrect(a->bbox, b->bbox) || a->poly == nil || b->poly == nil || hinged(a, b)) continue;
+ colldect(a, b);
+ colldect(b, a);
+ }
+ for(j = 0; j < 10; j++){
+ for(i = 0; i < icts; i++)
+ collresp(&cts[i]);
+ for(p = hinges; p != nil; p = p->anext)
+ hingeresp(p);
+ }
+ for(o = runo.next; o != &runo; o = o->next)
+ o->tab->move(o, o->p.x + o->v.x * Dt, o->p.y + o->v.y * Dt, o->θ + o->ω * Dt / DEG);
+ }
+}
--- /dev/null
+++ b/sys/src/games/timmy/poly.c
@@ -1,0 +1,323 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include <draw.h>
+#include <mouse.h>
+#include "dat.h"
+#include "fns.h"
+
+Poly *
+mkpoly(int n, ...)
+{
+ Poly *p;
+ int i;
+ va_list va;
+
+ p = emalloc(sizeof(Poly));
+ p->nvert = n;
+ p->vert = emalloc((n + 1) * sizeof(Vec));
+ va_start(va, n);
+ for(i = 0; i < n; i++){
+ p->vert[i].x = va_arg(va, double);
+ p->vert[i].y = va_arg(va, double);
+ }
+ p->vert[n] = p->vert[0];
+ va_end(va);
+ polyfix(p);
+ return p;
+}
+
+void
+polyfix(Poly *o)
+{
+ double I, A, x, y, t;
+ Vec *p, *q;
+ int i;
+
+ I = 0;
+ A = 0;
+ x = 0;
+ y = 0;
+ for(i = 0; i < o->nvert; i++){
+ p = &o->vert[i];
+ q = p + 1;
+ t = p->x * q->y - p->y * q->x;
+ A += t;
+ x += (p->x + q->x) * t / 3;
+ y += (p->y + q->y) * t / 3;
+ }
+ x /= A;
+ y /= A;
+ for(i = 0; i <= o->nvert; i++){
+ o->vert[i].x -= x;
+ o->vert[i].y -= y;
+ }
+ for(i = 0; i < o->nvert; i++){
+ p = &o->vert[i];
+ q = p + 1;
+ t = p->x * q->y - p->y * q->x;
+ I += (p->x * (p->x + q->x) + q->x * q->x + p->y * (p->y + q->y) + q->y * q->y) * t / 6;
+ }
+ o->invI = A / I;
+}
+
+Poly *
+polydup(Poly *p)
+{
+ Poly *q;
+ int i;
+
+ q = emalloc(sizeof(Poly));
+ q->nvert = p->nvert;
+ q->vert = emalloc((p->nvert + 1) * sizeof(Vec));
+ for(i = 0; i <= p->nvert; i++)
+ q->vert[i] = p->vert[i];
+ q->invI = p->invI;
+ return q;
+}
+
+void
+polytrans(Poly *sp, Poly *dp, double x0, double y0, double θ)
+{
+ int i;
+ double c, s, x, y;
+
+ assert(sp->nvert == dp->nvert);
+ c = cos(θ * DEG);
+ s = sin(θ * DEG);
+ for(i = 0; i <= sp->nvert; i++){
+ x = sp->vert[i].x;
+ y = sp->vert[i].y;
+ dp->vert[i].x = x0 + x * c - y * s;
+ dp->vert[i].y = y0 + x * s + y * c;
+ }
+ dp->invI = sp->invI;
+}
+
+void
+polybbox(Poly *sp, Rectangle *t)
+{
+ int fx, fy, cx, cy, i;
+
+ t->min.x = floor(sp->vert[0].x - Slop);
+ t->max.x = ceil(sp->vert[0].x + Slop) + 1;
+ t->min.y = floor(sp->vert[0].y - Slop);
+ t->max.y = ceil(sp->vert[0].y + Slop) + 1;
+ for(i = 1; i < sp->nvert; i++){
+ fx = sp->vert[i].x;
+ cx = ceil(fx + Slop); fx = floor(fx - Slop);
+ fy = sp->vert[i].y + 1;
+ cy = ceil(fy + Slop); fy = floor(fy - Slop);
+ if(fx < t->min.x) t->min.x = fx;
+ if(cx > t->max.x) t->max.x = cx;
+ if(fy < t->min.y) t->min.y = fy;
+ if(cy > t->max.y) t->max.y = cy;
+ }
+}
+
+void
+polydraw(Poly *p, Image *d, Image *fill, Image *line)
+{
+ int i;
+ static int maxp;
+ static Point *buf;
+
+ if(p->nvert + 1 > maxp){
+ maxp = p->nvert + 1;
+ free(buf);
+ buf = emalloc((p->nvert + 1) * sizeof(Point));
+ }
+ for(i = 0; i <= p->nvert; i++){
+ buf[i].x = d->r.min.x + (int)(p->vert[i].x + 0.5);
+ buf[i].y = d->r.min.y + (int)(p->vert[i].y + 0.5);
+ }
+ if(fill != nil) fillpoly(d, buf, p->nvert + 1, 0, fill, ZP);
+ if(line != nil) poly(d, buf, p->nvert + 1, 0, 0, 0, line, ZP);
+}
+
+void
+freepoly(Poly *p)
+{
+ if(p == nil) return;
+ free(p->vert);
+ free(p);
+}
+
+Hinge *
+hingedup(Hinge *h, Obj *o)
+{
+ Hinge *p, **hp, *r;
+
+ r = nil;
+ hp = &r;
+ for(; h != nil; h = h->onext){
+ p = emalloc(sizeof(Hinge));
+ p->p = h->p;
+ p->p0 = h->p0;
+ p->o = o;
+ p->cnext = p->cprev = p;
+ *hp = p;
+ hp = &p->onext;
+ }
+ return r;
+}
+
+Obj *
+mkobj(ObjT *t)
+{
+ Obj *o;
+
+ o = emalloc(sizeof(Obj));
+ o->tab = t;
+ o->hinge = hingedup(t->hinge, o);
+ o->tab->init(o);
+ o->next = o->prev = o;
+ return o;
+}
+
+Obj *
+objdup(Obj *o)
+{
+ Obj *p;
+
+ p = emalloc(sizeof(Obj));
+ *p = *o;
+ p->poly = polydup(o->poly);
+ p->next = p->prev = p;
+ p->hinge = hingedup(p->hinge, p);
+ return p;
+}
+
+void
+objcat(Obj *l, Obj *o)
+{
+ o->prev = l->prev;
+ o->next = l;
+ o->prev->next = o;
+ o->next->prev = o;
+}
+
+static int
+polycheck(Poly *a, Poly *b)
+{
+ int i, j;
+ Vec *x, *y, *z;
+ double d, m;
+
+ for(i = 0; i < a->nvert; i++){
+ z = &a->vert[i];
+ m = Inf(1);
+ for(j = 0; j < b->nvert; j++){
+ x = &b->vert[j];
+ y = x + 1;
+ d = (z->y - x->y) * (y->x - x->x) - (z->x - x->x) * (y->y - x->y);
+ d /= vecdist(*x, *y);
+ if(d < -Slop) goto next;
+ if(d < m){
+ if(m < 0) goto next;
+ m = d;
+ }else if(d < 0) goto next;
+ }
+ return 1;
+ next:;
+ }
+ return 0;
+}
+
+int
+objcoll(Obj *a, Obj *b)
+{
+ if(!rectXrect(a->bbox, b->bbox)) return 0;
+ if(a->poly == nil || b->poly == nil) return 0;
+ return polycheck(a->poly, b->poly) || polycheck(b->poly, a->poly);
+}
+
+void
+objexcise(Obj *o)
+{
+ Hinge *h;
+
+ o->next->prev = o->prev;
+ o->prev->next = o->next;
+ o->next = o;
+ o->prev = o;
+ for(h = o->hinge; h != nil; h = h->onext){
+ h->cprev->cnext = h->cnext;
+ h->cnext->cprev = h->cprev;
+ h->cprev = h;
+ h->cnext = h;
+ }
+}
+
+void
+freeobj(Obj *o)
+{
+ if(o == nil) return;
+ objexcise(o);
+ freepoly(o->poly);
+ free(o);
+}
+
+int
+hinged(Obj *a, Obj *b)
+{
+ Hinge *k, *l, *m;
+
+ if(b->hinge == nil) return 0;
+ for(k = a->hinge; k != nil; k = k->onext)
+ for(l = k->cnext; l != k; l = l->cnext)
+ for(m = b->hinge; m != nil; m = m->onext)
+ if(m == l)
+ return 1;
+ return 0;
+}
+
+Hinge *hinges;
+
+void
+hingepairs(Obj *l)
+{
+ Obj *o;
+ Hinge *h, *hh;
+ Hinge **p;
+
+ hinges = nil;
+ p = &hinges;
+ for(o = l->next; o != l; o = o->next)
+ for(h = o->hinge; h != nil; h = h->onext){
+ for(hh = h->cnext; hh != h; hh = hh->cnext)
+ if(hh < h)
+ break;
+ if(hh == h) continue;
+ *p = h;
+ p = &h->anext;
+ }
+}
+
+void
+copyhinges(Obj *sl, Obj *dl)
+{
+ Obj *o, *p, **ol;
+ Hinge *h, *k, *l;
+ int n;
+
+ for(n = 0, o = sl->next; o != sl; o = o->next)
+ o->idx = n++;
+ ol = emalloc(sizeof(Obj *) * n);
+ for(n = 0, o = dl->next; o != dl; o = o->next)
+ ol[n++] = o;
+ for(o = sl->next, p = dl->next; o != sl; o = o->next, p = p->next){
+ for(h = o->hinge, k = p->hinge; h != nil; h = h->onext, k = k->onext){
+ if(h->cnext == h) continue;
+ for(l = h->cnext->o->hinge, n = 0; l != h->cnext; l = l->onext)
+ n++;
+ for(l = ol[h->cnext->o->idx]->hinge; n != 0; n--)
+ l = l->onext;
+ l->cprev->cnext = k->cnext;
+ k->cnext->cprev = l->cprev;
+ k->cnext = l;
+ l->cprev = k;
+ }
+ }
+ hingepairs(dl);
+}
--- /dev/null
+++ b/sys/src/games/timmy/simple.c
@@ -1,0 +1,107 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include "dat.h"
+#include "fns.h"
+
+void
+objpolyinit(Obj *o)
+{
+ o->poly = polydup(o->tab->poly);
+}
+
+void
+objpolymove(Obj *o, double x, double y, double θ)
+{
+ Hinge *h;
+ double c, s;
+
+ o->p.x = x;
+ o->p.y = y;
+ o->θ = θ;
+ polytrans(o->tab->poly, o->poly, x, y, θ);
+ polybbox(o->poly, &o->bbox);
+ if(o->hinge != nil){
+ c = cos(θ * DEG);
+ s = sin(θ * DEG);
+ for(h = o->hinge; h != nil; h = h->onext){
+ h->p.x = c * h->p0.x - s * h->p0.y + x;
+ h->p.y = s * h->p0.x + c * h->p0.y + y;
+ }
+ }
+}
+
+void
+objpolydraw(Obj *o, Image *i)
+{
+ Hinge *h;
+ Point p;
+
+ polydraw(o->poly, i, o->tab->fill, o->tab->line);
+ for(h = o->hinge; h != nil; h = h->onext){
+ p.x = i->r.min.x + (int)(h->p.x + 0.5);
+ p.y = i->r.min.y + (int)(h->p.y + 0.5);
+ ellipse(i, p, 2, 2, 0, display->black, ZP);
+ }
+}
+
+void
+mkobjpoly(ObjT *t, Poly *p, Image *fill, Image *line, double imass)
+{
+ t->init = objpolyinit;
+ t->draw = objpolydraw;
+ t->move = objpolymove;
+ t->poly = p;
+ t->line = line;
+ t->fill = fill;
+ t->imass = imass;
+}
+
+void
+addhinge(ObjT *t, double x, double y)
+{
+ Hinge *h, **hp;
+
+ h = emalloc(sizeof(Hinge));
+ h->p.x = x;
+ h->p.y = y;
+ h->p0 = h->p;
+ h->cnext = h->cprev = h;
+ for(hp = &t->hinge; *hp != nil; hp = &(*hp)->onext)
+ ;
+ *hp = h;
+}
+
+Poly *
+mkball(int n)
+{
+ Poly *p;
+ int i;
+
+ p = emalloc(sizeof(Poly));
+ p->nvert = n;
+ p->vert = emalloc(sizeof(Vec) * (n + 1));
+ for(i = 0; i < n; i++){
+ p->vert[i].x = 10 * cos(2 * PI * i / n);
+ p->vert[i].y = 10 * sin(2 * PI * i / n);
+ }
+ p->vert[n] = p->vert[0];
+ polyfix(p);
+ return p;
+}
+
+ObjT tdomino, tboard, thboard, tball, tfix;
+
+void
+simpleinit(void)
+{
+ mkobjpoly(&tdomino, mkpoly(4, 0.0, 0.0, 10.0, 0.0, 10.0, 40.0, 0.0, 40.0), rgb(0xFF0000FF), display->black, 10);
+ mkobjpoly(&tboard, mkpoly(4, 0.0, 0.0, 100.0, 0.0, 100.0, 6.0, 0.0, 6.0), rgb(0x663300FF), nil, 0);
+ mkobjpoly(&thboard, mkpoly(4, 0.0, 0.0, 100.0, 0.0, 100.0, 6.0, 0.0, 6.0), rgb(0x884400FF), nil, 0.5);
+ addhinge(&thboard, 48.0, 0.0);
+ addhinge(&thboard, -48.0, 0.0);
+ mkobjpoly(&tball, mkball(17), rgb(0x00FF00FF), nil, 3);
+ mkobjpoly(&tfix, mkpoly(3, 0.0, 0.0, 10.0, -17.3, 20.0, 0.0), rgb(0x663300FF), display->black, 0);
+ addhinge(&tfix, 0.0, 0.0);
+ addtray(&tdomino, &tboard, &thboard, &tfix, &tball, nil);
+}
--- /dev/null
+++ b/sys/src/games/timmy/timmy.c
@@ -1,0 +1,328 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include <draw.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <cursor.h>
+#include "dat.h"
+#include "fns.h"
+
+Screen *scr;
+Image *work, *tray;
+Image *grey;
+Obj trayo;
+Obj worko;
+Obj runo;
+Mousectl *mc;
+Keyboardctl *kc;
+Obj *carry;
+int showcarry;
+extern int Steps;
+
+void *
+emalloc(ulong sz)
+{
+ void *v;
+
+ v = malloc(sz);
+ if(v == nil) sysfatal("malloc: %r");
+ memset(v, 0, sz);
+ setmalloctag(v, getcallerpc(&sz));
+ return v;
+}
+
+Image *
+rgb(u32int c)
+{
+ return allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, c);
+}
+
+void
+addtray(ObjT *t, ...)
+{
+ Obj *o;
+ va_list va;
+ static double trayw;
+
+ va_start(va, t);
+ for(; t != nil; t = va_arg(va, ObjT *)){
+ o = mkobj(t);
+ o->tab->move(o, 0, 0, 0);
+ trayw += TraySpc;
+ o->tab->move(o, trayw + Dx(o->bbox)/2, TrayH/2, 0);
+ trayw += Dx(o->bbox);
+ objcat(&trayo, o);
+ }
+ va_end(va);
+}
+
+static void
+drawtray(void)
+{
+ Obj *o;
+
+ for(o = trayo.next; o != &trayo; o = o->next)
+ o->tab->draw(o, tray);
+}
+
+static void
+screeninit(void)
+{
+ grey = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, 0xCCCCCCFF);
+ scr = allocscreen(screen, display->white, 0);
+ work = allocwindow(scr, Rect(screen->r.min.x, screen->r.min.y, screen->r.max.x, screen->r.max.y - TrayH), 0, 0xFFFFFFFF);
+ tray = allocwindow(scr, Rect(screen->r.min.x, screen->r.max.y - TrayH, screen->r.max.x, screen->r.max.y), 0, 0xCCCCCCFF);
+}
+
+static Obj *
+objclick(Point p, Obj *l)
+{
+ Obj *o;
+
+ for(o = l->next; o != l; o = o->next)
+ if(ptinrect(p, o->bbox))
+ return o;
+ return nil;
+}
+
+static void
+workdraw(void)
+{
+ Obj *o;
+
+ draw(work, work->r, display->white, nil, ZP);
+ for(o = worko.next; o != &worko; o = o->next)
+ o->tab->draw(o, work);
+ if(carry != nil && showcarry)
+ carry->tab->draw(carry, work);
+ flushimage(display, 1);
+}
+
+static void
+rundraw(void)
+{
+ Obj *o;
+
+ draw(work, work->r, display->white, nil, ZP);
+ for(o = runo.next; o != &runo; o = o->next)
+ o->tab->draw(o, work);
+ flushimage(display, 1);
+}
+
+static int
+canhinge(Obj *a, Obj *b)
+{
+ Hinge *h, *k;
+
+ if(a->hinge == nil || b->hinge == nil) return 0;
+ for(h = a->hinge; h != nil; h = h->onext)
+ for(k = b->hinge; k != nil; k = k->onext)
+ if(vecdist(h->p, k->p) <= HingeSep)
+ return 1;
+ return 0;
+}
+
+static int
+hinge(Obj *a, Obj *b)
+{
+ Hinge *h, *k, *l;
+
+ if(a->hinge == nil || b->hinge == nil) return 0;
+ for(h = a->hinge; h != nil; h = h->onext)
+ for(k = b->hinge; k != nil; k = k->onext)
+ if(vecdist(h->p, k->p) <= HingeSep){
+ h->cprev->cnext = k;
+ k->cprev->cnext = h;
+ l = h->cprev;
+ h->cprev = k->cprev;
+ k->cprev = l;
+ b->tab->move(b, b->p.x + h->p.x - k->p.x, b->p.y + h->p.y - k->p.y, b->θ);
+ return 1;
+ }
+ return 0;
+}
+
+static void
+place(void)
+{
+ Obj *o;
+ int hinges;
+
+ hinges = 0;
+ for(o = worko.next; o != &worko; o = o->next)
+ if(objcoll(o, carry))
+ if(canhinge(o, carry))
+ hinges++;
+ else
+ return;
+ for(o = worko.next; hinges > 0 && o != &worko; o = o->next)
+ if(objcoll(o, carry))
+ hinges -= hinge(o, carry);
+ if(hinges != 0) print("hinge error\n");
+ objcat(&worko, carry);
+ carry = nil;
+ workdraw();
+}
+
+static void
+mouse(void)
+{
+ static int lbut = -1;
+ Point p;
+
+ if(lbut < 0)
+ lbut = mc->buttons;
+ if(ptinrect(mc->xy, work->r)){
+ p = subpt(mc->xy, work->r.min);
+ if(carry != nil && (carry->p.x != p.x || carry->p.y != p.y || !showcarry)){
+ carry->tab->move(carry, p.x, p.y, carry->θ);
+ showcarry = 1;
+ workdraw();
+ }
+ }else if(showcarry){
+ showcarry = 0;
+ if(carry != nil)
+ workdraw();
+ }
+ if((~mc->buttons & lbut & 1) != 0){
+ if(ptinrect(mc->xy, tray->r)){
+ carry = objclick(subpt(mc->xy, tray->r.min), &trayo);
+ if(carry != nil)
+ carry = objdup(carry);
+ }else if(ptinrect(mc->xy, work->r)){
+ if(carry != nil)
+ place();
+ else{
+ carry = objclick(subpt(mc->xy, work->r.min), &worko);
+ if(carry != nil)
+ objexcise(carry);
+ }
+ }
+ }
+ if((~mc->buttons & lbut & 4) != 0){
+ if(carry != nil){
+ freeobj(carry);
+ carry = nil;
+ showcarry = 0;
+ workdraw();
+ }else if(ptinrect(mc->xy, work->r)){
+ carry = objclick(subpt(mc->xy, work->r.min), &worko);
+ if(carry != nil)
+ carry = objdup(carry);
+ }
+ }
+ lbut = mc->buttons;
+}
+
+static void
+run(void)
+{
+ Obj *o, *oo;
+ Rune r;
+ static Cursor cursor;
+
+ for(o = runo.next; o != &runo; o = oo){
+ oo = o->next;
+ freeobj(o);
+ }
+ for(o = worko.next; o != &worko; o = o->next)
+ objcat(&runo, objdup(o));
+ copyhinges(&worko, &runo);
+ setcursor(mc, &cursor);
+ for(;;){
+ Alt a[] = {
+ {mc->c, &mc->Mouse, CHANRCV},
+ {kc->c, &r, CHANRCV},
+ {nil, nil, CHANNOBLK}
+ };
+
+ switch(alt(a)){
+ case 0: mouse(); break;
+ case 1:
+ switch(r){
+ case ' ': goto out;
+ case Kdel: threadexitsall(nil);
+ }
+ }
+
+ physstep();
+ rundraw();
+ }
+out:
+ workdraw();
+ setcursor(mc, nil);
+}
+
+static void
+key(Rune r)
+{
+ switch(r){
+ case Kdel:
+ threadexitsall(nil);
+ case 'w':
+ if(carry != nil){
+ carry->tab->move(carry, carry->p.x, carry->p.y, carry->θ - 15);
+ workdraw();
+ }
+ break;
+ case 'e':
+ if(carry != nil){
+ carry->tab->move(carry, carry->p.x, carry->p.y, carry->θ + 15);
+ workdraw();
+ }
+ break;
+ case ' ':
+ run();
+ break;
+ }
+}
+
+static void
+usage(void)
+{
+ fprint(2, "usage: %s [-s steps]\n", argv0);
+ threadexitsall("usage");
+}
+
+void
+threadmain(int argc, char **argv)
+{
+ void simpleinit(void);
+ Rune r;
+ char *s;
+
+ ARGBEGIN{
+ case 's':
+ Steps = strtol(EARGF(usage()), &s, 0);
+ if(*s != 0) usage();
+ break;
+ default: usage();
+ }ARGEND;
+
+ if(initdraw(nil, nil, nil) < 0) sysfatal("initdraw: %r");
+ mc = initmouse(nil, screen);
+ if(mc == nil) sysfatal("initmouse: %r");
+ kc = initkeyboard(nil);
+ if(kc == nil) sysfatal("initkeyboard: %r");
+ screeninit();
+ trayo.prev = trayo.next = &trayo;
+ worko.prev = worko.next = &worko;
+ runo.prev = runo.next = &runo;
+ simpleinit();
+ drawtray();
+ flushimage(display, 1);
+
+ for(;;){
+ Alt a[] = {
+ {mc->c, &mc->Mouse, CHANRCV},
+ {kc->c, &r, CHANRCV},
+ {nil, nil, CHANEND}
+ };
+
+ switch(alt(a)){
+ case 0: mouse(); break;
+ case 1: key(r); break;
+ }
+ }
+}
--- /dev/null
+++ b/sys/src/games/timmy/util.c
@@ -1,0 +1,60 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include <draw.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include "dat.h"
+#include "fns.h"
+
+Vec
+vecadd(Vec a, Vec b)
+{
+ return (Vec){a.x + b.x, a.y + b.y};
+}
+
+Vec
+vecsub(Vec a, Vec b)
+{
+ return (Vec){a.x - b.x, a.y - b.y};
+}
+
+Vec
+vecmul(Vec v, double s)
+{
+ return (Vec){v.x * s, v.y * s};
+}
+
+Vec
+vecnorm(Vec v)
+{
+ double r;
+
+ r = hypot(v.x, v.y);
+ if(r == 0) return (Vec){0, 0};
+ v.x /= r;
+ v.y /= r;
+ return v;
+}
+
+double
+vecdist(Vec a, Vec b)
+{
+ return hypot(a.x - b.x, a.y - b.y);
+}
+
+double
+vecdot(Vec a, Vec b)
+{
+ return a.x * b.x + a.y * b.y;
+}
+
+Vec
+vecnormal(Vec n)
+{
+ Vec m;
+
+ m.x = -n.y;
+ m.y = n.x;
+ return vecnorm(m);
+}