Created
June 30, 2018 17:42
-
-
Save chebert/0443fa343804fe5cc203d35ddcbd1155 to your computer and use it in GitHub Desktop.
Tetris in C using NCURSES
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
/* | |
map <leader>g :!gcc % -o %:r -lncurses && ./%:r <CR> | |
gcc tetris.c -o tetris -lncurses | |
./tetris | |
*/ | |
#include <stdint.h> | |
#include <string.h> | |
#include <stdlib.h> | |
#include <stdio.h> | |
#include <ncurses.h> | |
#include <sys/time.h> | |
#define internal static | |
#define global_variable static | |
#define local_persist static | |
#define UNUSED(x) (void)(x); | |
#define MIN(x,y) ((x) < (y) ? (x) : (y)) | |
#define PRINT(fmt, ...)\ | |
printf(fmt "\n", ##__VA_ARGS__); | |
#define ARRAY_LENGTH(arr)\ | |
(sizeof(arr) / sizeof(arr[0])) | |
#define for_array(index, arr)\ | |
for (index; index < ARRAY_LENGTH(arr); ++index) | |
typedef int8_t int8; | |
typedef uint8_t uint8; | |
typedef int16_t int16; | |
typedef uint16_t uint16; | |
typedef int32_t int32; | |
typedef uint32_t uint32; | |
typedef int64_t int64; | |
typedef uint64_t uint64; | |
typedef float real32; | |
typedef double real64; | |
typedef int32_t bool32; | |
typedef char* c_str; | |
typedef struct { | |
int32 x, y; | |
} v2; | |
uint32 | |
time_in_us() { | |
struct timeval t; | |
gettimeofday(&t, NULL); | |
return t.tv_sec * 1000000 + t.tv_usec; | |
} | |
#define NUM_ROTATIONS 4 | |
#define NUM_BLOCKS 4 | |
typedef v2 shape_t[NUM_BLOCKS]; | |
typedef shape_t piece_t[NUM_ROTATIONS]; | |
typedef enum { | |
NO_TETR = -1, | |
T_TETR, | |
J_TETR, | |
L_TETR, | |
O_TETR, | |
S_TETR, | |
Z_TETR, | |
I_TETR, | |
NUM_TETRS | |
} tetr_t; | |
#define play_area_cols 10 | |
#define play_area_rows 22 | |
global_variable | |
tetr_t play_area[play_area_rows][play_area_cols] = { 0 }; | |
global_variable | |
char tetr_chars[NUM_TETRS] = "TJLOSZI"; | |
global_variable | |
bool32 has_color_terminal; | |
global_variable | |
piece_t pieces[NUM_TETRS] = { | |
/* T_TETR */ | |
{ | |
{ { 0, 1 }, { 1, 1 }, { 2, 1 }, { 1, 2 } }, | |
{ { 1, 0 }, { 0, 1 }, { 1, 1 }, { 1, 2 } }, | |
{ { 0, 1 }, { 1, 1 }, { 2, 1 }, { 1, 0 } }, | |
{ { 1, 0 }, { 2, 1 }, { 1, 1 }, { 1, 2 } } | |
}, | |
/*J_TETR*/ | |
{ | |
{ { 0, 1 }, { 1, 1 }, { 2, 1 }, { 2, 2 } }, | |
{ { 1, 0 }, { 1, 1 }, { 1, 2 }, { 0, 2 } }, | |
{ { 0, 1 }, { 1, 1 }, { 2, 1 }, { 0, 0 } }, | |
{ { 1, 0 }, { 1, 1 }, { 1, 2 }, { 2, 0 } }, | |
}, | |
/*L_TETR*/ | |
{ | |
{ { 0, 1 }, { 1, 1 }, { 2, 1 }, { 0, 2 } }, | |
{ { 1, 0 }, { 1, 1 }, { 1, 2 }, { 0, 0 } }, | |
{ { 0, 1 }, { 1, 1 }, { 2, 1 }, { 2, 0 } }, | |
{ { 1, 0 }, { 1, 1 }, { 1, 2 }, { 2, 2 } }, | |
}, | |
/*O_TETR*/ | |
{ | |
{ { 0, 0 }, { 0, 1 }, { 1, 0 }, { 1, 1 } }, | |
{ { 0, 0 }, { 0, 1 }, { 1, 0 }, { 1, 1 } }, | |
{ { 0, 0 }, { 0, 1 }, { 1, 0 }, { 1, 1 } }, | |
{ { 0, 0 }, { 0, 1 }, { 1, 0 }, { 1, 1 } }, | |
}, | |
/*S_TETR*/ | |
{ | |
{ { 0, 2 }, { 1, 2 }, { 1, 1 }, { 2, 1 } }, | |
{ { 1, 0 }, { 1, 1 }, { 2, 1 }, { 2, 2 } }, | |
{ { 0, 2 }, { 1, 2 }, { 1, 1 }, { 2, 1 } }, | |
{ { 1, 0 }, { 1, 1 }, { 2, 1 }, { 2, 2 } }, | |
}, | |
/*Z_TETR*/ | |
{ | |
{ { 0, 1 }, { 1, 1 }, { 1, 2 }, { 2, 2 } }, | |
{ { 1, 1 }, { 1, 2 }, { 2, 0 }, { 2, 1 } }, | |
{ { 0, 1 }, { 1, 1 }, { 1, 2 }, { 2, 2 } }, | |
{ { 1, 1 }, { 1, 2 }, { 2, 0 }, { 2, 1 } }, | |
}, | |
/*I_TETR*/ | |
{ | |
{ { 0, 2 }, { 1, 2 }, { 2, 2 }, { 3, 2 } }, | |
{ { 2, 0 }, { 2, 1 }, { 2, 2 }, { 2, 3 } }, | |
{ { 0, 2 }, { 1, 2 }, { 2, 2 }, { 3, 2 } }, | |
{ { 2, 0 }, { 2, 1 }, { 2, 2 }, { 2, 3 } }, | |
} | |
}; | |
internal int | |
get_score(int level, int num_lines) { | |
int modifier = 0; | |
switch (num_lines) { | |
case 1: | |
modifier = 40; | |
break; | |
case 2: | |
modifier = 100; | |
break; | |
case 3: | |
modifier = 300; | |
break; | |
case 4: | |
modifier = 1200; | |
break; | |
} | |
return modifier * (level + 1); | |
} | |
internal int | |
get_frames_per_cell(int level) { | |
const int fpcs[] = { | |
48, 43, 38, 33, 28, 23, 18, 13, 8, 6, 5, 5, 5, 4, 4, 4, 3, 3, 3 | |
}; | |
if (level >= 19) { | |
return level <= 28 ? 2 : 1; | |
} | |
return fpcs[level]; | |
} | |
internal bool32 | |
soft_drop_enabled(int level) { | |
return level < 19; | |
} | |
internal bool32 | |
check_collision(v2* shape, int x, int y) { | |
int i; | |
for (i = 0; i < NUM_BLOCKS; ++i) { | |
int posx = shape[i].x + x; | |
int posy = shape[i].y + y; | |
if (posx < 0 || posx >= play_area_cols || posy >= play_area_rows || play_area[posy][posx] != NO_TETR) { | |
return TRUE; | |
} | |
} | |
return FALSE; | |
} | |
#define MS(x) (1000*(x)) | |
#define SECONDS(x) (MS(1000*(x))) | |
#define LINES_PER_LEVEL 10 | |
#define SOFT_DROP_TIME 2 | |
typedef enum { | |
PLAYING, | |
CLEARING_LINES, | |
CLEARING_AREA, | |
GAME_OVER | |
} program_state_t; | |
typedef struct { | |
uint32 level; | |
uint32 score; | |
uint32 lines_cleared; | |
uint32 lines_until_next_level; | |
uint32 drop_time; | |
uint32 drop_timer; | |
int soft_drop_counter; | |
bool32 soft_dropping; | |
tetr_t next_tetr; | |
tetr_t tetr; | |
shape_t* piece_shape; | |
v2 pos; | |
uint32 rotation; | |
} game_t; | |
#define PLAY_AREA_TOP 2 | |
#define PLAY_AREA_LEFT 5 | |
global_variable game_t zero_game = {}; | |
internal | |
v2 tetr_offset(tetr_t tetr) { | |
v2 new_piece_pos = { -1, 1 }, new_i_piece_pos = { -2, 0 }, | |
new_o_piece_pos = { -1, 2 }; | |
if (tetr == I_TETR) { | |
return new_i_piece_pos; | |
} else if (tetr == O_TETR) { | |
return new_o_piece_pos; | |
} else { | |
return new_piece_pos; | |
} | |
} | |
internal void | |
start_new_piece(game_t* game) { | |
v2 pos = tetr_offset(game->tetr); | |
pos.x += play_area_cols / 2; | |
game->piece_shape = pieces[game->tetr]; | |
game->pos = pos; | |
game->soft_dropping = FALSE; | |
game->soft_drop_counter = 0; | |
game->rotation = 0; | |
} | |
internal tetr_t | |
random_tetr() { | |
return rand() % NUM_TETRS; | |
} | |
enum { | |
SYM_PAIR = 1, | |
LEFT_PAIR, | |
RIGHT_PAIR, | |
BG_PAIR, | |
FLASH_PAIR | |
}; | |
internal int | |
tetr_color_pair(tetr_t tetr) { | |
if (tetr == J_TETR || tetr == S_TETR) { | |
return LEFT_PAIR; | |
} else if (tetr == L_TETR || tetr == Z_TETR) { | |
return RIGHT_PAIR; | |
} | |
return SYM_PAIR; | |
} | |
internal void | |
draw_piece(tetr_t tetr, int rotation, int x, int y) { | |
int i; | |
if (has_color_terminal) { | |
wattron(stdscr, COLOR_PAIR(tetr_color_pair(tetr))); | |
} | |
for (i = 0; i < NUM_BLOCKS; ++i) { | |
int ch = tetr_chars[tetr]; | |
if (has_color_terminal) { | |
ch = '`'; | |
} | |
mvaddch( | |
pieces[tetr][rotation][i].y + y, | |
2*(pieces[tetr][rotation][i].x) + x, | |
ch); | |
mvaddch( | |
pieces[tetr][rotation][i].y + y, | |
2*(pieces[tetr][rotation][i].x)+1 + x, | |
ch); | |
} | |
} | |
enum { | |
COLOR_LEFT = 8, | |
COLOR_RIGHT | |
}; | |
internal void | |
new_game(game_t* game, int level) { | |
*game = zero_game; | |
memset(play_area, NO_TETR, sizeof(play_area)); | |
game->level = level; | |
game->lines_until_next_level = MIN(level * LINES_PER_LEVEL + LINES_PER_LEVEL, 100); | |
game->drop_time = get_frames_per_cell(level); | |
game->tetr = random_tetr(); | |
game->next_tetr = random_tetr(); | |
start_new_piece(game); | |
} | |
typedef struct { | |
int r, g, b; | |
} color_t; | |
internal color_t | |
make_color(uint8 r, uint8 g, uint8 b) { | |
color_t c; | |
c.r = r * 1000 / 255; | |
c.g = g * 1000 / 255; | |
c.b = b * 1000 / 255; | |
return c; | |
} | |
typedef enum { | |
BLUE_COLOR, | |
CYAN_COLOR, | |
GREEN_COLOR, | |
LIGHT_GREEN_COLOR, | |
PURPLE_COLOR, | |
PINK_COLOR, | |
ORANGE_COLOR, | |
GRAY_COLOR, | |
MAROON_COLOR, | |
LIGHT_ORANGE_COLOR, | |
LIGHT_BLUE_COLOR, | |
NUM_COLORS | |
} color_index_t; | |
global_variable | |
color_t colors[NUM_COLORS]; | |
typedef struct { | |
color_index_t left, right; | |
} level_colors_t; | |
global_variable | |
level_colors_t level_colors[] = { | |
{ BLUE_COLOR, CYAN_COLOR }, | |
{ GREEN_COLOR, LIGHT_GREEN_COLOR }, | |
{ PURPLE_COLOR, PINK_COLOR }, | |
{ BLUE_COLOR, LIGHT_GREEN_COLOR }, | |
{ PURPLE_COLOR, LIGHT_GREEN_COLOR }, | |
{ LIGHT_GREEN_COLOR, LIGHT_BLUE_COLOR }, | |
{ ORANGE_COLOR, GRAY_COLOR }, | |
{ PURPLE_COLOR, MAROON_COLOR }, | |
{ BLUE_COLOR, LIGHT_ORANGE_COLOR }, | |
{ ORANGE_COLOR, LIGHT_ORANGE_COLOR }, | |
}; | |
internal void | |
init_colors() { | |
colors[BLUE_COLOR] = make_color(0x5f, 0x5f, 0xff); | |
colors[LIGHT_BLUE_COLOR] = make_color(0x5f, 0xaf, 0xff); | |
colors[CYAN_COLOR] = make_color(0x5f, 0xff, 0xff); | |
colors[GREEN_COLOR] = make_color(0x00, 0x5f, 0x00); | |
colors[LIGHT_GREEN_COLOR] = make_color(0x00, 0xd7, 0x00); | |
colors[PURPLE_COLOR] = make_color(0x87, 0x00, 0xaf); | |
colors[PINK_COLOR] = make_color(0xff, 0x00, 0xaf); | |
colors[ORANGE_COLOR] = make_color(0xff, 0x5f, 0x00); | |
colors[GRAY_COLOR] = make_color(0xb2, 0xb2, 0xb2); | |
colors[MAROON_COLOR] = make_color(0x87, 0x00, 0x00); | |
colors[LIGHT_ORANGE_COLOR] = make_color(0xff, 0xaf, 0x00); | |
} | |
internal void | |
set_color(int curses_color, color_t* c) { | |
init_color(curses_color, c->r, c->g, c->b); | |
} | |
internal void | |
start_new_level(game_t* game) { | |
if (has_color_terminal) { | |
set_color(COLOR_LEFT, &colors[level_colors[game->level % 10].left]); | |
set_color(COLOR_RIGHT, &colors[level_colors[game->level % 10].right]); | |
} | |
game->drop_time = get_frames_per_cell(game->level); | |
} | |
internal void | |
draw_box(int x, int y, int width, int height) { | |
int i; | |
for (i = 0; i < width; ++i) { | |
int row; | |
for (row = 0; row < height; ++row) { | |
mvaddch(y + row, x + i, ' '); | |
} | |
} | |
for (i = 0; i < width; ++i) { | |
mvaddch(y, x + i, '-'); | |
mvaddch(y + height, x + i, '-'); | |
} | |
for (i = 0; i < height; ++i) { | |
mvaddch(y + i, x, '|'); | |
mvaddch(y + i, x + width, '|'); | |
} | |
mvaddch(y, x, '+'); | |
mvaddch(y, x + width, '+'); | |
mvaddch(y+height, x+width, '+'); | |
mvaddch(y+height, x, '+'); | |
} | |
int main(int argc, char** argv) { | |
UNUSED(argc); | |
UNUSED(argv); | |
srand(time(NULL)); | |
/* Initialize curses */ | |
initscr(); | |
cbreak(); | |
keypad(stdscr, TRUE); | |
noecho(); | |
curs_set(FALSE); | |
has_color_terminal = has_colors() && can_change_color(); | |
timeout(0); | |
game_t game; | |
new_game(&game, 0); | |
uint32 top_score = 0; | |
bool32 is_flashing = FALSE; | |
uint32 flash_timer = 0; | |
uint32 flash_time = 40; | |
if (has_color_terminal) { | |
init_colors(); | |
start_color(); | |
init_pair(SYM_PAIR, COLOR_LEFT, COLOR_WHITE); | |
init_pair(LEFT_PAIR, COLOR_WHITE, COLOR_LEFT); | |
init_pair(RIGHT_PAIR, COLOR_WHITE, COLOR_RIGHT); | |
init_pair(BG_PAIR, COLOR_WHITE, COLOR_BLACK); | |
init_pair(FLASH_PAIR, COLOR_BLACK, COLOR_WHITE); | |
bkgd(COLOR_PAIR(BG_PAIR)); | |
} | |
/* Vars */ | |
bool32 running = TRUE; | |
program_state_t prog_state = PLAYING; | |
uint32 last_update = time_in_us(); | |
uint32 frame_timer = 0; | |
uint32 clear_line_time = 30; | |
uint32 clear_area_time = 60; | |
uint32 clear_counter = 0; | |
uint32 num_filled_rows; | |
char filled_rows[play_area_rows]; | |
start_new_level(&game); | |
while (running) { | |
/* Get Input */ | |
int ch; | |
while ((ch = getch()) != -1) { | |
bool32 started_soft_dropping = FALSE; | |
switch (prog_state) { | |
case CLEARING_LINES: | |
case PLAYING: { | |
switch (ch) { | |
case 'q': | |
case 'Q': | |
running = FALSE; | |
continue; | |
case KEY_LEFT: | |
if (!check_collision(game.piece_shape[game.rotation], game.pos.x - 1, game.pos.y)) { | |
--game.pos.x; | |
} | |
break; | |
case KEY_RIGHT: | |
{ | |
if (!check_collision(game.piece_shape[game.rotation], game.pos.x + 1, game.pos.y)) { | |
++game.pos.x; | |
} | |
} | |
break; | |
case 'z': case 'Z': | |
case 'x': case 'X': | |
{ | |
int next_rotation; | |
if (ch == 'z' || ch == 'Z') { | |
next_rotation = game.rotation + 1; | |
if (next_rotation >= 4) { | |
next_rotation = 0; | |
} | |
} else { | |
next_rotation = game.rotation - 1; | |
if (next_rotation == -1) { | |
next_rotation = NUM_ROTATIONS - 1; | |
} | |
} | |
if (!check_collision(game.piece_shape[next_rotation], game.pos.x, game.pos.y)) { | |
game.rotation = next_rotation; | |
} | |
} | |
break; | |
case KEY_DOWN: | |
if (soft_drop_enabled(game.level)) { | |
if (!game.soft_dropping) { | |
started_soft_dropping = TRUE; | |
} | |
game.soft_dropping = TRUE; | |
} | |
break; | |
case ' ': | |
/* TODO: DEBUG. remove me.. */ | |
game.drop_timer = 0; | |
++game.tetr; | |
if (game.tetr == NUM_TETRS) { | |
game.tetr = 0; | |
} | |
start_new_piece(&game); | |
break; | |
} | |
if (!started_soft_dropping) { | |
game.soft_dropping = FALSE; | |
game.soft_drop_counter = 0; | |
} | |
} break; | |
case CLEARING_AREA: | |
case GAME_OVER: { | |
switch (ch) { | |
case '\n': | |
prog_state = PLAYING; | |
new_game(&game, 0); | |
start_new_level(&game); | |
break; | |
case 'q': | |
case 'Q': | |
running = FALSE; | |
continue; | |
} | |
} break; | |
} | |
} | |
frame_timer += time_in_us() - last_update; | |
last_update = time_in_us(); | |
if (frame_timer > MS(16)) { | |
/* drop piece if drop_timer expired */ | |
/* Draw */ | |
switch (prog_state) { | |
case GAME_OVER: { | |
} break; | |
case CLEARING_AREA: { | |
++clear_counter; | |
is_flashing = FALSE; | |
flash_timer = 0; | |
if (clear_counter > clear_area_time) { | |
prog_state = GAME_OVER; | |
} | |
} break; | |
case CLEARING_LINES: { | |
++clear_counter; | |
if (clear_counter > clear_line_time) { | |
/* copy down if filled */ | |
int row, col; | |
for (row = 0; row < num_filled_rows; ++row) { | |
int copy_row; | |
int filled_row = filled_rows[row]; | |
/* Move copy rows above filled_row down one */ | |
for (copy_row = filled_row; copy_row > 0; --copy_row) { | |
memcpy(play_area[copy_row], play_area[copy_row-1], play_area_cols * sizeof(play_area[0][0])); | |
} | |
++game.lines_cleared; | |
} | |
game.score += get_score(game.level, num_filled_rows); | |
if (game.lines_until_next_level > num_filled_rows) { | |
game.lines_until_next_level -= num_filled_rows; | |
} else { | |
game.lines_until_next_level = LINES_PER_LEVEL - (num_filled_rows - game.lines_until_next_level); | |
++game.level; | |
start_new_level(&game); | |
} | |
prog_state = PLAYING; | |
} | |
} break; | |
case PLAYING: { | |
{ | |
int time = game.soft_dropping ? SOFT_DROP_TIME : game.drop_time; | |
if (game.drop_timer > time) { | |
/* drop piece */ | |
if (!check_collision(game.piece_shape[game.rotation], game.pos.x, game.pos.y + 1)) { | |
game.pos.y++; | |
if (game.soft_dropping) { | |
++game.soft_drop_counter; | |
} | |
} else { | |
/* land piece */ | |
int i; | |
for (i = 0; i < NUM_BLOCKS; ++i) { | |
int row = game.piece_shape[game.rotation][i].y + game.pos.y; | |
int col = game.piece_shape[game.rotation][i].x + game.pos.x; | |
if (row < 2 || play_area[row][col] != NO_TETR) { | |
/* game over */ | |
prog_state = CLEARING_AREA; | |
clear_counter = 0; | |
if (game.score > top_score) { | |
top_score = game.score; | |
} | |
continue; | |
} | |
play_area[row][col] = game.tetr; | |
} | |
if (game.soft_dropping) { | |
game.score += game.soft_drop_counter; | |
} | |
game.tetr = game.next_tetr; | |
game.next_tetr = random_tetr(); | |
start_new_piece(&game); | |
/* check if any rows are filled */ | |
{ | |
int row, col; | |
num_filled_rows = 0; | |
for (row = 0; row < play_area_rows; ++row) { | |
bool32 filled = TRUE; | |
for (col = 0; col < play_area_cols; ++col) { | |
filled = play_area[row][col] != NO_TETR; | |
if (!filled) { | |
break; | |
} | |
} | |
if (filled) { | |
filled_rows[num_filled_rows++] = row; | |
} | |
} | |
if (num_filled_rows) { | |
prog_state = CLEARING_LINES; | |
clear_counter = 0; | |
if (num_filled_rows == 4) { | |
is_flashing = TRUE; | |
} | |
} | |
} | |
} | |
game.drop_timer = 0; | |
} | |
} | |
++game.drop_timer; | |
} break; | |
} | |
if (is_flashing) { | |
++flash_timer; | |
if (flash_timer > flash_time) { | |
flash_timer = 0; | |
is_flashing = FALSE; | |
} | |
} | |
erase(); | |
{ | |
int i; | |
if (has_color_terminal && is_flashing && flash_timer/6 % 2 == 0) { | |
int x, y, width = play_area_cols*4 + 4, height = play_area_rows+4; | |
wattron(stdscr, COLOR_PAIR(FLASH_PAIR)); | |
for (y = 0; y < height; ++y) { | |
for (x = 0; x < width; ++x) { | |
if (x % 2 != y % 2) { | |
mvaddch(y, x, ' '); | |
} | |
} | |
} | |
} | |
if (has_color_terminal) { | |
wattron(stdscr, COLOR_PAIR(BG_PAIR)); | |
} | |
draw_box(PLAY_AREA_LEFT + (play_area_cols+1)*2, PLAY_AREA_TOP + 2, 10, 5); | |
mvprintw(PLAY_AREA_TOP + 3, PLAY_AREA_LEFT+(play_area_cols+1)*2 + 1, "Top:"); | |
mvprintw(PLAY_AREA_TOP + 4, PLAY_AREA_LEFT+(play_area_cols+2)*2 + 1, "%06u", top_score); | |
mvprintw(PLAY_AREA_TOP + 5, PLAY_AREA_LEFT+(play_area_cols+1)*2 + 1, "Score:"); | |
mvprintw(PLAY_AREA_TOP + 6, PLAY_AREA_LEFT+(play_area_cols+2)*2 + 1, "%06u", game.score); | |
draw_box(PLAY_AREA_LEFT - 1, PLAY_AREA_TOP + 1, play_area_cols*2 + 1, play_area_rows - 1); | |
if (prog_state == PLAYING) { | |
draw_piece(game.tetr, game.rotation, 2*game.pos.x + PLAY_AREA_LEFT, game.pos.y + PLAY_AREA_TOP); | |
} | |
{ | |
int y = play_area_rows/2 - 3 + PLAY_AREA_TOP; | |
int x = 2*play_area_cols + PLAY_AREA_LEFT + 2; | |
if (has_color_terminal) { | |
wattron(stdscr, COLOR_PAIR(BG_PAIR)); | |
} | |
mvprintw(y + 1, x + 3, "Next:"); | |
int width = 11, height = 5; | |
draw_box(x, y, width, height); | |
draw_box(x, y + height + 1, width + 2, 2); | |
mvprintw(y + height + 2, x + 2, "Level: %03u", game.level); | |
v2 offset = tetr_offset(game.next_tetr); | |
draw_piece(game.next_tetr, 0, x + 2, y + offset.y + 1); | |
} | |
if (has_color_terminal) { | |
wattron(stdscr, COLOR_PAIR(BG_PAIR)); | |
} | |
for (i = 0; i < 2*play_area_cols; ++i) { | |
mvaddch(PLAY_AREA_TOP, PLAY_AREA_LEFT + i, ' '); | |
mvaddch(PLAY_AREA_TOP + 1, PLAY_AREA_LEFT + i, ' '); | |
} | |
draw_box(PLAY_AREA_LEFT - 1, PLAY_AREA_TOP - 1, play_area_cols*2 + 1, 2); | |
mvprintw(PLAY_AREA_TOP, PLAY_AREA_LEFT + 1, "Lines Cleared: %03u", game.lines_cleared); | |
int row, col; | |
for (row = 0; row < play_area_rows; ++row) { | |
for (col = 0; col < play_area_cols; ++col) { | |
tetr_t tetr = play_area[row][col]; | |
if (tetr != NO_TETR) { | |
char ch = tetr_chars[tetr]; | |
if (has_color_terminal) { | |
ch = '`'; | |
wattron(stdscr, COLOR_PAIR(tetr_color_pair(tetr))); | |
} | |
mvaddch(PLAY_AREA_TOP + row, PLAY_AREA_LEFT + 2*col, ch); | |
mvaddch(PLAY_AREA_TOP + row, PLAY_AREA_LEFT + 2*col+1, ch); | |
} | |
} | |
} | |
if (prog_state == CLEARING_LINES) { | |
int i; | |
int col_center = (play_area_cols / 2); | |
int num_to_clear = (play_area_cols / 2) * clear_counter / clear_line_time + 1; | |
if (has_color_terminal) { | |
wattron(stdscr, COLOR_PAIR(BG_PAIR)); | |
} | |
for (i = 0; i < num_filled_rows; ++i) { | |
row = filled_rows[i]; | |
for (col = 0; col < num_to_clear; ++col) { | |
mvaddch(PLAY_AREA_TOP + row, PLAY_AREA_LEFT + 2*(col+col_center), '>'); | |
mvaddch(PLAY_AREA_TOP + row, PLAY_AREA_LEFT + 2*(col+col_center)+1, '>'); | |
mvaddch(PLAY_AREA_TOP + row, PLAY_AREA_LEFT + 2*(-col-1+col_center), '<'); | |
mvaddch(PLAY_AREA_TOP + row, PLAY_AREA_LEFT + 2*(-col-1+col_center)+1, '<'); | |
} | |
} | |
} else if (prog_state == CLEARING_AREA || prog_state == GAME_OVER) { | |
int num_to_clear = prog_state == CLEARING_AREA ? (play_area_rows - 2) * clear_counter / clear_area_time + 1 : (play_area_rows - 2); | |
if (has_color_terminal) { | |
wattron(stdscr, COLOR_PAIR(BG_PAIR)); | |
} | |
for (row = 0; row < num_to_clear; ++row) { | |
for (col = 0; col < play_area_cols; ++col) { | |
mvaddch(PLAY_AREA_TOP + row + 2, PLAY_AREA_LEFT + 2*col, '='); | |
mvaddch(PLAY_AREA_TOP + row + 2, PLAY_AREA_LEFT + 2*col+1, '='); | |
} | |
} | |
} | |
} | |
refresh(); | |
frame_timer = 0; | |
} | |
usleep(1); | |
} | |
endwin(); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment