====== Tetris Take 10 ====== 作成日: 2023-07-27 (木) [[https://youtu.be/K2ZSzyf-4pM|初心者によるC++入門 #42 画像を適用する]] {{:youtube:tetris_take10.gif}} ==== main.cpp ==== #include "board.h" #include "gfxaux.h" #include "piece.h" #include "shape.h" #include "player.h" #include "eventprocessor.h" #include #include #include #include #include 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; } ==== game.cpp ==== #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; } ==== rectaux.h ==== #ifndef RECTAUX_H #define RECTAUX_H #include using APoint = SDL_Point; using APointF = SDL_FPoint; using PPoint = SDL_Point; #endif ==== renderstates.h ==== #ifndef RENDERSTATES_H #define RENDERSTATES_H struct RenderStates { float tx = 0; float ty = 0; }; #endif ==== gfxaux.h ==== #ifndef GFXAUX_H #define GFXAUX_H #include 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 ==== gfxaux.cpp ==== #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); } ==== board.h ==== #ifndef BOARD_H #define BOARD_H #include "texturedcell.h" #include "rectaux.h" #include #include 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; Fence setup_fence(BoardInfo const &board, SDL_Texture *texture, SDL_Color const &color); void draw_fence(SDL_Renderer *renderer, Fence const &fence); #endif ==== board.cpp ==== #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(arena.x * board_->cell_width); int y = static_cast(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); } } ==== cell.h (未使用) ==== #ifndef CELL_H #define CELL_H #include "rectaux.h" #include 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 ==== cell.cpp (未使用) ==== #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(states.tx), y + static_cast(states.ty), board_->cell_width, board_->cell_height}; draw_rectangle(renderer, rect, fill_color_, outline_color_); } ==== texturedcell.h ==== #ifndef TEXTUREDCELL_H #define TEXTUREDCELL_H #include "rectaux.h" #include 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 ==== texturedcell.cpp ==== #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(states.tx), y + static_cast(states.ty), board_->cell_width, board_->cell_height}; SDL_SetTextureColorMod(texture_, color_.r, color_.g, color_.b); SDL_RenderCopy(renderer, texture_, nullptr, &rect); } ==== block.h ==== #ifndef BLOCK_H #define BLOCK_H #include "texturedcell.h" #include "rectaux.h" #include "board.h" #include "renderstates.h" #include #include 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; void draw_blocks(SDL_Renderer *renderer, BlockList const &blocks, RenderStates states = {}); #endif ==== block.cpp ==== #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); } } ==== piece.h ==== #ifndef PIECE_H #define PIECE_H #include "rectaux.h" #include "block.h" #include "board.h" #include "blockshape.h" #include #include 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 ==== piece.cpp ==== #include "piece.h" #include "gfxaux.h" #include "renderstates.h" #include "board.h" #include "block.h" #include "shape.h" #include #include #include 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(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(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(std::floor(fxx)); y = static_cast(std::floor(fyy)); block.set_local_position(x, y); } } void draw_piece(SDL_Renderer *renderer, Piece const &piece) { piece.draw(renderer); } ==== eventprocessor.h ==== #ifndef EVENTPROCESSOR_H #define EVENTPROCESSOR_H class EventProcessor { public: EventProcessor(); void process(); bool should_quit_game() const; private: bool should_quit_; }; #endif ==== eventprocessor.cpp ==== #include "eventprocessor.h" #include 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_; } ==== keyboardstate.h ==== #ifndef KEYBOARDSTATE_H #define KEYBOARDSTATE_H #include #include 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 cur_state_; std::vector old_state_; }; #endif ==== keyboardstate.cpp ==== #include "keyboardstate.h" #include 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(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()); } ==== player.h ==== #ifndef PLAYER_H #define PLAYER_H #include "keyboardstate.h" #include #include #include class Piece; class KeyboardState; class Player { public: enum class Action { MoveLeft, MoveRight, RotateLeft, RotateRight, SoftDrop, HardDrop, }; using CommandQueue = std::queue; 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_map_; }; #endif ==== player.cpp ==== #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; } ==== shape.h ==== #ifndef SHAPE_H #define SHAPE_H #include "rectaux.h" #include #include #include class Shape { public: Shape(); void add_points(std::initializer_list 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 texture() const; void set_center(float x, float y); APointF center() const; private: std::vector 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 shapes_; }; #endif ==== shape.cpp ==== #include "shape.h" #include Shape::Shape() : pts_{} , texture_{} , color_{} , center_{} {} void Shape::add_points(std::initializer_list 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 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 distrib{0, shapes_.size() - 1}; size_t shape_index = distrib(random_generator_); return shapes_.at(shape_index); } ==== blockshape.h ==== #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 ==== blockshape.cpp ==== #include "blockshape.h" #include "shape.h" #include 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_); } } ==== Makefile ==== 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} ==== resources ==== resources/cell.png {{:youtube:cell.png?100|resources/cell.png}}