ユーザ用ツール

サイト用ツール


youtube:cpp-intro-041

差分

このページの2つのバージョン間の差分を表示します。

この比較画面へのリンク

両方とも前のリビジョン前のリビジョン
次のリビジョン
前のリビジョン
youtube:cpp-intro-041 [2024/02/28 18:26] – 削除 - 外部編集 (不明な日付) 127.0.0.1youtube:cpp-intro-041 [2024/02/29 11:54] (現在) – [Tetris Take 9] freemikan
行 1: 行 1:
 +====== Tetris Take 9 ======
 +
 +作成日: 2023-07-26 (水)
 +
 +[[https://youtu.be/AcBisl1CA_8|初心者によるC++入門 #41 揃ったら消えるようにする]]
 +
 +{{:youtube:tetris_take9d.gif}}
 +
 +
 +==== main.cpp ====
 +
 +<file cpp>
 +#include "board.h"
 +#include "gfxaux.h"
 +#include "piece.h"
 +#include "shape.h"
 +#include "player.h"
 +#include "eventprocessor.h"
 +
 +#include <SDL2/SDL.h>
 +#include <iostream>
 +#include <algorithm>
 +#include <vector>
 +#include <set>
 +
 +
 +ShapeGenerator setup_shapes();
 +
 +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 9";
 +    int const ScreenWidth = 600;  // px
 +    int const ScreenHeight = 550; // px
 +    int const MsecsPerUpdate = 16;
 +    
 +    if (SDL_Init(SDL_INIT_VIDEO) < 0) {
 +        std::cerr << "Error: init SDL\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);
 +    }
 +    
 +    // 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();
 +    
 +    EventProcessor event_processor;
 +    Fence fence = setup_fence(board, Silver, Gray);
 +    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, MsecsPerUpdate);
 +    }
 +    
 +    SDL_DestroyRenderer(renderer);
 +    SDL_DestroyWindow(window);
 +    SDL_Quit();
 +
 +    return 0;
 +}
 +</file>
 +
 +==== game.cpp ====
 +
 +<file cpp>
 +#include "shape.h"
 +
 +#include "gfxaux.h"
 +
 +ShapeGenerator setup_shapes() {
 +    Shape O;
 +    O.add_points({{0, 0}, {1, 0}, {0, 1}, {1, 1}});
 +    O.set_color(Yellow, Gray);
 +    O.set_center(0.5, 0.5);
 +    
 +    Shape T;
 +    T.add_points({{0, 0}, {1, 0}, {2, 0}, {1, 1}});
 +    T.set_color(Lime, Gray);
 +    T.set_center(1, 0);
 +
 +    Shape I;
 +    I.add_points({{0, 0}, {1, 0}, {2, 0}, {3, 0}});
 +    I.set_color(Red, Gray);
 +    I.set_center(1.5, 0.5);
 +
 +    Shape Z;
 +    Z.add_points({{0, 0}, {1, 0}, {1, 1}, {2, 1}});
 +    Z.set_color(Green, Gray);
 +    Z.set_center(1, 0);
 +
 +    Shape S;
 +    S.add_points({{1, 0}, {2, 0}, {0, 1}, {1, 1}});
 +    S.set_color(Teal, Gray);
 +    S.set_center(1, 0);
 +
 +    Shape L;
 +    L.add_points({{0, 0}, {0, 1}, {0, 2}, {1, 2}});
 +    L.set_color(Aqua, Gray);
 +    L.set_center(0, 1);
 +
 +    Shape rL;
 +    rL.add_points({{1, 0}, {1, 1}, {0, 2}, {1, 2}});
 +    rL.set_color(Fuchsia, Gray);
 +    rL.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(rL);
 +    return generator;
 +}
 +</file>
 +
 +==== rectaux.h ====
 +
 +<file cpp>
 +#ifndef RECTAUX_H
 +#define RECTAUX_H
 +
 +#include <SDL2/SDL.h>
 +
 +using APoint = SDL_Point;
 +using APointF = SDL_FPoint;
 +using PPoint = SDL_Point;
 +
 +#endif
 +</file>
 +
 +==== renderstates.h ====
 +
 +<file cpp>
 +#ifndef RENDERSTATES_H
 +#define RENDERSTATES_H
 +
 +struct RenderStates {
 +    float tx = 0;
 +    float ty = 0;
 +};
 +
 +#endif
 +</file>
 +
 +==== gfxaux.h ====
 +
 +<file cpp>
 +#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
 +</file>
 +
 +==== gfxaux.cpp ====
 +
 +<file 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);
 +}
 +</file>
 +
 +==== board.h ====
 +
 +<file cpp>
 +#ifndef BOARD_H
 +#define BOARD_H
 +
 +#include "cell.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<Cell>;
 +
 +Fence setup_fence(BoardInfo const &board,
 +   SDL_Color const &fill_color,
 +   SDL_Color const &outline_color);
 +
 +void draw_fence(SDL_Renderer *renderer, Fence const &fence);
 +
 +#endif
 +</file>
 +
 +==== board.cpp ====
 +
 +<file 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<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_Color const &fill_color,
 +   SDL_Color const &outline_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, fill_color, outline_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, fill_color, outline_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, fill_color, outline_color);
 + }
 +
 + return fence;
 +}
 +
 +void draw_fence(SDL_Renderer *renderer, Fence const &fence) {
 +    RenderStates states{};
 + for (Cell const &cell : fence) {
 + cell.draw(renderer, states);
 + }
 +}
 +</file>
 +
 +==== cell.h ====
 +
 +<file cpp>
 +#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
 +</file>
 +
 +==== cell.cpp ====
 +
 +<file 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<int>(states.tx),
 +                  y + static_cast<int>(states.ty),
 +                  board_->cell_width, board_->cell_height};
 +    draw_rectangle(renderer, rect, fill_color_, outline_color_);
 +}
 +</file>
 +
 +==== block.h ====
 +
 +<file cpp>
 +#ifndef BLOCK_H
 +#define BLOCK_H
 +
 +#include "cell.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_Color const &fc,
 +          SDL_Color const &oc,
 +          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:
 +    Cell 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
 +</file>
 +
 +==== block.cpp ====
 +
 +<file cpp>
 +#include "block.h"
 +
 +#include "gfxaux.h"
 +#include "renderstates.h"
 +#include "board.h"
 +#include "piece.h"
 +
 +namespace {
 +Cell create_cell(BoardInfo const *board,
 +                 SDL_Color const &fc,
 +                 SDL_Color const &oc) {
 +    CoordMediator mediator{board};
 +    PPoint origin = mediator.arena_origin_px();
 +    return Cell{board, origin, APoint{0, 0}, fc, oc};
 +}
 +} // ns anon 
 +
 +Block::Block(BoardInfo const *board,
 +             APoint const &position,
 +             SDL_Color const &fc,
 +             SDL_Color const &oc,
 +             Piece const *parent)
 +        : cell_{create_cell(board, fc, oc)}
 +        , 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);
 +    }
 +}</file>
 +
 +==== piece.h ====
 +
 +<file cpp>
 +#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
 +</file>
 +
 +==== piece.cpp ====
 +
 +<file cpp>
 +#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);
 +}
 +</file>
 +
 +==== eventprocessor.h ====
 +
 +<file cpp>
 +#ifndef EVENTPROCESSOR_H
 +#define EVENTPROCESSOR_H
 +
 +class EventProcessor {
 +public:
 +    EventProcessor();
 +    
 +    void process();
 +    bool should_quit_game() const;
 +    
 +private:
 +    bool should_quit_;
 +};
 +
 +#endif
 +</file>
 +
 +==== eventprocessor.cpp ====
 +
 +<file cpp>
 +#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_;
 +}
 +</file>
 +
 +==== keyboardstate.h ====
 +
 +<file cpp>
 +#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
 +</file>
 +
 +==== keyboardstate.cpp ====
 +
 +<file cpp>
 +#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());
 +}
 +</file>
 +
 +==== player.h ====
 +
 +<file cpp>
 +#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
 +</file>
 +
 +==== player.cpp ====
 +
 +<file 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;
 +}
 +</file>
 +
 +==== shape.h ====
 +
 +<file cpp>
 +#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);
 +    
 +    void set_color(SDL_Color const &fill, SDL_Color const &outline);
 +    SDL_Color fill_color() const;
 +    SDL_Color outline_color() const;
 +    void set_center(float x, float y);
 +    APointF center() const;
 +
 +    size_t point_count() const;
 +    APoint point_at(size_t i) const;
 +    
 +private:
 +    std::vector<APoint> pts_;
 +    SDL_Color fill_;
 +    SDL_Color outline_;
 +    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
 +</file>
 +
 +==== shape.cpp ====
 +
 +<file cpp>
 +#include "shape.h"
 +
 +#include <cassert>
 +
 +Shape::Shape()
 +    : pts_{}
 +    , fill_{}
 +    , outline_{}
 +    , 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);
 +}
 +
 +void Shape::set_color(SDL_Color const &fill, SDL_Color const &outline) {
 +    fill_ = fill;
 +    outline_ = outline;
 +}
 +
 +SDL_Color Shape::fill_color() const {
 +    return fill_;
 +}
 +
 +SDL_Color Shape::outline_color() const {
 +    return outline_;
 +}
 +
 +void Shape::set_center(float x, float y) {
 +    center_.x = x;
 +    center_.y = y;
 +}
 +APointF Shape::center() const {
 +    return center_;
 +}
 +
 +size_t Shape::point_count() const {
 +    return pts_.size();
 +}
 +
 +APoint Shape::point_at(size_t i) const {
 +    return pts_.at(i);
 +}
 +
 +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);
 +}
 +</file>
 +
 +==== blockshape.h ====
 +
 +<file cpp>
 +#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</file>
 +
 +==== blockshape.cpp ====
 +
 +<file cpp>
 +#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 {
 +    SDL_Color fill = shape.fill_color();
 +    SDL_Color outline = shape.outline_color();
 +    for (size_t i{}; i < shape.point_count(); ++i) {
 +        APoint point = shape.point_at(i);
 +        blocks.emplace_back(board_, point, fill, outline, parent_);
 +    }
 +}
 +</file>
 +
 +==== Makefile ====
 +
 +<file 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
 +
 +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
 +
 +.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}
 +</file>
  

特に明示されていない限り、本Wikiの内容は次のライセンスに従います: CC0 1.0 Universal
CC0 1.0 Universal Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki