ref: 20bca0c2029c53de3d4149d957240b17d44684a8
dir: /sys/src/games/xs.c/
#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) print("can't open %s: %r\n", 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, 8192); suspc= chancreate(sizeof(int), 0); mousec= chancreate(sizeof(Mouse), 0); kbdc= chancreate(sizeof(Rune), 0); threadcreate(suspproc, nil, 8192); points = 0; memset(board, 0, sizeof(board)); redraw(0); if(play() && scores >= 0){ endtime = time(0); fprint(scores, "%ld\t%s\t%lud\t%ld\n", points, getuser(), starttime, endtime-starttime); } threadexitsall(nil); exits(0); }