====== 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
==== 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}