patterncModerate
Tetris in C, in 200 lines
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.
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
You do four conversions from integer types to a double precision floating point type. And do two multiplications times a million.
Consider
This only does two conversions and one multiplication.
Or this answer suggests that you could instead use
Optimize the common path
Also, if this is usually false, consider flipping things around.
could become
with
and
This makes updating
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
Consider instead
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.
if (((double)after.tv_sec*1000000 + (double)after.tv_usec)-((double)before.tv_sec*1000000 + (double)before.tv_usec) > timer){ //time difference in microsec accuracyYou 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.