Thumbnail

rani/games.git

Clone URL: https://git.buni.party/rani/games.git

Viewing file on branch master

1#include <time.h>
2#include <stdlib.h>
3#include <stdbool.h>
4#include <ncurses.h>
5#include <string.h>
6
7
8/* BEGIN CONFIG */
9#ifndef Y
10#define Y 16
11#endif /* Y */
12
13#ifndef X
14#define X 16
15#endif /* X */
16
17#ifndef MINES
18// number of mines
19#define MINES 35
20#endif /* MINES */
21
22#ifndef HIGHLIGHT_SQUARES
23// reverse the highlighting of squares
24#define HIGHLIGHT_SQUARES true
25#endif /* HIGHLIGHT_SQUARES */
26
27#ifndef SEED
28#define SEED time(NULL)
29#endif /* SEED */
30/* END CONFIG */
31
32
33#define ARRLEN(a) (sizeof(a)/sizeof(*a))
34
35
36enum color {
37 RED = 1,
38 GREEN,
39 CYAN,
40 BLUE,
41 MAGENTA,
42 BLACK,
43};
44
45
46enum state {
47 RUNNING,
48 QUIT,
49 GAME_OVER,
50 WIN,
51};
52
53
54struct square {
55 bool revealed;
56 bool mine;
57 bool flagged;
58 int neighbors;
59};
60
61
62struct {
63 int x, y;
64} around[8] = {
65 {.x = -1, .y = -1}, {.x = 0, .y = -1}, {.x = +1, .y = -1},
66 {.x = -1, .y = 0}, {.x = +1, .y = 0},
67 {.x = -1, .y = +1}, {.x = 0, .y = +1}, {.x = +1, .y = +1}
68};
69
70
71struct square grid[X][Y] = {0};
72int total_revealed = 0;
73int total_flags = 0;
74
75
76void global_reset(void) {
77 memset(grid, 0, sizeof(grid));
78 total_revealed = 0;
79 total_flags = 0;
80}
81
82
83bool won(void) {
84 return total_revealed >= X*Y - MINES;
85}
86
87
88int count_mines(int x, int y) {
89 int n = 0;
90
91 for (int i = 0; i < ARRLEN(around); i++) {
92 int nx = x + around[i].x;
93 int ny = y + around[i].y;
94 if (nx < 0 || ny < 0 || nx >= X || ny >= Y) continue;
95 if (grid[nx][ny].mine) n++;
96 }
97
98 return n;
99}
100
101
102void reveal(int x, int y) {
103 if (grid[x][y].revealed) return;
104 total_revealed++;
105 grid[x][y].revealed = true;
106}
107
108
109void reveal_blank(int x, int y) {
110 struct coord {
111 int x, y;
112 } coords[8];
113 for (int i = 0; i < ARRLEN(coords); i++) coords[i] = (struct coord){-1, -1};
114 int n = 0;
115
116 for (int i = 0; i < ARRLEN(around); i++) {
117 int nx = x + around[i].x;
118 int ny = y + around[i].y;
119 if (nx < 0 || ny < 0 || nx >= X || ny >= Y) continue;
120 struct square s = grid[nx][ny];
121 if (s.mine) continue;
122 if (s.neighbors == 0 && !s.revealed) {
123 reveal(nx, ny);
124
125 coords[n].x = nx;
126 coords[n].y = ny;
127 n++;
128 } else if (s.neighbors != 0) reveal(nx, ny);
129 }
130
131 for (int i = 0; i < n; i++) {
132 reveal_blank(coords[i].x, coords[i].y);
133 }
134}
135
136
137void create_grid(int x, int y) {
138 struct {
139 int x, y;
140 } exclude[9] = {0};
141 int n = 0;
142
143 exclude[n].x = x;
144 exclude[n].y = y;
145 n++;
146
147 // squares around the grid that mines cannot spawn in
148 for (int i = 0; i < ARRLEN(around); i++) {
149 int nx, ny;
150 nx = x + around[i].x;
151 ny = y + around[i].y;
152
153 if (nx < 0 || ny < 0 || nx >= X || ny >= Y) continue;
154 else {
155 exclude[n].x = nx;
156 exclude[n].y = ny;
157 n++;
158 }
159 }
160
161 bool done = false;
162 int tries = 0;
163 do {
164 for (int i = 0; i < MINES; i++) {
165 int mx, my;
166 bool done = false;
167 do {
168 mx = rand() % X;
169 my = rand() % Y;
170
171 bool illegal = false;
172 for (int i = 0; i < n; i++) {
173 if (exclude[i].x == mx && exclude[i].y == my) {
174 illegal = true;
175 break;
176 }
177 }
178
179 if (illegal) continue;
180
181 if (!grid[mx][my].mine) {
182 grid[mx][my].mine = true;
183 done = true;
184 }
185 } while (!done);
186 }
187
188
189 for (int x = 0; x < X; x++) {
190 for (int y = 0; y < Y; y++) {
191 grid[x][y].neighbors = count_mines(x, y);
192 }
193 }
194
195 // prevent insta-wins
196 reveal_blank(x, y);
197 if (!won()) done = true;
198 else global_reset();
199 tries++;
200
201 } while (tries < 10 && !done);
202}
203
204
205int nlen(int n) {
206 int len = 0;
207 while (n > 0) {
208 len++;
209 n /= 10;
210 }
211 return len;
212}
213
214
215int main(void) {
216 srand(SEED);
217
218 initscr();
219 noecho();
220 curs_set(0);
221 keypad(stdscr, TRUE);
222
223 mousemask(BUTTON1_CLICKED | BUTTON3_CLICKED, NULL);
224
225 use_default_colors();
226 start_color();
227 init_pair(RED, COLOR_RED, -1);
228 init_pair(GREEN, COLOR_GREEN, -1);
229 init_pair(CYAN, COLOR_CYAN, -1);
230 init_pair(BLUE, COLOR_BLUE, -1);
231 init_pair(MAGENTA, COLOR_MAGENTA, -1);
232 init_pair(BLACK, COLOR_WHITE, COLOR_BLACK);
233
234
235 while (true) {
236 bool grid_exists = false;
237 bool flag_mode = false;
238
239 clear();
240
241 time_t start_time;
242
243 enum state done = RUNNING;
244 bool display_once_more = false;
245 while (!done || display_once_more) {
246 erase();
247 // draw the board
248 for (int x = 0; x < X; x++) addstr("+ - ");
249 addstr("+\n");
250
251 for (int y = 0; y < Y; y++) {
252 for (int x = 0; x < X; x++) {
253 addstr("| ");
254 struct square s = grid[x][y];
255 if (done == GAME_OVER && s.mine) {
256 attron(COLOR_PAIR(RED) | A_REVERSE);
257 addch('*');
258 attroff(COLOR_PAIR(RED) | A_REVERSE);
259 } else if (s.flagged) {
260 int cp = COLOR_PAIR(RED);
261 char flagc = '!';
262 if (!HIGHLIGHT_SQUARES) cp |= A_REVERSE;
263 if (done == GAME_OVER) {
264 cp = COLOR_PAIR(CYAN) | A_REVERSE;
265 flagc = '?';
266 }
267 attron(cp);
268 addch(flagc);
269 attroff(cp);
270 } else if (s.revealed) {
271 int cp = 0;
272 switch (s.neighbors) {
273 case 0: cp = 0; break;
274 case 1: cp = COLOR_PAIR(CYAN); break;
275 case 2: cp = COLOR_PAIR(MAGENTA); break;
276 case 3: cp = COLOR_PAIR(BLACK); break;
277 case 4: cp = COLOR_PAIR(BLUE); break;
278 case 5: cp = COLOR_PAIR(CYAN); break;
279 case 6: cp = COLOR_PAIR(MAGENTA); break;
280 case 7: cp = COLOR_PAIR(BLACK); break;
281 case 8: cp = COLOR_PAIR(BLUE); break;
282 }
283 if (HIGHLIGHT_SQUARES && cp) cp |= A_REVERSE;
284 if (done == GAME_OVER) cp = 0;
285 attron(cp);
286 addch(s.neighbors > 0 ? '0' + s.neighbors : ' ');
287 attroff(cp);
288 } else {
289 int cp = COLOR_PAIR(GREEN);
290 if (done == GAME_OVER) cp = 0;
291 attron(cp);
292 addch('#');
293 attroff(cp);
294 }
295 addch(' ');
296 }
297 addstr("|\n");
298
299 for (int x = 0; x < X; x++) addstr("+ - ");
300 addstr("+\n");
301 }
302
303 int y, x;
304 getyx(stdscr, y, x);
305 move(y, (X*4+2)/2 - (24+nlen(MINES)+nlen(total_flags))/2);
306 printw("MINES: %d FLAGS PLACED: %d\n", MINES, total_flags);
307 move(y+1, (X*4+2)/2 - 16);
308 printw("'f' to toggle flag mode: now %s\n", flag_mode ? "ON" : "OFF");
309
310 refresh();
311
312 if (display_once_more) break;
313
314 int c = getch();
315 switch (c) {
316 case KEY_MOUSE:;
317 MEVENT event;
318 if (getmouse(&event) != OK) break;
319 // ignore clicks on grid patterning
320 if (event.x % 4 == 0) break;
321 if (event.y % 2 == 0) break;
322 // convert to grid indices
323 int x = (event.x - 2) / 4;
324 int y = (event.y - 1) / 2;
325
326 // fix clicks on left space in each cell
327 if ((event.x - 1) % 4 == 0) x++;
328
329 if (x > X || y > Y) break;
330
331 if (!flag_mode && event.bstate & BUTTON1_CLICKED) {
332 if (!grid_exists) {
333 create_grid(x, y);
334 start_time = time(NULL);
335 grid_exists = true;
336 }
337
338 // clicking on a flag should do nothing
339 if (grid[x][y].flagged) break;
340 if (grid[x][y].mine) {
341 done = GAME_OVER;
342 display_once_more = true;
343 break;
344 }
345
346 // don't waste time if the square is already revealed
347 if (grid[x][y].revealed) break;
348 reveal(x, y);
349
350 if (grid[x][y].neighbors == 0) {
351 reveal_blank(x, y);
352 }
353
354 if (won()) {
355 done = WIN;
356 display_once_more = true;
357 }
358 } else if (flag_mode || (!grid[x][y].revealed && event.bstate & BUTTON3_CLICKED)) {
359 grid[x][y].flagged = !grid[x][y].flagged;
360 if (grid[x][y].flagged) total_flags++;
361 else total_flags--;
362 }
363 break;
364 case 'f':
365 flag_mode = !flag_mode;
366 break;
367 case 'q':
368 case 27:
369 done = QUIT;
370 break;
371 default:
372 break;
373 }
374 }
375
376 switch (done) {
377 case GAME_OVER:
378 move(0, (X*4+2) / 2 - 3);
379 attron(A_REVERSE | COLOR_PAIR(RED));
380 addstr("BOOM!");
381 attroff(A_REVERSE | COLOR_PAIR(RED));
382 break;
383 case WIN:
384 move((Y*2+1)/2, (X*4+2) / 2 - 16);
385 attron(A_REVERSE);
386 addstr("All the mines have been found :)");
387 attroff(A_REVERSE);
388 break;
389 case QUIT:
390 goto terminate;
391 default: break;
392 }
393
394
395 time_t end_time = time(NULL);
396
397 move(Y*2+1, 0);
398 clrtoeol();
399 move(Y*2+1, (X*4+2)/2 - 14);
400 attron(A_REVERSE);
401 addstr("Any to play again, q to quit");
402 attroff(A_REVERSE);
403 move(Y*2+2, 0);
404 clrtoeol();
405 move(Y*2+2, (X*4+2)/2 - (4 + nlen(end_time - start_time)/2));
406 printw("TIME: %ds", end_time - start_time);
407 refresh();
408 if (getch() == 'q') goto terminate;
409
410 global_reset();
411 }
412
413 terminate:
414 echo();
415 curs_set(1);
416 endwin();
417}
418