plan9front/sys/src/games/sokoban/sokoban.c

379 lines
6.1 KiB
C

#include <u.h>
#include <libc.h>
#include <draw.h>
#include <event.h>
#include "sokoban.h"
#define SOKOTREE "/sys/games/lib/sokoban/"
char *LEasy = SOKOTREE "levels/easy.slc";
char *LHard = SOKOTREE "levels/hard.slc";
char *levelfile;
#define SOKOIMG SOKOTREE "images/"
char *GRImage = SOKOIMG "right.bit";
char *GLImage = SOKOIMG "left.bit";
char *WallImage = SOKOIMG "wall.bit";
char *EmptyImage = SOKOIMG "empty.bit";
char *CargoImage = SOKOIMG "cargo.bit";
char *GoalCargoImage= SOKOIMG "goalcargo.bit";
char *GoalImage = SOKOIMG "goal.bit";
char *WinImage = SOKOIMG "win.bit";
char *buttons[] =
{
"restart",
"easy",
"hard",
"noanimate", /* this menu string initialized in main */
"exit",
0
};
char **levelnames;
Menu menu =
{
buttons,
};
Menu lmenu;
void
buildmenu(void)
{
int i;
if (levelnames != nil) {
for(i=0; levelnames[i] != 0; i++)
free(levelnames[i]);
}
levelnames = realloc(levelnames, sizeof(char*)*(numlevels+1));
if (levelnames == nil)
sysfatal("cannot allocate levelnames");
for(i=0; i < numlevels; i++)
levelnames[i] = genlevels(i);
levelnames[numlevels] = 0;
lmenu.item = levelnames;
}
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);
bg = eallocimage(one, 1, DDarkyellow);
text = eallocimage(one, 1, DBluegreen);
gright = eloadfile(GRImage);
gleft = eloadfile(GLImage);
wall = eloadfile(WallImage);
empty = eloadfile(EmptyImage);
empty->repl = 1;
goalcargo = eloadfile(GoalCargoImage);
cargo = eloadfile(CargoImage);
goal = eloadfile(GoalImage);
win = eloadfile(WinImage);
}
int
key2move(int key)
{
int k = 0;
switch(key) {
case 61454:
k = Up;
break;
case 63488:
k = Down;
break;
case 61457:
k = Left;
break;
case 61458:
k = Right;
break;
}
return k;
}
static Route*
mouse2route(Mouse m)
{
Point p, q;
Route *r;
p = subpt(m.xy, screen->r.min);
p.x /= BoardX;
p.y /= BoardY;
q = subpt(p, level.glenda);
// fprint(2, "x=%d y=%d\n", q.x, q.y);
if (q.x == 0 && q.y == 0)
return nil;
if (q.x == 0 || q.y == 0) {
if (q.x < 0)
r = extend(nil, Left, -q.x, Pt(level.glenda.x, p.y));
else if (q.x > 0)
r = extend(nil, Right, q.x, Pt(level.glenda.x, p.y));
else if (q.y < 0)
r = extend(nil, Up, -q.y, level.glenda);
else if (q.y > 0)
r = extend(nil, Down, q.y, level.glenda);
else
r = nil;
if (r != nil && isvalid(level.glenda, r, validpush))
return r;
freeroute(r);
}
return findroute(level.glenda, p);
}
char *
genlevels(int i)
{
if(i >= numlevels)
return 0;
return smprint("level %d", i+1);
}
int
finished(void)
{
int x, y;
for(x = 0; x < MazeX; x++)
for(y = 0; y < MazeY; y++)
if(level.board[x][y] == Goal)
return 0;
return 1;
}
void
eresized(int new)
{
Point p;
if(new && getwindow(display, Refnone) < 0)
sysfatal("can't reattach to window");
p = Pt(Dx(screen->r), Dy(screen->r));
if(!new || !eqpt(p, boardsize(level.max))) {
drawlevel();
}
drawscreen();
}
void
main(int argc, char **argv)
{
Mouse m;
Event ev;
int e;
Route *r;
int timer;
Animation a;
int animate;
if(argc == 2)
levelfile = argv[1];
else
levelfile = LEasy;
if(! loadlevels(levelfile)) {
fprint(2, "usage: %s [levelfile]\n", argv[0]);
exits("usage");
}
buildmenu();
animate = 0;
buttons[3] = animate ? "noanimate" : "animate";
if(initdraw(nil, nil, "sokoban") < 0)
sysfatal("initdraw failed: %r");
einit(Emouse|Ekeyboard);
timer = etimer(0, 200);
initanimation(&a);
allocimages();
glenda = gright;
eresized(0);
for(;;) {
e = event(&ev);
switch(e) {
case Emouse:
m = ev.mouse;
if(m.buttons&1) {
stopanimation(&a);
r = mouse2route(m);
if (r)
setupanimation(&a, r);
if (! animate) {
while(onestep(&a))
;
drawscreen();
}
}
if(m.buttons&2) {
int l;
/* levels start from 1 */
lmenu.lasthit = level.index;
l=emenuhit(2, &m, &lmenu);
if(l>=0){
stopanimation(&a);
level = levels[l];
drawlevel();
drawscreen();
}
}
if(m.buttons&4)
switch(emenuhit(3, &m, &menu)) {
case 0:
stopanimation(&a);
level = levels[level.index];
drawlevel();
drawscreen();
break;
case 1:
stopanimation(&a);
loadlevels(LEasy);
buildmenu();
drawlevel();
drawscreen();
break;
case 2:
stopanimation(&a);
loadlevels(LHard);
buildmenu();
drawlevel();
drawscreen();
break;
case 3:
animate = !animate;
buttons[3] = animate ? "noanimate" : "animate";
break;
case 4:
exits(nil);
}
break;
case Ekeyboard:
if(level.done)
break;
stopanimation(&a);
switch(ev.kbdc) {
case 127:
case 'q':
case 'Q':
exits(nil);
case 'n':
case 'N':
if(level.index < numlevels - 1) {
level = levels[++level.index];
drawlevel();
drawscreen();
}
break;
case 'p':
case 'P':
if(level.index > 0) {
level = levels[--level.index];
drawlevel();
drawscreen();
}
break;
case 'r':
case 'R':
level = levels[level.index];
drawlevel();
drawscreen();
break;
case 61454:
case 63488:
case 61457:
case 61458:
case ' ':
move(key2move(ev.kbdc));
drawscreen();
break;
default:
// fprint(2, "key: %d]\n", e.kbdc);
break;
}
break;
default:
if (e == timer) {
if (animate)
onestep(&a);
else
while(onestep(&a))
;
drawscreen();
}
break;
}
if(finished()) {
level.done = 1;
drawwin();
drawscreen();
sleep(3000);
if(level.index < numlevels - 1) {
level = levels[++level.index];
drawlevel();
drawscreen();
}
}
}
}