Now only redraws changed parts of the level. This gives a speed up (try hitting 'C') and allowed fixing a redraw bug for 'games/mahjongg -f'. The second larger change is an undo feature ('U' or 'Bksp'). Other changes: - added a mostly useless command 'deselect' so carelessly right-clicking doesn't end the game - some purely logical code moved from graphics.c to the new logic.c - tried to make some parts of the code clearer Reference: /n/sources/patch/applied/mahjongg-redrawundo Date: Sat Aug 18 15:26:18 CES 2007 Signed-off-by: rvollmert@gmx.net --- /sys/src/games/mahjongg/graphics.c Sat Aug 18 15:10:02 2007 +++ /sys/src/games/mahjongg/graphics.c Sat Aug 18 15:09:58 2007 @@ -5,142 +5,126 @@ #include "mahjongg.h" -int -freeup(int d, Point p) -{ - /* are we blocked from above? */ - if(d == Depth -1 || (level.board[d+1][p.x][p.y].which == 0 && - level.board[d+1][p.x+1][p.y].which == 0 && - level.board[d+1][p.x][p.y+1].which == 0 && - level.board[d+1][p.x+1][p.y+1].which == 0)) - return 1; - - return 0; -} -int -freeleft(int d, Point p) +/* mark tiles that partially obscure the given tile + relies on the fact that Depth*Dxy <= Tilex/2 */ +void +markabove(int d, int x, int y) { + int dx, dy; - /* blocked from the left? */ - if(p.x == 0 || (level.board[d][p.x-1][p.y].which == 0 && - level.board[d][p.x-1][p.y+1].which == 0)) - return 1; - - return 0; + for(d++; d < Depth; d++) + for(dx = -1; dx <= 2; dx++) + for(dy = -1; dy <= 2; dy++) + if(x+dx=0 && y+dy < Ly && y+dy >= 0) + level.board[d][x+dx][y+dy].redraw = 1; } -int -freeright(int d, Point p) -{ - if(p.x == Lx-2 || (level.board[d][p.x+2][p.y].which == 0 && - level.board[d][p.x+2][p.y+1].which == 0)) - return 1; +void +markbelow(int d, int x, int y) +{ + int dx, dy; - return 0; + for(d--; d >= 0; d--) + for(dx = -2; dx <= 1; dx++) + for(dy = -2; dy <= 1; dy++) + if(x+dx=0 && y+dy < Ly && y+dy >= 0) + level.board[d][x+dx][y+dy].redraw = 1; } -int -isfree(int d, Point p) +Rectangle +tilerect(Click c) { - return (freeleft(d, p) || freeright(d, p)) && freeup(d, p); -} + Point p; + Rectangle r; -void -clearbrick(int d, Point p) -{ - level.board[d][p.x][p.y].which = 0; - level.board[d][p.x+1][p.y].which = 0; - level.board[d][p.x][p.y+1].which = 0; - level.board[d][p.x+1][p.y+1].which = 0; + p = Pt(c.x*(Facex/2)-(c.d*TileDxy), c.y*(Facey/2)-(c.d*TileDxy)); + r = Rpt(p, addpt(p, Pt(Facex, Facey))); + return rectaddpt(r, Pt(Depth*TileDxy, Depth*TileDxy)); } void -resize(Point p) +clearbrick(Click c) { - /* resize to the size of the current level */ + Rectangle r; - int fd; + level.hist[--level.remaining] = c; - fd = open("/dev/wctl", OWRITE); - if(fd >= 0){ - fprint(fd, "resize -dx %d -dy %d", p.x, p.y); - close(fd); - } + level.board[c.d][c.x][c.y].which = None; + level.board[c.d][c.x+1][c.y].which = None; + level.board[c.d][c.x][c.y+1].which = None; + level.board[c.d][c.x+1][c.y+1].which = None; + + r = tilerect(c); + draw(img, r, background, nil, r.min); + markabove(c.d, c.x, c.y); + markbelow(c.d, c.x, c.y); } void -drawbrick(int d, int x, int y) +drawbrick(Click c) { - Point p; Rectangle r; - p = Pt(x*(Facex/2)-(d*TileDxy), y*(Facey/2)-(d*TileDxy)); - r = Rpt(p, addpt(p, Pt(Facex, Facey))); - r = rectaddpt(r, Pt(Depth*TileDxy, Depth*TileDxy)); - draw(img, r, tileset, nil, level.board[d][x][y].start); + r = tilerect(c); + draw(img, r, tileset, nil, level.board[c.d][c.x][c.y].start); - if(level.board[d][x][y].clicked) + if(level.board[c.d][c.x][c.y].clicked) draw(img, r, selected, nil, ZP); - if(level.l.d == d && eqpt(level.l.p, Pt(x, y))) - border(img, r, 2, litbrdr, level.board[d][x][y].start); + if(eqcl(level.l, c)) + border(img, r, 2, litbrdr, level.board[c.d][c.x][c.y].start); /* looks better without borders, uncomment to check it out with'em */ // r = Rpt(r.min, addpt(r.min, Pt(Tilex, Tiley))); // draw(img, r, brdr, nil, ZP); } - void -drawlevel(void) +redrawlevel(int all) { + Brick *b; int d, x, y; - draw(img, img->r, background, nil, ZP); - for(d = 0; d < Depth; d++) for(y = 0; y < Ly; y++) - for(x = 0; x < Lx; x++) - if(level.board[d][x][y].which == 1) - drawbrick(d, x, y); + for(x = 0; x < Lx; x++) { + b = &level.board[d][x][y]; + if(b->which == TL && (all || b->redraw)) { + drawbrick(Cl(d,x,y)); + markabove(d,x,y); + } + b->redraw = 0; + } draw(screen, screen->r, img, nil, ZP); flushimage(display, 1); } -Brick * -bmatch(int d, Point p) +void +updatelevel(void) { - int x, y; - int ld = d; - - do { - for(y = 0; y < Ly; y++) - for(x = 0; x < Lx; x++) - if(level.board[ld][x][y].which == 1 && isfree(ld, Pt(x, y)) && !eqpt(Pt(x, y), p) && - level.board[d][p.x][p.y].type == level.board[ld][x][y].type) - - return &level.board[ld][x][y]; - - } while(--ld >= 0); + redrawlevel(0); +} - return nil; +void +drawlevel(void) +{ + draw(img, img->r, background, nil, ZP); + redrawlevel(1); } -int -canmove(void) +void +resize(Point p) { - int d, x, y; + int fd; - for(d = Depth - 1; d >= 0; d--) - for(y = 0; y < Ly; y++) - for(x = 0; x < Lx; x++) - if(level.board[d][x][y].which == 1 && isfree(d, Pt(x, y))) - if(bmatch(d, Pt(x, y)) != nil) - return 1; - return 0; + fd = open("/dev/wctl", OWRITE); + if(fd >= 0){ + fprint(fd, "resize -dx %d -dy %d", p.x, p.y); + close(fd); + } } void @@ -150,17 +134,17 @@ int d = 0, x = 0, y = 0; if(level.c.d != -1) { - if((b = bmatch(level.c.d, level.c.p)) != nil) { + if((b = bmatch(level.c)) != nil) { d = level.c.d; - x = level.c.p.x; - y = level.c.p.y; + x = level.c.x; + y = level.c.y; } } else { for(d = Depth - 1; d >= 0; d--) for(y = 0; y < Ly; y++) for(x = 0; x < Lx; x++) - if(level.board[d][x][y].which == 1 && isfree(d, Pt(x, y))) - if((b = bmatch(d, Pt(x, y))) != nil) + if(level.board[d][x][y].which == TL && isfree(Cl(d,x,y))) + if((b = bmatch(Cl(d,x,y))) != nil) goto Matched; } @@ -168,21 +152,25 @@ if(b != nil) { level.board[d][x][y].clicked = 1; b->clicked = 1; - drawlevel(); + b->redraw = 1; + updatelevel(); sleep(500); if(level.c.d == -1) level.board[d][x][y].clicked = 0; b->clicked = 0; - drawlevel(); + b->redraw = 1; + updatelevel(); sleep(500); level.board[d][x][y].clicked = 1; b->clicked = 1; - drawlevel(); + b->redraw = 1; + updatelevel(); sleep(500); if(level.c.d == -1) level.board[d][x][y].clicked = 0; b->clicked = 0; - drawlevel(); + b->redraw = 1; + updatelevel(); } } @@ -196,182 +184,148 @@ flushimage(display, 1); } - -void -clicked(Point coord) +Click +findclick(Point coord) { - Point p; - int d; + Click c; - /* ugly on purpose */ - - for(d = Depth - 1; d >= 0; d--) { - p = Pt((coord.x + TileDxy*d)/(Facex/2), (coord.y + TileDxy*d)/(Facey/2)); - switch(level.board[d][p.x][p.y].which) { - case 0: + for(c.d = Depth - 1; c.d >= 0; c.d--) { + c.x = (coord.x + TileDxy*c.d)/(Facex/2); + c.y = (coord.y + TileDxy*c.d)/(Facey/2); + switch(level.board[c.d][c.x][c.y].which) { + case None: break; - case 1: - goto Found; - case 2: - p = Pt(p.x-1, p.y); - goto Found; - case 3: - p = Pt(p.x-1, p.y-1); - goto Found; - case 4: - p = Pt(p.x, p.y-1); - goto Found; + case TL: + return c; + case TR: + c.x = c.x - 1; + return c; + case BR: + c.x = c.x - 1; + c.y = c.y - 1; + return c; + case BL: + c.y = c.y - 1; + return c; } } + return NC; +} - return; - -Found: - if(freeup(d, p) && (freeleft(d, p) || freeright(d, p))) { - if(level.c.d == -1) { - level.c.d = d; - level.c.p = p; - level.board[d][p.x][p.y].clicked = 1; - } else if(!eqpt(p, level.c.p) && - (level.board[d][p.x][p.y].type == level.board[level.c.d][level.c.p.x][level.c.p.y].type)) { - - clearbrick(d, p); - clearbrick(level.c.d, level.c.p); +void +clicked(Point coord) +{ + Click c; + Brick *b, *bc; - level.c.d = -1; - level.c.p = Pt(0, 0); + c = findclick(coord); + if (c.d == -1) + return; - level.remaining -= 2; + b = &level.board[c.d][c.x][c.y]; + if(isfree(c)) { + if(level.c.d == -1) { + level.c = c; + b->clicked = 1; + b->redraw = 1; + } else if(eqcl(c, level.c)) { + level.c = NC; + b->clicked = 0; + b->redraw = 1; } else { - level.board[d][p.x][p.y].clicked = 0; - level.board[level.c.d][level.c.p.x][level.c.p.y].clicked = 0; - level.c.d = -1; - level.c.p = Pt(0, 0); - } - drawlevel(); + bc = &level.board[level.c.d][level.c.x][level.c.y]; + if(b->type == bc->type) { + clearbrick(c); + bc->clicked = 0; + clearbrick(level.c); + level.c = NC; + } else { + bc->clicked = 0; + bc->redraw = 1; + b->clicked = 1; + b->redraw = 1; + level.c = c; + } + } + updatelevel(); if(!canmove()) done(); } } void -light(Point coord) +undo(void) { - Point p; - int d; - - /* ugly on purpose */ - - for(d = Depth - 1; d >= 0; d--) { - p = Pt((coord.x + TileDxy*d)/(Facex/2), (coord.y + TileDxy*d)/(Facey/2)); - switch(level.board[d][p.x][p.y].which) { - case 0: - break; - case 1: - goto Found; - case 2: - p = Pt(p.x-1, p.y); - goto Found; - case 3: - p = Pt(p.x-1, p.y-1); - goto Found; - case 4: - p = Pt(p.x, p.y-1); - goto Found; - } - } - - return; + int i, j, d, x, y; -Found: - if(level.l.d == d && eqpt(level.l.p, p)) + if(level.remaining >= Tiles) return; - if(freeup(d, p) && (freeleft(d, p) || freeright(d, p))) { - Point tmpp; - int tmpd; - - tmpd = level.l.d; - tmpp = level.l.p; - - level.l.d = d; - level.l.p = p; - drawbrick(d, p.x, p.y); - - /* clean up the previously lit brick */ - if(tmpd != -1 && level.board[tmpd][tmpp.x][tmpp.y].which == 1) - drawbrick(tmpd, tmpp.x, tmpp.y); - - draw(screen, screen->r, img, nil, ZP); - flushimage(display, 1); - } else if(level.l.d != -1) { - d = level.l.d; - p = level.l.p; - level.l.d = -1; - level.l.p = Pt(0, 0); - - if(level.board[d][p.x][p.y].which == 1) { - drawbrick(d, p.x, p.y); - draw(screen, screen->r, img, nil, ZP); - flushimage(display, 1); - } + for(i=1; i<=2; i++) { + j = level.remaining++; + d = level.hist[j].d; + x = level.hist[j].x; + y = level.hist[j].y; + level.board[d][x][y].which = TL; + level.board[d][x+1][y].which = TR; + level.board[d][x+1][y+1].which = BR; + level.board[d][x][y+1].which = BL; + level.board[d][x][y].redraw = 1; } + updatelevel(); } -/* below only for testing */ - -Point -pmatch(int d, Point p) +void +deselect(void) { - int x, y; - int ld = d; - - do { - for(y = 0; y < Ly; y++) - for(x = 0; x < Lx; x++) - if(level.board[ld][x][y].which == 1 && isfree(ld, Pt(x, y)) && !eqpt(Pt(x, y), p) && - level.board[d][p.x][p.y].type == level.board[ld][x][y].type) - - return Pt(x, y); - - } while(--ld >= 0); - - return Pt(-1, -1); + Brick *b; + if(level.c.d == -1) + return; + b = &level.board[level.c.d][level.c.x][level.c.y]; + level.c = NC; + b->clicked = 0; + b->redraw = 1; + updatelevel(); } - -int -dmatch(int d, Point p) + +void +light(Point coord) { - int x, y; - int ld = d; + Click c = findclick(coord); + if (c.d == -1) + return; - do { - for(y = 0; y < Ly; y++) - for(x = 0; x < Lx; x++) - if(level.board[ld][x][y].which == 1 && isfree(ld, Pt(x, y)) && !eqpt(Pt(x, y), p) && - level.board[d][p.x][p.y].type == level.board[ld][x][y].type) - - return ld; + if(eqcl(level.l, c)) + return; - } while(--ld >= 0); + if (level.l.d != -1) { + level.board[level.l.d][level.l.x][level.l.y].redraw = 1; + level.l = NC; + } - return -1; + if(isfree(c)) { + level.l = c; + level.board[c.d][c.x][c.y].redraw = 1; + } + + updatelevel(); } void clearlevel(void) { - int d, x, y; + Click c, cm; - for(d = Depth - 1; d >= 0; d--) - for(y = 0; y < Ly; y++) - for(x = 0; x < Lx; x++) - if(level.board[d][x][y].which == 1 && isfree(d, Pt(x, y))) - if(bmatch(d, Pt(x, y)) != nil) { - clearbrick(dmatch(d, Pt(x, y)), pmatch(d, Pt(x, y))); - clearbrick(d, Pt(x, y)); - level.remaining -= 2; - drawlevel(); + for(c.d = Depth - 1; c.d >= 0; c.d--) + for(c.y = 0; c.y < Ly; c.y++) + for(c.x = 0; c.x < Lx; c.x++) + if(level.board[c.d][c.x][c.y].which == TL && isfree(c)) { + cm = cmatch(c, c.d); + if(cm.d != -1) { + clearbrick(cm); + clearbrick(c); + updatelevel(); clearlevel(); } + } } --- /sys/src/games/mahjongg/level.c Sat Aug 18 15:10:21 2007 +++ /sys/src/games/mahjongg/level.c Sat Aug 18 15:10:17 2007 @@ -143,10 +143,8 @@ if(n != orig.remaining) fprint(2, "level improperly generated: %d elements, should have %d\n", n, orig.remaining); - orig.c.d = -1; - orig.c.p = Pt(0, 0); - orig.l.d = -1; - orig.l.p = Pt(0, 0); + orig.c = NC; + orig.l = NC; orig.done = 0; level = orig; } --- /sys/src/games/mahjongg/logic.c Thu Jan 1 00:00:00 1970 +++ /sys/src/games/mahjongg/logic.c Sat Aug 18 15:10:41 2007 @@ -0,0 +1,105 @@ +#include +#include +#include +#include + +#include "mahjongg.h" + +Click NC = {-1, 0, 0,}; + +Click +Cl(int d, int x, int y) +{ + Click c = {d, x, y,}; + return c; +} + +int +eqcl(Click c1, Click c2) +{ + return c1.d == c2.d && c1.x == c2.x && c1.y == c2.y; +} + + +int +freeup(Click c) +{ + if(c.d == Depth -1 || (level.board[c.d+1][c.x][c.y].which == None && + level.board[c.d+1][c.x+1][c.y].which == None && + level.board[c.d+1][c.x][c.y+1].which == None && + level.board[c.d+1][c.x+1][c.y+1].which == None)) + return 1; + + return 0; +} + +int +freeleft(Click c) +{ + if(c.x == 0 || (level.board[c.d][c.x-1][c.y].which == None && + level.board[c.d][c.x-1][c.y+1].which == None)) + return 1; + + return 0; +} + +int +freeright(Click c) +{ + if(c.x == Lx-2 || (level.board[c.d][c.x+2][c.y].which == None && + level.board[c.d][c.x+2][c.y+1].which == None)) + return 1; + + return 0; +} + +int +isfree(Click c) +{ + return (freeleft(c) || freeright(c)) && freeup(c); +} + +Click +cmatch(Click c, int dtop) +{ + Click lc; + + lc.d = dtop; + do { + for(lc.y = 0; lc.y < Ly; lc.y++) + for(lc.x = 0; lc.x < Lx; lc.x++) + if(level.board[lc.d][lc.x][lc.y].which == TL && isfree(lc) && !eqcl(c, lc) && + level.board[c.d][c.x][c.y].type == level.board[lc.d][lc.x][lc.y].type) + + return lc; + + } while(--lc.d >= 0); + + return NC; +} + +Brick * +bmatch(Click c) +{ + Click lc; + + lc = cmatch(c, Depth); + if(lc.d == -1) + return nil; + else + return &level.board[lc.d][lc.x][lc.y]; +} + +int +canmove(void) +{ + Click c; + + for(c.d = Depth - 1; c.d >= 0; c.d--) + for(c.y = 0; c.y < Ly; c.y++) + for(c.x = 0; c.x < Lx; c.x++) + if(level.board[c.d][c.x][c.y].which == TL && isfree(c)) + if(bmatch(c) != nil) + return 1; + return 0; +} --- /sys/src/games/mahjongg/mahjongg.c Sat Aug 18 15:11:06 2007 +++ /sys/src/games/mahjongg/mahjongg.c Sat Aug 18 15:11:03 2007 @@ -3,6 +3,8 @@ #include #include +#include + #include "mahjongg.h" char *Border = "/sys/games/lib/mahjongg/images/border.bit"; @@ -16,9 +18,9 @@ int trace; - char *buttons[] = { + "deselect", "new", "restart", "resize", @@ -78,6 +80,7 @@ selected = eallocimage(one, 1, RGBA32, setalpha(DPalebluegreen, 0x5f)); litbrdr = eallocimage(one, 1, RGBA32, DGreen); img = eallocimage(Rect(0, 0, Sizex, Sizey), 0, defchan ? defchan : screen->chan, DBlack); + textcol = eallocimage(one, 1, RGBA32, DWhite); background = eloadfile(defbackgr); replclipr(background, 1, img->r); @@ -103,6 +106,7 @@ Mouse m; Event e; int clickety = 0; + Point origin = Pt(Bord, Bord); ARGBEGIN{ case 'h': @@ -137,6 +141,8 @@ einit(Emouse|Ekeyboard); allocimages(); + + /* resize to the size of the current level */ resize(img->r.max); generate(time(0)); @@ -152,12 +158,12 @@ break; if(!clickety && level.remaining > 0) { clickety = 1; - clicked(subpt(m.xy, addpt(screen->r.min, Pt(30, 30)))); + clicked(subpt(m.xy, addpt(screen->r.min, origin))); } } else { clickety = 0; if(trace) - light(subpt(m.xy, addpt(screen->r.min, Pt(30, 30)))); + light(subpt(m.xy, addpt(screen->r.min, origin))); } if(m.buttons&2) { /* nothing here for the moment */ @@ -165,17 +171,20 @@ if(m.buttons&4) switch(emenuhit(3, &m, &menu)) { case 0: + deselect(); + break; + case 1: generate(time(0)); drawlevel(); break; - case 1: + case 2: level = orig; drawlevel(); break; - case 2: + case 3: resize(img->r.max); break; - case 3: + case 4: exits(nil); } break; @@ -194,18 +203,30 @@ case 'N': /* new */ generate(time(0)); + drawlevel(); break; case 'r': case 'R': level = orig; + drawlevel(); break; case 'c': case 'C': - clearlevel(); + if(!level.done) { + clearlevel(); + done(); + } + break; + case 8: + case 'u': + case 'U': + if(level.done) { + level.done = 0; + drawlevel(); + } + undo(); break; } - if(! level.done) - drawlevel(); break; } } --- /sys/src/games/mahjongg/mahjongg.h Sat Aug 18 15:11:35 2007 +++ /sys/src/games/mahjongg/mahjongg.h Sat Aug 18 15:11:31 2007 @@ -22,6 +22,7 @@ TileDxy = 6, /* tile displacement when on a higher level */ Lx = 32, Ly = 16, + Bord = Depth*TileDxy, }; enum { /* the size of a complete tile */ @@ -35,32 +36,43 @@ /* and the entire window, giving room for 5*6 = 30 pixels * that are needed for the higher tiles */ - Sizex = 16*Facex + 2*Depth*TileDxy, - Sizey = 8*Facey + 2*Depth*TileDxy, + Sizex = Lx*Facex/2 + 2*Bord, + Sizey = Ly*Facey/2 + 2*Bord, }; +/* which part of a tile */ +typedef enum { + None, + TL, /* main brick */ + TR, + BR, + BL, +} Which; typedef struct { - Point start; /* where do we draw here */ + Point start; /* where is this brick in the tileset */ int clicked; - int which; /* 0 ↔ 4 */ + Which which; int type; + int redraw; } Brick; typedef struct { int d; - Point p; + int x; + int y; } Click; typedef struct { - Brick board[Depth][Lx][Ly]; - Click c; /* player has a brick selected */ - Click l; /* mouse-over-brick indicator */ - int done; + Brick board[Depth][Lx][Ly]; /* grid of quarter tiles */ + Click c; /* player has a brick selected */ + Click l; /* mouse-over-brick indicator */ + int done; + Click hist[Tiles]; int remaining; } Level; -Level level; /* the level played */ +Level level; /* the level played */ Level orig; /* same, sans modifications */ Image *img; /* buffer */ @@ -71,12 +83,24 @@ Image *background; Image *selected; Image *litbrdr; +Image *textcol; Image *gameover; +/* logic.c */ +Click NC; +Click Cl(int d, int x, int y); +int eqcl(Click c1, Click c2); +int isfree(Click c); +Click cmatch(Click c, int dtop); +Brick *bmatch(Click c); +int canmove(void); + /* graphics.c */ void drawlevel(void); void resize(Point); void clicked(Point); +void undo(void); +void deselect(void); void light(Point); void hint(void); void done(void); --- /sys/src/games/mahjongg/mkfile Sat Aug 18 15:12:07 2007 +++ /sys/src/games/mahjongg/mkfile Sat Aug 18 15:12:03 2007 @@ -6,6 +6,7 @@ mahjongg.$O\ graphics.$O\ level.$O\ + logic.$O\ HFILES=mahjongg.h\ --- /sys/man/1/games Sat Aug 18 15:12:43 2007 +++ /sys/man/1/games Sat Aug 18 15:12:39 2007 @@ -123,6 +123,12 @@ .LR H gives a hint, either trying to match the currently selected tile, or if no tile is selected finding out the first available tile. +.LR U +and +.LR Bksp +undo the last move, +.LR C +tries to solve the level. .TP .B sokoban Guide Glenda through a room full of walls, pebbles and holes to put