Skip to content

Instantly share code, notes, and snippets.

@rygorous
Created September 18, 2011 05:42
Show Gist options
  • Save rygorous/1224778 to your computer and use it in GitHub Desktop.
Save rygorous/1224778 to your computer and use it in GitHub Desktop.
Minitris
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <time.h>
// ---- platform specific part
enum {
CURSOR_LEFT = 256,
CURSOR_RIGHT,
CURSOR_UP,
CURSOR_DOWN,
};
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <conio.h>
static HANDLE conout;
static void set_cursor_visible(BOOL flag)
{
CONSOLE_CURSOR_INFO cursor;
GetConsoleCursorInfo(conout, &cursor);
cursor.bVisible = flag;
SetConsoleCursorInfo(conout, &cursor);
}
void term_init()
{
conout = CreateFileA("CONOUT$", GENERIC_READ|GENERIC_WRITE, 0, NULL, 0, 0, NULL);
set_cursor_visible(FALSE);
}
void term_close()
{
set_cursor_visible(TRUE);
}
void term_clear()
{
CONSOLE_SCREEN_BUFFER_INFO info;
COORD origin = { 0, 0 };
GetConsoleScreenBufferInfo(conout, &info);
FillConsoleOutputCharacterA(conout, ' ', info.dwSize.X * info.dwSize.Y, origin, NULL);
}
void term_move_cursor(int x, int y)
{
COORD pos = { x, y };
SetConsoleCursorPosition(conout, pos);
}
int term_getkey()
{
if (_kbhit()) {
int ch = _getch();
if (ch == 0xe0) { // escape
switch (_getch()) {
case 0x4b: return CURSOR_LEFT;
case 0x4d: return CURSOR_RIGHT;
case 0x48: return CURSOR_UP;
case 0x50: return CURSOR_DOWN;
}
} else
return ch;
}
else
return 0;
return _kbhit() ? _getch() : 0;
}
#else
#error Implement terminal interface for this platform!
#endif
// ---- actual game
typedef unsigned char U8;
struct Block {
U8 x, y; // 4 x/y coordinates, 2 bits each
};
static const Block tetrominos[] = { // I, J, L, O, S, Z, T
{ 0xaa, 0xe4 }, { 0x6a, 0xf9 }, { 0xea, 0xf9 }, { 0x99, 0xa5 },
{ 0x9e, 0xa5 }, { 0x94, 0xa5 }, { 0xb9, 0x95 },
};
static char playfield[4+20+1][3+10+3];
static Block current, next;
static int current_x, current_y;
static long score, lines, level;
static int unpack(U8 coord, int i)
{
return (coord >> (2*i)) & 3;
}
static Block rotate_block(Block in, bool ccw)
{
Block o = { ccw ? in.y : ~in.y, ccw ? ~in.x : in.x };
return o;
}
static Block random_tetromino()
{
Block b = tetrominos[rand() % 7];
int nrot = rand() % 4;
while (nrot--)
b = rotate_block(b, true);
return b;
}
static bool try_move(int dx, int dy)
{
int x = current_x + dx, y = current_y + dy;
if (x < 0 || x >= 3+10 || y < 0)
return false;
for (int i=0; i<4; i++)
if (playfield[y + unpack(current.y, i)][x + unpack(current.x, i)] != '.')
return false;
current_x = x;
current_y = y;
return true;
}
static void rotate(bool ccw)
{
current = rotate_block(current, ccw);
static const int jiggle[][2] = { {0,0}, {-1,0}, {1,0}, {0,-1}, {-1,-1}, {1,-1}, {-2,0}, {2,0} };
for (int i=0; i < sizeof(jiggle)/sizeof(*jiggle); i++)
if (try_move(jiggle[i][0], jiggle[i][1]))
return;
// no legal move possible if we do this rotation, so undo it
current = rotate_block(current, !ccw);
}
static void put_tetromino(char ch)
{
for (int i=0; i<4; i++)
playfield[current_y + unpack(current.y, i)][current_x + unpack(current.x, i)] = ch;
}
static void draw_playfield()
{
put_tetromino('@');
for (int y=4; y<25; y++) {
term_move_cursor(2, y-4);
puts(playfield[y]+2);
}
put_tetromino('.');
}
static void delay(double time)
{
clock_t timeout = clock() + (clock_t) (time * CLOCKS_PER_SEC);
while (clock() < timeout);
}
static void drop()
{
if (try_move(0, 1))
return;
put_tetromino('#');
int full = 0, nlines = 0, x, y=current_y;
for (int i=0; i<4; i++) {
for (x=3; playfield[y+i][x] == '#'; x++);
if (x == 3+10) { // line is full
full |= 1 << i;
nlines++;
}
}
current_y = 0; // doubles as a marker for "no active block"
if (full) {
for (int flash=0; flash<4; flash++) {
for (int i=0; i<4; i++)
if (full & (1 << i))
memset(&playfield[y+i][3], (flash & 1) ? '.' : '#', 10);
draw_playfield();
delay(0.25);
}
for (int i=0; i<4; i++)
if (full & (1 << i))
memmove(playfield[4], playfield[3], (y+i-3) * sizeof(playfield[0]));
static const long scores[] = { 0, 100, 200, 400, 800 };
lines += nlines;
score += scores[nlines];
}
}
static void draw_next()
{
char str[7][7] = {
" Next ", "+----+", "| |", "| |",
"| |", "| |", "+----+"
};
for (int i=0; i<4; i++)
str[2 + unpack(next.y, i)][1 + unpack(next.x, i)] = '@';
for (int i=0; i<7; i++) {
term_move_cursor(16, 14+i);
puts(str[i]);
}
}
static void draw_score()
{
static const char *desc[] = { "Score", "Lines", "Level" };
long values[] = { score, lines, level };
for (int i=0; i<3; i++) {
term_move_cursor(16, i*3);
puts(desc[i]);
term_move_cursor(16, i*3+1);
printf("%9ld", values[i]);
}
}
static bool game_over(const char *msg)
{
term_clear();
term_move_cursor(10, 10);
puts(msg);
term_move_cursor(10, 11);
printf("Final score: %ld", score);
term_move_cursor(10, 13);
puts("Continue? (Y/N)");
for (;;) {
switch (term_getkey()) {
case 'y': case 'Y': return true;
case 'n': case 'N':
case 27: return false;
}
}
}
static bool game()
{
term_clear();
// init the playfield
memset(playfield, '.', sizeof(playfield));
for (int y=0; y<25; y++) {
playfield[y][2] = '*';
playfield[y][13] = '*';
playfield[y][14] = 0;
}
for (int x=2; x<14; x++)
playfield[24][x] = '*';
clock_t ref_time, timeout;
bool dropped = true;
score = lines = 0;
next = random_tetromino();
for (;;) {
level = lines/10 + 1;
if (dropped) {
timeout = CLOCKS_PER_SEC * (22 - level) / 21;
if (!timeout) timeout = 1;
ref_time = clock();
dropped = false;
}
if (!current_y) {
current = next;
next = random_tetromino();
current_x = 6;
current_y = 2;
if (!try_move(0, 0))
return game_over("You lose!");
timeout *= 2; // extra pause on a new block
}
draw_score();
draw_next();
draw_playfield();
int key=0;
while (!key) {
key = term_getkey();
if (!key && clock() >= ref_time + timeout)
key = CURSOR_DOWN;
}
switch (key) {
case CURSOR_LEFT: try_move(-1, 0); break;
case CURSOR_RIGHT: try_move( 1, 0); break;
case CURSOR_DOWN: drop(); dropped = true; break;
case 'a': case 'A': rotate(true); break;
case CURSOR_UP:
case 'd': case 'D': rotate(false); break;
case ' ': while (current_y) drop(); dropped = true; break;
case 27: return false;
}
if (lines >= 200)
return game_over("You win!");
}
}
int main()
{
term_init();
while (game());
term_close();
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment