Created
November 11, 2017 09:15
-
-
Save leomao/130f070c7dbe1d051c1656723fcb48e5 to your computer and use it in GitHub Desktop.
Tetris battle in one cpp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/////////////////////////////////////////// | |
// 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