diff --git a/sys/src/games/glendy.c b/sys/src/games/glendy.c new file mode 100644 index 000000000..753bd0a55 --- /dev/null +++ b/sys/src/games/glendy.c @@ -0,0 +1,530 @@ +#include +#include +#include +#include + +enum{ + /* difficulty levels (how many circles are initially occupied) */ + DEasy, /* 10≤x<15 */ + DMed, /* 5≤x<10 */ + DHard, /* 0≤x<5 */ + + /* dynamic? original game has a fixed grid size, but we don't need to abide by it */ + SzX = 11, + SzY = 11, + + Border = 10, + /* movement directions */ + NE, + E, + SE, + SW, + W, + NW, + + Won = 1, /* game-ending states */ + Lost = 2, +}; + +Font *font; + +int difficulty = DMed; +int finished; + +int grid[SzX][SzY]; +int ogrid[SzX][SzY]; /* so we can restart levels */ + +Image *gl; /* glenda */ +Image *glm; /* glenda's mask */ +Image *cc; /* clicked */ +Image *ec; /* empty; not clicked */ +Image *bg; +Image *lost; +Image *won; + + +char *mbuttons[] = +{ + "easy", + "medium", + "hard", + 0 +}; + +char *rbuttons[] = +{ + "new", + "reset", + "exit", + 0 +}; + +Menu mmenu = +{ + mbuttons, +}; + +Menu rmenu = +{ + rbuttons, +}; + +Image * +eallocimage(Rectangle r, int repl, uint color) +{ + Image *tmp; + + tmp = allocimage(display, r, screen->chan, repl, color); + if(tmp == nil) + sysfatal("cannot allocate buffer image: %r"); + + return tmp; +} + +Image * +eloadfile(char *path) +{ + Image *img; + int fd; + + fd = open(path, OREAD); + if(fd < 0) { + fprint(2, "cannot open image file %s: %r\n", path); + exits("image"); + } + img = readimage(display, fd, 0); + if(img == nil) + sysfatal("cannot load image: %r"); + close(fd); + + return img; +} + + +void +allocimages(void) +{ + Rectangle one = Rect(0, 0, 1, 1); + + cc = eallocimage(one, 1, 0x777777FF); + ec = eallocimage(one, 1, DPalegreen); + bg = eallocimage(one, 1, DPurpleblue); + lost = eallocimage(one, 1, DRed); + won = eallocimage(one, 1, DGreen); + gl = eloadfile("/lib/face/48x48x4/g/glenda.1"); + + glm = allocimage(display, Rect(0, 0, 48, 48), gl->chan, 1, DCyan); + if(glm == nil) + sysfatal("cannot allocate mask: %r"); + + draw(glm, glm->r, display->white, nil, ZP); + gendraw(glm, glm->r, display->black, ZP, gl, gl->r.min); + freeimage(gl); + gl = display->black; + + +} + +/* unnecessary calculations here, but it's fine */ +Point +board2pix(int x, int y) +{ + float d, rx, ry, yh; + int nx, ny; + + d = (float)(Dx(screen->r) > Dy(screen->r)) ? Dy(screen->r) -20 : Dx(screen->r) -20; + rx = d/(float)SzX; + rx = rx/2.0; + ry = d/(float)SzY; + ry = ry/2.0; + + yh = ry/3.73205082; + + nx = (int)((float)x*rx*2.0+rx +(y%2?rx:0.0)); /* nx = x*(2rx) + rx + rx (conditional) */ + ny = (int)((float)y*(ry*2.0-(y>0?yh:0.0)) + ry); /* ny = y*(2ry-yh) +ry */ + return Pt(nx, ny); +} + +Point +pix2board(int x, int y) +{ + float d, rx, ry, yh; + int ny, nx; + + /* XXX: float→int causes small rounding errors */ + + d = (float)(Dx(screen->r) > Dy(screen->r)) ? Dy(screen->r) -20: Dx(screen->r)-20; + rx = d/(float)SzX; + rx = rx/2.0; + ry =d/(float)SzY; + ry = ry/2.0; + + yh = ry/3.73205082; + + /* reverse board2pix() */ + ny = (int)(((float)y - ry)/(2*ry - ((y>2*ry)?yh:0.0)) + 0.5); /* ny = (y - ry)/(2ry-yh) */ + nx = (int)(((float)x - rx - (ny%2?rx:0.0))/(rx*2.0) + 0.5); /* nx = (x - rx - rx)/2rx */ + + if (nx >= SzX) + nx = SzX-1; + if (ny >=SzY) + ny = SzY-1; + + return Pt(nx, ny); +} + +void +initlevel(void) +{ + int i, cnt = 10, x, y; + + for(x = 0; x < SzX; x++) + for(y = 0; y < SzY; y++) + ogrid[x][y] = 100; + + switch(difficulty){ + case DEasy: + cnt = 10 + nrand(5); + break; + case DMed: + cnt = 5 + nrand(5); + break; + case DHard: + cnt = nrand(5); + break; + } + for(i = 0; i < cnt; i++) { + do { + x = nrand(SzX); + y = nrand(SzY); + } while(ogrid[x][y] != 100); + ogrid[x][y] = 999; + } + + ogrid[SzX/2][SzY/2] = 1000; + + memcpy(grid, ogrid, sizeof grid); + + finished = 0; + +} + +void +drawlevel(void) +{ + Point p; + int x, y, rx, ry, d; + char *s = nil; + + if(finished) + draw(screen, screen->r, finished==Won?won:lost, nil, ZP); + else + draw(screen, screen->r, bg, nil, ZP); + + d = (Dx(screen->r) > Dy(screen->r)) ? Dy(screen->r) -20: Dx(screen->r) -20; + rx = (int)ceil((float)(d-2*Border)/(float)SzX)/2; + ry = (int)ceil((float)(d-2*Border)/(float)SzY)/2; + + for(x = 0; x < SzX; x++) { + for(y = 0; y < SzY; y++) { + p = board2pix(x, y); + switch(grid[x][y]){ + case 999: + fillellipse(screen, addpt(screen->r.min, p), rx, ry, cc, ZP); + break; + case 1000: + p = addpt(screen->r.min, p); + fillellipse(screen, p, rx, ry, ec, ZP); + p = subpt(p, Pt(24, 24)); + draw(screen, Rpt(p, addpt(p, Pt(48, 48))), gl, glm, ZP); + break; + default: + fillellipse(screen, addpt(screen->r.min, p), rx, ry, ec, ZP); + USED(s); + /* uncomment the following to see game state and field scores */ + /*s = smprint("%d", grid[x][y]); + string(screen, addpt(screen->r.min, p), display->black, ZP, font, s); + free(s); + */ + break; + } + } + } + flushimage(display, 1); +} + +void +domove(int dir, int x, int y) +{ + if(x == 0 || x == SzX-1 || y == 0 || y == SzY-1) + goto done; + + switch(dir){ + case NE: + if(y%2) + grid[x+1][y-1] = 1000; + else + grid[x][y-1] = 1000; + break; + case E: + grid[x+1][y] = 1000; + break; + case SE: + if(y%2) + grid[x+1][y+1] = 1000; + else + grid[x][y+1] = 1000; + break; + case SW: + if(y%2) + grid[x][y+1] = 1000; + else + grid[x-1][y+1] = 1000; + break; + case W: + grid[x-1][y] = 1000; + break; + case NW: + if(y%2) + grid[x][y-1] = 1000; + else + grid[x-1][y-1] = 1000; + break; + } +done: + grid[x][y] = 100; +} + +Point +findglenda(void) +{ + int x, y; + for(x = 0; x < SzX; x++) + for(y = 0; y < SzY; y++) + if(grid[x][y] == 1000) + return Pt(x, y); + return Pt(-1, -1); +} + +int +checknext(int dir, int x, int y) +{ + switch(dir){ + case NE: + return grid[x+(y%2?1:0)][y-1]; + case E: + return grid[x+1][y]; + case SE: + return grid[x+(y%2?1:0)][y+1]; + case SW: + return grid[x+(y%2?0:-1)][y+1]; + case W: + return grid[x-1][y]; + case NW: + return grid[x+(y%2?0:-1)][y-1]; + default: + sysfatal("andrey messed up big time"); + } + return 1000; +} +/* the following two routines constitute the "game AI" +* they score the field based on the number of moves +* required to reach the edge from a particular point +* scores > 100 are "dead spots" (this assumes the field +* is not larger than ~100*2 +* +* routines need to run at least twice to ensure a field is properly +* scored: there are errors that creep up due to the nature of +* traversing the board +*/ +int +score1(int x, int y) { + int dir, min = 999, next; + + if(x == 0 || x == SzX-1 || y == 0 || y == SzY-1) + return 1; /* we can always escape from the edges */ + + for(dir = NE; dir <= NW; dir++) { + next = checknext(dir, x, y); + if(next < min) + min = next; + } + return 1+min; +} + +void +calc(void) +{ + int i, x, y; + for(i = 0; i < SzX; i++) /* assumes SzX = SzY */ + for(x = i; x < SzX-i; x++) + for(y = i; y < SzY-i; y++) + if(grid[x][y] != 999) + grid[x][y] = score1(x, y); +} + +void +nextglenda(void) +{ + int min =1000, next, dir, nextdir = 0, count = 0; + Point p = findglenda(); + + calc(); + calc(); + calc(); + + grid[p.x][p.y] = 1000; + + for(dir = NE; dir <= NW; dir++) { + next = checknext(dir, p.x, p.y); + if(next < min) { + min = next; + nextdir = dir; + ++count; + } else if(next == min) { + nextdir = (nrand(++count) == 0)?dir:nextdir; + } + } + if(min < 100) + domove(nextdir, p.x, p.y); + else + finished = Won; + + if(eqpt(findglenda(), Pt(-1, -1))) + finished = Lost; +} + +int +checkfinished(void) +{ + int i, j; + for(i = 0; i < SzX; i++) + for(j = 0; j < SzY; j++) + if(grid[i][j] == 'E') + return 0; + return 1; +} + +void +move(Point m) +{ + Point p, nm; + int x, y; + + nm = subpt(m, screen->r.min); + + /* figure out where the click falls */ + p = pix2board(nm.x, nm.y); + + if(grid[p.x][p.y] >= 999) + return; + + /* reset the board scores */ + grid[p.x][p.y] = 999; + for(x = 0; x < SzX; x++) + for(y = 0; y < SzY; y++) + if(grid[x][y] != 999 && grid[x][y] != 1000) + grid[x][y] = 100; + + nextglenda(); +} + +void +resize(void) +{ + int fd, size = (Dx(screen->r) > Dy(screen->r)) ? Dy(screen->r) + 20 : Dx(screen->r)+20; + + fd = open("/dev/wctl", OWRITE); + if(fd >= 0){ + fprint(fd, "resize -dx %d -dy %d", size, size); + close(fd); + } + +} + + +void +eresized(int new) +{ + if(new && getwindow(display, Refnone) < 0) + sysfatal("can't reattach to window"); + + drawlevel(); +} + +void +main(int argc, char **argv) +{ + Mouse m; + Event ev; + int e, mousedown=0; + char *fontname; + + USED(argv, argc); + + if(initdraw(nil, nil, "glendy") < 0) + sysfatal("initdraw failed: %r"); + einit(Emouse); + + resize(); + + srand(time(0)); + + allocimages(); + initlevel(); /* must happen before "eresized" */ + eresized(0); + + fontname = "/lib/font/bit/lucidasans/unicode.8.font"; + if((font = openfont(display, fontname)) == nil) + sysfatal("font '%s' not found", fontname); + + for(;;) { + e = event(&ev); + switch(e) { + case Emouse: + m = ev.mouse; + if(m.buttons == 0) { + if(mousedown && !finished) { + mousedown = 0; + move(m.xy); + drawlevel(); + } + } + if(m.buttons&1) { + mousedown = 1; + } + if(m.buttons&2) { + switch(emenuhit(2, &m, &mmenu)) { + case 0: + difficulty = DEasy; + initlevel(); + break; + case 1: + difficulty = DMed; + initlevel(); + break; + case 2: + difficulty = DHard; + initlevel(); + break; + } + drawlevel(); + } + if(m.buttons&4) { + switch(emenuhit(3, &m, &rmenu)) { + case 0: + initlevel(); + break; + case 1: + memcpy(grid, ogrid, sizeof grid); + finished = 0; + break; + case 2: + exits(nil); + } + drawlevel(); + } + break; + } + } +} diff --git a/sys/src/games/mkfile b/sys/src/games/mkfile index 73467cc32..e59b1ad7f 100644 --- a/sys/src/games/mkfile +++ b/sys/src/games/mkfile @@ -9,6 +9,7 @@ TARG=4s\ life\ memo\ mole\ + glendy\ OFILES= HFILES=