xref: /plan9-contrib/sys/src/games/sokoban/sokoban.c (revision 68cc0f8287607b9765032c3ad1aaf884a180bcfb)
1 #include <u.h>
2 #include <libc.h>
3 #include <draw.h>
4 #include <event.h>
5 
6 #include "sokoban.h"
7 
8 #define SOKOTREE "/sys/games/lib/sokoban/"
9 
10 char *LEasy = SOKOTREE "levels/easy.slc";
11 char *LHard = SOKOTREE "levels/hard.slc";
12 char *levelfile;
13 
14 #define SOKOIMG SOKOTREE "images/"
15 
16 char	*GRImage =	SOKOIMG "right.bit";
17 char	*GLImage =	SOKOIMG "left.bit";
18 char	*WallImage =	SOKOIMG "wall.bit";
19 char	*EmptyImage =	SOKOIMG "empty.bit";
20 char	*CargoImage =	SOKOIMG "cargo.bit";
21 char	*GoalCargoImage= SOKOIMG "goalcargo.bit";
22 char	*GoalImage =	SOKOIMG "goal.bit";
23 char	*WinImage =	SOKOIMG "win.bit";
24 
25 char *buttons[] =
26 {
27 	"restart",
28 	"easy",
29 	"hard",
30 	"noanimate", /* this menu string initialized in main */
31 	"exit",
32 	0
33 };
34 
35 char **levelnames;
36 
37 Menu menu =
38 {
39 	buttons,
40 };
41 
42 Menu lmenu;
43 
44 void
buildmenu(void)45 buildmenu(void)
46 {
47 	int i;
48 
49 	if (levelnames != nil) {
50 		for(i=0; levelnames[i] != 0; i++)
51 			free(levelnames[i]);
52 	}
53 	levelnames = realloc(levelnames, sizeof(char*)*(numlevels+1));
54 	if (levelnames == nil)
55 		sysfatal("cannot allocate levelnames");
56 	for(i=0; i < numlevels; i++)
57 		levelnames[i] = genlevels(i);
58 	levelnames[numlevels] = 0;
59 	lmenu.item = levelnames;
60 }
61 
62 Image *
eallocimage(Rectangle r,int repl,uint color)63 eallocimage(Rectangle r, int repl, uint color)
64 {
65 	Image *tmp;
66 
67 	tmp = allocimage(display, r, screen->chan, repl, color);
68 	if(tmp == nil)
69 		sysfatal("cannot allocate buffer image: %r");
70 
71 	return tmp;
72 }
73 
74 Image *
eloadfile(char * path)75 eloadfile(char *path)
76 {
77 	Image *img;
78 	int fd;
79 
80 	fd = open(path, OREAD);
81 	if(fd < 0) {
82 		fprint(2, "cannot open image file %s: %r\n", path);
83 		exits("image");
84 	}
85 	img = readimage(display, fd, 0);
86 	if(img == nil)
87 		sysfatal("cannot load image: %r");
88 	close(fd);
89 
90 	return img;
91 }
92 
93 
94 void
allocimages(void)95 allocimages(void)
96 {
97 	Rectangle one = Rect(0, 0, 1, 1);
98 
99 	bg		= eallocimage(one, 1, DDarkyellow);
100 	text 		= eallocimage(one, 1, DBluegreen);
101 
102 	gright = eloadfile(GRImage);
103 	gleft = eloadfile(GLImage);
104 	wall = eloadfile(WallImage);
105 	empty = eloadfile(EmptyImage);
106 	empty->repl = 1;
107 	goalcargo = eloadfile(GoalCargoImage);
108 	cargo = eloadfile(CargoImage);
109 	goal = eloadfile(GoalImage);
110 	win = eloadfile(WinImage);
111 }
112 
113 int
key2move(int key)114 key2move(int key)
115 {
116 	int k = 0;
117 
118 	switch(key) {
119 	case 61454:
120 		k = Up;
121 		break;
122 	case 63488:
123 		k = Down;
124 		break;
125 	case 61457:
126 		k = Left;
127 		break;
128 	case 61458:
129 		k = Right;
130 		break;
131 	}
132 
133 	return k;
134 }
135 
136 static Route*
mouse2route(Mouse m)137 mouse2route(Mouse m)
138 {
139 	Point p, q;
140 	Route *r;
141 
142 	p = subpt(m.xy, screen->r.min);
143 	p.x /= BoardX;
144 	p.y /= BoardY;
145 
146 	q = subpt(p, level.glenda);
147 	// fprint(2, "x=%d y=%d\n", q.x, q.y);
148 
149 	if (q.x == 0 && q.y ==  0)
150 		return nil;
151 
152 	if (q.x == 0 || q.y ==  0) {
153 		if (q.x < 0)
154 			r = extend(nil, Left, -q.x, Pt(level.glenda.x, p.y));
155 		else if (q.x > 0)
156 			r = extend(nil, Right, q.x, Pt(level.glenda.x, p.y));
157 		else if (q.y < 0)
158 			r = extend(nil, Up, -q.y, level.glenda);
159 		else if (q.y > 0)
160 			r = extend(nil, Down, q.y, level.glenda);
161 		else
162 			r = nil;
163 
164 		if (r != nil && isvalid(level.glenda, r, validpush))
165 			return r;
166 		freeroute(r);
167 	}
168 
169 	return findroute(level.glenda, p);
170 }
171 
172 char *
genlevels(int i)173 genlevels(int i)
174 {
175 
176 	if(i >= numlevels)
177 		return 0;
178 
179 	return smprint("level %d", i+1);
180 }
181 
182 
183 int
finished(void)184 finished(void)
185 {
186 	int x, y;
187 	for(x = 0; x < MazeX; x++)
188 		for(y = 0; y < MazeY; y++)
189 			if(level.board[x][y] == Goal)
190 				return 0;
191 
192 	return 1;
193 }
194 
195 void
eresized(int new)196 eresized(int new)
197 {
198 	Point p;
199 
200 	if(new && getwindow(display, Refnone) < 0)
201 		sysfatal("can't reattach to window");
202 
203 	p = Pt(Dx(screen->r), Dy(screen->r));
204 
205 	if(!new || !eqpt(p, boardsize(level.max))) {
206 		drawlevel();
207 	}
208 	drawscreen();
209 }
210 
211 void
main(int argc,char ** argv)212 main(int argc, char **argv)
213 {
214 	Mouse m;
215 	Event ev;
216 	int e;
217 	Route *r;
218 	int timer;
219 	Animation a;
220 	int animate;
221 
222 
223 	if(argc == 2)
224 		levelfile = argv[1];
225 	else
226 		levelfile = LEasy;
227 
228 	if(! loadlevels(levelfile)) {
229 		fprint(2, "usage: %s [levelfile]\n", argv[0]);
230 		exits("usage");
231 	}
232 	buildmenu();
233 
234 	animate = 0;
235 	buttons[3] = animate ? "noanimate" : "animate";
236 
237 	if(initdraw(nil, nil, "sokoban") < 0)
238 		sysfatal("initdraw failed: %r");
239 	einit(Emouse|Ekeyboard);
240 
241 	timer = etimer(0, 200);
242 	initanimation(&a);
243 
244 	allocimages();
245 	glenda = gright;
246 	eresized(0);
247 
248 	for(;;) {
249 		e = event(&ev);
250 		switch(e) {
251 		case Emouse:
252 			m = ev.mouse;
253 			if(m.buttons&1) {
254 				stopanimation(&a);
255 				r = mouse2route(m);
256 				if (r)
257 					setupanimation(&a, r);
258 				if (! animate) {
259 					while(onestep(&a))
260 						;
261 					drawscreen();
262 				}
263 			}
264 			if(m.buttons&2) {
265 				int l;
266 				/* levels start from 1 */
267 				lmenu.lasthit = level.index;
268 				l=emenuhit(2, &m, &lmenu);
269 				if(l>=0){
270 					stopanimation(&a);
271 					level = levels[l];
272 					drawlevel();
273 					drawscreen();
274 				}
275 			}
276 			if(m.buttons&4)
277 				switch(emenuhit(3, &m, &menu)) {
278 				case 0:
279 					stopanimation(&a);
280 					level = levels[level.index];
281 					drawlevel();
282 					drawscreen();
283 					break;
284 				case 1:
285 					stopanimation(&a);
286 					loadlevels(LEasy);
287 					buildmenu();
288 					drawlevel();
289 					drawscreen();
290 					break;
291 				case 2:
292 					stopanimation(&a);
293 					loadlevels(LHard);
294 					buildmenu();
295 					drawlevel();
296 					drawscreen();
297 					break;
298 				case 3:
299 					animate = !animate;
300 					buttons[3] = animate ? "noanimate" : "animate";
301 					break;
302 				case 4:
303 					exits(nil);
304 				}
305 			break;
306 
307 		case Ekeyboard:
308 			if(level.done)
309 				break;
310 
311 			stopanimation(&a);
312 
313 			switch(ev.kbdc) {
314 			case 127:
315 			case 'q':
316 			case 'Q':
317 				exits(nil);
318 			case 'n':
319 			case 'N':
320 				if(level.index < numlevels - 1) {
321 					level = levels[++level.index];
322 					drawlevel();
323 					drawscreen();
324 				}
325 				break;
326 			case 'p':
327 			case 'P':
328 				if(level.index > 0) {
329 					level = levels[--level.index];
330 					drawlevel();
331 					drawscreen();
332 				}
333 				break;
334 			case 'r':
335 			case 'R':
336 				level = levels[level.index];
337 				drawlevel();
338 				drawscreen();
339 				break;
340 			case 61454:
341 			case 63488:
342 			case 61457:
343 			case 61458:
344 			case ' ':
345 				move(key2move(ev.kbdc));
346 				drawscreen();
347 				break;
348 			default:
349 				// fprint(2, "key: %d]\n", e.kbdc);
350 				break;
351 			}
352 			break;
353 
354 		default:
355 			if (e == timer) {
356 				if (animate)
357 					onestep(&a);
358 				else
359 					while(onestep(&a))
360 						;
361 				drawscreen();
362 			}
363 			break;
364 		}
365 
366 		if(finished()) {
367 			level.done = 1;
368 			drawwin();
369 			drawscreen();
370 			sleep(3000);
371 			if(level.index < numlevels - 1) {
372 				level = levels[++level.index];
373 				drawlevel();
374 				drawscreen();
375 			}
376 		}
377 	}
378 }
379