====== Tetris Take 5 ====== 作成日: 2023-07-22 (土) [[https://youtu.be/uex1TWI2_rI|初心者によるC++入門 #37 ブロックを操作できるようにする]] {{:youtube:tetris_take5.gif|}} ==== main.cpp ==== #include #include #include "board.h" #include "gfxaux.h" #include "shape.h" #include "player.h" #include "eventprocessor.h" #include "keyboardstate.h" int main(int argc, char **argv) { auto *WindowTitle = "TETRIS take 5"; int const ScreenWidth = 600; // px int const ScreenHeight = 550; // px int const MsecsPerUpdate = 16; BoardInfo board { 10, 20, 24, 24, SDL_Point{10, 10} }; 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 EventProcessor event_processor; Fence fence = setup_fence(board, Gray, White); CoordMediator coord_mediator{&board}; Shape test_shape{&coord_mediator, SDL_Point{5, 2}, 0.05}; KeyboardState keyboard_state; Player player{&test_shape, &keyboard_state}; player.install_action(Player::Action::MoveLeft, SDL_SCANCODE_LEFT); player.install_action(Player::Action::MoveRight, SDL_SCANCODE_RIGHT); // game loop while (!event_processor.should_quit_game()) { Uint32 start = SDL_GetTicks(); // process events event_processor.process(); keyboard_state.update(); // update player.update(); test_shape.update(); // render SDL_SetRenderDrawColor(renderer, 100, 100, 255, 255); SDL_RenderClear(renderer); draw_fence(renderer, fence); test_shape.draw(renderer); SDL_RenderPresent(renderer); // synch frame rate Uint32 next_start = start + MsecsPerUpdate; Uint32 current = SDL_GetTicks(); if (next_start >= current) { SDL_Delay(next_start - current); } } SDL_DestroyRenderer(renderer); SDL_DestroyWindow(window); SDL_Quit(); return 0; } ==== gfxaux.h ==== #ifndef GFXAUX_H #define GFXAUX_H #include SDL_Color const Gray{128, 128, 128, 255}; SDL_Color const White{255, 255, 255, 255}; void draw_rectangle(SDL_Renderer *renderer, SDL_Rect const &rect, SDL_Color const &fc, SDL_Color const &oc); struct RenderStates { float tx = 0; float ty = 0; }; #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 "cell.h" #include #include struct BoardInfo { int arena_width; int arena_height; int cell_width; int cell_height; SDL_Point position; }; class CoordMediator { public: CoordMediator(BoardInfo const *board); SDL_Point arena_to_pixel(SDL_Point const &arena) const; SDL_Point arena_to_pixel(SDL_FPoint const &arena) const; SDL_Point pixel_to_arena(SDL_Point const &pixel) const; SDL_Point arena_origin() const; BoardInfo const *board_info() const; private: BoardInfo const *board_; }; using Fence = std::vector; 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 ==== board.cpp ==== #include "board.h" #include "gfxaux.h" #include "cell.h" CoordMediator::CoordMediator(BoardInfo const *board) : board_{board} {} SDL_Point CoordMediator::arena_to_pixel(SDL_Point const &arena) const { int x = arena.x * board_->cell_width; int y = arena.y * board_->cell_height; return {x, y}; } SDL_Point CoordMediator::arena_to_pixel(SDL_FPoint const &arena) const { int x = static_cast(arena.x * board_->cell_width); int y = static_cast(arena.y * board_->cell_height); return {x, y}; } SDL_Point CoordMediator::pixel_to_arena(SDL_Point const &pixel) const { int x = pixel.x / board_->cell_width; int y = pixel.y / board_->cell_height; return {x, y}; } SDL_Point CoordMediator::arena_origin() const { int x = board_->position.x + board_->cell_width; int y = board_->position.y; return {x, y}; } 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) { SDL_Point 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) { SDL_Point 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) { SDL_Point 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); } } ==== cell.h ==== #ifndef CELL_H #define CELL_H #include struct BoardInfo; struct RenderStates; class Cell { public: Cell(BoardInfo const *board, SDL_Point const &origin, SDL_Point const &position, SDL_Color const &fill_color, SDL_Color const &outline_color); ~Cell() = default; SDL_Point global_position() const; SDL_Point local_position() const; void draw(SDL_Renderer *renderer, RenderStates states) const; private: BoardInfo const *board_; SDL_Point origin_; SDL_Point position_; SDL_Color fill_color_; SDL_Color outline_color_; }; #endif ==== cell.cpp ==== #include "cell.h" #include "gfxaux.h" #include "board.h" Cell::Cell(BoardInfo const *board, SDL_Point const &origin, SDL_Point 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} { } SDL_Point Cell::global_position() const { return {origin_.x + position_.x, origin_.y + position_.y}; } SDL_Point 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_); } ==== block.h ==== #ifndef BLOCK_H #define BLOCK_H #include "cell.h" #include struct RenderStates; class CoordMediator; class Shape; class Block { public: Block(CoordMediator const *coord_mediator, SDL_Point const &position, SDL_Color const &fc, SDL_Color const &oc, Shape const *parent = nullptr); SDL_Point local_position() const; SDL_Point global_position() const; void draw(SDL_Renderer *renderer, RenderStates states) const; private: Cell cell_; CoordMediator const *coord_mediator_; SDL_Point position_; Shape const *parent_; }; #endif ==== block.cpp ==== #include "block.h" #include "gfxaux.h" #include "board.h" #include "shape.h" namespace { Cell create_cell(CoordMediator const *mediator, SDL_Color const &fc, SDL_Color const &oc) { auto *board = mediator->board_info(); auto origin = mediator->arena_origin(); return Cell{board, origin, SDL_Point{0, 0}, fc, oc}; } } // ns anon Block::Block(CoordMediator const *coord_mediator, SDL_Point const &position, SDL_Color const &fc, SDL_Color const &oc, Shape const *parent) : cell_{create_cell(coord_mediator, fc, oc)} , coord_mediator_{coord_mediator} , position_{position} , parent_{parent} {} SDL_Point Block::local_position() const { return position_; } SDL_Point Block::global_position() const { if (parent_ == nullptr) { return position_; } auto [px, py] = parent_->global_position(); return {px + position_.x, py + position_.y}; } 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)}); } ==== shape.h ==== #ifndef SHAPE_H #define SHAPE_H #include "block.h" #include #include class CoordMediator; class Shape { public: Shape(CoordMediator const *coord_mediator, SDL_Point const &position, float falling_speed); SDL_Point local_position() const; SDL_Point global_position() const; void draw(SDL_Renderer *renderer) const; void update(); bool move_left(); bool move_right(); private: std::vector blocks_; CoordMediator const *coord_mediator_; SDL_Point position_; float falling_speed_; float fy_; }; #endif ==== shape.cpp ==== #include "shape.h" #include "gfxaux.h" #include "board.h" #include "block.h" #include Shape::Shape(CoordMediator const *coord_mediator, SDL_Point const &position, float falling_speed) : coord_mediator_{coord_mediator} , position_{position} , falling_speed_{falling_speed} , fy_{float(position.y)} { SDL_Color fc = White; SDL_Color oc = Gray; blocks_.emplace_back(coord_mediator_, SDL_Point{0, 0}, fc, oc, this); blocks_.emplace_back(coord_mediator_, SDL_Point{1, 0}, fc, oc, this); blocks_.emplace_back(coord_mediator_, SDL_Point{1, 1}, fc, oc, this); blocks_.emplace_back(coord_mediator_, SDL_Point{2, 1}, fc, oc, this); } SDL_Point Shape::local_position() const { return position_; } SDL_Point Shape::global_position() const { return position_; } void Shape::draw(SDL_Renderer *renderer) const { RenderStates states{float(position_.x), fy_}; for (auto &block : blocks_) { block.draw(renderer, states); } } void Shape::update() { if (position_.y < coord_mediator_->board_info()->arena_height) { fy_ += falling_speed_; position_.y = static_cast(fy_); } else { fy_ = std::trunc(fy_); } } bool Shape::move_left() { position_.x -= 1; return true; } bool Shape::move_right() { position_.x += 1; return true; } ==== 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() < 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 #include class Shape; class KeyboardState; class Player { public: enum class Action { MoveLeft, MoveRight, RotateLeft, RotateRight, SoftDrop, HardDrop, }; Player(Shape *shape, KeyboardState const *keyboard); void update(); void install_action(Action action, SDL_Scancode scancode); private: bool just_pressed(Action action) const; private: std::map action_map_; Shape *active_shape_; KeyboardState const *keyboard_; }; #endif ==== player.cpp ==== #include "player.h" #include "shape.h" #include "keyboardstate.h" Player::Player(Shape *shape, KeyboardState const *keyboard) : active_shape_{shape} , keyboard_{keyboard} { } void Player::update() { if (just_pressed(Action::MoveLeft)) { active_shape_->move_left(); } if (just_pressed(Action::MoveRight)) { active_shape_->move_right(); } } 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; } ==== Makefile ==== SRCS = \ main.cpp \ board.cpp \ gfxaux.cpp \ cell.cpp \ block.cpp \ shape.cpp \ player.cpp \ keyboardstate.cpp \ eventprocessor.cpp OBJS = ${SRCS:%.cpp=%.o} DEPS = ${SRCS:%.cpp=%.d} EXECUTABLE = tetris.exe CXXFLAGS = -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 -o $@ $< -MMD -MP ${CXXFLAGS} .PHONY: clean clean: ${RM} ${EXECUTABLE} ${OBJS} ${DEPS} -include ${DEPS}