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