]> git.lizzy.rs Git - plan9front.git/blob - sys/src/games/glendy.c
added glendy game!
[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         return 1+min;
353 }
354
355 void
356 calc(void)
357 {
358         int i, x, y;
359         for(i = 0; i < SzX; i++) /* assumes SzX = SzY */
360                 for(x = i; x < SzX-i; x++)
361                         for(y = i; y < SzY-i; y++)
362                                 if(grid[x][y] != 999)
363                                         grid[x][y] = score1(x, y);
364 }
365
366 void
367 nextglenda(void)
368 {
369         int min =1000, next, dir, nextdir = 0, count = 0;
370         Point p = findglenda();
371
372         calc();
373         calc();
374         calc();
375
376         grid[p.x][p.y] = 1000;
377         
378         for(dir = NE; dir <= NW; dir++) {
379                 next = checknext(dir, p.x, p.y);
380                 if(next < min) {
381                         min = next;
382                         nextdir = dir;
383                         ++count;
384                 } else if(next == min) {
385                         nextdir = (nrand(++count) == 0)?dir:nextdir;
386                 }
387         }
388         if(min < 100)
389                 domove(nextdir, p.x, p.y);
390         else
391                 finished = Won;
392
393         if(eqpt(findglenda(), Pt(-1, -1)))
394                 finished = Lost;
395 }
396
397 int
398 checkfinished(void)
399 {
400         int i, j;
401         for(i = 0; i < SzX; i++)
402                 for(j = 0; j < SzY; j++)
403                         if(grid[i][j] == 'E')
404                                 return 0;
405         return 1;
406 }
407
408 void
409 move(Point m)
410 {
411         Point p, nm;
412         int x, y;
413
414         nm = subpt(m, screen->r.min);
415
416         /* figure out where the click falls */
417         p = pix2board(nm.x, nm.y);
418         
419         if(grid[p.x][p.y] >= 999)
420                 return;
421
422         /* reset the board scores */
423         grid[p.x][p.y] = 999;
424         for(x = 0; x < SzX; x++)
425                 for(y = 0; y < SzY; y++)
426                         if(grid[x][y] != 999 && grid[x][y] != 1000)
427                                 grid[x][y] = 100;
428         
429         nextglenda();
430 }
431
432 void
433 resize(void)
434 {
435         int fd, size = (Dx(screen->r) > Dy(screen->r)) ? Dy(screen->r) + 20 : Dx(screen->r)+20; 
436
437         fd = open("/dev/wctl", OWRITE);
438         if(fd >= 0){
439                 fprint(fd, "resize -dx %d -dy %d", size, size);
440                 close(fd);
441         }
442
443 }
444
445
446 void
447 eresized(int new)
448 {
449         if(new && getwindow(display, Refnone) < 0)
450                 sysfatal("can't reattach to window");
451         
452         drawlevel();
453 }
454
455 void 
456 main(int argc, char **argv)
457 {
458         Mouse m;
459         Event ev;
460         int e, mousedown=0;
461         char *fontname;
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         fontname = "/lib/font/bit/lucidasans/unicode.8.font";
478         if((font = openfont(display, fontname)) == nil)
479                 sysfatal("font '%s' not found", fontname);      
480
481         for(;;) {
482                 e = event(&ev);
483                 switch(e) {
484                 case Emouse:
485                         m = ev.mouse;
486                         if(m.buttons == 0) {
487                                 if(mousedown && !finished) {
488                                         mousedown = 0;
489                                         move(m.xy);
490                                         drawlevel();
491                                 }
492                         }
493                         if(m.buttons&1) {
494                                 mousedown = 1;
495                         }
496                         if(m.buttons&2) {
497                                 switch(emenuhit(2, &m, &mmenu)) {
498                                 case 0:
499                                         difficulty = DEasy;
500                                         initlevel();
501                                         break;
502                                 case 1:                         
503                                         difficulty = DMed;
504                                         initlevel();
505                                         break;
506                                 case 2:
507                                         difficulty = DHard;
508                                         initlevel();
509                                         break;
510                                 }
511                                 drawlevel();
512                         }
513                         if(m.buttons&4) {
514                                 switch(emenuhit(3, &m, &rmenu)) {
515                                 case 0:
516                                         initlevel();
517                                         break;
518                                 case 1:
519                                         memcpy(grid, ogrid, sizeof grid);
520                                         finished = 0;
521                                         break;
522                                 case 2:
523                                         exits(nil);
524                                 }
525                                 drawlevel();
526                         }
527                         break;
528                 }
529         }
530 }