xref: /plan9/sys/src/games/sokoban/sokoban.c (revision 0a5ecf54db771ed558d00bf1584611954c2e5e4e)
1a7b22450SDavid du Colombier #include <u.h>
2a7b22450SDavid du Colombier #include <libc.h>
3a7b22450SDavid du Colombier #include <draw.h>
4a7b22450SDavid du Colombier #include <event.h>
5a7b22450SDavid du Colombier 
6a7b22450SDavid du Colombier #include "sokoban.h"
7a7b22450SDavid du Colombier 
8*0a5ecf54SDavid du Colombier #define SOKOTREE "/sys/games/lib/sokoban/"
9*0a5ecf54SDavid du Colombier 
10*0a5ecf54SDavid du Colombier char *LEasy = SOKOTREE "levels/easy.slc";
11*0a5ecf54SDavid du Colombier char *LHard = SOKOTREE "levels/hard.slc";
12a7b22450SDavid du Colombier char *levelfile;
13a7b22450SDavid du Colombier 
14*0a5ecf54SDavid du Colombier #define SOKOIMG SOKOTREE "images/"
15*0a5ecf54SDavid du Colombier 
16*0a5ecf54SDavid du Colombier char	*GRImage =	SOKOIMG "right.bit";
17*0a5ecf54SDavid du Colombier char	*GLImage =	SOKOIMG "left.bit";
18*0a5ecf54SDavid du Colombier char	*WallImage =	SOKOIMG "wall.bit";
19*0a5ecf54SDavid du Colombier char	*EmptyImage =	SOKOIMG "empty.bit";
20*0a5ecf54SDavid du Colombier char	*CargoImage =	SOKOIMG "cargo.bit";
21*0a5ecf54SDavid du Colombier char	*GoalCargoImage= SOKOIMG "goalcargo.bit";
22*0a5ecf54SDavid du Colombier char	*GoalImage =	SOKOIMG "goal.bit";
23*0a5ecf54SDavid du Colombier char	*WinImage =	SOKOIMG "win.bit";
24a7b22450SDavid du Colombier 
25a7b22450SDavid du Colombier char *buttons[] =
26a7b22450SDavid du Colombier {
27a7b22450SDavid du Colombier 	"restart",
28a7b22450SDavid du Colombier 	"easy",
29a7b22450SDavid du Colombier 	"hard",
3097787252SDavid du Colombier 	"noanimate", /* this menu string initialized in main */
31a7b22450SDavid du Colombier 	"exit",
32a7b22450SDavid du Colombier 	0
33a7b22450SDavid du Colombier };
34a7b22450SDavid du Colombier 
3501e2ed2dSDavid du Colombier char **levelnames;
3601e2ed2dSDavid du Colombier 
37a7b22450SDavid du Colombier Menu menu =
38a7b22450SDavid du Colombier {
3901e2ed2dSDavid du Colombier 	buttons,
40a7b22450SDavid du Colombier };
41a7b22450SDavid du Colombier 
42a7b22450SDavid du Colombier Menu lmenu =
43a7b22450SDavid du Colombier {
4401e2ed2dSDavid du Colombier 	levelnames,
45a7b22450SDavid du Colombier };
46a7b22450SDavid du Colombier 
4701e2ed2dSDavid du Colombier void
buildmenu(void)4801e2ed2dSDavid du Colombier buildmenu(void)
4901e2ed2dSDavid du Colombier {
5001e2ed2dSDavid du Colombier 	int i;
5101e2ed2dSDavid du Colombier 
5201e2ed2dSDavid du Colombier 	if (levelnames != nil) {
5301e2ed2dSDavid du Colombier 		for(i=0; levelnames[i] != 0; i++)
5401e2ed2dSDavid du Colombier 			free(levelnames[i]);
5501e2ed2dSDavid du Colombier 	}
5601e2ed2dSDavid du Colombier 	levelnames = realloc(levelnames, sizeof(char*)*(numlevels+1));
5701e2ed2dSDavid du Colombier 	if (levelnames == nil)
5801e2ed2dSDavid du Colombier 		sysfatal("cannot allocate levelnames");
5901e2ed2dSDavid du Colombier 	for(i=0; i < numlevels; i++)
6001e2ed2dSDavid du Colombier 		levelnames[i] = genlevels(i);
6101e2ed2dSDavid du Colombier 	levelnames[numlevels] = 0;
6201e2ed2dSDavid du Colombier 	lmenu.item = levelnames;
6301e2ed2dSDavid du Colombier }
6401e2ed2dSDavid du Colombier 
65a7b22450SDavid du Colombier Image *
eallocimage(Rectangle r,int repl,uint color)66a7b22450SDavid du Colombier eallocimage(Rectangle r, int repl, uint color)
67a7b22450SDavid du Colombier {
68a7b22450SDavid du Colombier 	Image *tmp;
69a7b22450SDavid du Colombier 
70a7b22450SDavid du Colombier 	tmp = allocimage(display, r, screen->chan, repl, color);
71a7b22450SDavid du Colombier 	if(tmp == nil)
72a7b22450SDavid du Colombier 		sysfatal("cannot allocate buffer image: %r");
73a7b22450SDavid du Colombier 
74a7b22450SDavid du Colombier 	return tmp;
75a7b22450SDavid du Colombier }
76a7b22450SDavid du Colombier 
77a7b22450SDavid du Colombier Image *
eloadfile(char * path)78a7b22450SDavid du Colombier eloadfile(char *path)
79a7b22450SDavid du Colombier {
80a7b22450SDavid du Colombier 	Image *img;
81a7b22450SDavid du Colombier 	int fd;
82a7b22450SDavid du Colombier 
83a7b22450SDavid du Colombier 	fd = open(path, OREAD);
84a7b22450SDavid du Colombier 	if(fd < 0) {
85a7b22450SDavid du Colombier 		fprint(2, "cannot open image file %s: %r\n", path);
86a7b22450SDavid du Colombier 		exits("image");
87a7b22450SDavid du Colombier 	}
88a7b22450SDavid du Colombier 	img = readimage(display, fd, 0);
89a7b22450SDavid du Colombier 	if(img == nil)
90a7b22450SDavid du Colombier 		sysfatal("cannot load image: %r");
91a7b22450SDavid du Colombier 	close(fd);
92a7b22450SDavid du Colombier 
93a7b22450SDavid du Colombier 	return img;
94a7b22450SDavid du Colombier }
95a7b22450SDavid du Colombier 
96a7b22450SDavid du Colombier 
97a7b22450SDavid du Colombier void
allocimages(void)98a7b22450SDavid du Colombier allocimages(void)
99a7b22450SDavid du Colombier {
100a7b22450SDavid du Colombier 	Rectangle one = Rect(0, 0, 1, 1);
101a7b22450SDavid du Colombier 
102a7b22450SDavid du Colombier 	bg		= eallocimage(one, 1, DDarkyellow);
103a7b22450SDavid du Colombier 	text 		= eallocimage(one, 1, DBluegreen);
104a7b22450SDavid du Colombier 
105a7b22450SDavid du Colombier 	gright = eloadfile(GRImage);
106a7b22450SDavid du Colombier 	gleft = eloadfile(GLImage);
107a7b22450SDavid du Colombier 	wall = eloadfile(WallImage);
108a7b22450SDavid du Colombier 	empty = eloadfile(EmptyImage);
109a7b22450SDavid du Colombier 	empty->repl = 1;
110a7b22450SDavid du Colombier 	goalcargo = eloadfile(GoalCargoImage);
111a7b22450SDavid du Colombier 	cargo = eloadfile(CargoImage);
112a7b22450SDavid du Colombier 	goal = eloadfile(GoalImage);
113a7b22450SDavid du Colombier 	win = eloadfile(WinImage);
114a7b22450SDavid du Colombier }
115a7b22450SDavid du Colombier 
116a7b22450SDavid du Colombier int
key2move(int key)117a7b22450SDavid du Colombier key2move(int key)
118a7b22450SDavid du Colombier {
119a7b22450SDavid du Colombier 	int k = 0;
120a7b22450SDavid du Colombier 
121a7b22450SDavid du Colombier 	switch(key) {
122a7b22450SDavid du Colombier 	case 61454:
123a7b22450SDavid du Colombier 		k = Up;
124a7b22450SDavid du Colombier 		break;
125a7b22450SDavid du Colombier 	case 63488:
126a7b22450SDavid du Colombier 		k = Down;
127a7b22450SDavid du Colombier 		break;
128a7b22450SDavid du Colombier 	case 61457:
129a7b22450SDavid du Colombier 		k = Left;
130a7b22450SDavid du Colombier 		break;
131a7b22450SDavid du Colombier 	case 61458:
132a7b22450SDavid du Colombier 		k = Right;
133a7b22450SDavid du Colombier 		break;
134a7b22450SDavid du Colombier 	}
135a7b22450SDavid du Colombier 
136a7b22450SDavid du Colombier 	return k;
137a7b22450SDavid du Colombier }
13897787252SDavid du Colombier 
13997787252SDavid du Colombier static Route*
mouse2route(Mouse m)14097787252SDavid du Colombier mouse2route(Mouse m)
141a7b22450SDavid du Colombier {
14297787252SDavid du Colombier 	Point p, q;
14301e2ed2dSDavid du Colombier 	Route *r;
144a7b22450SDavid du Colombier 
145a7b22450SDavid du Colombier 	p = subpt(m.xy, screen->r.min);
146a7b22450SDavid du Colombier 	p.x /= BoardX;
147a7b22450SDavid du Colombier 	p.y /= BoardY;
148a7b22450SDavid du Colombier 
14997787252SDavid du Colombier 	q = subpt(p, level.glenda);
15097787252SDavid du Colombier 	// fprint(2, "x=%d y=%d\n", q.x, q.y);
15197787252SDavid du Colombier 
15297787252SDavid du Colombier 	if (q.x == 0 && q.y ==  0)
15301e2ed2dSDavid du Colombier 		return nil;
15497787252SDavid du Colombier 
15501e2ed2dSDavid du Colombier 	if (q.x == 0 || q.y ==  0) {
15697787252SDavid du Colombier 		if (q.x < 0)
15701e2ed2dSDavid du Colombier 			r = extend(nil, Left, -q.x, Pt(level.glenda.x, p.y));
15801e2ed2dSDavid du Colombier 		else if (q.x > 0)
15901e2ed2dSDavid du Colombier 			r = extend(nil, Right, q.x, Pt(level.glenda.x, p.y));
16001e2ed2dSDavid du Colombier 		else if (q.y < 0)
16101e2ed2dSDavid du Colombier 			r = extend(nil, Up, -q.y, level.glenda);
16201e2ed2dSDavid du Colombier 		else if (q.y > 0)
16301e2ed2dSDavid du Colombier 			r = extend(nil, Down, q.y, level.glenda);
16401e2ed2dSDavid du Colombier 		else
16501e2ed2dSDavid du Colombier 			r = nil;
16697787252SDavid du Colombier 
16701e2ed2dSDavid du Colombier 		if (r != nil && isvalid(level.glenda, r, validpush))
16897787252SDavid du Colombier 			return r;
16997787252SDavid du Colombier 		freeroute(r);
17001e2ed2dSDavid du Colombier 	}
17197787252SDavid du Colombier 
17201e2ed2dSDavid du Colombier 	return findroute(level.glenda, p);
173a7b22450SDavid du Colombier }
174a7b22450SDavid du Colombier 
175a7b22450SDavid du Colombier char *
genlevels(int i)176a7b22450SDavid du Colombier genlevels(int i)
177a7b22450SDavid du Colombier {
178a7b22450SDavid du Colombier 
179a7b22450SDavid du Colombier 	if(i >= numlevels)
180a7b22450SDavid du Colombier 		return 0;
181a7b22450SDavid du Colombier 
182a7b22450SDavid du Colombier 	return smprint("level %d", i+1);
183a7b22450SDavid du Colombier }
184a7b22450SDavid du Colombier 
185a7b22450SDavid du Colombier 
186a7b22450SDavid du Colombier int
finished(void)187a7b22450SDavid du Colombier finished(void)
188a7b22450SDavid du Colombier {
189a7b22450SDavid du Colombier 	int x, y;
190a7b22450SDavid du Colombier 	for(x = 0; x < MazeX; x++)
191a7b22450SDavid du Colombier 		for(y = 0; y < MazeY; y++)
192a7b22450SDavid du Colombier 			if(level.board[x][y] == Goal)
193a7b22450SDavid du Colombier 				return 0;
194a7b22450SDavid du Colombier 
195a7b22450SDavid du Colombier 	return 1;
196a7b22450SDavid du Colombier }
197a7b22450SDavid du Colombier 
198a7b22450SDavid du Colombier void
eresized(int new)199a7b22450SDavid du Colombier eresized(int new)
200a7b22450SDavid du Colombier {
201a7b22450SDavid du Colombier 	Point p;
202a7b22450SDavid du Colombier 
203a7b22450SDavid du Colombier 	if(new && getwindow(display, Refnone) < 0)
204a7b22450SDavid du Colombier 		sysfatal("can't reattach to window");
205a7b22450SDavid du Colombier 
206a7b22450SDavid du Colombier 	p = Pt(Dx(screen->r), Dy(screen->r));
207a7b22450SDavid du Colombier 
208a7b22450SDavid du Colombier 	if(!new || !eqpt(p, boardsize(level.max))) {
209a7b22450SDavid du Colombier 		drawlevel();
210a7b22450SDavid du Colombier 	}
211a7b22450SDavid du Colombier 	drawscreen();
212a7b22450SDavid du Colombier }
213a7b22450SDavid du Colombier 
214a7b22450SDavid du Colombier void
main(int argc,char ** argv)215a7b22450SDavid du Colombier main(int argc, char **argv)
216a7b22450SDavid du Colombier {
217a7b22450SDavid du Colombier 	Mouse m;
21801e2ed2dSDavid du Colombier 	Event ev;
21901e2ed2dSDavid du Colombier 	int e;
22097787252SDavid du Colombier 	Route *r;
22101e2ed2dSDavid du Colombier 	int timer;
22201e2ed2dSDavid du Colombier 	Animation a;
22301e2ed2dSDavid du Colombier 	int animate;
22401e2ed2dSDavid du Colombier 
225a7b22450SDavid du Colombier 
226a7b22450SDavid du Colombier 	if(argc == 2)
227a7b22450SDavid du Colombier 		levelfile = argv[1];
228a7b22450SDavid du Colombier 	else
229a7b22450SDavid du Colombier 		levelfile = LEasy;
230a7b22450SDavid du Colombier 
231a7b22450SDavid du Colombier 	if(! loadlevels(levelfile)) {
232a7b22450SDavid du Colombier 		fprint(2, "usage: %s [levelfile]\n", argv[0]);
233a7b22450SDavid du Colombier 		exits("usage");
234a7b22450SDavid du Colombier 	}
23501e2ed2dSDavid du Colombier 	buildmenu();
236a7b22450SDavid du Colombier 
23797787252SDavid du Colombier 	animate = 0;
23897787252SDavid du Colombier 	buttons[3] = animate ? "noanimate" : "animate";
23997787252SDavid du Colombier 
240a7b22450SDavid du Colombier 	if(initdraw(nil, nil, "sokoban") < 0)
241a7b22450SDavid du Colombier 		sysfatal("initdraw failed: %r");
242a7b22450SDavid du Colombier 	einit(Emouse|Ekeyboard);
243a7b22450SDavid du Colombier 
24401e2ed2dSDavid du Colombier 	timer = etimer(0, 200);
24501e2ed2dSDavid du Colombier 	initanimation(&a);
24601e2ed2dSDavid du Colombier 
247a7b22450SDavid du Colombier 	allocimages();
248a7b22450SDavid du Colombier 	glenda = gright;
249a7b22450SDavid du Colombier 	eresized(0);
250a7b22450SDavid du Colombier 
251a7b22450SDavid du Colombier 	for(;;) {
25201e2ed2dSDavid du Colombier 		e = event(&ev);
25301e2ed2dSDavid du Colombier 		switch(e) {
254a7b22450SDavid du Colombier 		case Emouse:
25501e2ed2dSDavid du Colombier 			m = ev.mouse;
256a7b22450SDavid du Colombier 			if(m.buttons&1) {
25701e2ed2dSDavid du Colombier 				stopanimation(&a);
25897787252SDavid du Colombier 				r = mouse2route(m);
25901e2ed2dSDavid du Colombier 				if (r)
26001e2ed2dSDavid du Colombier 					setupanimation(&a, r);
26101e2ed2dSDavid du Colombier 				if (! animate) {
26201e2ed2dSDavid du Colombier 					while(onestep(&a))
26301e2ed2dSDavid du Colombier 						;
264a7b22450SDavid du Colombier 					drawscreen();
265a7b22450SDavid du Colombier 				}
26601e2ed2dSDavid du Colombier 			}
267a7b22450SDavid du Colombier 			if(m.buttons&2) {
268a7b22450SDavid du Colombier 				int l;
269a7b22450SDavid du Colombier 				/* levels start from 1 */
270a7b22450SDavid du Colombier 				lmenu.lasthit = level.index;
271a7b22450SDavid du Colombier 				l=emenuhit(2, &m, &lmenu);
272a7b22450SDavid du Colombier 				if(l>=0){
27301e2ed2dSDavid du Colombier 					stopanimation(&a);
274a7b22450SDavid du Colombier 					level = levels[l];
275a7b22450SDavid du Colombier 					drawlevel();
276a7b22450SDavid du Colombier 					drawscreen();
277a7b22450SDavid du Colombier 				}
278a7b22450SDavid du Colombier 			}
279a7b22450SDavid du Colombier 			if(m.buttons&4)
280a7b22450SDavid du Colombier 				switch(emenuhit(3, &m, &menu)) {
281a7b22450SDavid du Colombier 				case 0:
28201e2ed2dSDavid du Colombier 					stopanimation(&a);
283a7b22450SDavid du Colombier 					level = levels[level.index];
284a7b22450SDavid du Colombier 					drawlevel();
285a7b22450SDavid du Colombier 					drawscreen();
286a7b22450SDavid du Colombier 					break;
287a7b22450SDavid du Colombier 				case 1:
28801e2ed2dSDavid du Colombier 					stopanimation(&a);
289a7b22450SDavid du Colombier 					loadlevels(LEasy);
29001e2ed2dSDavid du Colombier 					buildmenu();
291a7b22450SDavid du Colombier 					drawlevel();
292a7b22450SDavid du Colombier 					drawscreen();
293a7b22450SDavid du Colombier 					break;
294a7b22450SDavid du Colombier 				case 2:
29501e2ed2dSDavid du Colombier 					stopanimation(&a);
296a7b22450SDavid du Colombier 					loadlevels(LHard);
29701e2ed2dSDavid du Colombier 					buildmenu();
298a7b22450SDavid du Colombier 					drawlevel();
299a7b22450SDavid du Colombier 					drawscreen();
300a7b22450SDavid du Colombier 					break;
301a7b22450SDavid du Colombier 				case 3:
30297787252SDavid du Colombier 					animate = !animate;
30397787252SDavid du Colombier 					buttons[3] = animate ? "noanimate" : "animate";
30497787252SDavid du Colombier 					break;
30597787252SDavid du Colombier 				case 4:
306a7b22450SDavid du Colombier 					exits(nil);
307a7b22450SDavid du Colombier 				}
308a7b22450SDavid du Colombier 			break;
309a7b22450SDavid du Colombier 
310a7b22450SDavid du Colombier 		case Ekeyboard:
311a7b22450SDavid du Colombier 			if(level.done)
312a7b22450SDavid du Colombier 				break;
313a7b22450SDavid du Colombier 
31401e2ed2dSDavid du Colombier 			stopanimation(&a);
31501e2ed2dSDavid du Colombier 
31601e2ed2dSDavid du Colombier 			switch(ev.kbdc) {
317a7b22450SDavid du Colombier 			case 127:
318a7b22450SDavid du Colombier 			case 'q':
319a7b22450SDavid du Colombier 			case 'Q':
320a7b22450SDavid du Colombier 				exits(nil);
321a7b22450SDavid du Colombier 			case 'n':
322a7b22450SDavid du Colombier 			case 'N':
323a7b22450SDavid du Colombier 				if(level.index < numlevels - 1) {
324a7b22450SDavid du Colombier 					level = levels[++level.index];
325a7b22450SDavid du Colombier 					drawlevel();
326a7b22450SDavid du Colombier 					drawscreen();
327a7b22450SDavid du Colombier 				}
328a7b22450SDavid du Colombier 				break;
329a7b22450SDavid du Colombier 			case 'p':
330a7b22450SDavid du Colombier 			case 'P':
331a7b22450SDavid du Colombier 				if(level.index > 0) {
332a7b22450SDavid du Colombier 					level = levels[--level.index];
333a7b22450SDavid du Colombier 					drawlevel();
334a7b22450SDavid du Colombier 					drawscreen();
335a7b22450SDavid du Colombier 				}
336a7b22450SDavid du Colombier 				break;
337a7b22450SDavid du Colombier 			case 'r':
338a7b22450SDavid du Colombier 			case 'R':
339a7b22450SDavid du Colombier 				level = levels[level.index];
340a7b22450SDavid du Colombier 				drawlevel();
341a7b22450SDavid du Colombier 				drawscreen();
342a7b22450SDavid du Colombier 				break;
343a7b22450SDavid du Colombier 			case 61454:
344a7b22450SDavid du Colombier 			case 63488:
345a7b22450SDavid du Colombier 			case 61457:
346a7b22450SDavid du Colombier 			case 61458:
347a7b22450SDavid du Colombier 			case ' ':
34801e2ed2dSDavid du Colombier 				move(key2move(ev.kbdc));
349a7b22450SDavid du Colombier 				drawscreen();
350a7b22450SDavid du Colombier 				break;
35197787252SDavid du Colombier 			default:
35297787252SDavid du Colombier 				// fprint(2, "key: %d]\n", e.kbdc);
35397787252SDavid du Colombier 				break;
354a7b22450SDavid du Colombier 			}
355a7b22450SDavid du Colombier 			break;
35601e2ed2dSDavid du Colombier 
35701e2ed2dSDavid du Colombier 		default:
35801e2ed2dSDavid du Colombier 			if (e == timer) {
35901e2ed2dSDavid du Colombier 				if (animate)
36001e2ed2dSDavid du Colombier 					onestep(&a);
36101e2ed2dSDavid du Colombier 				else
36201e2ed2dSDavid du Colombier 					while(onestep(&a))
36301e2ed2dSDavid du Colombier 						;
36401e2ed2dSDavid du Colombier 				drawscreen();
36501e2ed2dSDavid du Colombier 			}
36601e2ed2dSDavid du Colombier 			break;
367a7b22450SDavid du Colombier 		}
368a7b22450SDavid du Colombier 
369a7b22450SDavid du Colombier 		if(finished()) {
370a7b22450SDavid du Colombier 			level.done = 1;
371a7b22450SDavid du Colombier 			drawwin();
372a7b22450SDavid du Colombier 			drawscreen();
373a7b22450SDavid du Colombier 			sleep(3000);
374a7b22450SDavid du Colombier 			if(level.index < numlevels - 1) {
375a7b22450SDavid du Colombier 				level = levels[++level.index];
376a7b22450SDavid du Colombier 				drawlevel();
377a7b22450SDavid du Colombier 				drawscreen();
378a7b22450SDavid du Colombier 			}
379a7b22450SDavid du Colombier 		}
380a7b22450SDavid du Colombier 	}
381a7b22450SDavid du Colombier }
382