ref: 057ac29e3d28fff9b19ce20435e8b434bb80e7b0
parent: 826b2891b7cfff2e85fcfe70be64544e22ef3ef3
author: rodri <rgl@antares-labs.eu>
date: Wed Sep 10 16:12:35 EDT 2025
new game: rotzoomer
--- a/sys/src/games/mkfile
+++ b/sys/src/games/mkfile
@@ -23,6 +23,7 @@
turtle\
vocenc\
vocdec\
+ rotzoomer\
OFILES=
HFILES=
--- /dev/null
+++ b/sys/src/games/rotzoomer.c
@@ -1,0 +1,381 @@
+/*
+ * rotzoomer - an affinewarp demo
+ */
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include <draw.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <geometry.h>
+
+typedef struct Strobos Strobos;
+typedef struct Backlight Backlight;
+
+struct Strobos
+{+ Point p;
+ Point txtsz;
+ Image *c;
+ Image *back;
+ Image *txtim;
+ char *txt;
+ Warp txtwp;
+};
+
+struct Backlight
+{+ Image *c; /* bag of colors */
+ Image *b; /* chosen one */
+ Warp w; /* roulette */
+};
+
+Rectangle UR = {0,0,1,1};+
+Image *bg;
+Backlight *bl;
+Image *sprite;
+Image *screenb;
+Mousectl *mctl;
+Keyboardctl *kctl;
+Channel *drawc;
+Warp warp;
+int nframes;
+Strobos **strobostab;
+int nstrobos;
+int smooth;
+
+void *
+emalloc(ulong size)
+{+ void *p;
+
+ p = malloc(size);
+ if(p == nil)
+ sysfatal("malloc: %r");+ setmalloctag(p, getcallerpc(&size));
+ return p;
+}
+
+Image *
+eallocimage(Display *disp, Rectangle r, ulong chan, int repl, ulong col)
+{+ Image *i;
+
+ i = allocimage(disp, r, chan, repl, col);
+ if(i == nil)
+ sysfatal("allocimage: %r");+ setmalloctag(i, getcallerpc(&disp));
+ return i;
+}
+
+Image *
+allocbasketweave(void)
+{+ static uchar pattern[] = {+ 0x07, 0x8B, 0xDD, 0xB8,
+ 0x70, 0xE8, 0xDD, 0x8E,
+ };
+ Image *i;
+
+ i = eallocimage(display, Rect(0,0,8,8), GREY1, 1, DNofill);
+ if(loadimage(i, i->r, pattern, 8) != 8)
+ sysfatal("loadimage: %r");+ return i;
+}
+
+Backlight *
+allocbacklight(void)
+{+ static ulong pattern[] = {+ 0x888888, 0xDDDDDD,
+ 0xBBBBBB, 0xFFFFFF,
+ };
+ Backlight *b;
+
+ b = emalloc(sizeof(Backlight));
+ b->c = eallocimage(display, Rect(0,0,2,2), XRGB32, 1, DNofill);
+ b->b = eallocimage(display, UR, XRGB32, 1, DNofill);
+ if(loadimage(b->c, b->c->r, (uchar*)pattern, 2*2*4) != 2*2*4)
+ sysfatal("loadimage: %r");+ return b;
+}
+
+void
+drawstats(void)
+{+ static Point sp = {10,10};+ char s[128];
+
+ snprint(s, sizeof s, "frames %d", nframes);
+ stringbg(screenb, sp, display->white, ZP, font, s, display->black, ZP);
+}
+
+Strobos *
+allocstrobos(char *txt, ulong col)
+{+ Strobos *s;
+
+ s = emalloc(sizeof(Strobos));
+ s->c = eallocimage(display, UR, RGBA32, 1, col);
+ s->txt = txt;
+ s->txtsz = stringsize(font, txt);
+ s->txtim = eallocimage(display, Rpt(ZP, s->txtsz), RGBA32, 0, DTransparent);
+ string(s->txtim, ZP, s->c, ZP, font, txt);
+ s->back = eallocimage(display, Rpt(ZP, mulpt(s->txtsz, 4)), RGBA32, 0, DTransparent);
+ return s;
+}
+
+void
+initstrobos(void)
+{+ static struct {+ char *lbl;
+ ulong col;
+ } tab[] = {+ "MAKE ME AN OFFER", DPaleyellow,
+ "ONE HUNDRED BILLION DOLLARS", DYellow,
+ "THE PRICEMASTER HAS SPOKEN", DDarkyellow,
+ };
+ int i;
+
+ nstrobos = nelem(tab);
+ strobostab = emalloc(nstrobos*sizeof(Strobos *));
+ for(i = 0; i < nstrobos; i++)
+ strobostab[i] = allocstrobos(tab[i].lbl, setalpha(tab[i].col, 0x7F));
+}
+
+void
+drawstrobos(Strobos *s)
+{+ double ss, θ;
+
+ if(nframes % 75 == 0){ /* 2½ seconds */+ s->p.x = 50 + frand()*(Dx(screenb->r)-50-50 - Dx(s->back->r));
+ s->p.y = 50 + frand()*(Dy(screenb->r)-50-50 - Dy(s->back->r));
+ }
+
+ if((nframes & 1) == 0){ /* 1/15 second */+ ss = (frand() + 0.2)*(4/1.2);
+ θ = (frand()*10 - 10/2)*DEG;
+ Matrix S = {+ ss, 0, 0,
+ 0, ss, 0,
+ 0, 0, 1,
+ }, R = {+ cos(θ), -sin(θ), 0,
+ sin(θ), cos(θ), 0,
+ 0, 0, 1,
+ }, T₀ = {+ 1, 0, -s->txtsz.x/2,
+ 0, 1, -s->txtsz.y/2,
+ 0, 0, 1,
+ }, T₁ = {+ 1, 0, s->txtsz.x/2,
+ 0, 1, s->txtsz.y/2,
+ 0, 0, 1,
+ };
+ mulm(R, T₀);
+ mulm(T₁, R);
+ mulm(S, T₁);
+ mkwarp(s->txtwp, S);
+ }
+
+ affinewarp(s->back, s->back->r, s->txtim, s->txtim->r.min, s->txtwp, smooth);
+ draw(screenb, rectaddpt(s->back->r, addpt(screenb->r.min, s->p)), s->back, nil, ZP);
+}
+
+void
+redraw(void)
+{+ Rectangle cr0;
+ int i;
+
+ rlockdisplay(display);
+ affinewarp(bl->b, bl->b->r, bl->c, ZP, bl->w, 0);
+ draw(screenb, screenb->r, bl->b, bg, ZP);
+ cr0 = screenb->clipr;
+ replclipr(screenb, 0, insetrect(screenb->r, 50));
+ affinewarp(screenb, screenb->clipr, sprite, sprite->r.min, warp, smooth);
+ replclipr(screenb, 0, cr0);
+ drawstats();
+ for(i = 0; i < nstrobos; i++)
+ drawstrobos(strobostab[i]);
+ draw(screen, screen->r, screenb, nil, ZP);
+ flushimage(display, 1);
+ runlockdisplay(display);
+}
+
+void
+drawproc(void*)
+{+ threadsetname("drawproc");+
+ for(;;){+ recv(drawc, nil);
+ redraw();
+ nframes++;
+ }
+}
+
+void
+update(double f)
+{+ Point t;
+ double c, s, ss;
+
+ c = cos(f);
+ s = sin(f);
+ ss = sin(f*2 + 10);
+ t.x = s*Dx(sprite->r)/2;
+ t.y = 0;
+ Matrix R = {+ c, -s, 0,
+ s, c, 0,
+ 0, 0, 1,
+ }, S = {+ ss, 0, 0,
+ 0, ss, 0,
+ 0, 0, 1,
+ }, T₀ = {+ 1, 0, -Dx(sprite->r)/2,
+ 0, 1, -Dy(sprite->r)/2,
+ 0, 0, 1,
+ }, T₁ = {+ 1, 0, Dx(sprite->r)/2,
+ 0, 1, Dy(sprite->r)/2,
+ 0, 0, 1,
+ }, T = {+ 1, 0, t.x,
+ 0, 1, t.y,
+ 0, 0, 1,
+ };
+ mulm(S, T₀);
+ mulm(R, S);
+ mulm(T₁, R);
+ mulm(T, T₁);
+ mkwarp(warp, T);
+
+ /* spin it! */
+ t.x = Dx(bl->b->r)/2;
+ t.y = Dy(bl->b->r)/2;
+ Matrix R₁ = {+ c, -s, 0,
+ s, c, 0,
+ 0, 0, 1,
+ };
+ T₀[0][2] = -t.x;
+ T₀[1][2] = -t.y;
+ mulm(R₁, T₀);
+ mkwarp(bl->w, R₁);
+}
+
+void
+clkproc(void*)
+{+ uvlong t0, Δt;
+
+ threadsetname("clkproc");+
+ for(;;){+ t0 = nsec();
+ update(t0/1e9);
+ nbsend(drawc, nil);
+ Δt = nsec() - t0;
+ if(Δt < 33*1000*1000)
+ sleep(33 - Δt/1000000);
+ }
+}
+
+void
+mouse(void)
+{+}
+
+void
+key(Rune r)
+{+ switch(r){+ case Kdel:
+ case 'q':
+ threadexitsall(nil);
+ }
+}
+
+void
+resize(void)
+{+ static Point lastsz;
+ Point cursz;
+
+ lockdisplay(display);
+ if(getwindow(display, Refnone) < 0)
+ fprint(2, "can't reattach to window\n");
+ cursz = subpt(screen->r.max, screen->r.min);
+ if(!eqpt(cursz, lastsz)){+ freeimage(screenb);
+ screenb = eallocimage(display, Rpt(ZP, cursz), screen->chan, 0, DBlack);
+ lastsz = cursz;
+ }
+ unlockdisplay(display);
+ nbsend(drawc, nil);
+}
+
+void
+usage(void)
+{+ fprint(2, "usage: %s [-s]\n", argv0);
+ exits("usage");+}
+
+void
+threadmain(int argc, char *argv[])
+{+ Rune r;
+
+ ARGBEGIN{+ case 's':
+ smooth++;
+ break;
+ default:
+ usage();
+ }ARGEND;
+ if(argc != 0)
+ usage();
+
+ setfcr(getfcr() &~ FPINVAL);
+
+ if(initdraw(nil, nil, "rotzoomer") < 0)
+ sysfatal("initdraw: %r");+ if((mctl = initmouse(nil, screen)) == nil)
+ sysfatal("initmouse: %r");+ if((kctl = initkeyboard(nil)) == nil)
+ sysfatal("initkeyboard: %r");+
+ bl = allocbacklight();
+ bg = allocbasketweave();
+ sprite = display->image;
+ screenb = eallocimage(display, rectsubpt(screen->r, screen->r.min), screen->chan, 0, DBlack);
+ initstrobos();
+
+ unlockdisplay(display);
+ drawc = chancreate(sizeof(void*), 1);
+ if(drawc == nil)
+ sysfatal("chancreate: %r");+ proccreate(drawproc, nil, mainstacksize);
+ proccreate(clkproc, nil, mainstacksize);
+
+ enum {MOUSE, RESIZE, KEY};+ Alt a[] = {+ {mctl->c, &mctl->Mouse, CHANRCV},+ {mctl->resizec, nil, CHANRCV},+ {kctl->c, &r, CHANRCV},+ {nil, nil, CHANEND}+ };
+ for(;;)
+ switch(alt(a)){+ case MOUSE: mouse(); break;
+ case RESIZE: resize(); break;
+ case KEY: key(r); break;
+ }
+}
--
⑨