Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@tiqwab
Created September 6, 2019 10:18
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tiqwab/17a217881ac9adbc9af69478149c1c7a to your computer and use it in GitHub Desktop.
Save tiqwab/17a217881ac9adbc9af69478149c1c7a to your computer and use it in GitHub Desktop.
Tetris in C
#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