1/* Micro Tetris, based on an obfuscated tetris, 1989 IOCCC Best Game
2 *
3 * Copyright (c) 1989 John Tromp <john.tromp@gmail.com>
4 * Copyright (c) 2009, 2010 Joachim Nilsson <troglobit@gmail.com>
5 *
6 * Permission to use, copy, modify, and/or distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 *
18 * See the following URLs for more information, first John Tromp's page about
19 * the game http://homepages.cwi.nl/~tromp/tetris.html then there's the entry
20 * page at IOCCC http://www.ioccc.org/1989/tromp.hint
21 */
22
23/*! @file
24    @brief RefOS port of Micro Tetris.
25
26    <h1> RefOS Tetris </h1>
27
28    A classic Tetris clone, heavily based on (a port of)
29    Micro Tetris by Joachim Nilsson <joachim.nilsson@vmlinux.org>,
30    which itself is based on the 1989 IOCCC Best Game entry "An obfuscated
31    tetris" by John Tromp <john.tromp@gmail.com>.
32
33    Website about original IOCCC entry:
34    http://homepages.cwi.nl/~tromp/tetris.html
35
36    Website about Micro Tetris:
37    http://freecode.com/projects/micro-tetris
38    http://troglobit.com/tetris.html
39*/
40
41#include <stdio.h>
42#include <stdlib.h>
43#include <string.h>
44#include <unistd.h>
45
46#include "io.h"
47
48/* Game Settings Definitions. */
49#define LINES_PER_LEVEL 5
50#define INITIAL_DELAY_MS 300
51#define LEVEL_DECREASE_DELAY_MS 18
52#define RANDOM_SEED 0x177A0929
53#define MAX_LEVEL 8
54
55/* Board points. */
56#define      TL     -B_COLS-1       /* Top left. */
57#define      TC     -B_COLS         /* Top center. */
58#define      TR     -B_COLS+1       /* Top right. */
59#define      ML     -1              /* Middle left. */
60#define      MR     1               /* Middle right. */
61#define      BL     B_COLS-1        /* Bottom left. */
62#define      BC     B_COLS          /* Bottom center. */
63#define      BR     B_COLS+1        /* Bottom right. */
64
65/* Controls. */
66#define DEFAULT_KEYS "jkl pq"
67#define KEY_LEFT   0
68#define KEY_RIGHT  2
69#define KEY_ROTATE 1
70#define KEY_DROP   3
71#define KEY_PAUSE  4
72#define KEY_QUIT   5
73
74/* Game variables. */
75char exitGame = 0;
76char *keys = DEFAULT_KEYS;
77int level = 1;
78int points = 0;
79int linesCleared = 0;
80int pos = 17;
81
82/* Settings. */
83char enablePreview = 1;
84char enableScoreBoard = 1;
85
86/*! @brief Peek preview of next shape. */
87int *peekShape = NULL;
88
89/* Screen buffers. */
90int board[B_SIZE], shadow[B_SIZE];
91int preview[B_COLS * 10], shadowPreview[B_COLS * 10];
92
93/*! @brief Current shape. */
94int *shape;
95
96/*! @brief Shapes list. */
97int shapes[] = {
98    7,  TL,  TC,  MR,
99    8,  TR,  TC,  ML,
100    9,  ML,  MR,  BC,
101    3,  TL,  TC,  ML,
102   12,  ML,  BL,  MR,
103   15,  ML,  BR,  MR,
104   18,  ML,  MR,   2,           /* sticks out */
105    0,  TC,  ML,  BL,
106    1,  TC,  MR,  BR,
107   10,  TC,  MR,  BC,
108   11,  TC,  ML,  MR,
109    2,  TC,  ML,  BC,
110   13,  TC,  BC,  BR,
111   14,  TR,  ML,  MR,
112    4,  TL,  TC,  BC,
113   16,  TR,  TC,  BC,
114   17,  TL,  MR,  ML,
115    5,  TC,  BC,  BL,
116    6,  TC,  BC,  2 * B_COLS,   /* sticks out */
117};
118int colours[] = {
119    A_BG_R,
120    A_BG_C,
121    A_BG_Y,
122    A_BG_W,
123    A_BG_G,
124    A_BG_B,
125    A_BG_M,
126    A_BG_R,
127    A_BG_C,
128    A_BG_Y,
129    A_BG_Y,
130    A_BG_Y,
131    A_BG_G,
132    A_BG_G,
133    A_BG_G,
134    A_BG_B,
135    A_BG_B,
136    A_BG_B,
137    A_BG_M,
138};
139
140/*! @brief Render the online help. */
141void
142show_online_help(void)
143{
144    static int start = 11;
145    textattr(RESETATTR);
146    gotoxy (26 + 28, start);
147    puts("j     - left");
148    gotoxy (26 + 28, start + 1);
149    puts("k     - rotate");
150    gotoxy (26 + 28, start + 2);
151    puts("l     - right");
152    gotoxy (26 + 28, start + 3);
153    puts("space - drop");
154    gotoxy (26 + 28, start + 4);
155    puts("p     - pause");
156    gotoxy (26 + 28, start + 5);
157    puts("q     - quit");
158}
159
160/*! @brief Gets the corresponding colour of the shape. */
161int
162get_col(int *shape)
163{
164    return colours[(shape - shapes) / 4];
165}
166
167/*! @brief Render and update the current screen output. */
168int
169update(void)
170{
171    int x, y;
172
173    /* Render the next piece preview. */
174    if (enablePreview) {
175        static int start = 5;
176        memset (preview, 0, sizeof(preview));
177
178        int c = get_col(peekShape);
179        preview[2 * B_COLS + 1] = c;
180        preview[2 * B_COLS + 1 + peekShape[1]] = c;
181        preview[2 * B_COLS + 1 + peekShape[2]] = c;
182        preview[2 * B_COLS + 1 + peekShape[3]] = c;
183
184        for (y = 0; y < 4; y++) {
185            for (x = 0; x < B_COLS; x++) {
186                if (preview[y * B_COLS + x] - shadowPreview[y * B_COLS + x]) {
187                    shadowPreview[y * B_COLS + x] = preview[y * B_COLS + x];
188                    gotoxy (x * 2 + 26 + 28, start + y);
189                    printblock(preview[y * B_COLS + x]);
190                }
191            }
192        }
193    }
194
195    /* Render the actual board. */
196    for (y = 1; y < B_ROWS - 1; y++) {
197        for (x = 0; x < B_COLS; x++) {
198            if (board[y * B_COLS + x] - shadow[y * B_COLS + x]) {
199                shadow[y * B_COLS + x] = board[y * B_COLS + x];
200                gotoxy (x * 2 + 28, y);
201                printblock(board[y * B_COLS + x]);
202            }
203        }
204    }
205
206    /* Update points and level. */
207    while (linesCleared >= LINES_PER_LEVEL) {
208        linesCleared -= LINES_PER_LEVEL;
209        level++;
210        if (level > MAX_LEVEL) level = MAX_LEVEL;
211    }
212
213    /* Render the scoreboard. */
214    if (enableScoreBoard) {
215        textattr(RESETATTR);
216        gotoxy (26 + 28, 2);
217        printf ("Level  : %d", level);
218        fflush(stdout);
219        gotoxy (26 + 28, 3);
220        printf ("Points : %d", points);
221        fflush(stdout);
222    }
223
224    if (enablePreview) {
225        gotoxy (26 + 28, 5);
226        printf ("Preview:");
227        fflush(stdout);
228    }
229
230    gotoxy (26 + 28, 10);
231    printf ("Keys:");
232    fflush(stdout);
233    return 0;
234}
235
236/*! @brief Returns whether a piece fits in given board position. */
237int
238fits_in(int *shape, int pos) {
239    if (board[pos] || board[pos + shape[1]] || board[pos + shape[2]] ||
240            board[pos + shape[3]]) {
241        return 0;
242    }
243    return 1;
244}
245
246/*! @brief Place a piece on the board at a given position. */
247void
248place(int *shape, int pos, int b)
249{
250    board[pos] = b;
251    board[pos + shape[1]] = b;
252    board[pos + shape[2]] = b;
253    board[pos + shape[3]] = b;
254}
255
256/*! @brief Randomly select a next shape. */
257int
258*next_shape(void)
259{
260    int *next = peekShape;
261    peekShape = &shapes[rand() % 7 * 4];
262    if (!next) next = &shapes[rand() % 7 * 4];
263    return next;
264}
265
266/*! @brief Update game state forward a frame. */
267void
268game_frame(int c)
269{
270    int j;
271    int *backup;
272
273    if (c == 0) {
274        if (fits_in (shape, pos + B_COLS)) {
275            pos += B_COLS;
276        } else {
277            place(shape, pos, get_col(shape));
278            ++points;
279            for (j = 0; j < 252; j = B_COLS * (j / B_COLS + 1)) {
280                for (; board[++j];) {
281                    if (j % B_COLS == 10) {
282                        linesCleared++;
283
284                        for (; j % B_COLS; board[j--] = 0);
285                        update();
286                        for (; --j; board[j + B_COLS] = board[j]);
287                        update();
288                    }
289                }
290            }
291            shape = next_shape();
292            if (!fits_in (shape, pos = 17)) c = keys[KEY_QUIT];
293        }
294    }
295
296    if (c == keys[KEY_LEFT]) {
297        if (!fits_in (shape, --pos)) ++pos;
298    }
299    if (c == keys[KEY_ROTATE]) {
300        backup = shape;
301        shape = &shapes[4 * *shape]; /* Rotate */
302        /* Check if it fits, if not restore shape from backup. */
303        if (!fits_in (shape, pos)) shape = backup;
304    }
305
306    if (c == keys[KEY_RIGHT]) {
307        if (!fits_in (shape, ++pos)) --pos;
308    }
309    if (c == keys[KEY_DROP]) {
310        for (; fits_in (shape, pos + B_COLS); ++points) pos += B_COLS;
311    }
312    if (c == keys[KEY_PAUSE] || c == keys[KEY_QUIT]) {
313        exitGame = 1;
314        return;
315    }
316
317    place(shape, pos, get_col(shape));
318    update();
319    place(shape, pos, RESETATTR);
320}
321
322static void
323print_welcome_message(void)
324{
325    printf(
326        "  ��������������������������������������������������������������������������������������������������� ���������������������������������\n"
327        "  ���������������������������������������������������������������������������������������������������������������������������������������\n"
328        "     ���������   ������������������     ���������   ���������������������������������������������������������\n"
329        "     ���������   ������������������     ���������   ���������������������������������������������������������\n"
330        "     ���������   ������������������������   ���������   ���������  ������������������������������������������\n"
331        "     ���������   ������������������������   ���������   ���������  ������������������������������������������\n"
332    );
333}
334
335int
336main()
337{
338    /* Future Work 3:
339       How the selfloader bootstraps user processes needs to be modified further. Changes were
340       made to accomodate the new way that muslc expects process's stacks to be set up when
341       processes start, but the one part of this that still needs to changed is how user processes
342       find their system call table. Currently the selfloader sets up user processes so that
343       the selfloader's system call table is used by user processes by passing the address of the
344       selfloader's system call table to the user processes via the user process's environment
345       variables. Ideally, user processes would use their own system call table.
346    */
347
348    uintptr_t address = strtoll(getenv("SYSTABLE"), NULL, 16);
349    refos_init_selfload_child(address);
350    int c, i, *ptr;
351    int delay = INITIAL_DELAY_MS;
352    refos_initialise();
353
354    peekShape = NULL;
355
356    /* Initialise board. */
357    ptr = board;
358    for (i = B_SIZE - 1; i>=0; i--) {
359        *ptr++ = i < 25 || i % B_COLS < 2 ? A_BG_W : RESETATTR;
360    }
361
362    srand((unsigned int) RANDOM_SEED);
363
364    clrscr();
365
366    print_welcome_message();
367    printf("      Press the space bar to continue...\n");
368    show_online_help();
369
370    while (1) {
371        c = io_nonblock_getkey();
372        rand();
373        if (c == ' ') break;
374    }
375    clrscr();
376    show_online_help();
377
378    /* Main game loop. */
379    shape = next_shape();
380    while (!exitGame) {
381        c = io_nonblock_getkey();
382        rand();
383        if (c >= 0) {
384            game_frame(c);
385        } else {
386            game_frame(0);
387            usleep((delay - level * LEVEL_DECREASE_DELAY_MS) * 1000);
388        }
389    }
390
391    gotoxy (0, 25);
392    printf("Game over! You reached %d points.\n", points);
393}
394