main.c (12052B)
1#include <stdlib.h> 2#include <getopt.h> 3#include <time.h> 4 5#define LIB_TERM_IMPL 6#include <libterm.h> 7 8#define MASK_DIGIT 0x0f 9 10#define FLAG_OPEN 0x10 11#define FLAG_BOMB 0x20 12#define FLAG_FLAG 0x40 13#define FLAG_DIRTY 0x80 14 15#define BOARD_SIZE 2500 16 17typedef unsigned char u8_t; 18typedef unsigned int u32_t; 19 20typedef enum { 21 STATE_PLAYING, 22 STATE_DEFEAT, 23 STATE_VICTORY, 24} state_t; 25 26typedef struct { 27 int y; 28 int x; 29} vec2_t; 30 31typedef struct { 32 char name[100]; 33 int rows; 34 int cols; 35 int bombs; 36} level_t; 37 38typedef struct { 39 state_t state; 40 level_t level; 41 u8_t board[BOARD_SIZE]; 42 vec2_t size; 43 vec2_t window; 44 vec2_t cursor; 45 int seed; 46 int bombs; 47 int count; 48 time_t time; 49} cmine_t; 50 51static struct option OPTIONS_LEVEL[] = { 52 { "easy", no_argument, 0, 'e' }, 53 { "medium", no_argument, 0, 'm' }, 54 { "hard", no_argument, 0, 'h' }, 55 { "seed", required_argument, 0, 's' }, 56 { 0, 0, 0, 0 }, 57}; 58 59static level_t LEVEL_EASY = { "EASY", 9, 9, 10 }; 60static level_t LEVEL_MEDIUM = { "MEDIUM", 16, 16, 40 }; 61static level_t LEVEL_HARD = { "HARD", 16, 30, 99 }; 62 63static const u32_t COLOR_CURSOR = 0xffffff; 64static const u32_t COLOR_COVER = 0xcccccc; 65static const u32_t COLOR_BOMB = 0xff0000; 66static const u32_t COLOR_FLAG = 0xffff00; 67static const u32_t COLOR_DIGITS[9] = { 68 0x777777, 69 0x6666ff, 70 0x66a066, 71 0xff6666, 72 0xaa66ff, 73 0xffaa33, 74 0x00aaaa, 75 0xaaaa00, 76 0xaaaaaa, 77}; 78 79int cm_is_valid(cmine_t *self, int y, int x) { 80 return y >= 0 && y < self->size.y && x >= 0 && x < self->size.x; 81} 82 83int cm_is_flag(u8_t cell, int flag) { 84 return (cell & flag) != 0; 85} 86 87u8_t *cell_at(cmine_t *self, int y, int x) { 88 return &self->board[y * self->size.x + x]; 89} 90 91void cm_victory(cmine_t *self) { 92 self->state = STATE_VICTORY; 93 // flag all bombs 94 for (int y = 0; y < self->size.y; y++) { 95 for (int x = 0; x < self->size.x; x++) { 96 u8_t *cell = cell_at(self, y, x); 97 if (!cm_is_flag(*cell, FLAG_BOMB)) { 98 continue; 99 } 100 if (cm_is_flag(*cell, FLAG_FLAG)) { 101 continue; 102 } 103 *cell |= FLAG_FLAG; 104 *cell |= FLAG_DIRTY; 105 } 106 } 107} 108 109void cm_defeat(cmine_t *self) { 110 self->state = STATE_DEFEAT; 111 // reveal all bombs 112 for (int y = 0; y < self->size.y; y++) { 113 for (int x = 0; x < self->size.x; x++) { 114 u8_t *cell = cell_at(self, y, x); 115 if (!cm_is_flag(*cell, FLAG_BOMB)) { 116 continue; 117 } 118 if (cm_is_flag(*cell, FLAG_OPEN)) { 119 continue; 120 } 121 *cell |= FLAG_OPEN; 122 *cell |= FLAG_DIRTY; 123 } 124 } 125} 126 127void cm_open(cmine_t *self, int y, int x) { 128 if (self->state != STATE_PLAYING) { 129 return; 130 } 131 u8_t *cell = cell_at(self, y, x); 132 // ignore flags 133 if (cm_is_flag(*cell, FLAG_FLAG)) { 134 return; 135 } 136 // handle open cells 137 // chord opening 138 if (cm_is_flag(*cell, FLAG_OPEN)) { 139 int digit = *cell & MASK_DIGIT; 140 if (digit == 0) { 141 return; 142 } 143 int flags = 0; 144 for (int ly = -1; ly <= 1; ly++) { 145 for (int lx = -1; lx <= 1; lx++) { 146 if (!cm_is_valid(self, y + ly, x + lx)) { 147 continue; 148 } 149 if (ly == 0 && lx == 0) { 150 continue; 151 } 152 u8_t *l_cell = cell_at(self, y + ly, x + lx); 153 if (cm_is_flag(*l_cell, FLAG_FLAG)) { 154 flags++; 155 } 156 } 157 } 158 if (flags == digit) { 159 for (int ly = -1; ly <= 1; ly++) { 160 for (int lx = -1; lx <= 1; lx++) { 161 if (!cm_is_valid(self, y + ly, x + lx)) { 162 continue; 163 } 164 if (ly == 0 && lx == 0) { 165 continue; 166 } 167 u8_t *l_cell = cell_at(self, y + ly, x + lx); 168 if (!cm_is_flag(*l_cell, FLAG_OPEN)) { 169 cm_open(self, y + ly, x + lx); 170 } 171 } 172 } 173 } 174 } 175 // handle covered cells 176 else { 177 *cell |= FLAG_OPEN; 178 *cell |= FLAG_DIRTY; 179 // check if bomb 180 if (cm_is_flag(*cell, FLAG_BOMB)) { 181 cm_defeat(self); 182 return; 183 } 184 int digit = *cell & MASK_DIGIT; 185 // uncover surrounding cells 186 if (digit == 0) { 187 for (int ly = -1; ly <= 1; ly++) { 188 for (int lx = -1; lx <= 1; lx++) { 189 if (!cm_is_valid(self, y + ly, x + lx)) { 190 continue; 191 } 192 if (ly == 0 && lx == 0) { 193 continue; 194 } 195 cm_open(self, y + ly, x + lx); 196 } 197 } 198 } 199 } 200 // check if non-victory 201 for (int y = 0; y < self->size.y; y++) { 202 for (int x = 0; x < self->size.x; x++) { 203 u8_t *l_cell = cell_at(self, y, x); 204 if (!cm_is_flag(*l_cell, FLAG_BOMB) && !cm_is_flag(*l_cell, FLAG_OPEN)) { 205 return; 206 } 207 } 208 } 209 cm_victory(self); 210} 211 212void cm_flag(cmine_t *self, int y, int x) { 213 if (self->state != STATE_PLAYING) { 214 return; 215 } 216 u8_t *cell = cell_at(self, y, x); 217 // ignore open 218 if (cm_is_flag(*cell, FLAG_OPEN)) { 219 return; 220 } 221 *cell |= FLAG_DIRTY; 222 // unset flag 223 if (cm_is_flag(*cell, FLAG_FLAG)) { 224 *cell &= ~FLAG_FLAG; 225 self->count++; 226 } 227 // set flag 228 else { 229 *cell |= FLAG_FLAG; 230 self->count--; 231 } 232} 233 234void cm_move(cmine_t *self, int dy, int dx) { 235 if (self->state != STATE_PLAYING) { 236 return; 237 } 238 if (!cm_is_valid(self, self->cursor.y + dy, self->cursor.x + dx)) { 239 return; 240 } 241 *cell_at(self, self->cursor.y, self->cursor.x) |= FLAG_DIRTY; 242 self->cursor = (vec2_t) { self->cursor.y + dy, self->cursor.x + dx }; 243 *cell_at(self, self->cursor.y, self->cursor.x) |= FLAG_DIRTY; 244} 245 246void cm_generate(cmine_t *self, level_t level, int seed) { 247 // init self 248 (*self) = (cmine_t) { 249 .state = STATE_PLAYING, 250 .level = level, 251 .board = {0}, 252 .size = { level.rows, level.cols }, 253 .window = { 0, 0 }, 254 .cursor = { 0, 0 }, 255 .seed = seed, 256 .bombs = level.bombs, 257 .count = level.bombs, 258 .time = -1, 259 }; 260 srand(self->seed); 261 // flag all dirty 262 memset(self->board, 0, BOARD_SIZE); 263 for (int y = 0; y < self->size.y; y++) { 264 for (int x = 0; x < self->size.x; x++) { 265 *cell_at(self, y, x) = FLAG_DIRTY; 266 } 267 } 268 // place bombs 269 int bombs = self->bombs; 270 while (bombs > 0) { 271 int y = rand() % self->size.y; 272 int x = rand() % self->size.x; 273 u8_t *cell = cell_at(self, y, x); 274 if (!cm_is_flag(*cell, FLAG_BOMB)) { 275 *cell |= FLAG_BOMB; 276 bombs--; 277 } 278 } 279 // place numbers 280 for (int y = 0; y < self->size.y; y++) { 281 for (int x = 0; x < self->size.x; x++) { 282 u8_t *cell = cell_at(self, y, x); 283 for (int ly = -1; ly <= 1; ly++) { 284 for (int lx = -1; lx <= 1; lx++) { 285 if (!cm_is_valid(self, y + ly, x + lx)) { 286 continue; 287 } 288 if (ly == 0 && lx == 0) { 289 continue; 290 } 291 u8_t *l_cell = cell_at(self, y + ly, x + lx); 292 if (cm_is_flag(*l_cell, FLAG_BOMB)) { 293 (*cell)++; 294 } 295 } 296 } 297 } 298 } 299 // find starting position 300 int count = 0; 301 vec2_t sel = { -1, -1 }; 302 for (int y = 0; y < self->size.y; y++) { 303 for (int x = 0; x < self->size.x; x++) { 304 u8_t *cell = cell_at(self, y, x); 305 int digit = *cell & MASK_DIGIT; 306 if (cm_is_flag(*cell, FLAG_BOMB) || digit != 0) { 307 continue; 308 } 309 count++; 310 if (rand() % count != 0) { 311 continue; 312 } 313 sel = (vec2_t) { y, x }; 314 } 315 } 316 if (sel.y == -1 || sel.x == -1) { 317 self->cursor = (vec2_t) { 0, 0 }; 318 return; 319 } 320 self->cursor = sel; 321 // uncover starting position 322 cm_open(self, self->cursor.y, self->cursor.x); 323} 324 325void cm_update(term_t *term, cmine_t *self) { 326 // check window resize 327 size_t new_height; 328 size_t new_width; 329 term_read_window(term, &new_height, &new_width); 330 int is_resize = (int) new_height != self->window.y || (int) new_width != self->window.x; 331 term_write(term, "\x1b[?25l"); 332 if (is_resize) { 333 self->window = (vec2_t) { new_height, new_width }; 334 } 335 int set_y = self->window.y / 2 - self->size.y / 2; 336 int set_x = self->window.x / 2 - (2 * self->size.x) / 2; 337 if (is_resize) { 338 term_write(term, "\x1b[2J"); 339 } 340 // render info 341 if (self->state == STATE_PLAYING) { 342 time_t now = time(NULL); 343 time_t duration = difftime(now, self->time != -1 ? self->time : now); 344 int minutes = (duration % 3600) / 60; 345 int seconds = duration % 60; 346 term_writef(term, "\x1b[%d;%dH", set_y - 2 + 1, set_x + 1); 347 term_writef(term, "\x1b[38;2;255;255;255m%02d:%02d", minutes, seconds); 348 term_writef(term, " | %dx%d\x1b[39m", self->level.cols, self->level.rows); 349 term_writef(term, " \x1b[38;2;255;255;0m(%d)\x1b[39m\x1b[0K", self->count); 350 term_flush(term); 351 } 352 // render board 353 for (int y = 0; y < self->size.y; y++) { 354 for (int x = 0; x < self->size.x; x++) { 355 u8_t *cell = cell_at(self, y, x); 356 // ignore non-dirty cells 357 if (!is_resize && !cm_is_flag(*cell, FLAG_DIRTY)) { 358 continue; 359 } 360 *cell &= ~ FLAG_DIRTY; 361 // determine color and symbol 362 int is_bomb = cm_is_flag(*cell, FLAG_BOMB); 363 int is_open = cm_is_flag(*cell, FLAG_OPEN); 364 int digit = *cell & MASK_DIGIT; 365 char symbol = '?'; 366 u32_t color = COLOR_COVER; 367 if (!is_open && cm_is_flag(*cell, FLAG_FLAG)) { 368 symbol = 'F'; 369 color = COLOR_FLAG; 370 } 371 if (is_open && is_bomb) { 372 symbol = '*'; 373 color = COLOR_BOMB; 374 } 375 if (is_open && !is_bomb) { 376 symbol = '0' + digit; 377 if (digit == 0) { 378 symbol = '.'; 379 } 380 color = COLOR_DIGITS[digit]; 381 } 382 if (self->cursor.y == y && self->cursor.x == x) { 383 color = COLOR_CURSOR; 384 } 385 // render cell 386 term_writef(term, "\x1b[%d;%dH", set_y + y + 1, set_x + (2 * x) + 1); 387 int red = (color >> 16) & 0xff; 388 int green = (color >> 8) & 0xff; 389 int blue = (color >> 0) & 0xff; 390 term_writef(term, "\x1b[38;2;%d;%d;%dm", red, green, blue); 391 term_writef(term, "%c", symbol); 392 term_write(term, "\x1b[39m"); 393 } 394 } 395 term_write(term, "\x1b[?25h"); 396 // move cursor 397 int cy = set_y + self->cursor.y + 1; 398 int cx = set_x + (2 * self->cursor.x) + 1; 399 term_writef(term, "\x1b[%d;%dH", cy, cx); 400 term_flush(term); 401} 402 403int main(int argc, char *argv[]) { 404 // parse arguments 405 srand(time(NULL)); 406 int seed = rand(); 407 level_t *level = NULL; 408 opterr = 0; 409 int opt; 410 while ((opt = getopt_long(argc, argv, "emhs:", OPTIONS_LEVEL, NULL)) != -1) { 411 switch (opt) { 412 case 'e': 413 level = &LEVEL_EASY; 414 break; 415 case 'm': 416 level = &LEVEL_MEDIUM; 417 break; 418 case 'h': 419 level = &LEVEL_HARD; 420 break; 421 case 's': 422 seed = atoi(optarg); 423 break; 424 default: 425 fprintf(stderr, "Usage: %s [--easy|--medium|--hard]\n", argv[0]); 426 exit(EXIT_FAILURE); 427 } 428 } 429 if (level == NULL) { 430 fprintf(stderr, "Usage: %s [--easy|--medium|--hard]\n", argv[0]); 431 exit(EXIT_FAILURE); 432 } 433 // update loop 434 term_t term = term_init(); 435 term_rawmode_enable(&term); 436 cmine_t self; 437 cm_generate(&self, *level, seed); 438 term_write(&term, "\x1b[?1049h"); 439 term_flush(&term); 440 int quit = 0; 441 while (!quit) { 442 cm_update(&term, &self); 443 int key = term_poll_key(&term, 100); 444 switch (key) { 445 case 'q': 446 quit = 1; 447 break; 448 case KEY_ESC: 449 cm_generate(&self, *level, ++seed); 450 break; 451 case 'j': 452 case KEY_ARROW_DOWN: 453 cm_move(&self, 1, 0); 454 break; 455 case 'k': 456 case KEY_ARROW_UP: 457 cm_move(&self, -1, 0); 458 break; 459 case 'l': 460 case KEY_ARROW_RIGHT: 461 cm_move(&self, 0, 1); 462 break; 463 case 'h': 464 case KEY_ARROW_LEFT: 465 cm_move(&self, 0, -1); 466 break; 467 case ' ': 468 // start timer 469 if (self.time == -1) { 470 self.time = time(NULL); 471 } 472 cm_open(&self, self.cursor.y, self.cursor.x); 473 break; 474 case 'f': 475 cm_flag(&self, self.cursor.y, self.cursor.x); 476 break; 477 default: 478 break; 479 } 480 } 481 term_write(&term, "\x1b[?1049l"); 482 term_flush(&term); 483 term_rawmode_disable(&term); 484 term_cleanup(&term); 485 return EXIT_SUCCESS; 486}