ref: 05d9123a707dec0eaa0ab079b94e69cdb750c6db
dir: /appl/spree/clients/bounce.b/
implement Clientmod;
# bouncing balls demo. it uses tk and multiple processes to animate a
# number of balls bouncing around the screen. each ball has its own
# process; CPU time is doled out fairly to each process by using
# a central monitor loop.
include "sys.m";
sys: Sys;
include "draw.m";
draw: Draw;
Display, Point, Rect, Image: import draw;
include "tk.m";
tk: Tk;
include "tkclient.m";
tkclient: Tkclient;
include "math.m";
math: Math;
include "rand.m";
include "../client.m";
BALLSIZE: con 5;
ZERO: con 1e-6;
π: con Math->Pi;
Maxδ: con π / 4.0; # max bat angle deflection
Line: adt {
p, v: Realpoint;
s: real;
new: fn(p1, p2: Point): ref Line;
hittest: fn(l: self ref Line, p: Point): (Realpoint, real, real);
intersection: fn(b: self ref Line, p, v: Realpoint): (int, Realpoint, real, real);
point: fn(b: self ref Line, s: real): Point;
};
Realpoint: adt {
x, y: real;
};
cliquecmds := array[] of {
"canvas .c -bg black",
"bind .c <ButtonRelease-1> {send mouse 0 1 %x %y}",
"bind .c <ButtonRelease-2> {send mouse 0 2 %x %y}",
"bind .c <Button-1> {send mouse 1 1 %x %y}",
"bind .c <Button-2> {send mouse 1 2 %x %y}",
"bind . <Key-b> {send ucmd newball}",
"bind . <ButtonRelease-1> {focus .}",
"bind .Wm_t <ButtonRelease-1> +{focus .}",
"focus .",
"bind .c <Key-b> {send ucmd newball}",
"bind .c <Key-u> {grab release .c}",
"frame .f",
"button .f.b -text {Start} -command {send ucmd start}",
"button .f.n -text {New ball} -command {send ucmd newball}",
"pack .f.b .f.n -side left",
"pack .f -fill x",
"pack .c -fill both -expand 1",
"update",
};
Ballstate: adt {
owner: int; # index into member array
hitobs: ref Obstacle;
t0: int;
p, v: Realpoint;
speed: real;
};
Queue: adt {
h, t: list of T;
put: fn(q: self ref Queue, s: T);
get: fn(q: self ref Queue): T;
};
Obstacle: adt {
line: ref Line;
id: int;
isbat: int;
s1, s2: real;
srvid: int;
owner: int;
new: fn(id: int): ref Obstacle;
config: fn(b: self ref Obstacle);
};
Object: adt {
obstacle: ref Obstacle;
ballctl: chan of ref Ballstate;
};
Member: adt {
id: int;
colour: string;
};
win: ref Tk->Toplevel;
lines: list of ref Obstacle;
lineversion := 0;
memberid: int;
myturn: int;
stderr: ref Sys->FD;
timeoffset := 0;
objects: array of ref Object;
srvobjects: array of ref Obstacle; # all for lasthit...
members: array of ref Member;
CORNER: con 60;
INSET: con 20;
WIDTH: con 500;
HEIGHT: con 500;
bats: list of ref Obstacle;
mkball: chan of (int, chan of chan of ref Ballstate);
cliquefd: ref Sys->FD;
currentlydragging := -1;
Ballexit: ref Ballstate;
Noobs: ref Obstacle;
nomod(s: string)
{
sys->fprint(stderr, "bounce: cannot load %s: %r\n", s);
sys->raise("fail:bad module");
}
client(ctxt: ref Draw->Context, argv: list of string, nil: int)
{
sys = load Sys Sys->PATH;
stderr = sys->fildes(2);
draw = load Draw Draw->PATH;
math = load Math Math->PATH;
tk = load Tk Tk->PATH;
tkclient = load Tkclient Tkclient->PATH;
if (tkclient == nil)
nomod(Tkclient->PATH);
tkclient->init();
cliquefd = sys->fildes(0);
Ballexit = ref Ballstate;
Noobs = Obstacle.new(-1);
lines = tl lines; # XXX ahem.
if (len argv >= 3) # argv: modname mnt dir ...
membername = readfile(hd tl argv + "/name");
sys->pctl(Sys->NEWPGRP, nil);
wmctl: chan of string;
(win, wmctl) = tkclient->toplevel(ctxt.screen, nil, "Bounce", 0);
ucmd := chan of string;
tk->namechan(win, ucmd, "ucmd");
mouse := chan of string;
tk->namechan(win, mouse, "mouse");
for (i := 0; i < len cliquecmds; i++)
cmd(win, cliquecmds[i]);
cmd(win, ".c configure -width 500 -height 500");
cmd(win, ".c configure -width [.c cget -actwidth] -height [.c cget -actheight]");
imageinit();
mch := chan of (int, Point);
spawn mouseproc(mch);
mkball = chan of (int, chan of chan of ref Ballstate);
spawn monitor(mkball);
balls: list of chan of ref Ballstate;
spawn updateproc();
sys->sleep(500); # wait for things to calm down a little
cliquecmd("time " + string sys->millisec());
buts := 0;
for (;;) alt {
c := <-wmctl =>
if (c == "exit")
sys->write(cliquefd, array[0] of byte, 0);
tkclient->wmctl(win, c);
c := <-mouse =>
(nil, toks) := sys->tokenize(c, " ");
if ((hd toks)[0] == '1')
buts |= int hd tl toks;
else
buts &= ~int hd tl toks;
mch <-= (buts, Point(int hd tl tl toks, int hd tl tl tl toks));
c := <-ucmd =>
cliquecmd(c);
}
}
cliquecmd(s: string): int
{
if (sys->fprint(cliquefd, "%s\n", s) == -1) {
err := sys->sprint("%r");
notify(err);
sys->print("bounce: cmd error on '%s': %s\n", s, err);
return 0;
}
return 1;
}
updateproc()
{
wfd := sys->open("/prog/" + string sys->pctl(0, nil) + "/wait", Sys->OREAD);
spawn updateproc1();
buf := array[Sys->ATOMICIO] of byte;
n := sys->read(wfd, buf, len buf);
sys->print("updateproc process exited: %s\n", string buf[0:n]);
}
updateproc1()
{
buf := array[Sys->ATOMICIO] of byte;
while ((n := sys->read(cliquefd, buf, len buf)) > 0) {
(nil, lines) := sys->tokenize(string buf[0:n], "\n");
for (; lines != nil; lines = tl lines)
applyupdate(hd lines);
cmd(win, "update");
}
if (n < 0)
sys->fprint(stderr, "bounce: error reading updates: %r\n");
sys->fprint(stderr, "bounce: updateproc exiting\n");
}
UNKNOWN, BALL, OBSTACLE: con iota;
applyupdate(s: string)
{
# sys->print("bounce: got update %s\n", s);
(nt, toks) := sys->tokenize(s, " ");
case hd toks {
"create" =>
# create id parentid vis type
id := int hd tl toks;
if (id >= len objects) {
newobjects := array[id + 10] of ref Object;
newobjects[0:] = objects;
objects = newobjects;
}
objects[id] = ref Object;
"del" =>
# del parent start end objid...
for (toks = tl tl tl tl toks; toks != nil; toks = tl toks) {
id := int hd toks;
if (objects[id].obstacle != nil)
sys->fprint(stderr, "bounce: cannot delete obstructions yet\n");
else
objects[id].ballctl <-= Ballexit;
objects[id] = nil;
}
"set" =>
# set obj attr val
id := int hd tl toks;
attr := hd tl tl toks;
val := tl tl tl toks;
case attr {
"state" =>
# state lasthit owner p.x p.y v.x v.y s time
state := ref Ballstate;
(state.hitobs, val) = (srvobj(int hd val), tl val);
(state.owner, val) = (int hd val, tl val);
(state.p.x, val) = (real hd val, tl val);
(state.p.y, val) = (real hd val, tl val);
(state.v.x, val) = (real hd val, tl val);
(state.v.y, val) = (real hd val, tl val);
(state.speed, val) = (real hd val, tl val);
(state.t0, val) = (int hd val, tl val);
if (objects[id].ballctl == nil)
objects[id].ballctl = makeball(id, state);
else
objects[id].ballctl <-= state;
"pos" or "coords" or "owner" or "id" =>
if (objects[id].obstacle == nil)
objects[id].obstacle = Obstacle.new(id);
o := objects[id].obstacle;
case attr {
"pos" =>
(o.s1, val) = (real hd val, tl val);
(o.s2, val) = (real hd val, tl val);
o.isbat = 1;
"coords" =>
p1, p2: Point;
(p1.x, val) = (int hd val, tl val);
(p1.y, val) = (int hd val, tl val);
(p2.x, val) = (int hd val, tl val);
(p2.y, val) = (int hd val, tl val);
o.line = Line.new(p1, p2);
"owner" =>
o.owner = hd val;
if (o.owner == membername)
bats = o :: bats;
"id" =>
o.srvid = int hd val;
if (o.srvid >= len srvobjects) {
newobjects := array[id + 10] of ref Obstacle;
newobjects[0:] = srvobjects;
srvobjects = newobjects;
}
srvobjects[o.srvid] = o;
}
if (currentlydragging != id)
o.config();
"arenasize" =>
# arenasize w h
cmd(win, ".c configure -width " + hd val + " -height " + hd tl val);
* =>
if (len attr > 5 && attr[0:5] == "score") {
# scoreN val
n := int attr[5:];
w := ".f." + string n;
if (!tkexists(w)) {
cmd(win, "label " + w + "l -text '" + attr);
cmd(win, "label " + w + " -relief sunken -bd 5 -width 5w");
cmd(win, "pack " +w + "l " + w + " -side left");
}
cmd(win, w + " configure -text {" + hd val + "}");
} else if (len attr > 6 && attr[0:6] == "member") {
# memberN id colour
n := int attr[6:];
if (n >= len members) {
newmembers := array[n + 1] of ref Member;
newmembers[0:] = members;
members = newmembers;
}
p := members[n] = ref Member(int hd val, hd tl val);
cmd(win, ".c itemconfigure o" + string p.id + " -fill " + p.colour);
if (p.id == memberid)
myturn = n;
}
else
sys->fprint(stderr, "bounce: unknown attr '%s'\n", attr);
}
"time" =>
# time offset orig
now := sys->millisec();
time := int hd tl tl toks;
transit := now - time;
timeoffset = int hd tl toks - transit / 2;
sys->print("transit time %d, timeoffset: %d\n", transit, timeoffset);
* =>
sys->fprint(stderr, "chat: unknown update message '%s'\n", s);
}
}
tkexists(w: string): int
{
return tk->cmd(win, w + " cget -bd")[0] != '!';
}
srvobj(id: int): ref Obstacle
{
if (id < 0 || id >= len srvobjects || srvobjects[id] == nil)
return Noobs;
return srvobjects[id];
}
mouseproc(mch: chan of (int, Point))
{
procname("mouse");
for (;;) {
hitbat: ref Obstacle = nil;
minperp, hitdist: real;
(buts, p) := <-mch;
for (bl := bats; bl != nil; bl = tl bl) {
b := hd bl;
(normal, perp, dist) := b.line.hittest(p);
perp = abs(perp);
if ((hitbat == nil || perp < minperp) && (dist >= b.s1 && dist <= b.s2))
(hitbat, minperp, hitdist) = (b, perp, dist);
}
if (hitbat == nil || minperp > 30.0) {
while ((<-mch).t0)
;
continue;
}
offset := hitdist - hitbat.s1;
if (buts & 2)
(buts, p) = aim(mch, hitbat, p);
if (buts & 1)
drag(mch, hitbat, offset);
}
}
drag(mch: chan of (int, Point), hitbat: ref Obstacle, offset: real)
{
realtosrv := chan of string;
dummytosrv := chan of string;
tosrv := dummytosrv;
currevent := "";
currentlydragging = hitbat.id;
line := hitbat.line;
batlen := hitbat.s2 - hitbat.s1;
cvsorigin := Point(int cmd(win, ".c cget -actx"), int cmd(win, ".c cget -acty"));
spawn sendproc(realtosrv);
cmd(win, "grab set .c");
cmd(win, "focus .");
loop: for (;;) alt {
tosrv <-= currevent =>
tosrv = dummytosrv;
(buts, p) := <-mch =>
if (buts & 2)
(buts, p) = aim(mch, hitbat, p);
(v, perp, dist) := line.hittest(p);
dist -= offset;
# constrain bat and mouse positions
if (dist < 0.0 || dist + batlen > line.s) {
if (dist < 0.0) {
p = line.point(offset);
dist = 1.0;
} else {
p = line.point(line.s - batlen + offset);
dist = line.s - batlen;
}
p.x -= int (v.x * perp);
p.y -= int (v.y * perp);
win.image.display.cursorset(p.add(cvsorigin));
}
(hitbat.s1, hitbat.s2) = (dist, dist + batlen);
hitbat.config();
cmd(win, "update");
currevent = "bat " + string hitbat.s1;
tosrv = realtosrv;
if (!buts)
break loop;
}
cmd(win, "grab release .c");
realtosrv <-= nil;
currentlydragging = -1;
}
CHARGETIME: con 1000.0;
MAXCHARGE: con 50.0;
α: con 0.999; # decay in one millisecond
D: con 5;
aim(mch: chan of (int, Point), hitbat: ref Obstacle, p: Point): (int, Point)
{
cvsorigin := Point(int cmd(win, ".c cget -actx"), int cmd(win, ".c cget -acty"));
startms := ms := sys->millisec();
δ := Realpoint(0.0, 0.0);
line := hitbat.line;
charge := 0.0;
pivot := line.point((hitbat.s1 + hitbat.s2) / 2.0);
s1 := p2s(line.point(hitbat.s1));
s2 := p2s(line.point(hitbat.s2));
cmd(win, ".c create line 0 0 0 0 -tags wire -fill yellow");
ballid := makeballitem(-1, myturn);
bp, p2: Point;
buts := 2;
for (;;) {
v := makeunit(δ);
bp = pivot.add((int (v.x * charge), int (v.y * charge)));
cmd(win, ".c coords wire "+s1+" "+p2s(bp)+" "+s2);
ballmove(ballid, bp);
cmd(win, "update");
if ((buts & 2) == 0)
break;
(buts, p2) = <-mch;
now := sys->millisec();
fade := math->pow(α, real (now - ms));
charge = real (now - startms) * (MAXCHARGE / CHARGETIME);
if (charge > MAXCHARGE)
charge = MAXCHARGE;
ms = now;
dp := p2.sub(p);
δ.x = δ.x * fade + real dp.x;
δ.y = δ.y * fade + real dp.y;
mag := δ.x * δ.x + δ.y * δ.y;
if (dp.x != 0 || dp.y != 0)
win.image.display.cursorset(p.add(cvsorigin));
}
cmd(win, ".c delete wire " + ballid);
cmd(win, "update");
(δ.x, δ.y) = (-δ.x, -δ.y);
cliquecmd("newball " + string hitbat.id + " " +
p2s(bp) + " " + rp2s(makeunit(δ)) + " " + string (charge / 100.0));
return (buts, p2);
}
makeunit(v: Realpoint): Realpoint
{
mag := math->sqrt(v.x * v.x + v.y * v.y);
if (mag < ZERO)
return (1.0, 0.0);
return (v.x / mag, v.y / mag);
}
sendproc(tosrv: chan of string)
{
procname("send");
while ((ev := <-tosrv) != nil)
cliquecmd(ev);
}
makeball(id: int, state: ref Ballstate): chan of ref Ballstate
{
mkballreply := chan of chan of ref Ballstate;
mkball <-= (id, mkballreply);
ballctl := <-mkballreply;
ballctl <-= state;
return ballctl;
}
blankobstacle: Obstacle;
Obstacle.new(id: int): ref Obstacle
{
cmd(win, ".c create line 0 0 0 0 -width 3 -fill #aaaaaa" + " -tags l" + string id);
o := ref blankobstacle;
o.line = Line.new((0, 0), (0, 0));
o.id = id;
o.owner = -1;
o.srvid = -1;
lineversion++;
lines = o :: lines;
return o;
}
Obstacle.config(o: self ref Obstacle)
{
if (o.isbat) {
cmd(win, ".c coords l" + string o.id + " " +
p2s(o.line.point(o.s1)) + " " + p2s(o.line.point(o.s2)));
if (o.owner == memberid)
cmd(win, ".c itemconfigure l" + string o.id + " -fill red");
else
cmd(win, ".c itemconfigure l" + string o.id + " -fill white");
} else {
cmd(win, ".c coords l" + string o.id + " " +
p2s(o.line.point(0.0)) + " " + p2s(o.line.point(o.line.s)));
}
}
# make sure cpu time is handed to all ball processes fairly
# by passing a "token" around to each process in turn.
# each process does its work when it *hasn't* got its
# token but it can't go through two iterations without
# waiting its turn.
#
# new processes are created by sending on mkball.
# the channel sent back can be used to control the position
# and velocity of the ball and to destroy it.
monitor(mkball: chan of (int, chan of chan of ref Ballstate))
{
procname("mon");
procl, proc: list of (chan of ref Ballstate, chan of int);
rc := dummyrc := chan of int;
for (;;) {
alt {
(id, ch) := <-mkball =>
(newc, newrc) := (chan of ref Ballstate, chan of int);
procl = (newc, newrc) :: procl;
spawn animproc(id, newc, newrc);
ch <-= newc;
if (tl procl == nil) { # first ball
newc <-= nil;
rc = newrc;
proc = procl;
}
alive := <-rc => # got token.
if (!alive) {
# ball has exited: remove from list
newprocl: list of (chan of ref Ballstate, chan of int);
for (; procl != nil; procl = tl procl)
if ((hd procl).t1 != rc)
newprocl = hd procl :: newprocl;
procl = newprocl;
}
if ((proc = tl proc) == nil)
proc = procl;
if (proc == nil) {
rc = dummyrc;
} else {
c: chan of ref Ballstate;
(c, rc) = hd proc;
c <-= nil; # hand token to next process.
}
}
}
}
# buffer ball state commands, so at least balls we handle
# locally appear glitch free.
bufferproc(cmdch: chan of string)
{
procname("buffer");
buffer := ref Queue;
bufhd: string;
dummytosrv := chan of string;
realtosrv := chan of string;
spawn sendproc(realtosrv);
tosrv := dummytosrv;
for (;;) alt {
tosrv <-= bufhd =>
if ((bufhd = buffer.get()) == nil)
tosrv = dummytosrv;
s := <-cmdch =>
if (s == nil) {
# ignore other queued requests, as they're
# only state changes for a ball that's now been deleted.
realtosrv <-= nil;
exit;
}
buffer.put(s);
if (tosrv == dummytosrv) {
tosrv = realtosrv;
bufhd = buffer.get();
}
}
}
start: int;
# animate one ball. initial position and unit-velocity are
# given by p and v.
animproc(id: int, c: chan of ref Ballstate, rc: chan of int)
{
procname("anim");
while ((newstate := <-c) == nil)
rc <-= 1;
state := *newstate;
totaldist := 0.0; # distance ball has travelled from reference point to last intersection
ballid := makeballitem(id, state.owner);
smallcount := 0;
version := lineversion;
tosrv := chan of string;
start := sys->millisec();
spawn bufferproc(tosrv);
loop: for (;;) {
hitp: Realpoint;
dist := 1000000.0;
oldobs := state.hitobs;
hitt: real;
for (l := lines; l != nil; l = tl l) {
obs := hd l;
(ok, hp, hdist, t) := obs.line.intersection(state.p, state.v);
if (ok && hdist < dist && obs != oldobs && (smallcount < 10 || hdist > 1.5)) {
(hitp, state.hitobs, dist, hitt) = (hp, obs, hdist, t);
}
}
if (dist > 10000.0) {
sys->print("no intersection!\n");
state = ballexit(1, ballid, tosrv, c, rc);
totaldist = 0.0;
continue loop;
}
if (dist < 0.0001)
smallcount++;
else
smallcount = 0;
t0 := int (totaldist / state.speed) + state.t0 - timeoffset;
et := t0 + int (dist / state.speed);
t := sys->millisec() - t0;
dt := et - t0;
do {
s := real t * state.speed;
currp := Realpoint(state.p.x + s * state.v.x, state.p.y + s * state.v.y);
ballmove(ballid, (int currp.x, int currp.y));
cmd(win, "update");
if (lineversion > version) {
(state.p, state.hitobs, version) = (currp, oldobs, lineversion);
totaldist += s;
continue loop;
}
if ((newstate := <-c) != nil) {
if (newstate == Ballexit)
ballexit(0, ballid, tosrv, c, rc);
state = *newstate;
totaldist = 0.0;
continue loop;
}
rc <-= 1;
t = sys->millisec() - t0;
} while (t < dt);
totaldist += dist;
state.p = hitp;
hitobs := state.hitobs;
if (hitobs.isbat) {
if (hitobs.owner == memberid) {
if (hitt >= hitobs.s1 && hitt <= hitobs.s2)
state.v = batboing(hitobs, hitt, state.v);
tosrv <-= "state " +
string id +
" " + string hitobs.srvid +
" " + string state.owner +
" " + rp2s(state.p) + " " + rp2s(state.v) +
" " + string state.speed +
" " + string (sys->millisec() + timeoffset);
} else {
# wait for enlightenment
while ((newstate := <-c) == nil)
rc <-= 1;
if (newstate == Ballexit)
ballexit(0, ballid, tosrv, c, rc);
state = *newstate;
totaldist = 0.0;
}
} else if (hitobs.owner == memberid) {
# if line has an owner but isn't a bat, then it's
# a terminating line, so we inform server.
cliquecmd("lost " + string id);
state = ballexit(1, ballid, tosrv, c, rc);
totaldist = 0.0;
} else
state.v = boing(state.v, hitobs.line);
}
}
#ballmask: ref Image;
imageinit()
{
# displ := win.image.display;
# ballmask = displ.newimage(((0, 0), (BALLSIZE+1, BALLSIZE+1)), 0, 0, Draw->White);
# ballmask.draw(ballmask.r, displ.zeros, displ.ones, (0, 0));
# ballmask.fillellipse((BALLSIZE/2, BALLSIZE/2), BALLSIZE/2, BALLSIZE/2, displ.ones, (0, 0));
# End: con Draw->Endsquare;
# n := 5;
# θ := 0.0;
# δ := (2.0 * π) / real n;
# c := Point(BALLSIZE / 2, BALLSIZE / 2).sub((1, 1));
# r := real (BALLSIZE / 2);
# for (i := 0; i < n; i++) {
# p2 := Point(int (r * math->cos(θ)), int (r * math->sin(θ)));
# sys->print("drawing from %s to %s\n", p2s(c), p2s(p2.add(c)));
# ballmask.line(c, c.add(p2), End, End, 1, displ.ones, (0, 0));
# θ += δ;
# }
}
makeballitem(id, owner: int): string
{
displ := win.image.display;
return cmd(win, ".c create oval 0 0 1 1 -fill " + members[owner].colour +
" -tags o" + string owner);
}
ballmove(ballid: string, p: Point)
{
cmd(win, ".c coords " + ballid +
" " + string (p.x - BALLSIZE) +
" " + string (p.y - BALLSIZE) +
" " + string (p.x + BALLSIZE) +
" " + string (p.y + BALLSIZE));
}
ballexit(wait: int, ballid: string, tosrv: chan of string, c: chan of ref Ballstate, rc: chan of int): Ballstate
{
if (wait) {
while ((s := <-c) != Ballexit)
if (s == nil)
rc <-= 1;
else
return *s; # maybe we're not exiting, after all...
}
cmd(win, ".c delete " + ballid + ";update");
# cmd(win, "image delete " + ballid);
tosrv <-= nil;
<-c;
rc <-= 0; # inform monitor that we've gone
exit;
}
# thread-safe access to the Rand module
randgenproc(ch: chan of int)
{
procname("rand");
rand := load Rand Rand->PATH;
for (;;)
ch <-= rand->rand(16r7fffffff);
}
abs(x: real): real
{
if (x < 0.0)
return -x;
return x;
}
# bounce ball travelling in direction av off line b.
# return the new unit vector.
boing(av: Realpoint, b: ref Line): Realpoint
{
d := math->atan2(b.v.y, b.v.x) * 2.0 - math->atan2(av.y, av.x);
return (math->cos(d), math->sin(d));
}
# calculate how a bounce vector should be modified when
# hitting a bat. t gives the intersection point on the bat;
# ballv is the ball's vector.
batboing(bat: ref Obstacle, t: real, ballv: Realpoint): Realpoint
{
ballθ := math->atan2(ballv.y, ballv.x);
batθ := math->atan2(bat.line.v.y, bat.line.v.x);
φ := ballθ - batθ;
δ: real;
t -= bat.s1;
batlen := bat.s2 - bat.s1;
if (math->sin(φ) > 0.0)
δ = (t / batlen) * Maxδ * 2.0 - Maxδ;
else
δ = (t / batlen) * -Maxδ * 2.0 + Maxδ;
θ := math->atan2(bat.line.v.y, bat.line.v.x) * 2.0 - ballθ; # boing
θ += δ;
return (math->cos(θ), math->sin(θ));
}
Line.new(p1, p2: Point): ref Line
{
ln := ref Line;
ln.p = (real p1.x, real p1.y);
v := Realpoint(real (p2.x - p1.x), real (p2.y - p1.y));
ln.s = math->sqrt(v.x * v.x + v.y * v.y);
if (ln.s > ZERO)
ln.v = (v.x / ln.s, v.y / ln.s);
else
ln.v = (1.0, 0.0);
return ln;
}
# return normal from line, perpendicular distance from line and distance down line
Line.hittest(l: self ref Line, ip: Point): (Realpoint, real, real)
{
p := Realpoint(real ip.x, real ip.y);
v := Realpoint(-l.v.y, l.v.x);
(nil, nil, perp, ldist) := l.intersection(p, v);
return (v, perp, ldist);
}
Line.point(l: self ref Line, s: real): Point
{
return (int (l.p.x + s * l.v.x), int (l.p.y + s * l.v.y));
}
# compute the intersection of lines a and b.
# b is assumed to be fixed, and a is indefinitely long
# but doesn't extend backwards from its starting point.
# a is defined by the starting point p and the unit vector v.
# return whether it hit, the point at which it hit if so,
# the distance of the intersection point from p,
# and the distance of the intersection point from b.p.
Line.intersection(b: self ref Line, p, v: Realpoint): (int, Realpoint, real, real)
{
det := b.v.x * v.y - v.x * b.v.y;
if (det > -ZERO && det < ZERO)
return (0, (0.0, 0.0), 0.0, 0.0);
y21 := b.p.y - p.y;
x21 := b.p.x - p.x;
s := (b.v.x * y21 - b.v.y * x21) / det;
t := (v.x * y21 - v.y * x21) / det;
if (s < 0.0)
return (0, (0.0, 0.0), s, t);
hit := t >= 0.0 && t <= b.s;
hp: Realpoint;
if (hit)
hp = (p.x+v.x*s, p.y+v.y*s);
return (hit, hp, s, t);
}
cmd(top: ref Tk->Toplevel, s: string): string
{
e := tk->cmd(top, s);
if (e != nil && e[0] == '!')
sys->print("tk error %s on '%s'\n", e, s);
return e;
}
state2s(s: ref Ballstate): string
{
return sys->sprint("[hitobs:%d(id %d), t0: %d, p: %g %g; v: %g %g; s: %g",
s.hitobs.srvid, s.hitobs.id, s.t0, s.p.x, s.p.y, s.v.x, s.v.y, s.speed);
}
l2s(l: ref Line): string
{
return p2s(l.point(0.0)) + " " + p2s(l.point(l.s));
}
rp2s(rp: Realpoint): string
{
return string rp.x + " " + string rp.y;
}
p2s(p: Point): string
{
return string p.x + " " + string p.y;
}
notifypid := -1;
notify(s: string)
{
kill(notifypid);
sync := chan of int;
spawn notifyproc(s, sync);
notifypid = <-sync;
}
notifyproc(s: string, sync: chan of int)
{
procname("notify");
sync <-= sys->pctl(0, nil);
cmd(win, ".c delete notify");
id := cmd(win, ".c create text 0 0 -anchor nw -fill red -tags notify -text '" + s);
bbox := cmd(win, ".c bbox " + id);
cmd(win, ".c create rectangle " + bbox + " -fill #ffffaa -tags notify");
cmd(win, ".c raise " + id);
cmd(win, "update");
sys->sleep(750);
cmd(win, ".c delete notify");
cmd(win, "update");
notifypid = -1;
}
kill(pid: int)
{
if ((fd := sys->open("/prog/"+string pid+"/ctl", Sys->OWRITE)) != nil)
sys->write(fd, array of byte "kill", 4);
}
T: type string;
Queue.put(q: self ref Queue, s: T)
{
q.t = s :: q.t;
}
Queue.get(q: self ref Queue): T
{
s: T;
if(q.h == nil){
q.h = revlist(q.t);
q.t = nil;
}
if(q.h != nil){
s = hd q.h;
q.h = tl q.h;
}
return s;
}
revlist(ls: list of T) : list of T
{
rs: list of T;
for (; ls != nil; ls = tl ls)
rs = hd ls :: rs;
return rs;
}
procname(s: string)
{
# sys->procname(sys->procname(nil) + " " + s);
}