#include "board.h"
#include "gfxaux.h"
#include "piece.h"
#include "shape.h"
#include "player.h"
#include "eventprocessor.h"
#include <SDL2/SDL.h>
#include <SDL2/SDL_image.h>
#include <iostream>
#include <algorithm>
#include <vector>
ShapeGenerator setup_shapes(SDL_Texture *texture);
void cleanup_perfect_lines(BlockList &dead_list, int width) {
std::sort(dead_list.begin(), dead_list.end(),
[] (auto &a, auto &b) {
return a.global_position().y < b.global_position().y;
});
int const perfect_value = -10000;
int count = 0;
auto lap = dead_list.begin();
auto move_down = [perfect_value] (auto &block) {
auto [x, y] = block.local_position();
if (y != perfect_value) {
block.set_local_position(x, y + 1);
}
};
auto mark_as_perfect = [perfect_value] (auto &block) {
int x = block.local_position().x;
block.set_local_position(x, perfect_value);
};
for (auto iter = dead_list.begin(); iter != dead_list.end(); ) {
if (iter->global_position().y == lap->global_position().y) {
++count;
++iter;
} else {
if (count == width) {
std::for_each(dead_list.begin(), lap, move_down);
std::for_each(lap, iter, mark_as_perfect);
//iter = dead_list.erase(lap, iter);
}
count = 0;
lap = iter;
}
}
if (count == width) {
std::for_each(dead_list.begin(), lap, move_down);
std::for_each(lap, dead_list.end(), mark_as_perfect);
//dead_list.erase(lap, dead_list.end());
}
std::erase_if(dead_list, [perfect_value] (auto &block) {
return block.local_position().y == perfect_value;
});
}
void synch_frame_rate(Uint32 start, Uint32 msecs_per_update) {
Uint32 next_start = start + msecs_per_update;
Uint32 current = SDL_GetTicks();
if (next_start >= current) {
SDL_Delay(next_start - current);
}
}
void process_commands(Player::CommandQueue &command_queue,
Piece &piece,
BlockList const &dead_blocks) {
while (!command_queue.empty()) {
Player::Action action = command_queue.front();
switch (action) {
case Player::Action::MoveLeft:
piece.move_left(dead_blocks);
break;
case Player::Action::MoveRight:
piece.move_right(dead_blocks);
break;
case Player::Action::RotateLeft:
piece.rotate_left(dead_blocks);
break;
case Player::Action::RotateRight:
piece.rotate_right(dead_blocks);
break;
default:
break;
}
command_queue.pop();
}
}
void process_piece_stuck(Piece &piece,
BlockList &dead_blocks,
APoint const &piece_spawn_position,
BoardInfo const &board) {
if (piece.is_stuck()) {
piece.detach_blocks(dead_blocks);
cleanup_perfect_lines(dead_blocks, board.arena_width);
piece.generate_next_shape();
piece.reset_position(piece_spawn_position);
}
}
int main(int argc, char **argv) {
auto *WindowTitle = "TETRIS take 10";
auto *CellTexturePath = "resources/cell.png";
int const ScreenWidth = 600; // px
int const ScreenHeight = 550; // px
int const MillisecsPerUpdate = 16;
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
std::cerr << "Error: init SDL\n";
std::exit(1);
}
if (IMG_Init(IMG_INIT_PNG) != IMG_INIT_PNG) {
std::cerr << "Error: " << IMG_GetError() << '\n';
std::exit(1);
}
SDL_Window *window = SDL_CreateWindow(
WindowTitle,
SDL_WINDOWPOS_CENTERED,
SDL_WINDOWPOS_CENTERED,
ScreenWidth,
ScreenHeight,
0);
if (window == nullptr) {
std::cerr << "Error: create window\n";
std::exit(1);
}
SDL_Renderer *renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
if (renderer == nullptr) {
std::cerr << "Error: create renderer\n";
std::exit(1);
}
SDL_Texture *cell_texture = IMG_LoadTexture(renderer, CellTexturePath);
if (cell_texture == nullptr) {
std::cerr << "Error: " << IMG_GetError() << '\n';
std::exit(1);
}
// setup game
BoardInfo board {
10, 20, // arena width and height
24, 24, // cell width and height
PPoint{10, 10} // board position
};
APoint const piece_spawn_position{3, 0};
ShapeGenerator shape_generator = setup_shapes(cell_texture);
EventProcessor event_processor;
Fence fence = setup_fence(board, cell_texture, Silver);
Piece test_piece{&board, piece_spawn_position, 0.1, 40, shape_generator};
Player player;
player.install_action(Player::Action::MoveLeft, SDL_SCANCODE_LEFT);
player.install_action(Player::Action::MoveRight, SDL_SCANCODE_RIGHT);
player.install_action(Player::Action::RotateLeft, SDL_SCANCODE_Z);
player.install_action(Player::Action::RotateRight, SDL_SCANCODE_X);
Player::CommandQueue command_queue;
BlockList dead_blocks;
// game loop
while (!event_processor.should_quit_game()) {
Uint32 start = SDL_GetTicks();
// process events
event_processor.process();
// update
player.update(command_queue);
process_commands(command_queue, test_piece, dead_blocks);
test_piece.update(dead_blocks);
process_piece_stuck(test_piece, dead_blocks, piece_spawn_position, board);
// render
SDL_SetRenderDrawColor(renderer, 100, 100, 255, 255);
SDL_RenderClear(renderer);
draw_fence(renderer, fence);
draw_piece(renderer, test_piece);
draw_blocks(renderer, dead_blocks);
SDL_RenderPresent(renderer);
synch_frame_rate(start, MillisecsPerUpdate);
}
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
return 0;
}
#include "shape.h"
#include "gfxaux.h"
ShapeGenerator setup_shapes(SDL_Texture *texture) {
Shape O;
O.add_points({{0, 0}, {1, 0}, {0, 1}, {1, 1}});
O.set_texture(texture, Yellow);
O.set_center(0.5, 0.5);
Shape T;
T.add_points({{0, 0}, {1, 0}, {2, 0}, {1, 1}});
T.set_texture(texture, Lime);
T.set_center(1, 0);
Shape I;
I.add_points({{0, 0}, {1, 0}, {2, 0}, {3, 0}});
I.set_texture(texture, Red);
I.set_center(1.5, 0.5);
Shape Z;
Z.add_points({{0, 0}, {1, 0}, {1, 1}, {2, 1}});
Z.set_texture(texture, Green);
Z.set_center(1, 0);
Shape S;
S.add_points({{1, 0}, {2, 0}, {0, 1}, {1, 1}});
S.set_texture(texture, Maroon);
S.set_center(1, 0);
Shape L;
L.add_points({{0, 0}, {0, 1}, {0, 2}, {1, 2}});
L.set_texture(texture, Aqua);
L.set_center(0, 1);
Shape J;
J.add_points({{1, 0}, {1, 1}, {0, 2}, {1, 2}});
J.set_texture(texture, Fuchsia);
J.set_center(1, 1);
ShapeGenerator generator;
generator.register_shape(O);
generator.register_shape(T);
generator.register_shape(I);
generator.register_shape(Z);
generator.register_shape(S);
generator.register_shape(L);
generator.register_shape(J);
return generator;
}
#ifndef RECTAUX_H
#define RECTAUX_H
#include <SDL2/SDL.h>
using APoint = SDL_Point;
using APointF = SDL_FPoint;
using PPoint = SDL_Point;
#endif
#ifndef RENDERSTATES_H
#define RENDERSTATES_H
struct RenderStates {
float tx = 0;
float ty = 0;
};
#endif
#ifndef GFXAUX_H
#define GFXAUX_H
#include <SDL2/SDL.h>
SDL_Color const Aqua{0, 255, 255, 255};
SDL_Color const Black{0, 0, 0, 255};
SDL_Color const Blue{0, 0, 255, 255};
SDL_Color const Fuchsia{255, 0, 255, 255};
SDL_Color const Gray{128, 128, 128, 255};
SDL_Color const Green{0, 128, 0, 255};
SDL_Color const Lime{0, 255, 0, 255};
SDL_Color const Maroon{128, 0, 0, 255};
SDL_Color const Navy{0, 0, 128, 255};
SDL_Color const Olive{128, 128, 0, 255};
SDL_Color const Purple{128, 0, 128, 255};
SDL_Color const Red{255, 0, 0, 255};
SDL_Color const Silver{192, 192, 192, 255};
SDL_Color const Teal{0, 128, 128, 255};
SDL_Color const White{255, 255, 255, 255};
SDL_Color const Yellow{255, 255, 0, 255};
void draw_rectangle(SDL_Renderer *renderer, SDL_Rect const &rect, SDL_Color const &fc, SDL_Color const &oc);
#endif
#include "gfxaux.h"
void draw_rectangle(SDL_Renderer *renderer, SDL_Rect const &rect, SDL_Color const &fc, SDL_Color const &oc) {
SDL_SetRenderDrawColor(renderer, fc.r, fc.g, fc.b, fc.a);
SDL_RenderFillRect(renderer, &rect);
SDL_SetRenderDrawColor(renderer, oc.r, oc.g, oc.b, oc.a);
SDL_RenderDrawRect(renderer, &rect);
}
#ifndef BOARD_H
#define BOARD_H
#include "texturedcell.h"
#include "rectaux.h"
#include <SDL2/SDL.h>
#include <vector>
struct BoardInfo {
int arena_width;
int arena_height;
int cell_width;
int cell_height;
PPoint position;
};
class CoordMediator {
public:
CoordMediator(BoardInfo const *board);
PPoint arena_to_pixel(APoint const &arena) const;
PPoint arena_to_pixel(APointF const &arena) const;
APoint pixel_to_arena(PPoint const &pixel) const;
PPoint arena_origin_px() const;
int arena_top() const;
int arena_bottom() const;
int arena_left() const;
int arena_right() const;
BoardInfo const *board_info() const;
private:
BoardInfo const *board_;
};
using Fence = std::vector<TexturedCell>;
Fence setup_fence(BoardInfo const &board,
SDL_Texture *texture,
SDL_Color const &color);
void draw_fence(SDL_Renderer *renderer, Fence const &fence);
#endif
#include "board.h"
#include "gfxaux.h"
#include "renderstates.h"
// #include "cell.h"
CoordMediator::CoordMediator(BoardInfo const *board)
: board_{board}
{}
PPoint CoordMediator::arena_to_pixel(APoint const &arena) const {
int x = arena.x * board_->cell_width;
int y = arena.y * board_->cell_height;
return {x, y};
}
PPoint CoordMediator::arena_to_pixel(APointF const &arena) const {
int x = static_cast<int>(arena.x * board_->cell_width);
int y = static_cast<int>(arena.y * board_->cell_height);
return {x, y};
}
APoint CoordMediator::pixel_to_arena(PPoint const &pixel) const {
int x = pixel.x / board_->cell_width;
int y = pixel.y / board_->cell_height;
return {x, y};
}
PPoint CoordMediator::arena_origin_px() const {
int x = board_->position.x + board_->cell_width;
int y = board_->position.y;
return {x, y};
}
int CoordMediator::arena_top() const {
return 0;
}
int CoordMediator::arena_bottom() const {
return arena_top() + board_->arena_height - 1;
}
int CoordMediator::arena_left() const {
return 0;
}
int CoordMediator::arena_right() const {
return arena_left() + board_->arena_width - 1;
}
BoardInfo const *CoordMediator::board_info() const {
return board_;
}
Fence setup_fence(BoardInfo const &board,
SDL_Texture *texture,
SDL_Color const &color) {
Fence fence;
// left wall
for (int i{}; i < board.arena_height; ++i) {
PPoint pt{0, i * board.cell_height};
fence.emplace_back(&board, board.position, pt, texture, color);
}
// right wall
for (int i{}; i < board.arena_height; ++i) {
PPoint pt{(board.arena_width + 1) * board.cell_width, i * board.cell_height};
fence.emplace_back(&board, board.position, pt, texture, color);
}
// floor
for (int i{}; i < board.arena_width + 2; ++i) {
PPoint pt{i * board.cell_width, board.arena_height * board.cell_height};
fence.emplace_back(&board, board.position, pt, texture, color);
}
return fence;
}
void draw_fence(SDL_Renderer *renderer, Fence const &fence) {
RenderStates states{};
for (auto &cell : fence) {
cell.draw(renderer, states);
}
}
#ifndef CELL_H
#define CELL_H
#include "rectaux.h"
#include <SDL2/SDL.h>
struct BoardInfo;
struct RenderStates;
class Cell {
public:
Cell(BoardInfo const *board,
PPoint const &origin,
PPoint const &position,
SDL_Color const &fill_color,
SDL_Color const &outline_color);
~Cell() = default;
PPoint global_position() const;
PPoint local_position() const;
void draw(SDL_Renderer *renderer, RenderStates states) const;
private:
BoardInfo const *board_;
PPoint origin_;
PPoint position_;
SDL_Color fill_color_;
SDL_Color outline_color_;
};
#endif
#include "cell.h"
#include "gfxaux.h"
#include "renderstates.h"
#include "board.h"
Cell::Cell(BoardInfo const *board,
PPoint const &origin,
PPoint const &position,
SDL_Color const &fill_color,
SDL_Color const &outline_color)
: board_{board}
, origin_{origin}
, position_{position}
, fill_color_{fill_color}
, outline_color_{outline_color} {
}
PPoint Cell::global_position() const {
return {origin_.x + position_.x, origin_.y + position_.y};
}
PPoint Cell::local_position() const {
return position_;
}
void Cell::draw(SDL_Renderer *renderer, RenderStates states) const {
auto [x, y] = global_position();
SDL_Rect rect{x + static_cast<int>(states.tx),
y + static_cast<int>(states.ty),
board_->cell_width, board_->cell_height};
draw_rectangle(renderer, rect, fill_color_, outline_color_);
}
#ifndef TEXTUREDCELL_H
#define TEXTUREDCELL_H
#include "rectaux.h"
#include <SDL2/SDL.h>
struct BoardInfo;
struct RenderStates;
class TexturedCell {
public:
TexturedCell(BoardInfo const *board,
PPoint const &origin,
PPoint const &position,
SDL_Texture *texture,
SDL_Color const &color);
PPoint global_position() const;
PPoint local_position() const;
void draw(SDL_Renderer *renderer, RenderStates states) const;
private:
BoardInfo const *board_;
PPoint origin_;
PPoint position_;
SDL_Texture *texture_;
SDL_Color color_;
};
#endif
#include "texturedcell.h"
#include "renderstates.h"
#include "board.h"
TexturedCell::TexturedCell(BoardInfo const *board,
PPoint const &origin,
PPoint const &position,
SDL_Texture *texture,
SDL_Color const &color)
: board_{board}
, origin_{origin}
, position_{position}
, texture_{texture}
, color_{color}
{}
PPoint TexturedCell::global_position() const {
return {origin_.x + position_.x, origin_.y + position_.y};
}
PPoint TexturedCell::local_position() const {
return position_;
}
void TexturedCell::draw(SDL_Renderer *renderer, RenderStates states) const {
auto [x, y] = global_position();
SDL_Rect rect{x + static_cast<int>(states.tx),
y + static_cast<int>(states.ty),
board_->cell_width, board_->cell_height};
SDL_SetTextureColorMod(texture_, color_.r, color_.g, color_.b);
SDL_RenderCopy(renderer, texture_, nullptr, &rect);
}
#ifndef BLOCK_H
#define BLOCK_H
#include "texturedcell.h"
#include "rectaux.h"
#include "board.h"
#include "renderstates.h"
#include <SDL2/SDL.h>
#include <vector>
struct RenderStates;
class CoordMediator;
class Piece;
class Block {
public:
Block(BoardInfo const *board,
APoint const &position,
SDL_Texture *texture,
SDL_Color const &color,
Piece const *parent = nullptr);
void draw(SDL_Renderer *renderer, RenderStates states) const;
APoint local_position() const;
APoint global_position() const;
void set_local_position(APoint const &position);
void set_local_position(int x, int y);
void detach_from_parent();
private:
TexturedCell cell_;
CoordMediator coord_mediator_;
APoint position_;
Piece const *parent_;
};
using BlockList = std::vector<Block>;
void draw_blocks(SDL_Renderer *renderer, BlockList const &blocks, RenderStates states = {});
#endif
#include "block.h"
#include "gfxaux.h"
#include "renderstates.h"
#include "board.h"
#include "piece.h"
namespace {
TexturedCell create_cell(BoardInfo const *board,
SDL_Texture *texture,
SDL_Color const &color) {
CoordMediator mediator{board};
PPoint origin = mediator.arena_origin_px();
return TexturedCell{board, origin, APoint{0, 0}, texture, color};
}
} // ns anon
Block::Block(BoardInfo const *board,
APoint const &position,
SDL_Texture *texture,
SDL_Color const &color,
Piece const *parent)
: cell_{create_cell(board, texture, color)}
, coord_mediator_{board}
, position_{position}
, parent_{parent}
{}
void Block::draw(SDL_Renderer *renderer, RenderStates states) const {
SDL_FPoint tt;
tt.x = states.tx + position_.x;
tt.y = states.ty + position_.y;
auto [tx, ty] = coord_mediator_.arena_to_pixel(tt);
cell_.draw(renderer, RenderStates{float(tx), float(ty)});
}
APoint Block::local_position() const {
return position_;
}
APoint Block::global_position() const {
if (parent_ == nullptr) {
return position_;
}
auto [px, py] = parent_->global_position();
return {px + position_.x, py + position_.y};
}
void Block::set_local_position(APoint const &position) {
position_ = position;
}
void Block::set_local_position(int x, int y) {
position_.x = x;
position_.y = y;
}
void Block::detach_from_parent() {
position_ = global_position();
parent_ = nullptr;
}
void draw_blocks(SDL_Renderer *renderer, BlockList const &blocks, RenderStates states) {
for (auto &block : blocks) {
block.draw(renderer, states);
}
}
#ifndef PIECE_H
#define PIECE_H
#include "rectaux.h"
#include "block.h"
#include "board.h"
#include "blockshape.h"
#include <SDL2/SDL.h>
#include <vector>
class ShapeGenerator;
class Piece {
public:
Piece(BoardInfo const *board,
APoint const &position,
float falling_speed,
unsigned stuck_delay,
ShapeGenerator &shape_generator);
void draw(SDL_Renderer *renderer) const;
APoint local_position() const;
APoint global_position() const;
void update(BlockList const &dead_blocks);
bool move_left(BlockList const &dead_blocks);
bool move_right(BlockList const &dead_blocks);
bool rotate_left(BlockList const &dead_blocks);
bool rotate_right(BlockList const &dead_blocks);
bool is_stuck() const;
void detach_blocks(BlockList &destination);
void reset_position(APoint const &position);
void generate_next_shape();
private:
enum class Rotation { Left, Right };
int top() const;
int bottom() const;
int left() const;
int right() const;
bool is_landing(BlockList const &dead_blocks) const;
bool is_overlapping(BlockList const &dead_blocks) const;
bool inside_arena() const;
void unchecked_rotate(Rotation direction);
private:
BlockList blocks_;
unsigned stuck_count_;
CoordMediator coord_mediator_;
BlockShapeBuilder block_shape_builder_;
APoint position_;
float falling_speed_;
float fy_;
unsigned stuck_delay_;
ShapeGenerator &shape_generator_;
APointF shape_center_;
};
void draw_piece(SDL_Renderer *renderer, Piece const &piece);
#endif
#include "piece.h"
#include "gfxaux.h"
#include "renderstates.h"
#include "board.h"
#include "block.h"
#include "shape.h"
#include <cmath>
#include <cassert>
#include <algorithm>
namespace {
bool comp_block_x(Block const &a, Block const &b) {
return a.local_position().x < b.local_position().x;
}
bool comp_block_y(Block const &a, Block const &b) {
return a.local_position().y < b.local_position().y;
}
} // ns anon
Piece::Piece(BoardInfo const *board,
APoint const &position,
float falling_speed,
unsigned stuck_delay,
ShapeGenerator &shape_generator)
: blocks_{}
, stuck_count_{0}
, coord_mediator_{board}
, block_shape_builder_{board, this}
, position_{}
, falling_speed_{falling_speed}
, fy_{}
, stuck_delay_{stuck_delay}
, shape_generator_{shape_generator}
, shape_center_{} {
reset_position(position);
generate_next_shape();
}
void Piece::draw(SDL_Renderer *renderer) const {
RenderStates states{float(position_.x), fy_};
draw_blocks(renderer, blocks_, states);
}
APoint Piece::local_position() const {
return position_;
}
APoint Piece::global_position() const {
return position_;
}
void Piece::update(BlockList const &dead_blocks) {
if (is_landing(dead_blocks)) {
fy_ = std::trunc(fy_);
++stuck_count_;
} else {
fy_ += falling_speed_;
position_.y = static_cast<int>(fy_);
stuck_count_ = 0;
}
}
bool Piece::move_left(BlockList const &dead_blocks) {
position_.x -= 1;
if (left() < coord_mediator_.arena_left()) {
position_.x += 1;
return false;
}
if (is_overlapping(dead_blocks)) {
position_.x += 1;
return false;
}
return true;
}
bool Piece::move_right(BlockList const &dead_blocks) {
position_.x += 1;
if (right() > coord_mediator_.arena_right()) {
position_.x -= 1;
return false;
}
if (is_overlapping(dead_blocks)) {
position_.x -= 1;
return false;
}
return true;
}
bool Piece::rotate_left(BlockList const &dead_blocks) {
unchecked_rotate(Rotation::Left);
if (!inside_arena()) {
unchecked_rotate(Rotation::Right);
return false;
}
if (is_overlapping(dead_blocks)) {
unchecked_rotate(Rotation::Right);
return false;
}
return true;
}
bool Piece::rotate_right(BlockList const &dead_blocks) {
unchecked_rotate(Rotation::Right);
if (!inside_arena()) {
unchecked_rotate(Rotation::Left);
return false;
}
if (is_overlapping(dead_blocks)) {
unchecked_rotate(Rotation::Left);
return false;
}
return true;
}
bool Piece::is_stuck() const {
return stuck_count_ >= stuck_delay_;
}
void Piece::detach_blocks(BlockList &destination) {
for (auto &block : blocks_) {
block.detach_from_parent();
}
std::move(blocks_.begin(), blocks_.end(),
std::back_inserter(destination));
blocks_.clear();
}
void Piece::reset_position(APoint const &position) {
position_ = position;
fy_ = static_cast<float>(position_.y);
}
void Piece::generate_next_shape() {
assert(blocks_.empty());
Shape shape = shape_generator_.next_random_shape();
block_shape_builder_.emplace_blocks(blocks_, shape);
shape_center_ = shape.center();
}
int Piece::top() const {
assert(!blocks_.empty());
auto b = std::min_element(blocks_.begin(), blocks_.end(), comp_block_y);
return b->global_position().y;
}
int Piece::bottom() const {
assert(!blocks_.empty());
auto b = std::max_element(blocks_.begin(), blocks_.end(), comp_block_y);
return b->global_position().y;
}
int Piece::left() const {
assert(!blocks_.empty());
auto b = std::min_element(blocks_.begin(), blocks_.end(), comp_block_x);
return b->global_position().x;
}
int Piece::right() const {
assert(!blocks_.empty());
auto b = std::max_element(blocks_.begin(), blocks_.end(), comp_block_x);
return b->global_position().x;
}
bool Piece::is_landing(BlockList const &dead_blocks) const {
if (bottom() >= coord_mediator_.arena_bottom()) {
return true;
}
for (auto &myblock : blocks_) {
for (auto &dead_block : dead_blocks) {
auto [mx, my] = myblock.global_position();
auto [dx, dy] = dead_block.global_position();
if (mx == dx && my + 1 == dy) {
return true;
}
}
}
return false;
}
bool Piece::is_overlapping(BlockList const &dead_blocks) const {
for (auto &myblock : blocks_) {
for (auto &dead_block : dead_blocks) {
auto mx = myblock.global_position().x;
auto [dx, dy] = dead_block.global_position();
if (mx != dx) continue;
float my = myblock.local_position().y + fy_;
float diff = std::fabs(my - dy);
if (diff < 0.6) {
return true;
}
}
}
return false;
}
bool Piece::inside_arena() const {
if (left() < coord_mediator_.arena_left()) {
return false;
}
if (right() > coord_mediator_.arena_right()) {
return false;
}
if (bottom() > coord_mediator_.arena_bottom()) {
return false;
}
return true;
}
void Piece::unchecked_rotate(Rotation direction) {
for (auto &block : blocks_) {
auto [x, y] = block.local_position();
float fx = x - shape_center_.x;
float fy = y - shape_center_.y;
float fxx{}, fyy{};
if (direction == Rotation::Left) {
fxx = fy;
fyy = -fx;
} else {
assert(direction == Rotation::Right);
fxx = -fy;
fyy = fx;
}
fxx += shape_center_.x;
fyy += shape_center_.y;
x = static_cast<int>(std::floor(fxx));
y = static_cast<int>(std::floor(fyy));
block.set_local_position(x, y);
}
}
void draw_piece(SDL_Renderer *renderer, Piece const &piece) {
piece.draw(renderer);
}
#ifndef EVENTPROCESSOR_H
#define EVENTPROCESSOR_H
class EventProcessor {
public:
EventProcessor();
void process();
bool should_quit_game() const;
private:
bool should_quit_;
};
#endif
#include "eventprocessor.h"
#include <SDL2/SDL.h>
EventProcessor::EventProcessor()
: should_quit_{false}
{}
void EventProcessor::process() {
SDL_Event event;
while (SDL_PollEvent(&event)) {
switch (event.type) {
case SDL_QUIT:
should_quit_ = true;
break;
case SDL_KEYDOWN:
if (event.key.keysym.scancode == SDL_SCANCODE_ESCAPE) {
should_quit_ = true;
}
break;
default:
break;
}
}
}
bool EventProcessor::should_quit_game() const {
return should_quit_;
}
#ifndef KEYBOARDSTATE_H
#define KEYBOARDSTATE_H
#include <SDL2/SDL.h>
#include <vector>
class KeyboardState {
public:
KeyboardState();
void update();
bool pressed(SDL_Scancode scancode) const;
bool just_pressed(SDL_Scancode scancode) const;
private:
void init();
private:
std::vector<Uint8> cur_state_;
std::vector<Uint8> old_state_;
};
#endif
#include "keyboardstate.h"
#include <algorithm>
KeyboardState::KeyboardState() {
init();
}
void KeyboardState::update() {
int numkeys;
auto *state = SDL_GetKeyboardState(&numkeys);
if (old_state_.size() < cur_state_.size()) {
old_state_.resize(cur_state_.size(), 0);
}
std::copy(cur_state_.begin(), cur_state_.end(), old_state_.begin());
if (cur_state_.size() < static_cast<size_t>(numkeys)) {
cur_state_.resize(numkeys, 0);
}
std::copy(state, state + numkeys, cur_state_.begin());
}
bool KeyboardState::pressed(SDL_Scancode scancode) const {
return cur_state_[scancode] == 1;
}
bool KeyboardState::just_pressed(SDL_Scancode scancode) const {
return old_state_[scancode] == 0 && cur_state_[scancode] == 1;
}
void KeyboardState::init() {
cur_state_.clear();
old_state_.clear();
int numkeys;
auto *state = SDL_GetKeyboardState(&numkeys);
cur_state_.resize(numkeys, 0);
old_state_.resize(numkeys, 0);
std::copy(state, state + numkeys, cur_state_.begin());
}
#ifndef PLAYER_H
#define PLAYER_H
#include "keyboardstate.h"
#include <SDL2/SDL.h>
#include <map>
#include <queue>
class Piece;
class KeyboardState;
class Player {
public:
enum class Action {
MoveLeft,
MoveRight,
RotateLeft,
RotateRight,
SoftDrop,
HardDrop,
};
using CommandQueue = std::queue<Action>;
Player();
void update(CommandQueue &command_queue);
void install_action(Action action, SDL_Scancode scancode);
private:
bool just_pressed(Action action) const;
private:
KeyboardState keyboard_;
std::map<Action, SDL_Scancode> action_map_;
};
#endif
#include "player.h"
#include "keyboardstate.h"
Player::Player()
: keyboard_{}
{}
void Player::update(CommandQueue &command_queue) {
keyboard_.update();
for (auto [action, scancode] : action_map_) {
if (keyboard_.just_pressed(scancode)) {
command_queue.push(action);
}
}
}
void Player::install_action(Action action, SDL_Scancode scancode) {
action_map_.insert({action, scancode});
}
bool Player::just_pressed(Action action) const {
if (auto p = action_map_.find(action);
p != action_map_.end()) {
return keyboard_.just_pressed(p->second);
}
return false;
}
#ifndef SHAPE_H
#define SHAPE_H
#include "rectaux.h"
#include <SDL2/SDL.h>
#include <vector>
#include <random>
class Shape {
public:
Shape();
void add_points(std::initializer_list<APoint> points);
void add_point(APoint const &point);
size_t point_count() const;
APoint point_at(size_t i) const;
void set_texture(SDL_Texture *texture, SDL_Color const &color);
std::pair<SDL_Texture *, SDL_Color> texture() const;
void set_center(float x, float y);
APointF center() const;
private:
std::vector<APoint> pts_;
SDL_Texture *texture_;
SDL_Color color_;
APointF center_;
};
class ShapeGenerator {
public:
ShapeGenerator();
ShapeGenerator(ShapeGenerator const &) = delete;
ShapeGenerator &operator=(ShapeGenerator const &) = delete;
ShapeGenerator(ShapeGenerator &&) = default;
ShapeGenerator &operator=(ShapeGenerator &&) = default;
void register_shape(Shape const &shape);
Shape next_random_shape();
private:
std::mt19937 random_generator_;
std::vector<Shape> shapes_;
};
#endif
#include "shape.h"
#include <cassert>
Shape::Shape()
: pts_{}
, texture_{}
, color_{}
, center_{}
{}
void Shape::add_points(std::initializer_list<APoint> points) {
pts_.insert(pts_.end(), points.begin(), points.end());
}
void Shape::add_point(APoint const &point) {
pts_.push_back(point);
}
size_t Shape::point_count() const {
return pts_.size();
}
APoint Shape::point_at(size_t i) const {
return pts_.at(i);
}
void Shape::set_texture(SDL_Texture *texture, SDL_Color const &color) {
texture_ = texture;
color_ = color;
}
std::pair<SDL_Texture *, SDL_Color> Shape::texture() const {
return {texture_, color_};
}
void Shape::set_center(float x, float y) {
center_.x = x;
center_.y = y;
}
APointF Shape::center() const {
return center_;
}
ShapeGenerator::ShapeGenerator()
: random_generator_{std::random_device{}()}
, shapes_{}
{}
void ShapeGenerator::register_shape(Shape const &shape) {
shapes_.push_back(shape);
}
Shape ShapeGenerator::next_random_shape() {
assert(!shapes_.empty());
std::uniform_int_distribution<size_t> distrib{0, shapes_.size() - 1};
size_t shape_index = distrib(random_generator_);
return shapes_.at(shape_index);
}
#ifndef BLOCKSHAPE_H
#define BLOCKSHAPE_H
#include "block.h"
class Shape;
struct BoardInfo;
class Piece;
class BlockShapeBuilder {
public:
BlockShapeBuilder(BoardInfo const *board, Piece const *parent);
void emplace_blocks(BlockList &blocks, Shape const &shape) const;
private:
BoardInfo const *board_;
Piece const *parent_;
};
#endif
#include "blockshape.h"
#include "shape.h"
#include <SDL2/SDL.h>
BlockShapeBuilder::BlockShapeBuilder(BoardInfo const *board, Piece const *parent)
: board_{board}
, parent_{parent}
{}
void BlockShapeBuilder::emplace_blocks(BlockList &blocks, Shape const &shape) const {
auto const &[texture, color] = shape.texture();
for (size_t i{}; i < shape.point_count(); ++i) {
APoint point = shape.point_at(i);
blocks.emplace_back(board_, point, texture, color, parent_);
}
}
SRCS = \
main.cpp \
game.cpp \
board.cpp \
gfxaux.cpp \
cell.cpp \
block.cpp \
piece.cpp \
player.cpp \
keyboardstate.cpp \
eventprocessor.cpp \
shape.cpp \
blockshape.cpp \
texturedcell.cpp
OBJS = ${SRCS:%.cpp=%.o}
DEPS = ${SRCS:%.cpp=%.d}
EXECUTABLE = tetris.exe
CXXFLAGS = -std=c++20 -Wall
CPPFLAGS = -IC:\SDL2\include
LDFLAGS = -LC:\SDL2\lib
LDLIBS = -lmingw32 -lSDL2main -lSDL2 -lSDL2_image
.PHONY: all
all: ${EXECUTABLE}
${EXECUTABLE}: ${OBJS}
${CXX} -o $@ $^ ${LDFLAGS} ${LDLIBS}
%.o: %.cpp
${CXX} -c ${CXXFLAGS} -o $@ $< -MMD -MP ${CPPFLAGS}
.PHONY: clean
clean:
${RM} ${EXECUTABLE} ${OBJS} ${DEPS}
-include ${DEPS}