What sticks out and what would you have done better in this termina-tetris implementation? I do not intend to use namespaces or split it up into multiple files.
#include <ncurses.h>
#include <array>
#include <cstdlib>
// The tetris board
const int board_size_x = 15;
const int board_size_y = 20;
// In-game stats
int block_x = 0; // Position of the moving block inside the board
int block_y = 0;
int tick_force_down; // Ticks down. Force down block when < 0. Resets to 'level' every time the block moves down.
int level; // Max ticks before block is forced down. Decreases during the game as score increases.
int score; // Current score (number of lines taken)
// Where to print stuff
const int board_x = 10; // Tetris board
const int board_y = 3;
const int score_y = 1; // Scoreboard
const int score_x = 5;
const int next_block_x = 30; // Next block
const int next_block_y = 5;
const int blocksize = 3;
using block = std::array<std::array<int, blocksize>, blocksize>;
// Tetris board. 0 = empty
std::array<std::array<int, board_size_x>, board_size_y> board {};
block current_block {};
block next_block{};
// Draw a colored square
void drawsquare(int y, int x, int color){
move(y,x);
attron(COLOR_PAIR(color));
addch(' ');
attroff(COLOR_PAIR(color));
}
// Draw block
void drawblock(int row, int col, block & b){
for(int y=0; y < blocksize; ++y)
for(int x=0; x < blocksize; ++x)
if(b[y][x])
drawsquare(row + y, col + x, b[y][x]);
}
// Draw moving block
void drawmoving(){
drawblock(board_y+1+block_y, board_x+1+block_x, current_block);
}
// Draw next block
void drawnext(){
mvprintw(next_block_y, next_block_x, "Next: ");
drawblock(next_block_y+1, next_block_x+1, next_block);
}
// Randomize next block
void newnext(){
int c = 1 + rand()%7; // Color. 1-7 as initialized for ncurses.
switch(rand()%7){
case 0:
next_block = {0,c,0, 0,c,0, c,c,c};
break;
case 1:
next_block = {c,c,c, c,c,c, c,c,c};
break;
case 2:
next_block = {c,c,0, 0,c,c, 0,0,0};
break;
case 3:
next_block = {0,c,0, 0,c,0, 0,c,c};
break;
case 4:
next_block = {0,c,0, 0,c,0, 0,c,0};
break;
case 5:
next_block = {0,c,c, c,c,0, 0,0,0};
break;
case 6:
next_block = {0,c,0, 0,c,0, c,c,0};
break;
}
}
// Crystalizes moving block into the tetris board
void raster(){
for(int y=0; y < blocksize; ++y)
for(int x=0; x < blocksize; ++x){
if(! current_block[y][x])
continue;
board[block_y+y][block_x+x] = current_block[y][x];
}
}
// block is inside another rasterized block or outside the board?
bool collide(int row, int col, const block & b){
for(int y=0; y < blocksize; ++y)
for(int x=0; x < blocksize; ++x){
if(! b[y][x] )
continue;
int y_on_board = row + y;
int x_on_board = col + x;
if(x_on_board < 0 || x_on_board >= board_size_x || y_on_board >= board_size_y)
return true;
if(board[y_on_board][x_on_board])
return true;
}
return false;
}
// Drops the next block, makes a new next. False on collide.
bool drop(){
block_y = 1;
block_x = board_size_x/2 - 1;
current_block = next_block;
newnext();
return !collide(block_y, block_x, current_block);
}
// Rotated right if possible
void rotright(){
block rot;
for(int ny=0; ny < blocksize; ++ny)
for(int nx=0; nx < blocksize; ++nx)
rot[ny][nx] = current_block[blocksize-1-nx][ny];
if(collide(block_y, block_x, rot))
return;
current_block = rot;
}
// Rotated left if possible
void rotleft(){
block rot;
for(int ny=0; ny < blocksize; ++ny)
for(int nx=0; nx < blocksize; ++nx)
rot[ny][nx] = current_block[nx][blocksize-1-ny];
if(collide(block_y, block_x, rot))
return;
current_block = rot;
}
// false and refuse on collide
bool movedown(){
if(collide(block_y+1, block_x, current_block))
return false;
++block_y;
return true;
}
void moveleft(){
if(collide(block_y, block_x-1, current_block))
return;
--block_x;
}
void moveright(){
if(collide(block_y, block_x+1, current_block))
return;
++block_x;
}
void textout(int y, int x, const char* str){
mvprintw(y, x, str);
}
// Returns number of cleared lines
int clearlines(){
int cleared = 0;
for(int y=0; y < board_size_y; ++ y){
int squares = 0;
for(int x=0; x < board_size_x; ++ x){
if(board[y][x])
++squares;
}
// Drop down all the above lines
if(squares == board_size_x){
++cleared;
for(int xc=0; xc < board_size_x; ++xc) // Clear line. Important for row 0.
board[y][xc]=0;
for(int y2 = y; y2 > 0; --y2) // The line we're moving to
for(int x2 = 0; x2 < board_size_x; ++x2)
board[y2][x2] = board[y2-1][x2]; // Move above line to this line
}
}
return cleared;
}
void drawboard(){
// Draw a box around the tetris board
mvaddch(board_y, board_x, ACS_ULCORNER);
mvaddch(board_y, board_x + board_size_x + 1, ACS_URCORNER);
mvaddch(board_y + board_size_y + 1, board_x, ACS_LLCORNER);
mvaddch(board_y + board_size_y + 1, board_x + board_size_x + 1, ACS_LRCORNER);
for(int i = 1; i <= board_size_x; ++i){
mvaddch(board_y, board_x + i , ACS_HLINE);
mvaddch(board_y + board_size_y + 1, board_x + i, ACS_HLINE);
}
for(int i = 1; i <= board_size_y; ++i){
mvaddch(board_y + i, board_x, ACS_VLINE);
mvaddch(board_y + i, board_x + board_size_x + 1, ACS_VLINE);
}
// Draw the filled board squares
for(int y=0; y < board_size_y; ++y)
for(int x=0; x < board_size_x; ++x)
drawsquare(board_y + y + 1, board_x + x + 1, board[y][x]);
}
// Init a new game
void newgame(){
newnext();
drop();
level = 300;
tick_force_down = level;
score = 0;
}
bool lost = false;
bool ingame_loop(){
int c=getch();
if(c == 'q' || c == 'Q')
return false;
if(lost){
mvprintw(0,0,"You lost. Press q to quit.");
refresh();
return true;
}
bool down = false;
if(--tick_force_down < 0){
tick_force_down = level;
down = true;
}
switch(c){
case ' ':
down = true;
while(movedown())
;
break;
case 'z':
case 'Z':
rotleft();
break;
case 'x':
case 'X':
case KEY_UP:
rotright();
break;
case KEY_LEFT:
moveleft();
break;
case KEY_RIGHT:
moveright();
break;
case KEY_DOWN:
down = true;
break;
}
if(down){
tick_force_down = level;
if(!movedown()){
raster();
if(!drop())
lost = true;
else{
int lines = clearlines();
level -= lines;
score += lines;
}
}
}
// Update the screen
clear();
drawboard();
drawmoving();
drawnext();
mvprintw(score_y, score_x, "Score: %d", score);
refresh();
return true;
}
int main()
{
// Init ncurses
initscr();
start_color();
curs_set(0);
cbreak();
noecho();
keypad(stdscr,TRUE);
for(int i=1; i <= 7; ++i) // man init_pair
init_pair(i, COLOR_BLACK, i);
timeout(1);
newgame();
while(ingame_loop())
;
endwin();
return 0;
}