]> git.lizzy.rs Git - plan9front.git/blob - sys/src/games/glendy.c
audiohda: fix syntax error
[plan9front.git] / sys / src / games / glendy.c
1 #include <u.h>
2 #include <libc.h>
3 #include <draw.h>
4 #include <event.h>
5
6 enum{
7         /* difficulty levels (how many circles are initially occupied) */
8         DEasy,  /* 10≤x<15 */
9         DMed,   /* 5≤x<10 */
10         DHard,  /* 0≤x<5 */
11
12         /* dynamic? original game has a fixed grid size, but we don't need to abide by it */
13         SzX = 11,
14         SzY = 11, 
15
16         Border = 10,
17         /* movement directions */
18         NE,
19         E,
20         SE,
21         SW,
22         W,
23         NW,
24
25         Won = 1,        /* game-ending states */
26         Lost = 2,
27 };
28
29 Font *font;
30
31 int difficulty = DMed;
32 int finished;
33
34 int grid[SzX][SzY];
35 int ogrid[SzX][SzY];    /* so we can restart levels */
36
37 Image   *gl;    /* glenda */
38 Image   *glm;   /* glenda's mask */
39 Image   *cc; /* clicked */
40 Image   *ec; /* empty; not clicked */
41 Image   *bg;
42 Image   *lost;
43 Image   *won;
44
45
46 char *mbuttons[] = 
47 {
48         "Easy",
49         "Medium",
50         "Hard",
51         0
52 };
53
54 char *rbuttons[] = 
55 {
56         "New",
57         "Reset",
58         "Exit",
59         0
60 };
61
62 Menu mmenu = 
63 {
64         mbuttons,
65 };
66
67 Menu rmenu =
68 {
69         rbuttons,
70 };
71
72 Image *
73 eallocimage(Rectangle r, int repl, uint color)
74 {
75         Image *tmp;
76
77         tmp = allocimage(display, r, screen->chan, repl, color);
78         if(tmp == nil)
79                 sysfatal("cannot allocate buffer image: %r");
80
81         return tmp;
82 }
83
84 Image *
85 eloadfile(char *path)
86 {
87         Image *img;
88         int fd;
89
90         fd = open(path, OREAD);
91         if(fd < 0) {
92                 fprint(2, "cannot open image file %s: %r\n", path);
93                 exits("image");
94         }
95         img = readimage(display, fd, 0);
96         if(img == nil)
97                 sysfatal("cannot load image: %r");
98         close(fd);
99         
100         return img;
101 }
102
103
104 void
105 allocimages(void)
106 {
107         Rectangle one = Rect(0, 0, 1, 1);
108         
109         cc = eallocimage(one, 1, 0x777777FF);
110         ec = eallocimage(one, 1, DPalegreen);
111         bg = eallocimage(one, 1, DPurpleblue);
112         lost = eallocimage(one, 1, DRed);
113         won = eallocimage(one, 1, DGreen);
114         gl = eloadfile("/lib/face/48x48x4/g/glenda.1");
115
116         glm = allocimage(display, Rect(0, 0, 48, 48), gl->chan, 1, DCyan);
117         if(glm == nil)
118                         sysfatal("cannot allocate mask: %r");
119
120         draw(glm, glm->r, display->white, nil, ZP);
121         gendraw(glm, glm->r, display->black, ZP, gl, gl->r.min);
122         freeimage(gl);
123         gl = display->black;
124
125
126 }
127
128 /* unnecessary calculations here, but it's fine */
129 Point
130 board2pix(int x, int y)
131 {
132         float d, rx, ry, yh;
133         int nx, ny;
134
135         d = (float)(Dx(screen->r) > Dy(screen->r)) ? Dy(screen->r) -20 : Dx(screen->r) -20;
136         rx = d/(float)SzX;
137         rx = rx/2.0;
138         ry = d/(float)SzY;
139         ry = ry/2.0;
140
141         yh = ry/3.73205082;
142
143         nx = (int)((float)x*rx*2.0+rx +(y%2?rx:0.0)); /* nx = x*(2rx) + rx + rx (conditional) */
144         ny = (int)((float)y*(ry*2.0-(y>0?yh:0.0)) + ry); /* ny = y*(2ry-yh) +ry */
145         return Pt(nx, ny);
146 }
147
148 Point 
149 pix2board(int x, int y)
150 {
151         float d, rx, ry, yh;
152         int ny, nx;
153
154         /* XXX: float→int causes small rounding errors */
155
156         d = (float)(Dx(screen->r) > Dy(screen->r)) ? Dy(screen->r) -20: Dx(screen->r)-20;
157         rx = d/(float)SzX;
158         rx = rx/2.0;
159         ry =d/(float)SzY;
160         ry = ry/2.0;
161
162         yh = ry/3.73205082;
163
164         /* reverse board2pix() */
165         ny = (int)(((float)y - ry)/(2*ry - ((y>2*ry)?yh:0.0)) + 0.5); /* ny = (y - ry)/(2ry-yh) */
166         nx = (int)(((float)x - rx - (ny%2?rx:0.0))/(rx*2.0) + 0.5); /* nx = (x - rx - rx)/2rx */
167         
168         if (nx >= SzX)
169                 nx = SzX-1;
170         if (ny >=SzY)
171                 ny = SzY-1;
172
173         return Pt(nx, ny);
174 }
175
176 void
177 initlevel(void)
178 {
179         int i, cnt = 10, x, y;
180
181         for(x = 0; x < SzX; x++)
182                 for(y = 0; y < SzY; y++)
183                         ogrid[x][y] = 100;
184
185         switch(difficulty){
186         case DEasy:
187                 cnt = 10 + nrand(5);
188                 break;
189         case DMed:
190                 cnt = 5 + nrand(5);
191                 break;
192         case DHard:
193                 cnt = nrand(5);
194                 break;
195         }
196         for(i = 0; i < cnt; i++) {
197                 do {
198                         x = nrand(SzX);
199                         y = nrand(SzY);
200                 } while(ogrid[x][y] != 100);
201                 ogrid[x][y] = 999;
202         }
203
204         ogrid[SzX/2][SzY/2] = 1000;
205
206         memcpy(grid, ogrid, sizeof grid);
207
208         finished = 0;
209
210 }
211
212 void
213 drawlevel(void)
214 {
215         Point p;
216         int  x, y, rx, ry, d;
217         char *s = nil;
218
219         if(finished)
220                 draw(screen, screen->r, finished==Won?won:lost, nil, ZP);
221         else
222                 draw(screen, screen->r, bg, nil, ZP);
223
224         d = (Dx(screen->r) > Dy(screen->r)) ? Dy(screen->r) -20: Dx(screen->r) -20;
225         rx = (int)ceil((float)(d-2*Border)/(float)SzX)/2;
226         ry = (int)ceil((float)(d-2*Border)/(float)SzY)/2;
227
228         for(x = 0; x < SzX; x++) {
229                 for(y = 0; y < SzY; y++) {
230                         p = board2pix(x, y);
231                         switch(grid[x][y]){
232                         case 999: 
233                                 fillellipse(screen, addpt(screen->r.min, p), rx, ry, cc, ZP);
234                                 break;
235                         case 1000:
236                                 p = addpt(screen->r.min, p);
237                                 fillellipse(screen, p, rx, ry, ec, ZP);
238                                 p = subpt(p, Pt(24, 24));
239                                 draw(screen, Rpt(p, addpt(p, Pt(48, 48))), gl, glm, ZP);
240                                 break;
241                         default:
242                                 fillellipse(screen, addpt(screen->r.min, p), rx, ry, ec, ZP);
243                                 USED(s);
244                                 /* uncomment the following to see game state and field scores */
245                                 /*s = smprint("%d", grid[x][y]);
246                                 string(screen, addpt(screen->r.min, p), display->black, ZP, font, s);
247                                 free(s);
248                                 */
249                                 break;
250                         }
251                 }
252         }
253         flushimage(display, 1);
254 }
255
256 void
257 domove(int dir, int x, int y)
258 {
259         if(x == 0 || x == SzX-1 || y == 0 || y == SzY-1)
260                 goto done;
261
262         switch(dir){
263         case NE:
264                 if(y%2)
265                         grid[x+1][y-1] = 1000;
266                 else    
267                         grid[x][y-1] = 1000;
268                 break;
269         case E:
270                 grid[x+1][y] = 1000;
271                 break;
272         case SE:
273                 if(y%2)
274                         grid[x+1][y+1] = 1000;
275                 else
276                         grid[x][y+1] = 1000;
277                 break;
278         case SW:
279                 if(y%2)
280                         grid[x][y+1] = 1000;
281                 else
282                         grid[x-1][y+1] = 1000;
283                 break;
284         case W:
285                 grid[x-1][y] = 1000;
286                 break;
287         case NW:
288                 if(y%2)
289                         grid[x][y-1] = 1000;
290                 else
291                         grid[x-1][y-1] = 1000;
292                 break;
293         }
294 done:
295         grid[x][y] = 100;
296 }
297
298 Point
299 findglenda(void)
300 {
301         int x, y;
302         for(x = 0; x < SzX; x++)
303                 for(y = 0; y < SzY; y++)
304                         if(grid[x][y] == 1000)
305                                 return Pt(x, y);
306         return Pt(-1, -1);
307 }
308
309 int 
310 checknext(int dir, int x, int y)
311 {
312         switch(dir){
313         case NE: 
314                 return grid[x+(y%2?1:0)][y-1];
315         case E:
316                 return grid[x+1][y];
317         case SE:
318                 return grid[x+(y%2?1:0)][y+1];
319         case SW:
320                 return grid[x+(y%2?0:-1)][y+1];
321         case W:
322                 return grid[x-1][y];
323         case NW:
324                 return grid[x+(y%2?0:-1)][y-1];
325         default:
326                 sysfatal("andrey messed up big time");
327         }
328         return 1000;
329 }
330 /* the following two routines constitute the "game AI"
331 * they score the field based on the number of moves
332 * required to reach the edge from a particular point
333 * scores > 100 are "dead spots" (this assumes the field 
334 * is not larger than ~100*2
335
336 * routines need to run at least twice to ensure a field is properly
337 * scored: there are errors that creep up due to the nature of 
338 * traversing the board
339 */
340 int 
341 score1(int x, int y) {
342         int dir, min = 999, next;
343
344         if(x == 0 || x == SzX-1 || y == 0 || y == SzY-1)
345                 return 1;               /* we can always escape from the edges */
346
347         for(dir = NE; dir <= NW; dir++) {
348                 next = checknext(dir, x, y);
349                 if(next < min)
350                         min = next;
351         }
352         if(min == 999) return 998;
353         return 1+min;
354 }
355
356 void
357 calc(void)
358 {
359         int i, x, y;
360         for(i = 0; i < SzX; i++) /* assumes SzX = SzY */
361                 for(x = i; x < SzX-i; x++)
362                         for(y = i; y < SzY-i; y++)
363                                 if(grid[x][y] != 999)
364                                         grid[x][y] = score1(x, y);
365 }
366
367 void
368 nextglenda(void)
369 {
370         int min =1000, next, dir, nextdir = 0, count = 0;
371         Point p = findglenda();
372
373         calc();
374         calc();
375         calc();
376
377         grid[p.x][p.y] = 1000;
378         
379         for(dir = NE; dir <= NW; dir++) {
380                 next = checknext(dir, p.x, p.y);
381                 if(next < min) {
382                         min = next;
383                         nextdir = dir;
384                         ++count;
385                 } else if(next == min) {
386                         nextdir = (nrand(++count) == 0)?dir:nextdir;
387                 }
388         }
389         if(min < 100 || min == 999)
390                 domove(nextdir, p.x, p.y);
391         else
392                 finished = Won;
393
394         if(eqpt(findglenda(), Pt(-1, -1)))
395                 finished = Lost;
396 }
397
398 int
399 checkfinished(void)
400 {
401         int i, j;
402         for(i = 0; i < SzX; i++)
403                 for(j = 0; j < SzY; j++)
404                         if(grid[i][j] == 'E')
405                                 return 0;
406         return 1;
407 }
408
409 void
410 move(Point m)
411 {
412         Point p, nm;
413         int x, y;
414
415         nm = subpt(m, screen->r.min);
416
417         /* figure out where the click falls */
418         p = pix2board(nm.x, nm.y);
419         
420         if(grid[p.x][p.y] >= 999)
421                 return;
422
423         /* reset the board scores */
424         grid[p.x][p.y] = 999;
425         for(x = 0; x < SzX; x++)
426                 for(y = 0; y < SzY; y++)
427                         if(grid[x][y] != 999 && grid[x][y] != 1000)
428                                 grid[x][y] = 100;
429         
430         nextglenda();
431 }
432
433 void
434 resize(void)
435 {
436         int fd, size = (Dx(screen->r) > Dy(screen->r)) ? Dy(screen->r) + 20 : Dx(screen->r)+20; 
437
438         fd = open("/dev/wctl", OWRITE);
439         if(fd >= 0){
440                 fprint(fd, "resize -dx %d -dy %d", size, size);
441                 close(fd);
442         }
443
444 }
445
446
447 void
448 eresized(int new)
449 {
450         if(new && getwindow(display, Refnone) < 0)
451                 sysfatal("can't reattach to window");
452         
453         drawlevel();
454 }
455
456 void 
457 main(int argc, char **argv)
458 {
459         Mouse m;
460         Event ev;
461         int e, mousedown=0;
462
463         USED(argv, argc);
464
465         if(initdraw(nil, nil, "glendy") < 0)
466                 sysfatal("initdraw failed: %r");
467         einit(Emouse);
468
469         resize();
470
471         srand(time(0));
472
473         allocimages();
474         initlevel();    /* must happen before "eresized" */
475         eresized(0);
476
477         for(;;) {
478                 e = event(&ev);
479                 switch(e) {
480                 case Emouse:
481                         m = ev.mouse;
482                         if(m.buttons == 0) {
483                                 if(mousedown && !finished) {
484                                         mousedown = 0;
485                                         move(m.xy);
486                                         drawlevel();
487                                 }
488                         }
489                         if(m.buttons&1) {
490                                 mousedown = 1;
491                         }
492                         if(m.buttons&2) {
493                                 switch(emenuhit(2, &m, &mmenu)) {
494                                 case 0:
495                                         difficulty = DEasy;
496                                         initlevel();
497                                         break;
498                                 case 1:                         
499                                         difficulty = DMed;
500                                         initlevel();
501                                         break;
502                                 case 2:
503                                         difficulty = DHard;
504                                         initlevel();
505                                         break;
506                                 }
507                                 drawlevel();
508                         }
509                         if(m.buttons&4) {
510                                 switch(emenuhit(3, &m, &rmenu)) {
511                                 case 0:
512                                         initlevel();
513                                         break;
514                                 case 1:
515                                         memcpy(grid, ogrid, sizeof grid);
516                                         finished = 0;
517                                         break;
518                                 case 2:
519                                         exits(nil);
520                                 }
521                                 drawlevel();
522                         }
523                         break;
524                 }
525         }
526 }