youtube:cpp-intro-043
差分
このページの2つのバージョン間の差分を表示します。
両方とも前のリビジョン前のリビジョン次のリビジョン | 前のリビジョン | ||
youtube:cpp-intro-043 [2024/02/28 18:26] – 削除 - 外部編集 (不明な日付) 127.0.0.1 | youtube:cpp-intro-043 [2024/07/12 02:49] (現在) – [resources] freemikan | ||
---|---|---|---|
行 1: | 行 1: | ||
+ | ====== Tetris Take 11 ====== | ||
+ | |||
+ | 作成日: 2023-07-27 (木) | ||
+ | |||
+ | [[https:// | ||
+ | |||
+ | {{: | ||
+ | |||
+ | ==== main.cpp ==== | ||
+ | |||
+ | <file cpp> | ||
+ | #include " | ||
+ | #include " | ||
+ | #include " | ||
+ | #include " | ||
+ | #include " | ||
+ | #include " | ||
+ | |||
+ | #include < | ||
+ | #include < | ||
+ | #include < | ||
+ | #include < | ||
+ | #include < | ||
+ | |||
+ | auto setup_board() -> BoardInfo; | ||
+ | auto setup_shapes(SDL_Texture *texture) -> ShapeGenerator; | ||
+ | auto setup_piece(BoardInfo const &board, | ||
+ | | ||
+ | | ||
+ | auto setup_player() -> Player; | ||
+ | auto setup_fence(BoardInfo const &board, SDL_Texture *texture) -> Fence; | ||
+ | |||
+ | void reset_game(Piece &piece, | ||
+ | APoint spawn_point, | ||
+ | BlockList & | ||
+ | Player:: | ||
+ | piece.detach_blocks(dead_blocks); | ||
+ | dead_blocks.clear(); | ||
+ | // clear queue | ||
+ | while (!command_queue.empty()) { | ||
+ | command_queue.pop(); | ||
+ | } | ||
+ | piece.generate_next_shape(); | ||
+ | piece.reset_position(spawn_point); | ||
+ | } | ||
+ | |||
+ | void cleanup_perfect_lines(BlockList & | ||
+ | std:: | ||
+ | [] (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, | ||
+ | } | ||
+ | }; | ||
+ | | ||
+ | auto mark_as_perfect = [perfect_value] (auto &block) { | ||
+ | int x = block.local_position().x; | ||
+ | block.set_local_position(x, | ||
+ | }; | ||
+ | |||
+ | for (auto iter = dead_list.begin(); | ||
+ | if (iter-> | ||
+ | ++count; | ||
+ | ++iter; | ||
+ | } else { | ||
+ | if (count == width) { | ||
+ | std:: | ||
+ | std:: | ||
+ | //iter = dead_list.erase(lap, | ||
+ | } | ||
+ | count = 0; | ||
+ | lap = iter; | ||
+ | } | ||
+ | } | ||
+ | | ||
+ | if (count == width) { | ||
+ | std:: | ||
+ | std:: | ||
+ | // | ||
+ | } | ||
+ | | ||
+ | std:: | ||
+ | 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:: | ||
+ | Piece &piece, | ||
+ | BlockList const & | ||
+ | while (!command_queue.empty()) { | ||
+ | Player:: | ||
+ | switch (action) { | ||
+ | case Player:: | ||
+ | piece.move_left(dead_blocks); | ||
+ | break; | ||
+ | case Player:: | ||
+ | piece.move_right(dead_blocks); | ||
+ | break; | ||
+ | case Player:: | ||
+ | piece.rotate_left(dead_blocks); | ||
+ | break; | ||
+ | case Player:: | ||
+ | piece.rotate_right(dead_blocks); | ||
+ | break; | ||
+ | default: | ||
+ | break; | ||
+ | } | ||
+ | command_queue.pop(); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | void process_piece_stuck(Piece &piece, | ||
+ | | ||
+ | | ||
+ | | ||
+ | if (piece.is_stuck()) { | ||
+ | piece.detach_blocks(dead_blocks); | ||
+ | cleanup_perfect_lines(dead_blocks, | ||
+ | | ||
+ | piece.generate_next_shape(); | ||
+ | piece.reset_position(PieceSpawnPoint); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | int main(int argc, char **argv) { | ||
+ | auto *WindowTitle = " | ||
+ | auto *CellTexturePath = " | ||
+ | int const ScreenWidth = 600; // px | ||
+ | int const ScreenHeight = 550; // px | ||
+ | | ||
+ | if (SDL_Init(SDL_INIT_VIDEO) < 0) { | ||
+ | std::cerr << " | ||
+ | std:: | ||
+ | } | ||
+ | | ||
+ | if (IMG_Init(IMG_INIT_PNG) != IMG_INIT_PNG) { | ||
+ | std::cerr << " | ||
+ | std:: | ||
+ | } | ||
+ | | ||
+ | SDL_Window *window = SDL_CreateWindow( | ||
+ | WindowTitle, | ||
+ | SDL_WINDOWPOS_CENTERED, | ||
+ | SDL_WINDOWPOS_CENTERED, | ||
+ | ScreenWidth, | ||
+ | ScreenHeight, | ||
+ | 0); | ||
+ | if (window == nullptr) { | ||
+ | std::cerr << " | ||
+ | std:: | ||
+ | } | ||
+ | | ||
+ | SDL_Renderer *renderer = SDL_CreateRenderer(window, | ||
+ | if (renderer == nullptr) { | ||
+ | std::cerr << " | ||
+ | std:: | ||
+ | } | ||
+ | | ||
+ | SDL_Texture *cell_texture = IMG_LoadTexture(renderer, | ||
+ | if (cell_texture == nullptr) { | ||
+ | std::cerr << " | ||
+ | std:: | ||
+ | } | ||
+ | | ||
+ | |||
+ | // setup game | ||
+ | int const ReadyCount = 40; | ||
+ | APoint const PieceSpawnPoint{3, | ||
+ | int const MillisecsPerUpdate = 16; | ||
+ | |||
+ | auto board = setup_board(); | ||
+ | auto shape_generator = setup_shapes(cell_texture); | ||
+ | auto active_piece = setup_piece(board, | ||
+ | auto player = setup_player(); | ||
+ | auto fence = setup_fence(board, | ||
+ | |||
+ | Player:: | ||
+ | BlockList dead_blocks; | ||
+ | |||
+ | // game loop | ||
+ | EventProcessor event_processor; | ||
+ | int ready_counter = ReadyCount; | ||
+ | while (!event_processor.should_quit_game()) { | ||
+ | Uint32 start = SDL_GetTicks(); | ||
+ | | ||
+ | // process events | ||
+ | event_processor.process(); | ||
+ | if (event_processor.should_reset_game()) { | ||
+ | reset_game(active_piece, | ||
+ | event_processor.reset(); | ||
+ | ready_counter = ReadyCount; | ||
+ | } | ||
+ | | ||
+ | // update | ||
+ | if (ready_counter <= 0) { | ||
+ | player.update(command_queue); | ||
+ | process_commands(command_queue, | ||
+ | active_piece.update(dead_blocks); | ||
+ | process_piece_stuck(active_piece, | ||
+ | } else { | ||
+ | --ready_counter; | ||
+ | } | ||
+ | | ||
+ | // render | ||
+ | SDL_SetRenderDrawColor(renderer, | ||
+ | SDL_RenderClear(renderer); | ||
+ | draw_background(renderer, | ||
+ | draw_fence(renderer, | ||
+ | draw_piece(renderer, | ||
+ | draw_blocks(renderer, | ||
+ | SDL_RenderPresent(renderer); | ||
+ | |||
+ | synch_frame_rate(start, | ||
+ | } | ||
+ | |||
+ | // cleanup all texture references | ||
+ | active_piece.detach_blocks(dead_blocks); | ||
+ | dead_blocks.clear(); | ||
+ | fence.clear(); | ||
+ | shape_generator.unregister_all_shapes(); | ||
+ | | ||
+ | // destroy SDL objects | ||
+ | SDL_DestroyTexture(cell_texture); | ||
+ | SDL_DestroyRenderer(renderer); | ||
+ | SDL_DestroyWindow(window); | ||
+ | |||
+ | IMG_Quit(); | ||
+ | SDL_Quit(); | ||
+ | |||
+ | return 0; | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | ==== setupgame.cpp ==== | ||
+ | |||
+ | <file cpp> | ||
+ | #include " | ||
+ | #include " | ||
+ | #include " | ||
+ | #include " | ||
+ | #include " | ||
+ | |||
+ | BoardInfo setup_board() { | ||
+ | return BoardInfo { | ||
+ | 30, 30, // arena width and height | ||
+ | 16, 16, // cell width and height | ||
+ | PPoint{10, 10} // board position | ||
+ | }; | ||
+ | } | ||
+ | |||
+ | ShapeGenerator setup_shapes(SDL_Texture *texture) { | ||
+ | Shape O; | ||
+ | O.add_points({{0, | ||
+ | O.set_texture(texture, | ||
+ | O.set_center(0.5, | ||
+ | | ||
+ | Shape T; | ||
+ | T.add_points({{0, | ||
+ | T.set_texture(texture, | ||
+ | T.set_center(1, | ||
+ | |||
+ | Shape I; | ||
+ | I.add_points({{0, | ||
+ | I.set_texture(texture, | ||
+ | I.set_center(1.5, | ||
+ | |||
+ | Shape Z; | ||
+ | Z.add_points({{0, | ||
+ | Z.set_texture(texture, | ||
+ | Z.set_center(1, | ||
+ | |||
+ | Shape S; | ||
+ | S.add_points({{1, | ||
+ | S.set_texture(texture, | ||
+ | S.set_center(1, | ||
+ | |||
+ | Shape L; | ||
+ | L.add_points({{0, | ||
+ | L.set_texture(texture, | ||
+ | L.set_center(0, | ||
+ | |||
+ | Shape J; | ||
+ | J.add_points({{1, | ||
+ | J.set_texture(texture, | ||
+ | J.set_center(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; | ||
+ | } | ||
+ | |||
+ | Piece setup_piece(BoardInfo const &board, APoint const & | ||
+ | float falling_speed = 0.1; | ||
+ | unsigned stuck_delay = 40; | ||
+ | return Piece{& | ||
+ | } | ||
+ | |||
+ | Player setup_player() { | ||
+ | Player player; | ||
+ | | ||
+ | player.install_action(Player:: | ||
+ | player.install_action(Player:: | ||
+ | player.install_action(Player:: | ||
+ | player.install_action(Player:: | ||
+ | | ||
+ | return player; | ||
+ | } | ||
+ | |||
+ | Fence setup_fence(BoardInfo const &board, SDL_Texture *texture) { | ||
+ | SDL_Color color = Silver; | ||
+ | |||
+ | Fence fence; | ||
+ | |||
+ | // left wall | ||
+ | for (int i{}; i < board.arena_height; | ||
+ | PPoint pt{0, i * board.cell_height}; | ||
+ | fence.emplace_back(& | ||
+ | } | ||
+ | |||
+ | // right wall | ||
+ | for (int i{}; i < board.arena_height; | ||
+ | PPoint pt{(board.arena_width + 1) * board.cell_width, | ||
+ | fence.emplace_back(& | ||
+ | } | ||
+ | |||
+ | // floor | ||
+ | for (int i{}; i < board.arena_width + 2; ++i) { | ||
+ | PPoint pt{i * board.cell_width, | ||
+ | fence.emplace_back(& | ||
+ | } | ||
+ | |||
+ | return fence; | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | ==== rectaux.h ==== | ||
+ | |||
+ | <file cpp> | ||
+ | #ifndef RECTAUX_H | ||
+ | #define RECTAUX_H | ||
+ | |||
+ | #include < | ||
+ | |||
+ | using APoint = SDL_Point; | ||
+ | using APointF = SDL_FPoint; | ||
+ | using PPoint = SDL_Point; | ||
+ | |||
+ | #endif | ||
+ | </ | ||
+ | |||
+ | ==== renderstates.h ==== | ||
+ | |||
+ | <file cpp> | ||
+ | #ifndef RENDERSTATES_H | ||
+ | #define RENDERSTATES_H | ||
+ | |||
+ | struct RenderStates { | ||
+ | float tx = 0; | ||
+ | float ty = 0; | ||
+ | }; | ||
+ | |||
+ | #endif | ||
+ | </ | ||
+ | |||
+ | ==== gfxaux.h ==== | ||
+ | |||
+ | <file cpp> | ||
+ | #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, | ||
+ | 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 ==== | ||
+ | |||
+ | <file cpp> | ||
+ | #include " | ||
+ | |||
+ | void draw_rectangle(SDL_Renderer *renderer, SDL_Rect const &rect, SDL_Color const &fc, SDL_Color const &oc) { | ||
+ | SDL_SetRenderDrawColor(renderer, | ||
+ | SDL_RenderFillRect(renderer, | ||
+ | SDL_SetRenderDrawColor(renderer, | ||
+ | SDL_RenderDrawRect(renderer, | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | ==== board.h ==== | ||
+ | |||
+ | <file cpp> | ||
+ | #ifndef BOARD_H | ||
+ | #define BOARD_H | ||
+ | |||
+ | #include " | ||
+ | #include " | ||
+ | #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:: | ||
+ | |||
+ | void draw_background(SDL_Renderer *renderer, BoardInfo const & | ||
+ | void draw_fence(SDL_Renderer *renderer, Fence const & | ||
+ | |||
+ | #endif | ||
+ | </ | ||
+ | |||
+ | ==== board.cpp ==== | ||
+ | |||
+ | <file cpp> | ||
+ | #include " | ||
+ | |||
+ | #include " | ||
+ | #include " | ||
+ | |||
+ | CoordMediator:: | ||
+ | : board_{board} | ||
+ | {} | ||
+ | |||
+ | PPoint CoordMediator:: | ||
+ | int x = arena.x * board_-> | ||
+ | int y = arena.y * board_-> | ||
+ | return {x, y}; | ||
+ | } | ||
+ | |||
+ | PPoint CoordMediator:: | ||
+ | int x = static_cast< | ||
+ | int y = static_cast< | ||
+ | return {x, y}; | ||
+ | } | ||
+ | |||
+ | APoint CoordMediator:: | ||
+ | int x = pixel.x / board_-> | ||
+ | int y = pixel.y / board_-> | ||
+ | return {x, y}; | ||
+ | } | ||
+ | |||
+ | PPoint CoordMediator:: | ||
+ | int x = board_-> | ||
+ | int y = board_-> | ||
+ | return {x, y}; | ||
+ | } | ||
+ | |||
+ | int CoordMediator:: | ||
+ | return 0; | ||
+ | } | ||
+ | |||
+ | int CoordMediator:: | ||
+ | return arena_top() + board_-> | ||
+ | } | ||
+ | |||
+ | int CoordMediator:: | ||
+ | return 0; | ||
+ | } | ||
+ | int CoordMediator:: | ||
+ | return arena_left() + board_-> | ||
+ | } | ||
+ | |||
+ | BoardInfo const *CoordMediator:: | ||
+ | return board_; | ||
+ | } | ||
+ | |||
+ | void draw_background(SDL_Renderer *renderer, BoardInfo const &board) { | ||
+ | CoordMediator mediator{& | ||
+ | auto [ox, oy] = mediator.arena_origin_px(); | ||
+ | SDL_Rect rect{ox, oy, | ||
+ | board.cell_width * board.arena_width, | ||
+ | board.cell_height * board.arena_height}; | ||
+ | draw_rectangle(renderer, | ||
+ | } | ||
+ | |||
+ | void draw_fence(SDL_Renderer *renderer, Fence const &fence) { | ||
+ | RenderStates states{}; | ||
+ | for (auto &cell : fence) { | ||
+ | cell.draw(renderer, | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | ==== texturedcell.h ==== | ||
+ | |||
+ | <file cpp> | ||
+ | #ifndef TEXTUREDCELL_H | ||
+ | #define TEXTUREDCELL_H | ||
+ | |||
+ | #include " | ||
+ | #include < | ||
+ | |||
+ | struct BoardInfo; | ||
+ | struct RenderStates; | ||
+ | |||
+ | class TexturedCell { | ||
+ | public: | ||
+ | TexturedCell(BoardInfo const *board, | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | 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 ==== | ||
+ | |||
+ | <file cpp> | ||
+ | #include " | ||
+ | |||
+ | #include " | ||
+ | #include " | ||
+ | |||
+ | TexturedCell:: | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | : board_{board} | ||
+ | , origin_{origin} | ||
+ | , position_{position} | ||
+ | , texture_{texture} | ||
+ | , color_{color} | ||
+ | {} | ||
+ | |||
+ | PPoint TexturedCell:: | ||
+ | return {origin_.x + position_.x, | ||
+ | } | ||
+ | |||
+ | PPoint TexturedCell:: | ||
+ | return position_; | ||
+ | } | ||
+ | |||
+ | void TexturedCell:: | ||
+ | auto [x, y] = global_position(); | ||
+ | SDL_Rect rect{x + static_cast< | ||
+ | y + static_cast< | ||
+ | board_-> | ||
+ | | ||
+ | SDL_SetTextureColorMod(texture_, | ||
+ | SDL_RenderCopy(renderer, | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | ==== block.h ==== | ||
+ | |||
+ | <file cpp> | ||
+ | #ifndef BLOCK_H | ||
+ | #define BLOCK_H | ||
+ | |||
+ | #include " | ||
+ | #include " | ||
+ | #include " | ||
+ | #include " | ||
+ | #include < | ||
+ | #include < | ||
+ | |||
+ | struct RenderStates; | ||
+ | class CoordMediator; | ||
+ | class Piece; | ||
+ | |||
+ | class Block { | ||
+ | public: | ||
+ | Block(BoardInfo const *board, | ||
+ | APoint const & | ||
+ | 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 & | ||
+ | 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:: | ||
+ | |||
+ | void draw_blocks(SDL_Renderer *renderer, BlockList const & | ||
+ | |||
+ | #endif | ||
+ | </ | ||
+ | |||
+ | ==== block.cpp ==== | ||
+ | |||
+ | <file cpp> | ||
+ | #include " | ||
+ | |||
+ | #include " | ||
+ | #include " | ||
+ | #include " | ||
+ | #include " | ||
+ | |||
+ | namespace { | ||
+ | TexturedCell create_cell(BoardInfo const *board, | ||
+ | | ||
+ | | ||
+ | CoordMediator mediator{board}; | ||
+ | PPoint origin = mediator.arena_origin_px(); | ||
+ | return TexturedCell{board, | ||
+ | } | ||
+ | } // ns anon | ||
+ | |||
+ | Block:: | ||
+ | | ||
+ | | ||
+ | | ||
+ | Piece const *parent) | ||
+ | : cell_{create_cell(board, | ||
+ | , coord_mediator_{board} | ||
+ | , position_{position} | ||
+ | , parent_{parent} | ||
+ | {} | ||
+ | | ||
+ | void Block:: | ||
+ | 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, | ||
+ | } | ||
+ | |||
+ | APoint Block:: | ||
+ | return position_; | ||
+ | } | ||
+ | |||
+ | APoint Block:: | ||
+ | if (parent_ == nullptr) { | ||
+ | return position_; | ||
+ | } | ||
+ | auto [px, py] = parent_-> | ||
+ | return {px + position_.x, | ||
+ | } | ||
+ | |||
+ | void Block:: | ||
+ | position_ = position; | ||
+ | } | ||
+ | |||
+ | void Block:: | ||
+ | position_.x = x; | ||
+ | position_.y = y; | ||
+ | } | ||
+ | |||
+ | void Block:: | ||
+ | position_ = global_position(); | ||
+ | parent_ = nullptr; | ||
+ | } | ||
+ | |||
+ | void draw_blocks(SDL_Renderer *renderer, BlockList const & | ||
+ | for (auto &block : blocks) { | ||
+ | block.draw(renderer, | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | ==== piece.h ==== | ||
+ | |||
+ | <file cpp> | ||
+ | #ifndef PIECE_H | ||
+ | #define PIECE_H | ||
+ | |||
+ | #include " | ||
+ | #include " | ||
+ | #include " | ||
+ | #include " | ||
+ | #include < | ||
+ | #include < | ||
+ | |||
+ | class ShapeGenerator; | ||
+ | |||
+ | class Piece { | ||
+ | public: | ||
+ | Piece(BoardInfo const *board, | ||
+ | APoint const & | ||
+ | float falling_speed, | ||
+ | unsigned stuck_delay, | ||
+ | ShapeGenerator & | ||
+ | | ||
+ | void draw(SDL_Renderer *renderer) const; | ||
+ | APoint local_position() const; | ||
+ | APoint global_position() const; | ||
+ | void update(BlockList const & | ||
+ | |||
+ | bool move_left(BlockList const & | ||
+ | bool move_right(BlockList const & | ||
+ | bool rotate_left(BlockList const & | ||
+ | bool rotate_right(BlockList const & | ||
+ | |||
+ | bool is_stuck() const; | ||
+ | void detach_blocks(BlockList & | ||
+ | void reset_position(APoint const & | ||
+ | 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 & | ||
+ | bool is_overlapping(BlockList 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 & | ||
+ | APointF shape_center_; | ||
+ | }; | ||
+ | |||
+ | void draw_piece(SDL_Renderer *renderer, Piece const & | ||
+ | |||
+ | #endif | ||
+ | </ | ||
+ | |||
+ | ==== piece.cpp ==== | ||
+ | |||
+ | <file cpp> | ||
+ | #include " | ||
+ | |||
+ | #include " | ||
+ | #include " | ||
+ | #include " | ||
+ | #include " | ||
+ | #include " | ||
+ | #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:: | ||
+ | | ||
+ | float falling_speed, | ||
+ | | ||
+ | | ||
+ | : blocks_{} | ||
+ | , stuck_count_{0} | ||
+ | , coord_mediator_{board} | ||
+ | , block_shape_builder_{board, | ||
+ | , position_{} | ||
+ | , falling_speed_{falling_speed} | ||
+ | , fy_{} | ||
+ | , stuck_delay_{stuck_delay} | ||
+ | , shape_generator_{shape_generator} | ||
+ | , shape_center_{} { | ||
+ | reset_position(position); | ||
+ | generate_next_shape(); | ||
+ | } | ||
+ | |||
+ | void Piece:: | ||
+ | RenderStates states{float(position_.x), | ||
+ | draw_blocks(renderer, | ||
+ | } | ||
+ | |||
+ | APoint Piece:: | ||
+ | return position_; | ||
+ | } | ||
+ | |||
+ | APoint Piece:: | ||
+ | return position_; | ||
+ | } | ||
+ | |||
+ | void Piece:: | ||
+ | if (is_landing(dead_blocks)) { | ||
+ | fy_ = std:: | ||
+ | ++stuck_count_; | ||
+ | } else { | ||
+ | fy_ += falling_speed_; | ||
+ | position_.y = static_cast< | ||
+ | stuck_count_ = 0; | ||
+ | } | ||
+ | } | ||
+ | |||
+ | bool Piece:: | ||
+ | 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:: | ||
+ | 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:: | ||
+ | unchecked_rotate(Rotation:: | ||
+ | if (!inside_arena()) { | ||
+ | unchecked_rotate(Rotation:: | ||
+ | return false; | ||
+ | } | ||
+ | if (is_overlapping(dead_blocks)) { | ||
+ | unchecked_rotate(Rotation:: | ||
+ | return false; | ||
+ | } | ||
+ | return true; | ||
+ | } | ||
+ | |||
+ | bool Piece:: | ||
+ | unchecked_rotate(Rotation:: | ||
+ | if (!inside_arena()) { | ||
+ | unchecked_rotate(Rotation:: | ||
+ | return false; | ||
+ | } | ||
+ | if (is_overlapping(dead_blocks)) { | ||
+ | unchecked_rotate(Rotation:: | ||
+ | return false; | ||
+ | } | ||
+ | return true; | ||
+ | } | ||
+ | |||
+ | bool Piece:: | ||
+ | return stuck_count_ >= stuck_delay_; | ||
+ | } | ||
+ | |||
+ | void Piece:: | ||
+ | for (auto &block : blocks_) { | ||
+ | block.detach_from_parent(); | ||
+ | } | ||
+ | std:: | ||
+ | std:: | ||
+ | blocks_.clear(); | ||
+ | } | ||
+ | |||
+ | void Piece:: | ||
+ | position_ = position; | ||
+ | fy_ = static_cast< | ||
+ | } | ||
+ | |||
+ | void Piece:: | ||
+ | assert(blocks_.empty()); | ||
+ | Shape shape = shape_generator_.next_random_shape(); | ||
+ | block_shape_builder_.emplace_blocks(blocks_, | ||
+ | shape_center_ = shape.center(); | ||
+ | } | ||
+ | |||
+ | int Piece:: | ||
+ | assert(!blocks_.empty()); | ||
+ | auto b = std:: | ||
+ | return b-> | ||
+ | } | ||
+ | |||
+ | int Piece:: | ||
+ | assert(!blocks_.empty()); | ||
+ | auto b = std:: | ||
+ | return b-> | ||
+ | } | ||
+ | |||
+ | int Piece:: | ||
+ | assert(!blocks_.empty()); | ||
+ | auto b = std:: | ||
+ | return b-> | ||
+ | } | ||
+ | |||
+ | int Piece:: | ||
+ | assert(!blocks_.empty()); | ||
+ | auto b = std:: | ||
+ | return b-> | ||
+ | } | ||
+ | |||
+ | bool Piece:: | ||
+ | if (bottom() >= coord_mediator_.arena_bottom()) { | ||
+ | return true; | ||
+ | } | ||
+ | | ||
+ | for (auto & | ||
+ | for (auto & | ||
+ | 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:: | ||
+ | for (auto & | ||
+ | for (auto & | ||
+ | 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:: | ||
+ | if (diff < 0.6) { | ||
+ | return true; | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | return false; | ||
+ | } | ||
+ | |||
+ | bool Piece:: | ||
+ | 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:: | ||
+ | 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:: | ||
+ | fxx = fy; | ||
+ | fyy = -fx; | ||
+ | } else { | ||
+ | assert(direction == Rotation:: | ||
+ | fxx = -fy; | ||
+ | fyy = fx; | ||
+ | } | ||
+ | | ||
+ | fxx += shape_center_.x; | ||
+ | fyy += shape_center_.y; | ||
+ | x = static_cast< | ||
+ | y = static_cast< | ||
+ | block.set_local_position(x, | ||
+ | } | ||
+ | } | ||
+ | |||
+ | void draw_piece(SDL_Renderer *renderer, Piece const &piece) { | ||
+ | piece.draw(renderer); | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | ==== eventprocessor.h ==== | ||
+ | |||
+ | <file cpp> | ||
+ | #ifndef EVENTPROCESSOR_H | ||
+ | #define EVENTPROCESSOR_H | ||
+ | |||
+ | class EventProcessor { | ||
+ | public: | ||
+ | EventProcessor(); | ||
+ | | ||
+ | void process(); | ||
+ | void reset(); | ||
+ | bool should_quit_game() const; | ||
+ | bool should_reset_game() const; | ||
+ | | ||
+ | private: | ||
+ | bool should_quit_; | ||
+ | bool should_reset_; | ||
+ | }; | ||
+ | |||
+ | #endif | ||
+ | </ | ||
+ | |||
+ | ==== eventprocessor.cpp ==== | ||
+ | |||
+ | <file cpp> | ||
+ | #include " | ||
+ | |||
+ | #include < | ||
+ | |||
+ | EventProcessor:: | ||
+ | : should_quit_{false} | ||
+ | , should_reset_{false} | ||
+ | {} | ||
+ | |||
+ | void EventProcessor:: | ||
+ | SDL_Event event; | ||
+ | while (SDL_PollEvent(& | ||
+ | switch (event.type) { | ||
+ | case SDL_QUIT: | ||
+ | should_quit_ = true; | ||
+ | break; | ||
+ | case SDL_KEYDOWN: | ||
+ | if (event.key.keysym.scancode == SDL_SCANCODE_ESCAPE) { | ||
+ | should_quit_ = true; | ||
+ | } | ||
+ | if (event.key.keysym.scancode == SDL_SCANCODE_R) { | ||
+ | should_reset_ = true; | ||
+ | } | ||
+ | break; | ||
+ | default: | ||
+ | break; | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | |||
+ | void EventProcessor:: | ||
+ | should_quit_ = false; | ||
+ | should_reset_ = false; | ||
+ | } | ||
+ | |||
+ | bool EventProcessor:: | ||
+ | return should_quit_; | ||
+ | } | ||
+ | |||
+ | bool EventProcessor:: | ||
+ | return should_reset_; | ||
+ | }</ | ||
+ | |||
+ | ==== keyboardstate.h ==== | ||
+ | |||
+ | <file cpp> | ||
+ | #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:: | ||
+ | std:: | ||
+ | }; | ||
+ | |||
+ | #endif | ||
+ | </ | ||
+ | |||
+ | ==== keyboardstate.cpp ==== | ||
+ | |||
+ | <file cpp> | ||
+ | #include " | ||
+ | |||
+ | #include < | ||
+ | |||
+ | KeyboardState:: | ||
+ | init(); | ||
+ | } | ||
+ | |||
+ | void KeyboardState:: | ||
+ | int numkeys; | ||
+ | auto *state = SDL_GetKeyboardState(& | ||
+ | | ||
+ | if (old_state_.size() < cur_state_.size()) { | ||
+ | old_state_.resize(cur_state_.size(), | ||
+ | } | ||
+ | std:: | ||
+ | | ||
+ | if (cur_state_.size() < static_cast< | ||
+ | cur_state_.resize(numkeys, | ||
+ | } | ||
+ | std:: | ||
+ | } | ||
+ | |||
+ | bool KeyboardState:: | ||
+ | return cur_state_[scancode] == 1; | ||
+ | } | ||
+ | |||
+ | bool KeyboardState:: | ||
+ | return old_state_[scancode] == 0 && cur_state_[scancode] == 1; | ||
+ | } | ||
+ | |||
+ | void KeyboardState:: | ||
+ | cur_state_.clear(); | ||
+ | old_state_.clear(); | ||
+ | | ||
+ | int numkeys; | ||
+ | auto *state = SDL_GetKeyboardState(& | ||
+ | cur_state_.resize(numkeys, | ||
+ | old_state_.resize(numkeys, | ||
+ | | ||
+ | std:: | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | ==== player.h ==== | ||
+ | |||
+ | <file cpp> | ||
+ | #ifndef PLAYER_H | ||
+ | #define PLAYER_H | ||
+ | |||
+ | #include " | ||
+ | #include < | ||
+ | #include <map> | ||
+ | #include < | ||
+ | |||
+ | class Piece; | ||
+ | class KeyboardState; | ||
+ | |||
+ | class Player { | ||
+ | public: | ||
+ | enum class Action { | ||
+ | MoveLeft, | ||
+ | MoveRight, | ||
+ | RotateLeft, | ||
+ | RotateRight, | ||
+ | SoftDrop, | ||
+ | HardDrop, | ||
+ | }; | ||
+ | | ||
+ | using CommandQueue = std:: | ||
+ | |||
+ | Player(); | ||
+ | | ||
+ | void update(CommandQueue & | ||
+ | void install_action(Action action, SDL_Scancode scancode); | ||
+ | | ||
+ | private: | ||
+ | bool just_pressed(Action action) const; | ||
+ | |||
+ | private: | ||
+ | KeyboardState keyboard_; | ||
+ | std:: | ||
+ | }; | ||
+ | |||
+ | #endif | ||
+ | </ | ||
+ | |||
+ | ==== player.cpp ==== | ||
+ | |||
+ | <file cpp> | ||
+ | #include " | ||
+ | |||
+ | #include " | ||
+ | |||
+ | Player:: | ||
+ | : keyboard_{} | ||
+ | {} | ||
+ | | ||
+ | void Player:: | ||
+ | keyboard_.update(); | ||
+ | for (auto [action, scancode] : action_map_) { | ||
+ | if (keyboard_.just_pressed(scancode)) { | ||
+ | command_queue.push(action); | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | |||
+ | void Player:: | ||
+ | action_map_.insert({action, | ||
+ | } | ||
+ | |||
+ | bool Player:: | ||
+ | if (auto p = action_map_.find(action); | ||
+ | p != action_map_.end()) { | ||
+ | return keyboard_.just_pressed(p-> | ||
+ | } | ||
+ | return false; | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | ==== shape.h ==== | ||
+ | |||
+ | <file cpp> | ||
+ | #ifndef SHAPE_H | ||
+ | #define SHAPE_H | ||
+ | |||
+ | #include " | ||
+ | #include < | ||
+ | #include < | ||
+ | #include < | ||
+ | |||
+ | class Shape { | ||
+ | public: | ||
+ | Shape(); | ||
+ | | ||
+ | void add_points(std:: | ||
+ | void add_point(APoint const & | ||
+ | size_t point_count() const; | ||
+ | APoint point_at(size_t i) const; | ||
+ | | ||
+ | void set_texture(SDL_Texture *texture, SDL_Color const & | ||
+ | std:: | ||
+ | |||
+ | void set_center(float x, float y); | ||
+ | APointF center() const; | ||
+ | |||
+ | private: | ||
+ | std:: | ||
+ | SDL_Texture *texture_; | ||
+ | SDL_Color color_; | ||
+ | APointF center_; | ||
+ | }; | ||
+ | |||
+ | |||
+ | class ShapeGenerator { | ||
+ | public: | ||
+ | ShapeGenerator(); | ||
+ | ShapeGenerator(ShapeGenerator const &) = delete; | ||
+ | ShapeGenerator & | ||
+ | ShapeGenerator(ShapeGenerator &&) = default; | ||
+ | ShapeGenerator & | ||
+ | | ||
+ | void register_shape(Shape const & | ||
+ | void unregister_all_shapes(); | ||
+ | Shape next_random_shape(); | ||
+ | | ||
+ | private: | ||
+ | std:: | ||
+ | std:: | ||
+ | }; | ||
+ | |||
+ | #endif | ||
+ | </ | ||
+ | |||
+ | ==== shape.cpp ==== | ||
+ | |||
+ | <file cpp> | ||
+ | #include " | ||
+ | |||
+ | #include < | ||
+ | |||
+ | Shape:: | ||
+ | : pts_{} | ||
+ | , texture_{} | ||
+ | , color_{} | ||
+ | , center_{} | ||
+ | {} | ||
+ | |||
+ | void Shape:: | ||
+ | pts_.insert(pts_.end(), | ||
+ | } | ||
+ | |||
+ | void Shape:: | ||
+ | pts_.push_back(point); | ||
+ | } | ||
+ | |||
+ | size_t Shape:: | ||
+ | return pts_.size(); | ||
+ | } | ||
+ | |||
+ | APoint Shape:: | ||
+ | return pts_.at(i); | ||
+ | } | ||
+ | |||
+ | void Shape:: | ||
+ | texture_ = texture; | ||
+ | color_ = color; | ||
+ | } | ||
+ | |||
+ | std:: | ||
+ | return {texture_, color_}; | ||
+ | } | ||
+ | |||
+ | void Shape:: | ||
+ | center_.x = x; | ||
+ | center_.y = y; | ||
+ | } | ||
+ | APointF Shape:: | ||
+ | return center_; | ||
+ | } | ||
+ | |||
+ | ShapeGenerator:: | ||
+ | : random_generator_{std:: | ||
+ | , shapes_{} | ||
+ | {} | ||
+ | |||
+ | void ShapeGenerator:: | ||
+ | shapes_.push_back(shape); | ||
+ | } | ||
+ | |||
+ | void ShapeGenerator:: | ||
+ | shapes_.clear(); | ||
+ | } | ||
+ | |||
+ | Shape ShapeGenerator:: | ||
+ | assert(!shapes_.empty()); | ||
+ | std:: | ||
+ | size_t shape_index = distrib(random_generator_); | ||
+ | return shapes_.at(shape_index); | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | ==== blockshape.h ==== | ||
+ | |||
+ | <file cpp> | ||
+ | #ifndef BLOCKSHAPE_H | ||
+ | #define BLOCKSHAPE_H | ||
+ | |||
+ | #include " | ||
+ | |||
+ | class Shape; | ||
+ | struct BoardInfo; | ||
+ | class Piece; | ||
+ | |||
+ | class BlockShapeBuilder { | ||
+ | public: | ||
+ | BlockShapeBuilder(BoardInfo const *board, Piece const *parent); | ||
+ | void emplace_blocks(BlockList & | ||
+ | |||
+ | private: | ||
+ | BoardInfo const *board_; | ||
+ | Piece const *parent_; | ||
+ | }; | ||
+ | |||
+ | # | ||
+ | |||
+ | ==== blockshape.cpp ==== | ||
+ | |||
+ | <file cpp> | ||
+ | #include " | ||
+ | |||
+ | #include " | ||
+ | #include < | ||
+ | |||
+ | BlockShapeBuilder:: | ||
+ | : board_{board} | ||
+ | , parent_{parent} | ||
+ | {} | ||
+ | |||
+ | void BlockShapeBuilder:: | ||
+ | auto const & | ||
+ | for (size_t i{}; i < shape.point_count(); | ||
+ | APoint point = shape.point_at(i); | ||
+ | blocks.emplace_back(board_, | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | ==== Makefile ==== | ||
+ | |||
+ | <file makefile> | ||
+ | SRCS = \ | ||
+ | main.cpp \ | ||
+ | setupgame.cpp \ | ||
+ | board.cpp \ | ||
+ | gfxaux.cpp \ | ||
+ | block.cpp \ | ||
+ | piece.cpp \ | ||
+ | player.cpp \ | ||
+ | keyboardstate.cpp \ | ||
+ | eventprocessor.cpp \ | ||
+ | shape.cpp \ | ||
+ | blockshape.cpp \ | ||
+ | texturedcell.cpp | ||
+ | |||
+ | OBJS = ${SRCS: | ||
+ | DEPS = ${SRCS: | ||
+ | EXECUTABLE = tetris.exe | ||
+ | CXXFLAGS = -std=c++20 -Wall | ||
+ | CPPFLAGS = -IC: | ||
+ | LDFLAGS = -LC: | ||
+ | LDLIBS = -lmingw32 -lSDL2main -lSDL2 -lSDL2_image | ||
+ | |||
+ | .PHONY: all | ||
+ | all: ${EXECUTABLE} | ||
+ | |||
+ | ${EXECUTABLE}: | ||
+ | ${CXX} -o $@ $^ ${LDFLAGS} ${LDLIBS} | ||
+ | |||
+ | %.o: %.cpp | ||
+ | ${CXX} -c ${CXXFLAGS} -o $@ $< -MMD -MP ${CPPFLAGS} | ||
+ | |||
+ | .PHONY: clean | ||
+ | clean: | ||
+ | ${RM} ${EXECUTABLE} ${OBJS} ${DEPS} | ||
+ | |||
+ | -include ${DEPS} | ||
+ | </ | ||
+ | |||
+ | ==== resources ==== | ||
+ | |||
+ | resources/ | ||
+ | |||
+ | {{: | ||