HiveBrain v1.2.0
Get Started
← Back to all entries
patterncModerate

Tetris in C, in 200 lines

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
200tetrislines

Problem

I aimed at making fully functional (real) Tetris, in the shortest way possible in C. The result is a terminal game for Linux, that uses ncurses. I made it for my friend, who wanted to play it on his Arduino with LED matrix. Before doing it, I browsed for the shortest Tetris code so far. I found the 140-bytes (buggy) JS "Tetris" and 36 lines of JS Tetris with additional HTML.

The main idea is to make it even shorter is using bitwise operations: shifting and logical OR. That was my first idea, but when I got to rotation, I had to switch to the array structure.

However, there is a problem: it is insanely CPU-intensive. My bet is the main loop that gets cycled too many times. So how can I regulate this problem? And do you think this can be done even shorter in C?

```
#include
#include
#include
#include
#include

#define ROWS 20
#define COLS 11
#define TRUE 1
#define FALSE 0

char Table[ROWS][COLS] = {0};
int score = 0;
char GameOn = TRUE;
double timer = 500000; //half second

typedef struct {
char **array;
int width, row, col;
} Shape;
Shape current;

const Shape ShapesArray[7]= {
{(char *[]){(char []){0,1,1},(char []){1,1,0}, (char []){0,0,0}}, 3}, //S_shape
{(char *[]){(char []){1,1,0},(char []){0,1,1}, (char []){0,0,0}}, 3}, //Z_shape
{(char *[]){(char []){0,1,0},(char []){1,1,1}, (char []){0,0,0}}, 3}, //T_shape
{(char *[]){(char []){0,0,1},(char []){1,1,1}, (char []){0,0,0}}, 3}, //L_shape
{(char *[]){(char []){1,0,0},(char []){1,1,1}, (char []){0,0,0}}, 3}, //ML_shape
{(char *[]){(char []){1,1},(char []){1,1}}, 2}, //SQ_shape
{(char *[]){(char []){0,0,0,0}, (char []){1,1,1,1}, (char []){0,0,0,0}, (char []){0,0,0,0}}, 4} //R_shape
};

Shape CopyShape(Shape shape){
Shape new_shape = shape;
char **copyshape = shape.

Solution

Minimize math operations

if (((double)after.tv_sec*1000000 + (double)after.tv_usec)-((double)before.tv_sec*1000000 + (double)before.tv_usec) > timer){ //time difference in microsec accuracy


You do four conversions from integer types to a double precision floating point type. And do two multiplications times a million.

Consider

//time difference in microsec accuracy
        if (((double)(after.tv_sec - before.tv_sec)*1000000 + (double)(after.tv_usec - before.tv_usec)) > timer) {


This only does two conversions and one multiplication.

Or this answer suggests that you could instead use uint64_t.

//time difference in microsec accuracy
        if (((after.tv_sec - before.tv_sec)*(uint64_t)1000000 + (after.tv_usec - before.tv_usec)) > timer) {


Optimize the common path

Also, if this is usually false, consider flipping things around.

if (((double)after.tv_sec*1000000 + (double)after.tv_usec)-((double)before.tv_sec*1000000 + (double)before.tv_usec) > timer){ //time difference in microsec accuracy
            before = after;
            ManipulateCurrent('s');
        }


could become

if (IS_LATER(after, before)) {
            before = ADD_TO_TIMEVALUE(after, 500000);
            ManipulateCurrent('s');
        }


with

#define IS_LATER(a, b) ((a.tv_sec == b.tv_sec && a.tv_usec > b.tv_usec) || a.tv_sec > b.tv_sec)


and

#define ADD_TO_TIMEVALUE(tv, t) do {\
    tv.tv_usec += t; \
    while (tv.tv_usec >= 1000000) { \
        tv.tv_usec -= 1000000; \
        tv.tv_sec++; \
    } \
} while (0)


This makes updating before more expensive, but makes comparing before and after cheaper (up to three comparisons rather than two conversions, a multiplication, an addition, subtractions, and a comparison). If you usually don't update, this is better.

Also note that it is more modern (C99 and later) to do this with inline functions than macros.

But is that the problem?

This will make the loop operate faster, but that's not really the problem. You keep looping until a certain amount of time has passed. Looping faster won't change that. You might better find a way to loop more slowly to use less CPU.

You are using

nodelay(stdscr, TRUE);


Consider instead

halfdelay(1);


Then it should block on input until it times out (after a tenth of a second). So if the user isn't hitting keys, this will only process five times (at most) before advancing the row.

Note: I haven't tried it.

Code Snippets

if (((double)after.tv_sec*1000000 + (double)after.tv_usec)-((double)before.tv_sec*1000000 + (double)before.tv_usec) > timer){ //time difference in microsec accuracy
//time difference in microsec accuracy
        if (((double)(after.tv_sec - before.tv_sec)*1000000 + (double)(after.tv_usec - before.tv_usec)) > timer) {
//time difference in microsec accuracy
        if (((after.tv_sec - before.tv_sec)*(uint64_t)1000000 + (after.tv_usec - before.tv_usec)) > timer) {
if (((double)after.tv_sec*1000000 + (double)after.tv_usec)-((double)before.tv_sec*1000000 + (double)before.tv_usec) > timer){ //time difference in microsec accuracy
            before = after;
            ManipulateCurrent('s');
        }
if (IS_LATER(after, before)) {
            before = ADD_TO_TIMEVALUE(after, 500000);
            ManipulateCurrent('s');
        }

Context

StackExchange Code Review Q#136406, answer score: 17

Revisions (0)

No revisions yet.