Thumbnail

rani/games.git

Clone URL: https://git.buni.party/rani/games.git

Viewing file on branch master

1#include <time.h>
2#include <stdlib.h>
3#include <ncurses.h>
4#include <stdbool.h>
5#include <sys/time.h>
6
7
8/* BEGIN CONFIG */
9#ifndef X
10#define X 10
11#endif /* X */
12
13#ifndef Y
14#define Y 20
15#endif /* Y */
16
17#ifndef HIGHLIGHT
18#define HIGHLIGHT false
19#endif /* HIGHLIGHT */
20
21#ifndef SEED
22#define SEED time(NULL)
23#endif /* SEED */
24
25#ifndef GRAVITY
26#define GRAVITY 750
27#endif /* GRAVITY */
28/* END CONFIG */
29
30#define TSTOMS(TS) ((TS.tv_sec * 1000000 + TS.tv_nsec / 1000) / 1000)
31
32
33struct coord {
34 int x, y;
35};
36
37
38enum tetromino {
39 EMPTY,
40 TET_Q, /* SQUARE */
41 TET_I,
42 TET_L,
43 TET_J,
44 TET_S,
45 TET_Z,
46 TET_T,
47 LAST,
48};
49
50
51enum color {
52 RED = 1,
53 GREEN,
54 YELLOW,
55 BLUE,
56 MAGENTA,
57 CYAN,
58 WHITE
59};
60
61
62enum state {
63 RUNNING,
64 GAME_OVER,
65 QUIT,
66};
67
68
69/* (0,0) is the center of the top row */
70struct coord tetrominos[][4] = {
71 [TET_Q] = {{0, 0}, {1, 0}, {0, 1}, {1, 1}},
72 [TET_I] = {{0, 1}, {0, 0}, {0, 2}, {0, 3}},
73 [TET_L] = {{0, 1}, {0, 0}, {0, 2}, {1, 2}},
74 [TET_J] = {{0, 1}, {0, 0}, {0, 2}, {-1,2}},
75 [TET_S] = {{0, 0}, {1, 0}, {0, 1}, {-1,1}},
76 [TET_Z] = {{0, 0}, {-1,0}, {0, 1}, {1, 1}},
77 [TET_T] = {{0, 0}, {-1,0}, {1, 0}, {0, 1}},
78};
79
80
81bool valid(int x, int y) {
82 return x >= 0 && y >= 0 && x < X && y < Y;
83}
84
85
86void cptogrid(struct coord tet[4], enum tetromino t, enum tetromino grid[X][Y]) {
87 for (int i = 0; i < 4; i++) {
88 int x = tet[i].x;
89 int y = tet[i].y;
90 grid[x][y] = t;
91 }
92}
93
94
95bool down1(struct coord tet[4], enum tetromino t, enum tetromino grid[X][Y]) {
96 bool active = true;
97
98 for (int i = 0; i < 4; i++) {
99 int nx = tet[i].x;
100 int ny = tet[i].y + 1;
101 if (!valid(nx, ny) || grid[nx][ny]) {
102 cptogrid(tet, t, grid);
103 active = false;
104 break;
105 }
106 }
107 if (active) for (int i = 0; i < 4; i++) tet[i].y++;
108
109 return active;
110}
111
112
113int nlen(int n) {
114 int l = 1;
115 while ((n /= 10) != 0) l++;
116 return l;
117}
118
119
120void draw_grid(enum tetromino grid[X][Y], int level, int score) {
121 erase();
122 for (int x = 0; x < X; x++) addstr("++++");
123 addstr("+\n");
124 for (int y = 0; y < Y; y++) {
125 addch('+');
126 for (int x = 0; x < X; x++) {
127 addch(' ');
128 int cp = COLOR_PAIR((enum color)grid[x][y]);
129 if (grid[x][y] && HIGHLIGHT) cp |= A_REVERSE;
130 attron(cp);
131 if (!HIGHLIGHT) addch(grid[x][y] ? '#' : ' ');
132 else addch(' ');
133 attroff(cp);
134 addch(' ');
135 if (x < X - 1) addch('.');
136 }
137 addstr("+\n");
138 }
139 for (int x = 0; x < X; x++) addstr("++++");
140 addch('+');
141
142 mvprintw(0, 0, "Level: %d", level);
143 mvprintw(0, X * 4 + 1 - 7 - nlen(score), "Score: %d", score);
144}
145
146
147int main(void) {
148 srand(SEED);
149
150 initscr();
151 noecho();
152 curs_set(0);
153 keypad(stdscr, TRUE);
154 timeout(0);
155
156 use_default_colors();
157 start_color();
158 init_pair(RED, COLOR_RED, -1);
159 init_pair(GREEN, COLOR_GREEN, -1);
160 init_pair(YELLOW, COLOR_YELLOW, -1);
161 init_pair(BLUE, COLOR_BLUE, -1);
162 init_pair(MAGENTA, COLOR_MAGENTA, -1);
163 init_pair(CYAN, COLOR_CYAN, -1);
164 init_pair(WHITE, COLOR_WHITE, -1);
165
166 while (true) {
167 enum tetromino grid[X][Y] = {0};
168
169 int level = 0;
170 int score = 0;
171 int total_cleared = 0;
172
173 bool active = false;
174 struct coord cur_tet[4];
175 enum tetromino tet_type;
176
177 int reftime = 0;
178 {
179 struct timespec ts;
180 clock_gettime(CLOCK_MONOTONIC, &ts);
181 reftime = TSTOMS(ts);
182 }
183
184 enum state done = RUNNING;
185 while (!done) {
186 if (!active) {
187 int t = rand() % ((LAST-1) - (EMPTY+1) + 1) + (EMPTY+1);
188 tet_type = t;
189 for (int i = 0; i < 4; i++) {
190 int y = tetrominos[t][i].y;
191 int x = tetrominos[t][i].x + X / 2;
192 if (grid[x][y]) {
193 done = GAME_OVER;
194 goto finished;
195 }
196 cur_tet[i].x = x;
197 cur_tet[i].y = y;
198 }
199 active = true;
200 }
201
202 draw_grid(grid, level, score);
203
204 // draw current tetromino
205 int cp = COLOR_PAIR((enum color)tet_type);
206 if (HIGHLIGHT) cp |= A_REVERSE;
207 attron(cp);
208 for (int i = 0; i < 4; i++) {
209 int x = cur_tet[i].x * 4 + 2;
210 int y = cur_tet[i].y + 1;
211 if (!HIGHLIGHT) mvaddch(y, x, '@');
212 else mvaddch(y, x, ' ');
213 }
214 attroff(cp);
215
216 // draw tetromino shadow
217 int x_lo = X;
218 int x_hi = -1;
219 for (int i = 0; i < 4; i++) {
220 int x = cur_tet[i].x;
221 if (x > x_hi) x_hi = x;
222 if (x < x_lo) x_lo = x;
223 }
224
225 x_lo = x_lo * 4 + 2;
226 x_hi = x_hi * 4 + 2;
227
228 for (int i = x_lo; i <= x_hi; i++) {
229 int y = Y + 1;
230 mvaddch(y, i, '_');
231 }
232
233 refresh();
234
235 switch (getch()) {
236 case KEY_DOWN:
237 active = down1(cur_tet, tet_type, grid);
238 if (active) score++;
239 break;
240 case KEY_LEFT:
241 {
242 bool can_move = true;
243 for (int i = 0; i < 4; i++) {
244 int x = cur_tet[i].x - 1;
245 int y = cur_tet[i].y;
246 if (!valid(x, y) || grid[x][y]) {
247 can_move = false;
248 break;
249 }
250 }
251 if (!can_move) break;
252
253 for (int i = 0; i < 4; i++) cur_tet[i].x--;
254 }
255 break;
256 case KEY_RIGHT:
257 {
258 bool can_move = true;
259 for (int i = 0; i < 4; i++) {
260 int x = cur_tet[i].x + 1;
261 int y = cur_tet[i].y;
262 if (!valid(x, y) || grid[x][y]) {
263 can_move = false;
264 break;
265 }
266 }
267 if (!can_move) break;
268
269 for (int i = 0; i < 4; i++) cur_tet[i].x++;
270 }
271 break;
272 case KEY_UP:
273 // squares don't really rotate
274 if (tet_type == TET_Q) break;
275 /* x' = y_c - y + x_c
276 * y' = x - x_c + y_c
277 */
278 bool can_rotate = true;
279 struct coord ncoords[4];
280 int x_c = cur_tet[0].x;
281 int y_c = cur_tet[0].y;
282
283 for (int i = 0; i < 4; i++) {
284 int x = y_c - cur_tet[i].y + x_c;
285 int y = cur_tet[i].x - x_c + y_c;
286 ncoords[i] = (struct coord){x, y};
287
288 if (!valid(ncoords[i].x, ncoords[i].y) || grid[x][y]) {
289 can_rotate = false;
290 break;
291 }
292 }
293 if (!can_rotate) break;
294
295 for (int i = 0; i < 4; i++) {
296 cur_tet[i] = ncoords[i];
297 }
298 break;
299 case ' ':
300 while ((active = down1(cur_tet, tet_type, grid)))
301 score++;
302 break;
303 case 'q':
304 done = QUIT;
305 goto finished;
306 case ERR:
307 deafult: break;
308 }
309
310 struct timespec ts;
311 clock_gettime(CLOCK_MONOTONIC, &ts);
312 int dt = TSTOMS(ts) - reftime;
313 if (dt >= GRAVITY - ((GRAVITY/10) * level)) {
314 reftime = TSTOMS(ts);
315 active = down1(cur_tet, tet_type, grid);
316 }
317
318 if (!active) {
319 int first_row = -1;
320
321 int rows_cleared = 0;
322 for (int y = Y - 1; y >= 0; y--) {
323 bool full_row = true;
324 for (int x = 0; x < X; x++) {
325 if (!grid[x][y]) {
326 full_row = false;
327 break;
328 }
329 }
330
331 if (full_row) {
332 draw_grid(grid, level, score);
333
334 if (first_row < 0) first_row = y;
335
336 attron(COLOR_PAIR(GREEN) | A_REVERSE);
337 for (int x = 1; x < 4 * X; x++) {
338 mvaddch(y + 1, x, '#');
339 }
340 attroff(COLOR_PAIR(GREEN) | A_REVERSE);
341
342 for (int x = 0; x < X; x++) {
343 grid[x][y] = 0;
344 }
345
346 refresh();
347 napms(1000);
348 rows_cleared++;
349 total_cleared++;
350
351 if (total_cleared >= 10) {
352 level++;
353 total_cleared -= 10;
354 }
355 }
356 }
357
358 if (rows_cleared > 0) {
359 for (int y = first_row - rows_cleared; y >= 0; y--) {
360 for (int x = 0; x < X; x++) {
361 grid[x][y + rows_cleared] = grid[x][y];
362 }
363 }
364 }
365
366 switch (rows_cleared) {
367 case 1:
368 score += 40 * (level + 1);
369 break;
370 case 2:
371 score += 100 * (level + 1);
372 break;
373 case 3:
374 score += 300 * (level + 1);
375 break;
376 case 4:
377 score += 1200 * (level + 1);
378 break;
379 }
380 }
381 }
382
383 finished:
384 switch (done) {
385 case GAME_OVER:
386 move((Y + 1) / 2, (X * 4 + 2) / 2 - 5);
387 if (!HIGHLIGHT) attron(A_REVERSE);
388 addstr("Game over");
389 move((Y + 1) / 2 + 1, (X * 4 + 2) / 2 - 13);
390 addstr("Press any key to continue");
391 if (!HIGHLIGHT) attroff(A_REVERSE);
392 refresh();
393 timeout(-1);
394 getch();
395 timeout(0);
396 break;
397 case QUIT:
398 goto terminate;
399 }
400 }
401
402 terminate:
403 keypad(stdscr, FALSE);
404 curs_set(1);
405 echo();
406 endwin();
407}
408