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