1 /*
2 * Copyright (c) 2019 Georg Brein. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are met:
6 *
7 * 1. Redistributions of source code must retain the above copyright notice,
8 * this list of conditions and the following disclaimer.
9 *
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 *
14 * 3. Neither the name of the copyright holder nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
22 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28 * POSSIBILITY OF SUCH DAMAGE.
29 */
30
31
32 /*
33 * This is a simple text-based implementation of a minesweeper-like
34 * game as a demonstration of tnylpo; it expects tnylpo's VT52
35 * emulation and the tnylpo character set.
36 *
37 * This program was developed using the HI-TECH Z80 CP/M C Compiler V3.09;
38 * Compile it with the command "tnylpo -b c -o mine.c", which should produce
39 * the executable file "mine.com".
40 *
41 * Execute it with the command "tnylpo -f mine.conf mine".
42 */
43
44 /*
45 * BIOS functions
46 */
47 extern int bios(int, ...);
48
49 #define WBOOT 1
50 #define CONST 2
51 #define CONIN 3
52 #define CONOUT 4
53
54
55 /*
56 * display size
57 */
58 #define X_SIZE 80
59 #define Y_SIZE 24
60
61
62 /*
63 * key codes
64 */
65 #define KEY_CTRLD 0x04
66 #define KEY_CTRLE 0x05
67 #define KEY_TAB 0x09
68 #define KEY_CR 0x0d
69 #define KEY_CTRLS 0x13
70 #define KEY_CTRLX 0x18
71 #define KEY_ESC 0x1b
72
73
74 /*
75 * representation of a mine, a flag, and a falsely flagged square on
76 * the screen
77 */
78 #define MINE '\xa4'
79 #define FLAG 'F'
80 #define WRONG '*'
81
82
83 /*
84 * levels of the game; the total number of squares (x * y) must be
85 * relative prime to 17, y may not be larger than Y_SIZE - 4, x may not
86 * be larger then XSIZE - 2
87 */
88 static struct {
89 char x, y;
90 unsigned char mines;
91 } levels[5] = {
92 { 10, 10, 20 },
93 { 20, 10, 40 },
94 { 30, 15, 90 },
95 { 40, 15, 120 },
96 { 60, 20, 240 }
97 };
98
99
100 /*
101 * global variables
102 */
103 /*
104 * seed of the pseudo random number generator
105 */
106 static unsigned long random_seed = 0;
107 /*
108 * current level of the game: board dimensions, number of mines
109 */
110 static char x_size, y_size;
111 static unsigned char nr_mines;
112 /*
113 * offsets of the board on screen
114 */
115 static char x_offset = 0, y_offset = 0;
116 /*
117 * termination flag: mine hit
118 */
119 static char mine_hit;
120 /*
121 * number of remaining unchecked fields
122 */
123 static int unchecked;
124 /*
125 * one past the maximal legal index of board
126 */
127 static int l_field;
128 /*
129 * board flags
130 */
131 #define M_MINE 0x80
132 #define M_FLAG 0x40
133 #define M_OPEN 0x20
134 #define M_NUMBER 0x0f
135 static unsigned char field[(Y_SIZE - 4) * (X_SIZE - 2)];
136
137
138 /*
139 * macros describing the lines above and below the framed board
140 */
141 #define ABOVE (y_offset - 2)
142 #define BELOW (y_offset + y_size + 1)
143
144
145 /*
146 * return a pseudo random number in the range [0..32767]
147 */
148 static int
rand(void)149 rand(void) {
150 random_seed = random_seed * 1103515245 + 12345;
151 return (int) ((random_seed >> 16) & 0x7fff);
152 }
153
154
155 /*
156 * print a single character on the console
157 */
158 static void
pchar(char c)159 pchar(char c) { bios(CONOUT, c); }
160
161
162 /*
163 * print a null terminated string on the console
164 */
165 static void
pstr(char * cp)166 pstr(char *cp) { while (*cp) bios(CONOUT, *cp++); }
167
168
169 /*
170 * display a unsigned byte as decimal number on the console
171 */
172 static void
pubyte(unsigned char uc)173 pubyte(unsigned char uc) {
174 pchar(uc / 100 + '0');
175 pchar(uc % 100 / 10 + '0');
176 pchar(uc % 10 + '0');
177 }
178
179
180 /*
181 * turn bold reverse video on and off
182 */
183
184 static void
br_on(void)185 br_on(void) { pstr("\33e\33i"); }
186
187 static void
br_off(void)188 br_off(void) { pstr("\33j\33f"); }
189
190
191 /*
192 * turn bold video on and off
193 */
194
195 static void
b_on(void)196 b_on(void) { pstr("\33e"); }
197
198 static void
b_off(void)199 b_off(void) { pstr("\33f"); }
200
201
202 /*
203 * turn graphic characters on and off
204 */
205
206 static void
graph_on(void)207 graph_on(void) { pstr("\33F"); }
208
209 static
graph_off(void)210 void graph_off(void) { pstr("\33G"); }
211
212
213 /*
214 * clear screen
215 */
216 static void
clear(void)217 clear(void) { pstr("\33E"); }
218
219
220 /*
221 * clear to end of line
222 */
223 static void
clear_eoln(void)224 clear_eoln(void) { pstr("\33K"); }
225
226
227 /*
228 * read a character from the console
229 */
230 static char
gchar(void)231 gchar(void) { return bios(CONIN); }
232
233
234 /*
235 * position cursor to (x, y)
236 */
237 static void
goto_xy(char x,char y)238 goto_xy(char x, char y) {
239 pstr("\33Y");
240 pchar(y + ' ');
241 pchar(x + ' ');
242 }
243
244
245 /*
246 * display x_size characters c on the console
247 */
248 static void
hline(char c)249 hline(char c) {
250 char i;
251 for (i = 0; i < x_size; i++) pchar(c);
252 }
253
254
255 /*
256 * check if the square (x, y) contains a mine
257 */
258 static unsigned char
is_mine(char x,char y)259 is_mine(char x, char y) {
260 if (x < 0 || x > x_size - 1 || y < 0 || y > y_size - 1) return 0;
261 return((field[x_size * y + x] & M_MINE) == M_MINE);
262 }
263
264
265 /*
266 * show the square (x, y); square must not contain a mine; if square has
267 * no neighbouring mines, recursively open all adjacent unflagged squares
268 */
269 static void
open_field(char x,char y)270 open_field(char x, char y) {
271 int i;
272 char n;
273 /*
274 * skip square out of range
275 */
276 if (x < 0 || x > x_size - 1 || y < 0 || y > y_size - 1) return;
277 /*
278 * skip flagged or already opened square
279 */
280 i = x_size * y + x;
281 if (field[i] & (M_OPEN | M_FLAG)) return;
282 /*
283 * mark square as opened, decrease number of unopened squares
284 */
285 field[i] |= M_OPEN;
286 unchecked--;
287 /*
288 * get and display number of neighbouring mines
289 */
290 n = field[i] & M_NUMBER;
291 goto_xy(x + x_offset, y + y_offset);
292 if (n) {
293 pchar(n + '0');
294 return;
295 }
296 /*
297 * if the square has no neighbouring mines, try to open all neighbours
298 */
299 pchar(' ');
300 open_field(x - 1, y - 1);
301 open_field(x, y - 1);
302 open_field(x + 1, y - 1);
303 open_field(x - 1, y);
304 open_field(x + 1, y);
305 open_field(x - 1, y + 1);
306 open_field(x, y + 1);
307 open_field(x + 1, y + 1);
308 }
309
310
311 /*
312 * test square (x, y); if there is a mine, the game is lost, if not,
313 * the square is opened
314 */
315 static void
touch_field(char x,char y)316 touch_field(char x, char y) {
317 int i;
318 /*
319 * skip square out of range
320 */
321 if (x < 0 || x > x_size - 1 || y < 0 || y > y_size - 1) return;
322 i = x_size * y + x;
323 /*
324 * skip opened or flagged square
325 */
326 if (field[i] & (M_OPEN | M_FLAG)) return;
327 /*
328 * if there is a mine, it explodes and the game is lost
329 */
330 if (field[i] & M_MINE) {
331 mine_hit = 1;
332 return;
333 }
334 /*
335 * otherwise, open the square (and potentially, its neighbours
336 */
337 open_field(x, y);
338 }
339
340
341 /*
342 * play a game of Mine Disposal
343 */
344 static void
play(void)345 play(void) {
346 /*
347 * message strings for centering
348 */
349 static char you_win[] = "You win!";
350 static char you_lose[] = "You lose!";
351 static char mines[] = "Mines:";
352 char x, y, key, mines_x;
353 unsigned char n, mines_left, old_left;
354 int i;
355 /*
356 * number of mines not flagged
357 */
358 mines_left = old_left = nr_mines;
359 /*
360 * clear the screen and paint the board
361 */
362 clear();
363 goto_xy(x_offset - 1, y_offset - 1);
364 graph_on();
365 pchar('l');
366 hline('q');
367 pchar('k');
368 for (y = 0; y < y_size; y++) {
369 goto_xy(x_offset - 1, y_offset + y);
370 pchar('x');
371 br_on();
372 hline(' ');
373 br_off();
374 pchar('x');
375 }
376 goto_xy(x_offset - 1, y_offset + y_size);
377 pchar('m');
378 hline('q');
379 pchar('j');
380 graph_off();
381 /*
382 * display number of unflagged mines
383 */
384 mines_x = (X_SIZE - sizeof mines - 2) / 2;
385 goto_xy(mines_x, ABOVE);
386 pstr(mines);
387 mines_x += sizeof mines;
388 goto_xy(mines_x, ABOVE);
389 pubyte(mines_left);
390 /*
391 * initialize the board
392 */
393 for (i = 0; i < l_field; i++) field[i] = 0;
394 /*
395 * randomly set the mines
396 */
397 for (n = 0; n < nr_mines; n++) {
398 i = rand() % l_field;
399 while (field[i] & M_MINE) {
400 i += 17;
401 if (i >= l_field) i -= l_field;
402 }
403 field[i] |= M_MINE;
404 }
405 /*
406 * calculate the number of neighbouring mines for all other squares
407 */
408 for (y = 0; y < y_size; y++) {
409 for (x = 0; x < x_size; x++) {
410 i = x_size * y + x;
411 if (field[i] & M_MINE) continue;
412 n = is_mine(x - 1, y - 1);
413 n += is_mine(x, y - 1);
414 n += is_mine(x + 1, y - 1);
415 n += is_mine(x - 1, y);
416 n += is_mine(x + 1, y);
417 n += is_mine(x - 1, y + 1);
418 n += is_mine(x, y + 1);
419 n += is_mine(x + 1, y + 1);
420 field[i] = n;
421 }
422 }
423 /*
424 * main loop is performed until all unmined squares have been opened
425 * or a mine has been hit
426 */
427 x = y = 0;
428 unchecked = l_field - nr_mines;
429 mine_hit = 0;
430 while (unchecked && ! mine_hit) {
431 /*
432 * display number of unflagged mines, if it has changed
433 */
434 if (mines_left != old_left) {
435 goto_xy(mines_x, ABOVE);
436 clear_eoln();
437 pubyte(mines_left);
438 old_left = mines_left;
439 }
440 /*
441 * get a character from the current position;
442 * if it is escape, it is a cursor/function key
443 */
444 goto_xy(x + x_offset, y + y_offset);
445 key = gchar();
446 if (key == KEY_ESC) {
447 key = gchar();
448 switch (key) {
449 case 'A': key = KEY_CTRLE; break;
450 case 'B': key = KEY_CTRLX; break;
451 case 'C': key = KEY_CTRLD; break;
452 case 'D': key = KEY_CTRLS; break;
453 case 'P': key = ' '; break;
454 case 'Q': key = KEY_TAB; break;
455 case 'R': key = KEY_CR; break;
456 default: continue;
457 }
458 }
459 switch (key) {
460 case KEY_CTRLE:
461 /*
462 * cursor up
463 */
464 if (y > 0) y--;
465 continue;
466 case KEY_CTRLX:
467 /*
468 * cursor down
469 */
470 if (y < y_size - 1) y++;
471 continue;
472 case KEY_CTRLS:
473 /*
474 * cursor left
475 */
476 if (x > 0) x--;
477 continue;
478 case KEY_CTRLD:
479 /*
480 * cursor right
481 */
482 if (x < x_size - 1) x++;
483 continue;
484 case KEY_CR:
485 /*
486 * set/reset flag
487 */
488 i = x_size * y + x;
489 if (field[i] & M_OPEN) continue;
490 br_on();
491 if (field[i] & M_FLAG) {
492 field[i] &= ~M_FLAG;
493 pchar(' ');
494 mines_left++;
495 } else {
496 field[i] |= M_FLAG;
497 pchar(FLAG);
498 mines_left--;
499 }
500 br_off();
501 continue;
502 case KEY_TAB:
503 /*
504 * hit all adjacent squares, unless flagged
505 */
506 i = x_size * y + x;
507 if (! (field[i] & M_OPEN)) continue;
508 if (! field[i]) continue;
509 touch_field(x - 1, y - 1);
510 touch_field(x, y - 1);
511 touch_field(x + 1, y - 1);
512 touch_field(x - 1, y);
513 touch_field(x + 1, y);
514 touch_field(x - 1, y + 1);
515 touch_field(x, y + 1);
516 touch_field(x + 1, y + 1);
517 continue;
518 case ' ':
519 /*
520 * hit field
521 */
522 touch_field(x, y);
523 continue;
524 }
525 }
526 /*
527 * display win or lose message
528 */
529 goto_xy(0, ABOVE);
530 clear_eoln();
531 goto_xy((X_SIZE - sizeof you_win + 1) / 2, ABOVE);
532 b_on();
533 pstr(mine_hit ? you_lose : you_win);
534 b_off();
535 /*
536 * show all falsely flagged squares or unflagged mines
537 */
538 i = 0;
539 for (y = 0; y < y_size; y++) {
540 for (x = 0; x < x_size; x++) {
541 if (field[i] & M_FLAG) {
542 if (! (field[i] & M_MINE)) {
543 goto_xy(x + x_offset, y + y_offset);
544 br_on();
545 pchar(WRONG);
546 br_off();
547 }
548 } else if (field[i] & M_MINE) {
549 /*
550 * in case of loss, show all undiscovered
551 * mines; in case of a win (i. e., if all
552 * remaining unopened fields contain mines),
553 * show them as flagged
554 */
555 goto_xy(x + x_offset, y + y_offset);
556 br_on();
557 pchar(mine_hit ? MINE : FLAG);
558 br_off();
559 }
560 i++;
561 }
562 }
563 }
564
565
566 void
main(void)567 main(void) {
568 /*
569 * message string for centering
570 */
571 static char again[] = "Play again (y/n): ";
572 char key, level;
573 /*
574 * clear screen and show title
575 */
576 clear();
577 b_on();
578 pstr("Mine Disposal");
579 b_off();
580 /*
581 * query level; the time to the keypress of the user's answer
582 * is used to initialize the pseudo random number generator
583 */
584 pstr("\r\n\nEnter level (1-5): ");
585 while (! bios(CONST)) random_seed++;
586 do {
587 key = gchar();
588 } while (key < '1' || key > '5');
589 pchar(key);
590 level = key - '1';
591 /*
592 * get parameters of selected level
593 */
594 x_size = levels[level].x;
595 y_size = levels[level].y;
596 nr_mines = levels[level].mines;
597 /*
598 * calculate board limit
599 */
600 l_field = (int) x_size * (int) y_size;
601 /*
602 * calculate board offset on screen
603 */
604 x_offset = (X_SIZE - x_size) / 2;
605 y_offset = (Y_SIZE - y_size) / 2;
606 /*
607 * repeat game until user doesn't want to play again
608 */
609 do {
610 play();
611 goto_xy((X_SIZE - sizeof again) / 2, BELOW);
612 pstr(again);
613 do {
614 key = gchar();
615 } while (key != 'N' && key != 'n' && key != 'y' && key != 'Y');
616 pchar(key);
617 } while (key == 'Y' || key == 'y');
618 /*
619 * exit game
620 */
621 goto_xy(0, Y_SIZE - 1);
622 bios(WBOOT);
623 }
624