Created
September 6, 2019 10:18
-
-
Save tiqwab/17a217881ac9adbc9af69478149c1c7a to your computer and use it in GitHub Desktop.
Tetris in C
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <stdbool.h> | |
#include <curses.h> | |
#include <locale.h> | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include <time.h> | |
#include <unistd.h> | |
#define STAGE_ROW 20 | |
#define STAGE_COL 10 | |
/* | |
* Macros for stage | |
*/ | |
#define IS_EMPTY(s, y, x) (s[y][x] == 0) | |
#define IS_BLOCK(s, y, x) (s[y][x] == 1) | |
#define IS_FILLED(s, y, x) (s[y][x] != 0) | |
#define SET_EMPTY(s, y, x) (s[y][x] = 0) | |
#define SET_BLOCK(s, y, x) (s[y][x] = 1) | |
// ref. https://ja.wikipedia.org/wiki/%E3%83%86%E3%83%88%E3%83%AA%E3%82%B9 | |
#define block_type int | |
#define BLOCK_I 0 | |
#define BLOCK_O 1 | |
#define BLOCK_S 2 | |
#define BLOCK_Z 3 | |
#define BLOCK_J 4 | |
#define BLOCK_L 5 | |
#define BLOCK_T 6 | |
#define BLOCK_KINDS 7 | |
#define DROP_COUNT 3 | |
#define FRAME 10 | |
typedef int stage[STAGE_ROW][STAGE_COL]; | |
struct block { | |
int b_base_x; | |
int b_base_y; | |
int b_type; | |
int b_size; // fixed as 4 | |
int b_rotate; // 0 -> 1 -> 2 -> 3 -> 0 -> ... (if right rotation) | |
int b_area[4][4]; // 0 if empty, 1 if block exists | |
}; | |
int display_h, display_w; | |
static void teardown() { | |
endwin(); | |
} | |
static void display(stage stg, struct block *blk) { | |
int start_x, start_y, i, j; | |
/* | |
* Display outline | |
*/ | |
start_x = (display_w - STAGE_COL) / 2; | |
start_y = (display_h - STAGE_ROW) / 2; | |
mvaddch(start_y, start_x, '+'); | |
mvaddch(start_y, start_x + STAGE_COL + 1, '+'); | |
mvaddch(start_y + STAGE_ROW + 1, start_x, '+'); | |
mvaddch(start_y + STAGE_ROW + 1, start_x + STAGE_COL + 1, '+'); | |
for (i = 0; i < STAGE_COL; i++) { | |
mvaddch(start_y, start_x + 1 + i, '-'); | |
mvaddch(start_y + STAGE_ROW + 1, start_x + 1 + i, '-'); | |
} | |
for (i = 0; i < STAGE_ROW; i++) { | |
mvaddch(start_y + 1 + i, start_x, '|'); | |
mvaddch(start_y + 1 + i, start_x + STAGE_COL + 1, '|'); | |
} | |
/* | |
* Display stage | |
*/ | |
for (i = 0; i < STAGE_ROW; i++) { | |
for (j = 0; j < STAGE_COL; j++) { | |
if (IS_FILLED(stg, i, j)) { | |
mvaddch(start_y + 1 + i, start_x + 1 + j, 'x'); | |
} | |
} | |
} | |
/* | |
* Display player | |
*/ | |
for (i = 0; i < blk->b_size; i++) { | |
for (j = 0; j < blk->b_size; j++) { | |
if (blk->b_area[i][j]) { | |
int cur_y = blk->b_base_y + i; | |
int cur_x = blk->b_base_x + j; | |
mvaddch(start_y + 1 + cur_y, start_x + 1 + cur_x, 'x'); | |
} | |
} | |
} | |
} | |
static bool is_movable(stage stg, struct block *blk, int dir_x, int dir_y) { | |
int i, j; | |
for (i = blk->b_size-1; i >= 0; i--) { | |
for (j = blk->b_size-1; j >= 0; j--) { | |
if (blk->b_area[i][j]) { | |
int cur_y = blk->b_base_y + i; | |
int cur_x = blk->b_base_x + j; | |
if (cur_y + dir_y < 0 || cur_y + dir_y >= STAGE_ROW) { | |
return false; | |
} | |
if (cur_x + dir_x < 0 || cur_x + dir_x >= STAGE_COL) { | |
return false; | |
} | |
if (IS_BLOCK(stg, cur_y + dir_y, cur_x + dir_x)) { | |
return false; | |
} | |
} | |
} | |
} | |
return true; | |
} | |
static bool is_piled(stage stg, struct block *blk) { | |
return !is_movable(stg, blk, 0, 1); | |
} | |
/* | |
* Precondition: is_movable returns true. | |
*/ | |
static void move_player(struct block *blk, int dir_x, int dir_y) { | |
if (dir_x > 0) { | |
blk->b_base_x++; | |
} else if (dir_x < 0) { | |
blk->b_base_x--; | |
} | |
if (dir_y > 0) { | |
blk->b_base_y++; | |
} else if (dir_y < 0) { | |
blk->b_base_y--; | |
} | |
} | |
/* | |
* Return 0 if success, -1 if fail | |
*/ | |
static int generate_block(struct block *blk, bool should_init) { | |
static int blocks[BLOCK_KINDS] = { | |
BLOCK_I, | |
BLOCK_O, | |
BLOCK_S, | |
BLOCK_Z, | |
BLOCK_J, | |
BLOCK_L, | |
BLOCK_T | |
}; | |
static int blocks_idx = 0; | |
if (blocks_idx >= BLOCK_KINDS || should_init) { | |
for (int i = BLOCK_KINDS-1; i > 0; i--) { | |
int x = (int) (rand() * (i + 1.0) / (1.0 + RAND_MAX)); // random number from 0 to i | |
int tmp = blocks[i]; | |
blocks[i] = blocks[x]; | |
blocks[x] = tmp; | |
} | |
blocks_idx = 0; | |
} | |
block_type typ = blocks[blocks_idx++]; | |
memset(blk->b_area, 0, sizeof(blk->b_area)); | |
switch (typ) { | |
case BLOCK_I: | |
blk->b_type = typ; | |
blk->b_size = 4; | |
blk->b_base_x = (STAGE_COL - blk->b_size) / 2; | |
blk->b_base_y = -1; // to make a block appear at the top of stage | |
blk->b_rotate = 0; | |
blk->b_area[1][0] = 1; | |
blk->b_area[1][1] = 1; | |
blk->b_area[1][2] = 1; | |
blk->b_area[1][3] = 1; | |
break; | |
case BLOCK_O: | |
blk->b_type = typ; | |
blk->b_size = 2; | |
blk->b_base_x = (STAGE_COL - blk->b_size) / 2; | |
blk->b_base_y = 0; | |
blk->b_rotate = 0; | |
blk->b_area[0][0] = 1; | |
blk->b_area[0][1] = 1; | |
blk->b_area[1][0] = 1; | |
blk->b_area[1][1] = 1; | |
break; | |
case BLOCK_S: | |
blk->b_type = typ; | |
blk->b_size = 3; | |
blk->b_base_x = (STAGE_COL - blk->b_size) / 2; | |
blk->b_base_y = 0; | |
blk->b_rotate = 0; | |
blk->b_area[0][1] = 1; | |
blk->b_area[0][2] = 1; | |
blk->b_area[1][0] = 1; | |
blk->b_area[1][1] = 1; | |
break; | |
case BLOCK_Z: | |
blk->b_type = typ; | |
blk->b_size = 3; | |
blk->b_base_x = (STAGE_COL - blk->b_size) / 2; | |
blk->b_base_y = 0; | |
blk->b_rotate = 0; | |
blk->b_area[0][0] = 1; | |
blk->b_area[0][1] = 1; | |
blk->b_area[1][1] = 1; | |
blk->b_area[1][2] = 1; | |
break; | |
case BLOCK_J: | |
blk->b_type = typ; | |
blk->b_size = 3; | |
blk->b_base_x = (STAGE_COL - blk->b_size) / 2; | |
blk->b_base_y = 0; | |
blk->b_rotate = 0; | |
blk->b_area[0][0] = 1; | |
blk->b_area[1][0] = 1; | |
blk->b_area[1][1] = 1; | |
blk->b_area[1][2] = 1; | |
break; | |
case BLOCK_L: | |
blk->b_type = typ; | |
blk->b_size = 3; | |
blk->b_base_x = (STAGE_COL - blk->b_size) / 2; | |
blk->b_base_y = 0; | |
blk->b_rotate = 0; | |
blk->b_area[0][2] = 1; | |
blk->b_area[1][0] = 1; | |
blk->b_area[1][1] = 1; | |
blk->b_area[1][2] = 1; | |
break; | |
case BLOCK_T: | |
blk->b_type = typ; | |
blk->b_size = 3; | |
blk->b_base_x = (STAGE_COL - blk->b_size) / 2; | |
blk->b_base_y = 0; | |
blk->b_rotate = 0; | |
blk->b_area[0][1] = 1; | |
blk->b_area[1][0] = 1; | |
blk->b_area[1][1] = 1; | |
blk->b_area[1][2] = 1; | |
break; | |
default: | |
return -1; | |
} | |
return 0; | |
} | |
/* | |
* Return 0 if a block can rotate, -1 if not. | |
*/ | |
static int rotate_block_r(stage stg, struct block *blk) { | |
int i, j; | |
struct block rotated; | |
rotated = *blk; | |
memcpy(&rotated, blk, sizeof(struct block)); | |
memset(&rotated.b_area, 0, sizeof(rotated.b_area)); | |
// ref. http://bamboo-satellites.com/as/tetris/tetris05.html | |
while (rotated.b_base_x + rotated.b_size > STAGE_COL) { | |
rotated.b_base_x--; | |
} | |
while (rotated.b_base_x < 0) { | |
rotated.b_base_x++; | |
} | |
switch (blk->b_type) { | |
case BLOCK_I: | |
switch (blk->b_rotate) { | |
case 0: | |
rotated.b_area[0][2] = 1; | |
rotated.b_area[1][2] = 1; | |
rotated.b_area[2][2] = 1; | |
rotated.b_area[3][2] = 1; | |
rotated.b_rotate = 1; | |
break; | |
case 1: | |
rotated.b_area[2][0] = 1; | |
rotated.b_area[2][1] = 1; | |
rotated.b_area[2][2] = 1; | |
rotated.b_area[2][3] = 1; | |
rotated.b_rotate = 2; | |
break; | |
case 2: | |
rotated.b_area[0][1] = 1; | |
rotated.b_area[1][1] = 1; | |
rotated.b_area[2][1] = 1; | |
rotated.b_area[3][1] = 1; | |
rotated.b_rotate = 3; | |
break; | |
case 3: | |
rotated.b_area[1][0] = 1; | |
rotated.b_area[1][1] = 1; | |
rotated.b_area[1][2] = 1; | |
rotated.b_area[1][3] = 1; | |
rotated.b_rotate = 0; | |
break; | |
} | |
break; | |
case BLOCK_O: | |
// actually do nothing | |
rotated.b_area[0][0] = 1; | |
rotated.b_area[0][1] = 1; | |
rotated.b_area[1][0] = 1; | |
rotated.b_area[1][1] = 1; | |
switch (blk->b_rotate) { | |
case 0: | |
rotated.b_rotate = 1; | |
break; | |
case 1: | |
rotated.b_rotate = 2; | |
break; | |
case 2: | |
rotated.b_rotate = 3; | |
break; | |
case 3: | |
rotated.b_rotate = 0; | |
break; | |
} | |
break; | |
case BLOCK_S: | |
switch (blk->b_rotate) { | |
case 0: | |
rotated.b_area[0][1] = 1; | |
rotated.b_area[1][1] = 1; | |
rotated.b_area[1][2] = 1; | |
rotated.b_area[2][2] = 1; | |
rotated.b_rotate = 1; | |
break; | |
case 1: | |
rotated.b_area[1][1] = 1; | |
rotated.b_area[1][2] = 1; | |
rotated.b_area[2][0] = 1; | |
rotated.b_area[2][1] = 1; | |
rotated.b_rotate = 2; | |
break; | |
case 2: | |
rotated.b_area[0][0] = 1; | |
rotated.b_area[1][0] = 1; | |
rotated.b_area[1][1] = 1; | |
rotated.b_area[2][1] = 1; | |
rotated.b_rotate = 3; | |
break; | |
case 3: | |
rotated.b_area[0][1] = 1; | |
rotated.b_area[0][2] = 1; | |
rotated.b_area[1][0] = 1; | |
rotated.b_area[1][1] = 1; | |
rotated.b_rotate = 0; | |
break; | |
} | |
break; | |
case BLOCK_Z: | |
switch (blk->b_rotate) { | |
case 0: | |
rotated.b_area[0][2] = 1; | |
rotated.b_area[1][1] = 1; | |
rotated.b_area[1][2] = 1; | |
rotated.b_area[2][1] = 1; | |
rotated.b_rotate = 1; | |
break; | |
case 1: | |
rotated.b_area[1][0] = 1; | |
rotated.b_area[1][1] = 1; | |
rotated.b_area[2][1] = 1; | |
rotated.b_area[2][2] = 1; | |
rotated.b_rotate = 2; | |
break; | |
case 2: | |
rotated.b_area[0][1] = 1; | |
rotated.b_area[1][0] = 1; | |
rotated.b_area[1][1] = 1; | |
rotated.b_area[2][0] = 1; | |
rotated.b_rotate = 3; | |
break; | |
case 3: | |
rotated.b_area[0][0] = 1; | |
rotated.b_area[0][1] = 1; | |
rotated.b_area[1][1] = 1; | |
rotated.b_area[1][2] = 1; | |
rotated.b_rotate = 0; | |
break; | |
} | |
break; | |
case BLOCK_J: | |
switch (blk->b_rotate) { | |
case 0: | |
rotated.b_area[0][1] = 1; | |
rotated.b_area[0][2] = 1; | |
rotated.b_area[1][1] = 1; | |
rotated.b_area[2][1] = 1; | |
rotated.b_rotate = 1; | |
break; | |
case 1: | |
rotated.b_area[1][0] = 1; | |
rotated.b_area[1][1] = 1; | |
rotated.b_area[1][2] = 1; | |
rotated.b_area[2][2] = 1; | |
rotated.b_rotate = 2; | |
break; | |
case 2: | |
rotated.b_area[0][1] = 1; | |
rotated.b_area[1][1] = 1; | |
rotated.b_area[2][0] = 1; | |
rotated.b_area[2][1] = 1; | |
rotated.b_rotate = 3; | |
break; | |
case 3: | |
rotated.b_area[0][0] = 1; | |
rotated.b_area[1][0] = 1; | |
rotated.b_area[1][1] = 1; | |
rotated.b_area[1][2] = 1; | |
rotated.b_rotate = 0; | |
break; | |
} | |
break; | |
case BLOCK_L: | |
switch (blk->b_rotate) { | |
case 0: | |
rotated.b_area[0][1] = 1; | |
rotated.b_area[1][1] = 1; | |
rotated.b_area[2][1] = 1; | |
rotated.b_area[2][2] = 1; | |
rotated.b_rotate = 1; | |
break; | |
case 1: | |
rotated.b_area[1][0] = 1; | |
rotated.b_area[1][1] = 1; | |
rotated.b_area[1][2] = 1; | |
rotated.b_area[2][0] = 1; | |
rotated.b_rotate = 2; | |
break; | |
case 2: | |
rotated.b_area[0][0] = 1; | |
rotated.b_area[0][1] = 1; | |
rotated.b_area[1][1] = 1; | |
rotated.b_area[2][1] = 1; | |
rotated.b_rotate = 3; | |
break; | |
case 3: | |
rotated.b_area[0][2] = 1; | |
rotated.b_area[1][0] = 1; | |
rotated.b_area[1][1] = 1; | |
rotated.b_area[1][2] = 1; | |
rotated.b_rotate = 0; | |
break; | |
} | |
break; | |
case BLOCK_T: | |
switch (blk->b_rotate) { | |
case 0: | |
rotated.b_area[0][1] = 1; | |
rotated.b_area[1][1] = 1; | |
rotated.b_area[1][2] = 1; | |
rotated.b_area[2][1] = 1; | |
rotated.b_rotate = 1; | |
break; | |
case 1: | |
rotated.b_area[1][0] = 1; | |
rotated.b_area[1][1] = 1; | |
rotated.b_area[1][2] = 1; | |
rotated.b_area[2][1] = 1; | |
rotated.b_rotate = 2; | |
break; | |
case 2: | |
rotated.b_area[0][1] = 1; | |
rotated.b_area[1][0] = 1; | |
rotated.b_area[1][1] = 1; | |
rotated.b_area[2][1] = 1; | |
rotated.b_rotate = 3; | |
break; | |
case 3: | |
rotated.b_area[0][1] = 1; | |
rotated.b_area[1][0] = 1; | |
rotated.b_area[1][1] = 1; | |
rotated.b_area[1][2] = 1; | |
rotated.b_rotate = 0; | |
break; | |
} | |
break; | |
} | |
for (i = 0; i < rotated.b_size; i++) { | |
for (j = 0; j < rotated.b_size; j++) { | |
if (rotated.b_area[i][j]) { | |
int cur_y = rotated.b_base_y + i; | |
int cur_x = rotated.b_base_x + j; | |
if (cur_y < 0 || cur_y >= STAGE_ROW) { | |
return -1; | |
} | |
if (cur_x < 0 || cur_x >= STAGE_COL) { | |
return -1; | |
} | |
if (IS_BLOCK(stg, cur_y, cur_x)) { | |
return -1; | |
} | |
} | |
} | |
} | |
memcpy(blk, &rotated, sizeof(struct block)); | |
return 0; | |
} | |
/* | |
* Return 0 if successfully, -1 if game over. | |
*/ | |
int pile_block(stage stg, struct block *blk) { | |
int i, j; | |
for (i = blk->b_size-1; i >= 0; i--) { | |
for (j = blk->b_size-1; j >= 0; j--) { | |
if (blk->b_area[i][j]) { | |
int cur_y = blk->b_base_y + i; | |
int cur_x = blk->b_base_x + j; | |
SET_BLOCK(stg, cur_y, cur_x); | |
} | |
} | |
} | |
// check rows are filled with block | |
for (i = STAGE_ROW - 1; i >= 0; i--) { | |
int isFilled = true; | |
for (j = 0; j < STAGE_COL; j++) { | |
if (IS_EMPTY(stg, i, j)) { | |
isFilled = false; | |
break; | |
} | |
} | |
if (isFilled) { | |
memmove(stg[1], stg, i * sizeof(stg[0])); | |
memset(stg, 0, sizeof(stg[0])); | |
i++; | |
} | |
} | |
// check game over | |
for (i = 0; i < STAGE_COL; i++) { | |
if (IS_BLOCK(stg, 0, i)) { | |
return -1; | |
} | |
} | |
generate_block(blk, false); | |
return 0; | |
} | |
int main(int argc, char *argv[]) { | |
// for ncurses | |
setlocale(LC_ALL, ""); | |
initscr(); // man 3 curs_initscr | |
cbreak(); | |
noecho(); | |
nonl(); | |
// stdscr is WINDOWS * (which is the same pointer initscr returned) | |
intrflush(stdscr, FALSE); | |
keypad(stdscr, TRUE); | |
curs_set(0); | |
getmaxyx(stdscr, display_h, display_w); | |
if (display_h < STAGE_ROW || display_w < STAGE_COL) { | |
fprintf(stderr, "too small screen\n"); | |
teardown(); | |
exit(EXIT_FAILURE); | |
} | |
srand((unsigned int) time(NULL)); | |
int key, drop_count; | |
struct block blk; | |
stage stg; | |
start: | |
timeout(0); | |
// initialize | |
memset(stg, 0, sizeof(stage)); | |
generate_block(&blk, true); | |
drop_count = 0; | |
for (;;) { | |
drop_count++; | |
if (drop_count++ >= DROP_COUNT) { | |
drop_count = 0; | |
if (is_piled(stg, &blk)) { | |
if (pile_block(stg, &blk) < 0) { | |
goto gameover; | |
} | |
} else { | |
move_player(&blk, 0, 1); | |
} | |
} | |
key = getch(); | |
switch (key) { | |
case KEY_LEFT: | |
if (is_movable(stg, &blk, -1, 0)) { | |
move_player(&blk, -1, 0); | |
} | |
break; | |
case KEY_RIGHT: | |
if (is_movable(stg, &blk, 1, 0)) { | |
move_player(&blk, 1, 0); | |
} | |
break; | |
case KEY_UP: | |
rotate_block_r(stg, &blk); | |
break; | |
case KEY_DOWN: | |
while (!is_piled(stg, &blk)) { | |
move_player(&blk, 0, 1); | |
} | |
if (pile_block(stg, &blk) < 0) { | |
goto gameover; | |
} | |
break; | |
default: | |
break; | |
} | |
erase(); | |
display(stg, &blk); | |
mvprintw(0, 0, "(%d, %d, %d)", blk.b_base_x, blk.b_base_y, blk.b_rotate); | |
refresh(); | |
usleep((1000 / FRAME) * 1000); | |
} | |
gameover: | |
mvprintw(1, 0, "Game Over. Press 'r' to continue."); | |
refresh(); | |
timeout(-1); | |
while (1) { | |
key = getch(); | |
if (key == 'r') { | |
break; | |
} | |
} | |
erase(); | |
goto start; | |
teardown(); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment