fixes, optional beeps, accept arrow keys for control
[ansitris] / src / game.c
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include <time.h>
5 #include <inttypes.h>
6 #include "game.h"
7 #include "pieces.h"
8 #include "ansi.h"
9
10 enum {
11         G_DIAMOND       = 0x04,
12         G_CHECKER       = 0xb1,
13         G_LR_CORNER     = 0xd9,
14         G_UR_CORNER     = 0xbf,
15         G_UL_CORNER     = 0xda,
16         G_LL_CORNER     = 0xc0,
17         G_CROSS         = 0xc5,
18         G_HLINE         = 0xc4,
19         G_L_TEE         = 0xc3,
20         G_R_TEE         = 0xb4,
21         G_B_TEE         = 0xc1,
22         G_T_TEE         = 0xc2,
23         G_VLINE         = 0xb3,
24         G_CDOT          = 0xf8
25 };
26
27 enum { ERASE_PIECE, DRAW_PIECE };
28
29 enum { BLACK, BLUE, GREEN, CYAN, RED, MAGENTA, YELLOW, WHITE };
30
31 /* dimensions of the whole screen */
32 #define SCR_ROWS        20
33 #define SCR_COLS        20
34
35 /* dimensions of the playfield */
36 #define PF_ROWS         18
37 #define PF_COLS         10
38 /* offset of the playfield from the left side of the screen */
39 #define PF_XOFFS        2
40 #define PF_YOFFS        0
41
42 #define CHAR(c, fg, bg) \
43         ((uint16_t)(c) | ((uint16_t)(fg) << 12) | ((uint16_t)(bg) << 8))
44
45 int scr[SCR_COLS * SCR_ROWS];
46
47 static int collision(int piece, const int *pos);
48 static void stick(int piece, const int *pos);
49 static void draw_piece(int piece, const int *pos, int rot, int mode);
50 static void drawbg(void);
51 static void wrtile(int tileid);
52
53
54 static int pos[2], next_pos[2];
55 static int cur_piece = -1;
56 static int cur_rot, prev_rot;
57
58 enum {
59         TILE_BLACK,
60         TILE_PF,
61         TILE_PFSEP,
62         TILE_IPIECE,
63         TILE_OPIECE,
64         TILE_JPIECE,
65         TILE_LPIECE,
66         TILE_SPIECE,
67         TILE_TPIECE,
68         TILE_ZPIECE
69 };
70 #define FIRST_PIECE_TILE        TILE_IPIECE
71
72 static uint16_t tiles[][2] = {
73         { CHAR(' ', BLACK, BLACK), CHAR(' ', BLACK, BLACK) },                   /* black tile */
74         { CHAR(' ', WHITE, WHITE), CHAR(' ', WHITE, WHITE) },                   /* playfield background */
75         { CHAR(G_CHECKER, WHITE, BLACK), CHAR(G_CHECKER, WHITE, BLACK) },       /* well separator */
76         { CHAR(' ', CYAN, CYAN), CHAR(' ', CYAN, CYAN) },                               /* straight */
77         { CHAR(' ', BLUE, BLUE), CHAR(' ', BLUE, BLUE) },                               /* box */
78         { CHAR(' ', GREEN, GREEN), CHAR(' ', GREEN, GREEN) },                   /* J */
79         { CHAR(' ', YELLOW, YELLOW), CHAR(' ', YELLOW, YELLOW) },               /* L */
80         { CHAR(' ', MAGENTA, MAGENTA), CHAR(' ', MAGENTA, MAGENTA) },   /* S */
81         { CHAR(' ', RED, BLACK), CHAR(' ', RED, BLACK) },               /* T */
82         { CHAR(' ', RED, RED), CHAR(' ', RED, RED) },                                   /* Z */
83 };
84
85
86 int init_game(void)
87 {
88         int i, j;
89         int *row = scr;
90
91         srand(time(0));
92
93         tick_interval = 1000;
94
95         ansi_clearscr();
96         ansi_cursor(0);
97
98         /* fill the screen buffer, and draw */
99         for(i=0; i<SCR_ROWS; i++) {
100                 for(j=0; j<SCR_COLS; j++) {
101                         if(i > PF_ROWS || j < PF_XOFFS - 1 || j > PF_XOFFS + PF_COLS) {
102                                 row[j] = TILE_BLACK;
103                         } else if((i == PF_ROWS && j >= PF_XOFFS && j < PF_XOFFS + PF_COLS) ||
104                                         j == PF_XOFFS - 1 || j == PF_XOFFS + PF_COLS) {
105                                 row[j] = TILE_PFSEP;
106                         } else {
107                                 row[j] = TILE_PF;
108                         }
109                 }
110                 row += SCR_COLS;
111         }
112
113         drawbg();
114         fflush(stdout);
115
116         return 0;
117 }
118
119 void cleanup_game(void)
120 {
121         ansi_reset();
122 }
123
124 long update(long msec)
125 {
126         static long prev_tick;
127         long dt;
128
129         dt = msec - prev_tick;
130
131         /* fall */
132         while(dt >= tick_interval) {
133                 if(cur_piece >= 0) {
134                         next_pos[0] = pos[0] + 1;
135                         if(collision(cur_piece, next_pos)) {
136                                 next_pos[0] = pos[0];
137                                 fprintf(stderr, "stick at row %d col %d\n", pos[0], pos[1]);
138                                 stick(cur_piece, next_pos);
139                                 cur_piece = -1;
140                                 return 0;
141                         }
142                 } else {
143                         cur_piece = rand() % NUM_PIECES;
144                         prev_rot = cur_rot = 0;
145                         fprintf(stderr, "spawn: %d\n", cur_piece);
146                         pos[0] = piece_spawnpos[cur_piece][0];
147                         next_pos[0] = pos[0] + 1;
148                         pos[1] = next_pos[1] = PF_COLS / 2 + piece_spawnpos[cur_piece][1];
149                 }
150
151                 dt -= tick_interval;
152                 prev_tick = msec;
153         }
154
155         if(cur_piece >= 0 && (memcmp(pos, next_pos, sizeof pos) != 0 || cur_rot != prev_rot)) {
156                 draw_piece(cur_piece, pos, prev_rot, ERASE_PIECE);
157                 draw_piece(cur_piece, next_pos, cur_rot, DRAW_PIECE);
158                 memcpy(pos, next_pos, sizeof pos);
159                 prev_rot = cur_rot;
160         }
161         return tick_interval - dt;
162 }
163
164
165 #define C0      0x9b
166 #define SS3     0x8f
167
168 static void runesc(int csi, char *buf)
169 {
170         if(csi != C0) return;
171
172         if(buf[1] == 0) {
173                 switch(buf[0]) {
174                 case 'A':
175                         game_input('w');        /* up */
176                         break;
177                 case 'B':
178                         game_input('s');        /* down */
179                         break;
180                 case 'C':
181                         game_input('d');        /* right */
182                         break;
183                 case 'D':
184                         game_input('a');        /* left */
185                         break;
186                 default:
187                         break;
188                 }
189         }
190 }
191
192 void game_input(int c)
193 {
194         static int esc, csi;
195         static int esctop;
196         static char escbuf[64];
197
198         if(esc) {
199                 esc = 0;
200                 if(c == 27) {
201                         quit = 1;
202                         return;
203                 }
204
205                 switch(c) {
206                 case '[':
207                         csi = C0;
208                         return;
209                 case 'O':
210                         csi = SS3;
211                         return;
212                 default:
213                         break;
214                 }
215         }
216
217         if(csi) {
218                 if(c < 0x20 || c >= 0x80) {
219                         csi = 0;
220                         esctop = 0;
221                 }
222
223                 escbuf[esctop++] = c;
224
225                 if(c >= 0x40) {
226                         int prevcsi = csi;
227                         escbuf[esctop] = 0;
228                         csi = 0;
229                         esctop = 0;
230                         runesc(prevcsi, escbuf);
231                 }
232                 return;
233         }
234
235         switch(c) {
236         case 27:
237                 esc = 1;
238                 break;
239
240         case C0:
241                 esc = 1;
242                 csi = C0;
243                 break;
244
245         case 'q':
246                 quit = 1;
247                 break;
248
249         case 'a':
250                 next_pos[1] = pos[1] - 1;
251                 if(collision(cur_piece, next_pos)) {
252                         next_pos[1] = pos[1];
253                 }
254                 break;
255
256         case 'd':
257                 next_pos[1] = pos[1] + 1;
258                 if(collision(cur_piece, next_pos)) {
259                         next_pos[1] = pos[1];
260                 }
261                 break;
262
263         case 'w':
264         case ' ':
265                 prev_rot = cur_rot;
266                 cur_rot = (cur_rot + 1) & 3;
267                 if(collision(cur_piece, next_pos)) {
268                         cur_rot = prev_rot;
269                 }
270                 break;
271
272         case 's':
273                 next_pos[0] = pos[0] + 1;
274                 if(collision(cur_piece, next_pos)) {
275                         next_pos[0] = pos[0];
276                 }
277                 break;
278
279         default:
280                 fprintf(stderr, "unhandled input: %x\n", c);
281                 break;
282         }
283 }
284
285 static int collision(int piece, const int *pos)
286 {
287         int i;
288         unsigned char *p = pieces[piece][cur_rot];
289
290         for(i=0; i<4; i++) {
291                 int x = PF_XOFFS + pos[1] + BLKX(*p);
292                 int y = PF_YOFFS + pos[0] + BLKY(*p);
293                 p++;
294
295                 if(scr[y * SCR_COLS + x] != TILE_PF) return 1;
296         }
297
298         return 0;
299 }
300
301 static void stick(int piece, const int *pos)
302 {
303         int i;
304         unsigned char *p = pieces[piece][cur_rot];
305
306         for(i=0; i<4; i++) {
307                 int x = PF_XOFFS + pos[1] + BLKX(*p);
308                 int y = PF_YOFFS + pos[0] + BLKY(*p);
309                 p++;
310
311                 scr[y * SCR_COLS + x] = piece + FIRST_PIECE_TILE;
312         }
313
314         if(use_bell) {
315                 putchar('\a');
316                 fflush(stdout);
317         }
318 }
319
320 static void draw_piece(int piece, const int *pos, int rot, int mode)
321 {
322         int i;
323         int tile = mode == ERASE_PIECE ? TILE_PF : FIRST_PIECE_TILE + piece;
324         unsigned char *p = pieces[piece][rot];
325
326         for(i=0; i<4; i++) {
327                 int x = PF_XOFFS + pos[1] + BLKX(*p);
328                 int y = PF_YOFFS + pos[0] + BLKY(*p);
329                 p++;
330
331                 if(y < 0) continue;
332
333                 ansi_setcursor(y, x * 2);
334                 wrtile(tile);
335         }
336         fflush(stdout);
337 }
338
339 static void drawbg(void)
340 {
341         int i, j;
342         int *sptr = scr;
343
344         for(i=0; i<SCR_ROWS; i++) {
345                 ansi_setcursor(i, 0);
346                 for(j=0; j<SCR_COLS; j++) {
347                         wrtile(*sptr++);
348                 }
349         }
350 }
351
352 static void wrtile(int tileid)
353 {
354         int i;
355
356         for(i=0; i<2; i++) {
357                 uint16_t c = tiles[tileid][i];
358                 unsigned char cc = c & 0xff;
359                 unsigned char ca = c >> 8;
360
361                 ansi_ibmchar(cc, ca);
362         }
363 }