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