line completion and removal
[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 erase_completed(void);
50 static void draw_piece(int piece, const int *pos, int rot, int mode);
51 static void drawbg(void);
52 static void drawpf(void);
53 static void draw_line(int row, int blink);
54 static void wrtile(int tileid);
55
56
57 static int pos[2], next_pos[2];
58 static int cur_piece = -1;
59 static int cur_rot, prev_rot;
60 static int complines[4] = {-1, -1, -1, -1};
61 static int num_complines;
62
63 enum {
64         TILE_BLACK,
65         TILE_PF,
66         TILE_PFSEP,
67         TILE_IPIECE,
68         TILE_OPIECE,
69         TILE_JPIECE,
70         TILE_LPIECE,
71         TILE_SPIECE,
72         TILE_TPIECE,
73         TILE_ZPIECE
74 };
75 #define FIRST_PIECE_TILE        TILE_IPIECE
76
77 static uint16_t tiles[][2] = {
78         { CHAR(' ', BLACK, BLACK), CHAR(' ', BLACK, BLACK) },                   /* black tile */
79         { CHAR(' ', WHITE, WHITE), CHAR(' ', WHITE, WHITE) },                   /* playfield background */
80         { CHAR(G_CHECKER, WHITE, BLACK), CHAR(G_CHECKER, WHITE, BLACK) },       /* well separator */
81         { CHAR(' ', CYAN, CYAN), CHAR(' ', CYAN, CYAN) },                               /* straight */
82         { CHAR(' ', BLUE, BLUE), CHAR(' ', BLUE, BLUE) },                               /* box */
83         { CHAR(' ', GREEN, GREEN), CHAR(' ', GREEN, GREEN) },                   /* J */
84         { CHAR(' ', YELLOW, YELLOW), CHAR(' ', YELLOW, YELLOW) },               /* L */
85         { CHAR(' ', MAGENTA, MAGENTA), CHAR(' ', MAGENTA, MAGENTA) },   /* S */
86         { CHAR(' ', RED, BLACK), CHAR(' ', RED, BLACK) },               /* T */
87         { CHAR(' ', RED, RED), CHAR(' ', RED, RED) },                                   /* Z */
88 };
89
90
91 int init_game(void)
92 {
93         int i, j;
94         int *row = scr;
95
96         srand(time(0));
97
98         tick_interval = 1000;
99
100         ansi_clearscr();
101         ansi_cursor(0);
102
103         /* fill the screen buffer, and draw */
104         for(i=0; i<SCR_ROWS; i++) {
105                 for(j=0; j<SCR_COLS; j++) {
106                         if(i > PF_ROWS || j < PF_XOFFS - 1 || j > PF_XOFFS + PF_COLS) {
107                                 row[j] = TILE_BLACK;
108                         } else if((i == PF_ROWS && j >= PF_XOFFS && j < PF_XOFFS + PF_COLS) ||
109                                         j == PF_XOFFS - 1 || j == PF_XOFFS + PF_COLS) {
110                                 row[j] = TILE_PFSEP;
111                         } else {
112                                 row[j] = TILE_PF;
113                         }
114                 }
115                 row += SCR_COLS;
116         }
117
118         drawbg();
119         fflush(stdout);
120
121         return 0;
122 }
123
124 void cleanup_game(void)
125 {
126         ansi_reset();
127 }
128
129 #define BLINK_UPD_RATE  100
130
131 long update(long msec)
132 {
133         static long prev_tick;
134         long dt;
135
136         dt = msec - prev_tick;
137
138         if(num_complines) {
139                 /* lines where completed, we're in blinking mode */
140                 int i, blink = dt >> 8;
141
142                 if(blink > 6) {
143                         erase_completed();
144                         num_complines = 0;
145                         return 0;
146                 }
147
148                 for(i=0; i<num_complines; i++) {
149                         draw_line(complines[i], blink & 1);
150                 }
151                 fflush(stdout);
152                 return BLINK_UPD_RATE;
153         }
154
155
156         /* fall */
157         while(dt >= tick_interval) {
158                 if(cur_piece >= 0) {
159                         next_pos[0] = pos[0] + 1;
160                         if(collision(cur_piece, next_pos)) {
161                                 next_pos[0] = pos[0];
162                                 stick(cur_piece, next_pos);
163                                 cur_piece = -1;
164                                 return 0;
165                         }
166                 } else {
167                         /* respawn */
168                         cur_piece = rand() % NUM_PIECES;
169                         prev_rot = cur_rot = 0;
170                         pos[0] = piece_spawnpos[cur_piece][0];
171                         next_pos[0] = pos[0] + 1;
172                         pos[1] = next_pos[1] = PF_COLS / 2 + piece_spawnpos[cur_piece][1];
173                 }
174
175                 dt -= tick_interval;
176                 prev_tick = msec;
177         }
178
179         if(cur_piece >= 0 && (memcmp(pos, next_pos, sizeof pos) != 0 || cur_rot != prev_rot)) {
180                 draw_piece(cur_piece, pos, prev_rot, ERASE_PIECE);
181                 draw_piece(cur_piece, next_pos, cur_rot, DRAW_PIECE);
182                 memcpy(pos, next_pos, sizeof pos);
183                 prev_rot = cur_rot;
184         }
185
186         return tick_interval - dt;
187 }
188
189
190 #define C0      0x9b
191 #define SS3     0x8f
192
193 static void runesc(int csi, char *buf)
194 {
195         if(csi != C0) return;
196
197         if(buf[1] == 0) {
198                 switch(buf[0]) {
199                 case 'A':
200                         game_input('w');        /* up */
201                         break;
202                 case 'B':
203                         game_input('s');        /* down */
204                         break;
205                 case 'C':
206                         game_input('d');        /* right */
207                         break;
208                 case 'D':
209                         game_input('a');        /* left */
210                         break;
211                 default:
212                         break;
213                 }
214         }
215 }
216
217 void game_input(int c)
218 {
219         static int esc, csi;
220         static int esctop;
221         static char escbuf[64];
222
223         if(esc) {
224                 esc = 0;
225                 if(c == 27) {
226                         quit = 1;
227                         return;
228                 }
229
230                 switch(c) {
231                 case '[':
232                         csi = C0;
233                         return;
234                 case 'O':
235                         csi = SS3;
236                         return;
237                 default:
238                         break;
239                 }
240         }
241
242         if(csi) {
243                 if(c < 0x20 || c >= 0x80) {
244                         csi = 0;
245                         esctop = 0;
246                 }
247
248                 escbuf[esctop++] = c;
249
250                 if(c >= 0x40) {
251                         int prevcsi = csi;
252                         escbuf[esctop] = 0;
253                         csi = 0;
254                         esctop = 0;
255                         runesc(prevcsi, escbuf);
256                 }
257                 return;
258         }
259
260         switch(c) {
261         case 27:
262                 esc = 1;
263                 break;
264
265         case C0:
266                 esc = 1;
267                 csi = C0;
268                 break;
269
270         case 'q':
271                 quit = 1;
272                 break;
273
274         case 'a':
275                 next_pos[1] = pos[1] - 1;
276                 if(collision(cur_piece, next_pos)) {
277                         next_pos[1] = pos[1];
278                 }
279                 break;
280
281         case 'd':
282                 next_pos[1] = pos[1] + 1;
283                 if(collision(cur_piece, next_pos)) {
284                         next_pos[1] = pos[1];
285                 }
286                 break;
287
288         case 'w':
289         case ' ':
290                 prev_rot = cur_rot;
291                 cur_rot = (cur_rot + 1) & 3;
292                 if(collision(cur_piece, next_pos)) {
293                         cur_rot = prev_rot;
294                 }
295                 break;
296
297         case 's':
298                 next_pos[0] = pos[0] + 1;
299                 if(collision(cur_piece, next_pos)) {
300                         next_pos[0] = pos[0];
301                 }
302                 break;
303
304         default:
305                 fprintf(stderr, "unhandled input: %x\n", c);
306                 break;
307         }
308 }
309
310 static int collision(int piece, const int *pos)
311 {
312         int i;
313         unsigned char *p = pieces[piece][cur_rot];
314
315         for(i=0; i<4; i++) {
316                 int x = PF_XOFFS + pos[1] + BLKX(*p);
317                 int y = PF_YOFFS + pos[0] + BLKY(*p);
318                 p++;
319
320                 if(y < 0) continue;
321
322                 if(scr[y * SCR_COLS + x] != TILE_PF) return 1;
323         }
324
325         return 0;
326 }
327
328 static void stick(int piece, const int *pos)
329 {
330         int i, j, nblank;
331         int *pfline;
332         unsigned char *p = pieces[piece][cur_rot];
333
334         for(i=0; i<4; i++) {
335                 int x = pos[1] + BLKX(*p);
336                 int y = pos[0] + BLKY(*p);
337                 p++;
338
339                 pfline = scr + (y + PF_YOFFS) * SCR_COLS + PF_XOFFS;
340                 pfline[x] = piece + FIRST_PIECE_TILE;
341
342                 nblank = 0;
343                 for(j=0; j<PF_COLS; j++) {
344                         if(pfline[j] == TILE_PF) {
345                                 nblank++;
346                         }
347                 }
348
349                 if(nblank == 0) {
350                         complines[num_complines++] = y;
351                 }
352         }
353
354         if(use_bell) {
355                 putchar('\a');
356                 fflush(stdout);
357         }
358 }
359
360 static void erase_completed(void)
361 {
362         int i, j, srow, drow;
363         int *pfstart = scr + PF_YOFFS * SCR_COLS + PF_XOFFS;
364
365         /* sort completed lines from highest to lowest row number */
366         for(i=0; i<num_complines-1; i++) {
367                 for(j=i+1; j<num_complines; j++) {
368                         if(complines[j] > complines[i]) {
369                                 int tmp = complines[j];
370                                 complines[j] = complines[i];
371                                 complines[i] = tmp;
372                         }
373                 }
374         }
375
376         srow = drow = PF_ROWS - 1;
377
378         for(i=0; i<PF_ROWS; i++) {
379                 for(j=0; j<num_complines; j++) {
380                         if(complines[j] == srow) {
381                                 srow--;
382                         }
383                 }
384
385                 if(srow < 0) break;
386
387                 if(srow != drow) {
388                         int *sptr = pfstart + srow * SCR_COLS;
389                         int *dptr = pfstart + drow * SCR_COLS;
390                         memcpy(dptr, sptr, PF_COLS * sizeof *dptr);
391                 }
392
393                 srow--;
394                 drow--;
395         }
396
397         drawpf();
398         fflush(stdout);
399 }
400
401 static void draw_piece(int piece, const int *pos, int rot, int mode)
402 {
403         int i;
404         int tile = mode == ERASE_PIECE ? TILE_PF : FIRST_PIECE_TILE + piece;
405         unsigned char *p = pieces[piece][rot];
406
407         for(i=0; i<4; i++) {
408                 int x = PF_XOFFS + pos[1] + BLKX(*p);
409                 int y = PF_YOFFS + pos[0] + BLKY(*p);
410                 p++;
411
412                 if(y < 0) continue;
413
414                 ansi_setcursor(y, x * 2);
415                 wrtile(tile);
416         }
417         fflush(stdout);
418 }
419
420 static void drawbg(void)
421 {
422         int i, j;
423         int *sptr = scr;
424
425         for(i=0; i<SCR_ROWS; i++) {
426                 ansi_setcursor(i, 0);
427                 for(j=0; j<SCR_COLS; j++) {
428                         wrtile(*sptr++);
429                 }
430         }
431 }
432
433 static void drawpf(void)
434 {
435         int i, j;
436         int *sptr = scr + PF_YOFFS * SCR_COLS + PF_XOFFS;
437
438         for(i=0; i<PF_ROWS; i++) {
439                 ansi_setcursor(i, PF_XOFFS * 2);
440                 for(j=0; j<PF_COLS; j++) {
441                         wrtile(sptr[j]);
442                 }
443                 sptr += SCR_COLS;
444         }
445 }
446
447 static void draw_line(int row, int blink)
448 {
449         int i;
450
451         ansi_setcursor(row, PF_XOFFS * 2);
452
453         if(blink) {
454                 int *sptr = scr + (row + PF_YOFFS) * SCR_COLS + PF_XOFFS;
455
456                 for(i=0; i<PF_COLS; i++) {
457                         wrtile(*sptr++);
458                 }
459         } else {
460                 for(i=0; i<PF_COLS; i++) {
461                         wrtile(TILE_PF);
462                 }
463         }
464 }
465
466 static void wrtile(int tileid)
467 {
468         int i;
469
470         for(i=0; i<2; i++) {
471                 uint16_t c = tiles[tileid][i];
472                 unsigned char cc = c & 0xff;
473                 unsigned char ca = c >> 8;
474
475                 ansi_ibmchar(cc, ca);
476         }
477 }