Skip to content

Instantly share code, notes, and snippets.

@leomao
Created November 11, 2017 09:15
Show Gist options
  • Save leomao/130f070c7dbe1d051c1656723fcb48e5 to your computer and use it in GitHub Desktop.
Save leomao/130f070c7dbe1d051c1656723fcb48e5 to your computer and use it in GitHub Desktop.
Tetris battle in one cpp
///////////////////////////////////////////
// Author: LeoMao
// Filename: tetris.cpp
// Purpose: simulate tetris battle
// encoding: UTF-8
//
// Features:
// => basic tetris battle game control
// => can see next 5 pieces
// => can hold just like tetris battle
// => realtime gameplay (need curses lib)
//
// TODO:
// Tetris AI
//
// NOTES:
// must compile with ncurses (linux)
// play with terminals that support 256 colors to get the
// best gaming experience
///////////////////////////////////////////
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <ctime>
#include <string>
#include <deque>
#include <algorithm>
#include <ncurses.h>
using namespace std;
// Tetromino class
class Tetromino
{
public:
Tetromino(char, int, int, int);
int get(int, int) const;
int get_width(int) const;
int get_offset(int) const;
int get_color() const;
private:
int shape_[4];
// 0 1 2 3
// 4 5 6 7
int width_; // actual width
char type_;
int color_;
};
// pass shape in binary
Tetromino::Tetromino(char t, int c, int s, int w)
{
type_ = t;
color_ = c;
int p = 0;
int i = 0;
while (i < 4) {
if (s & 1<<p) {
shape_[i] = p;
i++;
}
p++;
}
width_ = w;
}
// return pos in 4x4 grid
// 0 1 2 3
// 4 5 6 7
// 8 9 10 11
// 12 13 14 15
int Tetromino::get(int p, int rotate=0) const
{
int pos = 4 + shape_[p];
if (rotate == 0)
return pos;
else if (width_ == 2) {
return pos;
}
else {
if (width_ == 4) {
if (rotate == 1)
return 3 - pos/4 + 4 * (pos%4);
else if (rotate == 2)
return 15 - pos;
else
return 12 + pos/4 - 4 * (pos%4);
}
else {
if (rotate == 1)
return 7 - pos/4 + 4 * (pos%4);
else if (rotate == 2)
return 18 - pos;
else
return 11 + pos/4 - 4 * (pos%4);
}
}
}
int Tetromino::get_width(int r) const
{
if (r & 1)
return width_ == 4 ? 1 : 2;
else
return width_;
}
// return the left offset
int Tetromino::get_offset(int r) const
{
if (width_ == 2)
return 1;
else if (width_ == 3) {
return r == 1 ? 1 : 0;
}
else {
if (r == 1)
return 2;
else if (r == 3)
return 1;
else
return 0;
}
}
int Tetromino::get_color() const
{
return color_;
}
// Tetris game class
class Tetris
{
public:
Tetris();
~Tetris();
void play(char);
bool is_playing() const;
void update();
static Tetromino* const tetrominos[8];
static int colors[8];
static const int rotation_check_len = 12;
static pair<int, int> rotation_check_list[12];
private:
bool is_playing_;
char mode_;
char tetrominos_[7];
int count_; // use to random tetrominos in next queue
Tetromino* now_; // current tetromino
int now_row_; // current tetromino's row
int now_col_; // current tetromino's column
int now_rotation_; // current tetromino's orientation
Tetromino* hold_; // hold piece
bool is_holded_;
deque<Tetromino*> next_; // next 5 pieces
int lines_; // how many lines clean
int frame_count_;
void clear_display();
void place_tetromino(Tetromino*, int, int, int, char);
Tetromino* get_next();
void reset();
void get_operation();
void hold();
void rotate(int);
void move_piece(int);
void soft_drop();
void drop_down();
int predict_drop();
bool check_place(int, int);
bool check_result();
};
// tetrominos (7 shapes + none)
Tetromino* const Tetris::tetrominos[8] = {
new Tetromino('I', 1, 15, 4),
// 0 1 2 3
new Tetromino('O', 2, 102, 2),
// 1 2
// 5 6
new Tetromino('L', 3, 116, 3),
// 2
// 4 5 6
new Tetromino('J', 4, 113, 3),
// 0
// 4 5 6
new Tetromino('S', 5, 54, 3),
// 1 2
// 4 5
new Tetromino('Z', 6, 99, 3),
// 0 1
// 5 6
new Tetromino('T', 7, 114, 3),
// 1
// 4 5 6
new Tetromino('N', 0, -1, 0)
}; // 7 shapes of tetrominos
int Tetris::colors[8] = {0, 1, 2, 3, 4, 5, 6, 7};
// Rotation check list: (row_offset, col_offset)
pair<int, int> Tetris::rotation_check_list[12] = {
{ 0, 0}, { 1, 0}, { 0, 1},
{ 0, -1}, { 1, 1}, { 1, -1},
{ 2, 1}, { 2, -1}, {-1, 1},
{-1, -1}, {-2, 1}, {-2, -1}
};
Tetris::Tetris()
{
is_playing_ = false;
hold_ = Tetris::tetrominos[7];
for (int i = 0; i < 7; i++)
tetrominos_[i] = i;
}
Tetris::~Tetris()
{
}
// game enter point
void Tetris::play(char mode)
{
mode_ = mode;
reset();
}
void Tetris::reset()
{
clear();
for (int i = 4; i < 25; i++) {
mvprintw(i, 1, " | |");
}
mvprintw(25, 6, "^^^^^^^^^^^^");
mvprintw(4, 1, "hold");
mvprintw(18, 1, "line");
mvprintw(19, 1, "sent");
mvprintw(4, 19, "next");
is_holded_ = false;
hold_ = Tetris::tetrominos[7];
frame_count_ = 0;
count_ = 7;
lines_ = 0;
now_ = get_next();
next_.clear();
for (int i = 0; i < 5; i++)
next_.push_back(get_next());
now_rotation_ = 0;
now_row_ = 1;
now_col_ = 4;
for (int i = 4; i < 25; i++) {
for (int j = 7; j <= 16; j++)
mvaddch(i, j, '.');
}
mvprintw(20, 1, "%4d", lines_);
is_playing_ = true;
}
bool Tetris::is_playing() const
{
return is_playing_;
}
void Tetris::clear_display()
{
for (int i = 0; i < 4; i++) {
for (int j = 7; j <= 16; j++) {
char c = mvinch(i, j);
if (c != 'O') {
mvaddch(i, j, ' ');
}
}
}
for (int i = 4; i < 25; i++) {
for (int j = 7; j <= 16; j++) {
char c = mvinch(i, j);
if (c != 'O' && c != '.') {
mvaddch(i, j, '.');
}
}
}
for (int i = 5; i < 22; i++) {
mvprintw(i, 19, "%4s", "");
}
for (int i = 5; i < 9; i++) {
mvprintw(i, 1, "%4s", "");
}
}
void Tetris::place_tetromino(Tetromino* t, int row,
int col, int r=0, char symbol='O')
{
int pos;
chtype c = symbol;
if (colors[t->get_color()])
c |= COLOR_PAIR(colors[t->get_color()]);
for (int i = 0; i < 4; i++) {
pos = t->get(i, r);
mvaddch(row + pos/4, col + pos%4, c);
}
}
void Tetris::update()
{
clear_display();
for (int i = 0; i < 5; i++) {
place_tetromino(next_[i], 5 + i*3, 19);
}
if (hold_ != Tetris::tetrominos[7])
place_tetromino(hold_, 5, 1);
if (frame_count_ >= 100) {
frame_count_ = 0;
if (check_place(now_row_ + 1, now_col_))
now_row_++;
else
drop_down();
}
place_tetromino(now_, predict_drop(), 6 + now_col_, now_rotation_, 'o');
place_tetromino(now_, now_row_, 6 + now_col_, now_rotation_, 'X');
mvprintw(20, 1, "%4d", lines_);
move(26, 0);
refresh();
get_operation();
}
Tetromino* Tetris::get_next()
{
if (count_ == 7) {
random_shuffle(tetrominos_, tetrominos_ + 7);
count_ = 0;
}
return Tetris::tetrominos[tetrominos_[count_++]];
}
void Tetris::get_operation()
{
chtype op;
op = getch();
if (op == 'c')
hold();
else if (op == KEY_UP || op == 'x')
rotate(1);
else if (op == 'z')
rotate(3);
else if (op == KEY_LEFT)
move_piece(-1);
else if (op == KEY_RIGHT)
move_piece(1);
else if (op == KEY_DOWN)
soft_drop();
else if (op == ' ')
drop_down();
else if (op == 'q')
is_playing_ = false;
frame_count_++;
}
void Tetris::hold()
{
if (!is_holded_) {
now_rotation_ = 0;
now_row_ = 1;
now_col_ = 4;
if (hold_ == Tetris::tetrominos[7]) {
hold_ = now_;
now_ = next_.front();
next_.pop_front();
next_.push_back(get_next());
}
else {
Tetromino* tmp = hold_;
hold_ = now_;
now_ = tmp;
}
is_holded_ = true;
}
frame_count_ = 0;
}
void Tetris::rotate(int direction)
{
int origin_r = now_rotation_; // origin rotation
now_rotation_ += direction;
now_rotation_ %= 4;
int pos = now_->get_offset(now_rotation_);
int width = now_->get_width(now_rotation_);
if (pos + now_col_ < 1)
now_col_ = 1 - pos;
else if (pos + width + now_col_ > 10)
now_col_ = 11 - pos - width;
bool rotated = false;
for (int i = 0; i < Tetris::rotation_check_len; ++i) {
int r_offset = Tetris::rotation_check_list[i].first;
int c_offset = Tetris::rotation_check_list[i].second;
if (check_place(now_row_ + r_offset, now_col_ + c_offset)) {
now_row_ += r_offset;
now_col_ += c_offset;
rotated = true;
break;
}
}
if (!rotated) {
now_rotation_ = origin_r;
}
}
void Tetris::move_piece(int direction)
{
if (check_place(now_row_, now_col_ + direction))
now_col_ += direction;
}
void Tetris::soft_drop()
{
if (check_place(now_row_ + 1, now_col_))
now_row_++;
frame_count_ = 0;
}
void Tetris::drop_down()
{
// drop step by step and check if the tetromino hit other blocks
bool no_hit = true;
now_row_ = predict_drop();
place_tetromino(now_, now_row_, 6 + now_col_, now_rotation_);
now_ = next_.front();
next_.pop_front();
next_.push_back(get_next());
now_rotation_ = 0;
now_col_ = 4;
now_row_ = 1;
frame_count_ = 0;
is_holded_ = false;
if (check_result()) {
mvprintw(27, 0, "Game is over T___T, play again? [y/n]");
char c = 'a';
while (c != 'y' && c != 'n')
c = getch();
if (c == 'y')
reset();
else
is_playing_ = false;
}
}
int Tetris::predict_drop()
{
bool no_hit = true;
int row = now_row_;
while (check_place(row, now_col_))
row++;
row--;
return row;
}
bool Tetris::check_place(int row, int col)
{
int pos;
for (int i = 0; i < 4; i++) {
pos = now_->get(i, now_rotation_);
int tmpr = row + pos/4;
int tmpc = col + pos%4;
if (tmpr < 0 || tmpr > 24 || tmpc < 1 || tmpc > 10)
return false;
char here = mvinch(tmpr, 6 + tmpc);
if (here == 'O' || here == '^' || here == '|') {
return false;
}
}
return true;
}
bool Tetris::check_result()
{
int row = 24;
while (row >= 4) {
bool clean = true;
for (int j = 7; j <= 16; j++) {
if (static_cast<char>(mvinch(row, j)) != 'O') {
clean = false;
break;
}
}
if (clean) {
lines_++;
for (int i = row; i > 0; i--) {
for (int j = 7; j <= 16; j++) {
chtype c = mvinch(i - 1, j);
mvaddch(i, j, '.');
if (static_cast<char>(c) == 'O')
mvaddch(i, j, c);
}
}
}
else
row--;
}
return !check_place(now_row_, now_col_);
}
int main()
{
srand(time(NULL));
initscr();
if(has_colors()) {
start_color();
int orange = COLOR_WHITE;
int yellow = COLOR_YELLOW;
if (COLORS > 8) {
orange = 215;
yellow = 221;
}
init_pair(1, COLOR_CYAN, COLOR_BLACK);
init_pair(2, yellow, COLOR_BLACK);
init_pair(3, orange, COLOR_BLACK);
init_pair(4, COLOR_BLUE, COLOR_BLACK);
init_pair(5, COLOR_GREEN, COLOR_BLACK);
init_pair(6, COLOR_RED, COLOR_BLACK);
init_pair(7, COLOR_MAGENTA, COLOR_BLACK);
}
else {
fill(Tetris::colors + 1, Tetris::colors + 8, 0);
}
// raw();
keypad(stdscr, true);
noecho();
timeout(10);
Tetris tetris = Tetris();
char mode = '1';
tetris.play(mode);
while(tetris.is_playing()) {
tetris.update();
refresh();
}
refresh();
endwin();
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment