#include "xs.h"
/*
* engine for 4s, 5s, etc
*/
Cursor whitearrow = {
{0, 0},
{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFC,
0xFF, 0xF0, 0xFF, 0xF0, 0xFF, 0xF8, 0xFF, 0xFC,
0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFC,
0xF3, 0xF8, 0xF1, 0xF0, 0xE0, 0xE0, 0xC0, 0x40, },
{0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x06, 0xC0, 0x1C,
0xC0, 0x30, 0xC0, 0x30, 0xC0, 0x38, 0xC0, 0x1C,
0xC0, 0x0E, 0xC0, 0x07, 0xCE, 0x0E, 0xDF, 0x1C,
0xD3, 0xB8, 0xF1, 0xF0, 0xE0, 0xE0, 0xC0, 0x40, }
};
enum
{
CNone = 0,
CBounds = 1,
CPiece = 2,
NX = 10,
NY = 20,
};
enum{
TIMER,
MOUSE,
RESHAPE,
KBD,
SUSPEND,
NALT
};
char board[NY][NX];
Rectangle rboard;
Point pscore;
Point scoresz;
int pcsz = 32;
Point pos;
Image *bb, *bbmask, *bb2, *bb2mask;
Image *whitemask;
Rectangle br, br2;
long points;
int dt;
int DY;
int DMOUSE;
int lastmx;
Mouse mouse;
int newscreen;
Channel *timerc;
Channel *suspc;
Channel *mousec;
Channel *kbdc;
Mousectl *mousectl;
Keyboardctl *kbdctl;
int suspended;
void redraw(int);
int tsleep;
Piece *piece;
#define NCOL 10
uchar txbits[NCOL][32]={
{0xDD,0xDD,0xFF,0xFF,0x77,0x77,0xFF,0xFF,
0xDD,0xDD,0xFF,0xFF,0x77,0x77,0xFF,0xFF,
0xDD,0xDD,0xFF,0xFF,0x77,0x77,0xFF,0xFF,
0xDD,0xDD,0xFF,0xFF,0x77,0x77,0xFF,0xFF},
{0xDD,0xDD,0x77,0x77,0xDD,0xDD,0x77,0x77,
0xDD,0xDD,0x77,0x77,0xDD,0xDD,0x77,0x77,
0xDD,0xDD,0x77,0x77,0xDD,0xDD,0x77,0x77,
0xDD,0xDD,0x77,0x77,0xDD,0xDD,0x77,0x77},
{0xAA,0xAA,0x55,0x55,0xAA,0xAA,0x55,0x55,
0xAA,0xAA,0x55,0x55,0xAA,0xAA,0x55,0x55,
0xAA,0xAA,0x55,0x55,0xAA,0xAA,0x55,0x55,
0xAA,0xAA,0x55,0x55,0xAA,0xAA,0x55,0x55},
{0xAA,0xAA,0x55,0x55,0xAA,0xAA,0x55,0x55,
0xAA,0xAA,0x55,0x55,0xAA,0xAA,0x55,0x55,
0xAA,0xAA,0x55,0x55,0xAA,0xAA,0x55,0x55,
0xAA,0xAA,0x55,0x55,0xAA,0xAA,0x55,0x55},
{0x22,0x22,0x88,0x88,0x22,0x22,0x88,0x88,
0x22,0x22,0x88,0x88,0x22,0x22,0x88,0x88,
0x22,0x22,0x88,0x88,0x22,0x22,0x88,0x88,
0x22,0x22,0x88,0x88,0x22,0x22,0x88,0x88},
{0x22,0x22,0x00,0x00,0x88,0x88,0x00,0x00,
0x22,0x22,0x00,0x00,0x88,0x88,0x00,0x00,
0x22,0x22,0x00,0x00,0x88,0x88,0x00,0x00,
0x22,0x22,0x00,0x00,0x88,0x88,0x00,0x00},
{0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00,
0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00,
0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00,
0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00},
{0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00,
0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00,
0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00,
0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00},
{0xCC,0xCC,0xCC,0xCC,0xCC,0xCC,0xCC,0xCC,
0xCC,0xCC,0xCC,0xCC,0xCC,0xCC,0xCC,0xCC,
0xCC,0xCC,0xCC,0xCC,0xCC,0xCC,0xCC,0xCC,
0xCC,0xCC,0xCC,0xCC,0xCC,0xCC,0xCC,0xCC},
{0xCC,0xCC,0xCC,0xCC,0x33,0x33,0x33,0x33,
0xCC,0xCC,0xCC,0xCC,0x33,0x33,0x33,0x33,
0xCC,0xCC,0xCC,0xCC,0x33,0x33,0x33,0x33,
0xCC,0xCC,0xCC,0xCC,0x33,0x33,0x33,0x33},
};
int txpix[NCOL] = {
DYellow, /* yellow */
DCyan, /* cyan */
DGreen, /* lime green */
DGreyblue, /* slate */
DRed, /* red */
DGreygreen, /* olive green */
DBlue, /* blue */
0xFF55AAFF, /* pink */
0xFFAAFFFF, /* lavender */
0xBB005DFF, /* maroon */
};
Image *tx[NCOL];
int
movemouse(void)
{
mouse.xy = Pt(rboard.min.x + Dx(rboard)/2, rboard.min.y +Dy(rboard)/2);
moveto(mousectl, mouse.xy);
return mouse.xy.x;
}
int
warp(Point p, int x)
{
if (!suspended && piece != nil) {
x = pos.x + piece->sz.x*pcsz/2;
if (p.y < rboard.min.y)
p.y = rboard.min.y;
if (p.y >= rboard.max.y)
p.y = rboard.max.y - 1;
moveto(mousectl, Pt(x, p.y));
}
return x;
}
Piece *
rotr(Piece *p)
{
if(p->rot == 3)
return p-3;
return p+1;
}
Piece *
rotl(Piece *p)
{
if(p->rot == 0)
return p+3;
return p-1;
}
int
collide(Point pt, Piece *p)
{
int i;
int c = CNone;
pt.x = (pt.x - rboard.min.x) / pcsz;
pt.y = (pt.y - rboard.min.y) / pcsz;
for(i=0; i<N; i++){
pt.x += p->d[i].x;
pt.y += p->d[i].y;
if(pt.x<0 || pt.x>=NX || pt.y<0 || pt.y>=NY)
c |= CBounds;
if(board[pt.y][pt.x])
c |= CPiece;
}
return c;
}
int
collider(Point pt, Point pmax)
{
int i, j, pi, pj, n, m;
pi = (pt.x - rboard.min.x) / pcsz;
pj = (pt.y - rboard.min.y) / pcsz;
n = pmax.x / pcsz;
m = pmax.y / pcsz + 1;
for(i = pi; i < pi+n && i < NX; i++)
for(j = pj; j < pj+m && j < NY; j++)
if(board[j][i])
return 1;
return 0;
}
void
setpiece(Piece *p){
int i;
Rectangle r, r2;
Point op, delta;
draw(bb, bb->r, display->white, nil, ZP);
draw(bbmask, bbmask->r, display->transparent, nil, ZP);
br = Rect(0, 0, 0, 0);
br2 = br;
piece = p;
if(p == 0)
return;
r.min = bb->r.min;
for(i=0; i<N; i++){
r.min.x += p->d[i].x*pcsz;
r.min.y += p->d[i].y*pcsz;
r.max.x = r.min.x + pcsz;
r.max.y = r.min.y + pcsz;
if(i == 0){
draw(bb, r, display->black, nil, ZP);
draw(bb, insetrect(r, 1), tx[piece->tx], nil, ZP);
draw(bbmask, r, display->opaque, nil, ZP);
op = r.min;
}else{
draw(bb, r, bb, nil, op);
draw(bbmask, r, bbmask, nil, op);
}
if(br.max.x < r.max.x)
br.max.x = r.max.x;
if(br.max.y < r.max.y)
br.max.y = r.max.y;
}
br.max = subpt(br.max, bb->r.min);
delta = Pt(0,DY);
br2.max = addpt(br.max, delta);
r = rectaddpt(br, bb2->r.min);
r2 = rectaddpt(br2, bb2->r.min);
draw(bb2, r2, display->white, nil, ZP);
draw(bb2, rectaddpt(r,delta), bb, nil, bb->r.min);
draw(bb2mask, r2, display->transparent, nil, ZP);
draw(bb2mask, r, display->opaque, bbmask, bb->r.min);
draw(bb2mask, rectaddpt(r,delta), display->opaque, bbmask, bb->r.min);
}
void
drawpiece(void){
draw(screen, rectaddpt(br, pos), bb, bbmask, bb->r.min);
if (suspended)
draw(screen, rectaddpt(br, pos), display->white, whitemask, ZP);
}
void
undrawpiece(void)
{
Image *mask = nil;
if(collider(pos, br.max))
mask = bbmask;
draw(screen, rectaddpt(br, pos), display->white, mask, bb->r.min);
}
void
rest(void)
{
int i;
Point pt;
pt = divpt(subpt(pos, rboard.min), pcsz);
for(i=0; i<N; i++){
pt.x += piece->d[i].x;
pt.y += piece->d[i].y;
board[pt.y][pt.x] = piece->tx+16;
}
}
int
canfit(Piece *p)
{
static int dx[]={0, -1, 1, -2, 2, -3, 3, 4, -4};
int i, j;
Point z;
j = N + 1;
if(j >= 4){
j = p->sz.x;
if(j<p->sz.y)
j = p->sz.y;
j = 2*j-1;
}
for(i=0; i<j; i++){
z.x = pos.x + dx[i]*pcsz;
z.y = pos.y;
if(!collide(z, p)){
z.y = pos.y + pcsz-1;
if(!collide(z, p)){
undrawpiece();
pos.x = z.x;
return 1;
}
}
}
return 0;
}
void
score(int p)
{
char buf[128];
points += p;
snprint(buf, sizeof(buf), "%.6ld", points);
draw(screen, Rpt(pscore, addpt(pscore, scoresz)), display->white, nil, ZP);
string(screen, pscore, display->black, ZP, font, buf);
}
void
drawsq(Image *b, Point p, int ptx){
Rectangle r;
r.min = p;
r.max.x = r.min.x+pcsz;
r.max.y = r.min.y+pcsz;
draw(b, r, display->black, nil, ZP);
draw(b, insetrect(r, 1), tx[ptx], nil, ZP);
}
void
drawboard(void)
{
int i, j;
border(screen, insetrect(rboard, -2), 2, display->black, ZP);
draw(screen, Rect(rboard.min.x, rboard.min.y-2, rboard.max.x, rboard.min.y),
display->white, nil, ZP);
for(i=0; i<NY; i++)
for(j=0; j<NX; j++)
if(board[i][j])
drawsq(screen, Pt(rboard.min.x+j*pcsz, rboard.min.y+i*pcsz), board[i][j]-16);
score(0);
if (suspended)
draw(screen, screen->r, display->white, whitemask, ZP);
}
void
choosepiece(void)
{
int i;
do{
i = nrand(NP);
setpiece(&pieces[i]);
pos = rboard.min;
pos.x += nrand(NX)*pcsz;
}while(collide(Pt(pos.x, pos.y+pcsz-DY), piece));
drawpiece();
flushimage(display, 1);
}
int
movepiece(void)
{
Image *mask = nil;
if(collide(Pt(pos.x, pos.y+pcsz), piece))
return 0;
if(collider(pos, br2.max))
mask = bb2mask;
draw(screen, rectaddpt(br2, pos), bb2, mask, bb2->r.min);
pos.y += DY;
flushimage(display, 1);
return 1;
}
void
suspend(int s)
{
suspended = s;
if (suspended)
setcursor(mousectl, &whitearrow);
else
setcursor(mousectl, nil);
if (!suspended)
drawpiece();
drawboard();
flushimage(display, 1);
}
void
pause(int t)
{
int s;
Alt alts[NALT+1];
alts[TIMER].c = timerc;
alts[TIMER].v = nil;
alts[TIMER].op = CHANRCV;
alts[SUSPEND].c = suspc;
alts[SUSPEND].v = &s;
alts[SUSPEND].op = CHANRCV;
alts[RESHAPE].c = mousectl->resizec;
alts[RESHAPE].v = nil;
alts[RESHAPE].op = CHANRCV;
// avoid hanging up those writing ong mousec and kbdc
// so just accept it all and keep mouse up-to-date
alts[MOUSE].c = mousec;
alts[MOUSE].v = &mouse;
alts[MOUSE].op = CHANRCV;
alts[KBD].c = kbdc;
alts[KBD].v = nil;
alts[KBD].op = CHANRCV;
alts[NALT].op = CHANEND;
flushimage(display, 1);
for(;;)
switch(alt(alts)){
case SUSPEND:
if (!suspended && s) {
suspend(1);
} else if (suspended && !s) {
suspend(0);
lastmx = warp(mouse.xy, lastmx);
}
break;
case TIMER:
if(suspended)
break;
if((t -= tsleep) < 0)
return;
break;
case RESHAPE:
redraw(1);
break;
}
}
int
horiz(void)
{
int lev[MAXN];
int i, j, h;
Rectangle r;
h = 0;
for(i=0; i<NY; i++){
for(j=0; board[i][j]; j++)
if(j == NX-1){
lev[h++] = i;
break;
}
}
if(h == 0)
return 0;
r = rboard;
newscreen = 0;
for(j=0; j<h; j++){
r.min.y = rboard.min.y + lev[j]*pcsz;
r.max.y = r.min.y + pcsz;
draw(screen, r, display->white, whitemask, ZP);
flushimage(display, 1);
}
for(i=0; i<3; i++){
pause(250);
if(newscreen){
drawboard();
break;
}
for(j=0; j<h; j++){
r.min.y = rboard.min.y + lev[j]*pcsz;
r.max.y = r.min.y + pcsz;
draw(screen, r, display->white, whitemask, ZP);
}
flushimage(display, 1);
}
r = rboard;
for(j=0; j<h; j++){
i = NY - lev[j] - 1;
score(250+10*i*i);
r.min.y = rboard.min.y;
r.max.y = rboard.min.y+lev[j]*pcsz;
draw(screen, rectaddpt(r, Pt(0,pcsz)), screen, nil, r.min);
r.max.y = rboard.min.y+pcsz;
draw(screen, r, display->white, nil, ZP);
memcpy(&board[1][0], &board[0][0], NX*lev[j]);
memset(&board[0][0], 0, NX);
}
flushimage(display, 1);
return 1;
}
void
mright(void)
{
if(!collide(Pt(pos.x+pcsz, pos.y), piece))
if(!collide(Pt(pos.x+pcsz, pos.y+pcsz-DY), piece)){
undrawpiece();
pos.x += pcsz;
drawpiece();
flushimage(display, 1);
}
}
void
mleft(void)
{
if(!collide(Pt(pos.x-pcsz, pos.y), piece))
if(!collide(Pt(pos.x-pcsz, pos.y+pcsz-DY), piece)){
undrawpiece();
pos.x -= pcsz;
drawpiece();
flushimage(display, 1);
}
}
void
rright(void)
{
if(canfit(rotr(piece))){
setpiece(rotr(piece));
drawpiece();
flushimage(display, 1);
}
}
void
rleft(void)
{
if(canfit(rotl(piece))){
setpiece(rotl(piece));
drawpiece();
flushimage(display, 1);
}
}
int fusst = 0;
int
drop(int f)
{
if(f){
score(5L*(rboard.max.y-pos.y)/pcsz);
do; while(movepiece());
}
fusst = 0;
rest();
if(pos.y==rboard.min.y && !horiz())
return 1;
horiz();
setpiece(0);
pause(1500);
choosepiece();
lastmx = warp(mouse.xy, lastmx);
return 0;
}
int
play(void)
{
int i;
Mouse om;
int s;
Rune r;
Alt alts[NALT+1];
alts[TIMER].c = timerc;
alts[TIMER].v = nil;
alts[TIMER].op = CHANRCV;
alts[MOUSE].c = mousec;
alts[MOUSE].v = &mouse;
alts[MOUSE].op = CHANRCV;
alts[SUSPEND].c = suspc;
alts[SUSPEND].v = &s;
alts[SUSPEND].op = CHANRCV;
alts[RESHAPE].c = mousectl->resizec;
alts[RESHAPE].v = nil;
alts[RESHAPE].op = CHANRCV;
alts[KBD].c = kbdc;
alts[KBD].v = &r;
alts[KBD].op = CHANRCV;
alts[NALT].op = CHANEND;
dt = 64;
lastmx = -1;
lastmx = movemouse();
choosepiece();
lastmx = warp(mouse.xy, lastmx);
for(;;)
switch(alt(alts)){
case MOUSE:
if(suspended) {
om = mouse;
break;
}
if(lastmx < 0)
lastmx = mouse.xy.x;
if(mouse.xy.x > lastmx+DMOUSE){
mright();
lastmx = mouse.xy.x;
}
if(mouse.xy.x < lastmx-DMOUSE){
mleft();
lastmx = mouse.xy.x;
}
if(mouse.buttons&1 && !(om.buttons&1))
rleft();
if(mouse.buttons&2 && !(om.buttons&2))
if(drop(1))
return 1;
if(mouse.buttons&4 && !(om.buttons&4))
rright();
om = mouse;
break;
case SUSPEND:
if (!suspended && s)
suspend(1);
else
if (suspended && !s) {
suspend(0);
lastmx = warp(mouse.xy, lastmx);
}
break;
case RESHAPE:
redraw(1);
break;
case KBD:
if(suspended)
break;
switch(r){
case 'f':
case ';':
mright();
break;
case 'a':
case 'j':
mleft();
break;
case 'd':
case 'l':
rright();
break;
case 's':
case 'k':
rleft();
break;
case ' ':
if(drop(1))
return 1;
break;
}
break;
case TIMER:
if(suspended)
break;
dt -= tsleep;
if(dt < 0){
i = 1;
dt = 16 * (points+nrand(10000)-5000) / 10000;
if(dt >= 32){
i += (dt-32)/16;
dt = 32;
}
dt = 52-dt;
while(i-- > 0)
if(movepiece()==0 && ++fusst==40){
if(drop(0))
return 1;
break;
}
}
break;
}
}
void
setparms(void)
{
char buf[32];
int fd, n;
tsleep = 50;
fd = open("/dev/hz", OREAD);
if(fd < 0)
return;
n = read(fd, buf, sizeof buf - 1);
close(fd);
if(n < 0)
return;
buf[n] = '\0';
tsleep = strtoul(buf, 0, 10);
tsleep = (1000 + tsleep - 1) / tsleep;
}
void
timerproc(void *v)
{
Channel *c;
void **arg;
arg = v;
c = (Channel*)arg;
for(;;){
sleep(tsleep);
send(c, nil);
}
}
void
suspproc(void *)
{
Mouse mouse;
Rune r;
int s;
Alt alts[NALT+1];
alts[TIMER].op = CHANNOP;
alts[MOUSE].c = mousectl->c;
alts[MOUSE].v = &mouse;
alts[MOUSE].op = CHANRCV;
alts[SUSPEND].op = CHANNOP;
alts[RESHAPE].op = CHANNOP;
alts[KBD].c = kbdctl->c;
alts[KBD].v = &r;
alts[KBD].op = CHANRCV;
alts[NALT].op = CHANEND;
s = 0;
for(;;)
switch(alt(alts)){
case MOUSE:
send(mousec, &mouse);
break;
case KBD:
switch(r){
case 'q':
case 'Q':
case 0x04:
case 0x7F:
threadexitsall(nil);
default:
if(s) {
s = 0;
send(suspc, &s);
} else
switch(r){
case 'z':
case 'Z':
case 'p':
case 'P':
case 0x1B:
s = 1;
send(suspc, &s);
break;
default:
send(kbdc, &r);
}
break;
}
}
}
void
redraw(int new)
{
Rectangle r;
long dx, dy;
if(new && getwindow(display, Refmesg) < 0)
sysfatal("can't reattach to window");
r = screen->r;
pos.x = (pos.x - rboard.min.x) / pcsz;
pos.y = (pos.y - rboard.min.y) / pcsz;
dx = r.max.x - r.min.x;
dy = r.max.y - r.min.y - 2*32;
DY = dx / NX;
if(DY > dy / NY)
DY = dy / NY;
DY /= 8;
if(DY > 4)
DY = 4;
pcsz = DY*8;
DMOUSE = pcsz/3;
if(pcsz < 8)
sysfatal("screen too small: %d", pcsz);
rboard = screen->r;
rboard.min.x += (dx-pcsz*NX)/2;
rboard.min.y += (dy-pcsz*NY)/2+32;
rboard.max.x = rboard.min.x+NX*pcsz;
rboard.max.y = rboard.min.y+NY*pcsz;
pscore.x = rboard.min.x+8;
pscore.y = rboard.min.y-32;
scoresz = stringsize(font, "000000");
pos.x = pos.x*pcsz + rboard.min.x;
pos.y = pos.y*pcsz + rboard.min.y;
if(bb){
freeimage(bb);
freeimage(bbmask);
freeimage(bb2);
freeimage(bb2mask);
}
bb = allocimage(display, Rect(0,0,N*pcsz,N*pcsz), screen->chan, 0, 0);
bbmask = allocimage(display, Rect(0,0,N*pcsz,N*pcsz), GREY1, 0, 0);
bb2 = allocimage(display, Rect(0,0,N*pcsz,N*pcsz+DY), screen->chan, 0, 0);
bb2mask = allocimage(display, bb2->r, GREY1, 0, 0);
if(bb==0 || bbmask==0 || bb2==0 || bb2mask==0)
sysfatal("allocimage fail (bb)");
draw(screen, screen->r, display->white, nil, ZP);
drawboard();
setpiece(piece);
if(piece)
drawpiece();
lastmx = movemouse();
newscreen = 1;
flushimage(display, 1);
}
void
usage(void)
{
fprint(2, "usage: %s\n", argv0);
exits("usage");
}
void
threadmain(int argc, char *argv[])
{
Image *tb;
char buf[200];
int i, scores;
long starttime, endtime;
ARGBEGIN{
default:
usage();
}ARGEND
if(argc)
usage();
suspended = 0;
setparms();
snprint(buf, sizeof(buf), "%ds", N);
initdraw(0, 0, buf);
mousectl = initmouse(nil, display->image); /* BUG? */
if(mousectl == nil)
sysfatal("[45]s: mouse init failed: %r");
kbdctl = initkeyboard(nil); /* BUG? */
if(kbdctl == nil)
sysfatal("[45]s: keyboard init failed: %r");
starttime = time(0);
srand(starttime);
snprint(buf, sizeof(buf), "/sys/games/lib/%dscores", N);
scores = open(buf, OWRITE);
if(scores < 0)
sysfatal("can't open %s: %r", buf);
tb = 0;
if(screen->depth < 3){
tb = allocimage(display, Rect(0,0,16,16), 0, 1, -1);
if(tb == 0)
sysfatal("allocimage fail (tb)");
}
for(i = 0; i<NCOL; i++){
tx[i] = allocimage(display, Rect(0, 0, 16, 16), screen->chan, 1, txpix[i]);
if(tx[i] == 0)
sysfatal("allocimage fail (tx)");
if(screen->depth < 3){
loadimage(tb, tb->r, txbits[i], 32);
draw(tx[i], tx[i]->r, tb, nil, ZP);
}
}
if(tb != 0)
freeimage(tb);
whitemask = allocimage(display, Rect(0,0,1,1), CMAP8, 1, setalpha(DWhite, 0x7F));
if(whitemask==0)
sysfatal("allocimage fail (whitemask)");
threadsetname("4s-5s");
timerc= chancreate(sizeof(int), 0);
proccreate(timerproc, timerc, 1024);
suspc= chancreate(sizeof(int), 0);
mousec= chancreate(sizeof(Mouse), 0);
kbdc= chancreate(sizeof(Rune), 0);
threadcreate(suspproc, nil, 1024);
points = 0;
memset(board, 0, sizeof(board));
redraw(0);
if(play()){
endtime = time(0);
fprint(scores, "%ld\t%s\t%lud\t%ld\n",
points, getuser(), starttime, endtime-starttime);
}
threadexitsall(nil);
exits(0);
}
|