目次

Tetris Take 4

作成日: 2023-07-22 (土)

初心者によるC++入門 #36 ブロックを落とす

main.cpp

#include <SDL2/SDL.h>
#include <iostream>

#include "board.h"
#include "gfxaux.h"
#include "shape.h"

int main(int argc, char **argv) {
    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(
            "TETRIS take 4]",
            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
    Fence fence = setup_fence(board, Gray, White);
    CoordMediator coord_mediator{&board};
    Shape test_shape{&coord_mediator, SDL_Point{5, 2}, White, Gray, 0.05};

    // game loop
    bool running = true;
    Uint32 last_updated = SDL_GetTicks();
    int count = 0;
    while (running) {
        Uint32 start = SDL_GetTicks();
        
        // process events
        SDL_Event event;
        while (SDL_PollEvent(&event)) {
            if (event.type == SDL_QUIT) {
                running = false;
            }
        }
        
        // update
        if (count++ % 10 == 0) {
        Uint32 elapsed_time = start - last_updated;
        std::cout << 1000.0f / elapsed_time << '\n';
        }
        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);
        
        Uint32 next_start = start + MsecsPerUpdate;
        Uint32 current = SDL_GetTicks();
        if (next_start >= current) {
            SDL_Delay(next_start - current);
        }
        
        last_updated = start;
    }
    
    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);
    SDL_Quit();

    return 0;
}

gfxaux.h

#ifndef GFXAUX_H
#define GFXAUX_H

#include <SDL2/SDL.h>

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 <SDL2/SDL.h>
#include <vector>

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

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<int>(arena.x * board_->cell_width);
    int y = static_cast<int>(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 <SDL2/SDL.h>

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<int>(states.tx),
                  y + static_cast<int>(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 <SDL2/SDL.h>

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 <SDL2/SDL.h>
#include <vector>

class CoordMediator;

class Shape {
public:
    Shape(CoordMediator const *coord_mediator,
          SDL_Point const &position,
          SDL_Color const &fc,
          SDL_Color const &oc,
          float falling_speed);
    
    SDL_Point local_position() const;
    SDL_Point global_position() const;
    void draw(SDL_Renderer *renderer) const;

    void update();

private:
    std::vector<Block> 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 <cmath>

Shape::Shape(CoordMediator const *coord_mediator,
             SDL_Point const &position,
             SDL_Color const &fc,
             SDL_Color const &oc,
             float falling_speed)
        : coord_mediator_{coord_mediator}
        , position_{position}
        , falling_speed_{falling_speed}
        , fy_{float(position.y)}
{
    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<int>(fy_);
    } else {
        fy_ = std::trunc(fy_);
        if (position_.x > 0) {
            --position_.x;
        }
    }
}

Makefile

SRCS = \
	main.cpp \
	board.cpp \
	gfxaux.cpp \
	cell.cpp \
    block.cpp \
    shape.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}