Skip to content

Instantly share code, notes, and snippets.

@chobby
Created February 14, 2016 14:50
Show Gist options
  • Save chobby/1d6994a993f19956f888 to your computer and use it in GitHub Desktop.
Save chobby/1d6994a993f19956f888 to your computer and use it in GitHub Desktop.
テトリス風ゲーム
# include <Siv3D.hpp>
# include <HamFramework.hpp>
enum GridState
{
I, O, S, Z, J, L, T, Null, Wall
};
struct BlockDefinition
{
std::array<Point, 4> figure;
GridState gridState;
int angleLimit;
};
const Size worldSize(10 + 2, 20 + 2 + 2);
const Size cellSize(20, 20);
const std::array<int, 5> scoringFormula = { 0, 10, 50, 200, 500 };
const std::array<BlockDefinition, 7> blockDefinitions =
{ {
{ { { { -1, 0 },{ 0, 0 },{ 1, 0 },{ 2, 0 } } }, GridState::I, 2 },
{ { { { 0, 0 },{ 1, 0 },{ 0, 1 },{ 1, 1 } } }, GridState::O, 1 },
{ { { { 0, 0 },{ -1, 0 },{ 0, -1 },{ 1, -1 } } }, GridState::S, 2 },
{ { { { 0, 0 },{ 1, 0 },{ 0, -1 },{ -1, -1 } } }, GridState::Z, 2 },
{ { { { 0, 0 },{ 1, 0 },{ -1, 0 },{ -1, -1 } } }, GridState::J, 4 },
{ { { { 0, 0 },{ -1, 0 },{ 1, 0 },{ 1, -1 } } }, GridState::L, 4 },
{ { { { 0, 0 },{ -1, 0 },{ 1, 0 },{ 0, -1 } } }, GridState::T, 4 }
} };
const std::array<Color, 9> gridStateColors = { Palette::Lightblue, Palette::Yellow, Palette::Greenyellow, Palette::Red, Palette::Blue, Palette::Orange, Palette::Purple, Color(0, 0, 0), Color(50, 50, 50) };
struct CommonData
{
CommonData()
{
const auto holdFunction = [](double t) {return Atan(Tan(t * 440 * TwoPi * 2.0)) * 0.05 * Exp(-10.0 * t) * ((t > 0) ? 1.0 : 0.0); };
sounds[0] = Sound(Wave(1.0s, [](double t) {return Sin(t * 440 * TwoPi * 3.0) * Exp(-10.0 * t); }));
sounds[1] = Sound(Wave(0.2s, [](double t) {return (Sin(t * 440 * TwoPi) * 0.95 + Sin(t * 440 * TwoPi * 0.25) * 0.05) * Exp(-10.0 * t); }));
sounds[2] = Sound(Wave(0.1s, [](double t) {return Sin(t * 440 * TwoPi * 2.0) * Exp(-100.0 * t); }));
sounds[3] = Sound(Wave(1.0s, [&](double t) {return holdFunction(t) + holdFunction(t - 0.2) * 0.1 + holdFunction(t - 0.4) * 0.01; }));
}
Sound sounds[4];
Grid<GridState> worldGrid{ worldSize.x, worldSize.y, GridState::Null };
Array<int> highScores = Array<int>(5, 0);
int currentScore{ 0 };
};
class Block
{
public:
Block(const BlockDefinition& blockDefinition)
: m_blockDefinition(blockDefinition)
, m_angle(Random(m_blockDefinition.angleLimit - 1))
{
calculatePositions();
}
bool move(const Grid<GridState>& grid, int x, int y)
{
const Point offset{ x, y };
if (checkCollision(grid, m_centerPos + offset, m_angle))
return false;
m_centerPos += offset;
calculatePositions();
return true;
}
bool rotate(const Grid<GridState>& grid, bool isClockwize)
{
const int defferenceAngle = isClockwize ? 1 : -1;
if (checkCollision(grid, m_centerPos, m_angle + (4 + defferenceAngle)))
return false;
m_angle = (m_angle + (m_blockDefinition.angleLimit + defferenceAngle)) % m_blockDefinition.angleLimit;
calculatePositions();
return true;
}
const std::array<Point, 4>& getPositions() const { return m_positions; }
GridState getGridState() const { return m_blockDefinition.gridState; }
void setCenterPos(const Point& centerPos)
{
m_centerPos = centerPos;
calculatePositions();
}
const Point& getCenterPos() const { return m_centerPos; }
bool checkCollision(const Grid<GridState>& grid, const Point& centerPos, int angle) const
{
return AnyOf(m_blockDefinition.figure, [&](const Point& figurePos)
{
const Point pos = rotatedPoint(figurePos, angle) + centerPos;
return grid[pos.y][pos.x] != GridState::Null;
});
}
void setAngle(int angle) { m_angle = angle; }
int getAngle() const { return m_angle; }
private:
void calculatePositions()
{
for (int i = 0; i < 4; ++i)
m_positions[i] = m_centerPos + rotatedPoint(m_blockDefinition.figure[i], m_angle);
}
Point rotatedPoint(const Point& p, int angle) const
{
const std::array<int, 4> rotationValue = { 0, 1, 0, -1 };
const int a = rotationValue[(angle % m_blockDefinition.angleLimit) % 4];
const int b = rotationValue[(angle % m_blockDefinition.angleLimit + 1) % 4];
return{ b * p.x - a * p.y, a * p.x + b * p.y };
}
BlockDefinition m_blockDefinition;
Point m_centerPos{ 5, 2 };
std::array<Point, 4> m_positions;
int m_angle = 0;
};
using MyApp = SceneManager<String, CommonData>;
class Title : public MyApp::Scene
{
public:
void update() override
{
if (Input::AnyKeyClicked())
changeScene(L"GameMain");
}
void draw() const override
{
FontAsset(L"H40")(L"テトリス風ゲーム").drawCenter(Window::Center().y - 80);
FontAsset(L"H24")(L"PRESS ANY KEY TO START").drawCenter(Window::Center().y + 60);
}
};
class GameMain : public MyApp::Scene
{
public:
void init() override
{
m_data->currentScore = 0;
for (auto p : step(worldSize))
m_data->worldGrid[p.y][p.x] = (p.x == 0 || p.x == worldSize.x - 1 || p.y == worldSize.y - 1) ? GridState::Wall : GridState::Null;
m_nextBlocks.clear();
addNewBlocks();
addNewBlocks();
popFrontNewBlock();
}
void update() override
{
if (Input::KeySpace.clicked)
m_isPausing = !m_isPausing;
if (m_isPausing)
return;
if ((Input::KeyRight | Input::KeyLeft).clicked)
m_block->move(m_data->worldGrid, Input::KeyRight.clicked - Input::KeyLeft.clicked, 0);
if ((Input::KeyZ | Input::KeyX).clicked && m_block->rotate(m_data->worldGrid, Input::KeyX.clicked))
m_data->sounds[2].playMulti();
if (Input::KeyUp.clicked)
{
while (m_block->move(m_data->worldGrid, 0, 1));
for (auto& pos : m_block->getPositions())
for (int i = 0; i < 3; ++i)
m_particleEffects.emplace_back(((pos + Vec2::One * 0.5 + RandomVec2(Circle(1.0))) * cellSize), 0.1, Random());
putBlock();
}
if (Input::KeyC.clicked && (!m_holdedBlock || !m_holdedBlock->checkCollision(m_data->worldGrid, m_block->getCenterPos(), m_holdedBlock->getAngle())))
{
if (m_holdedBlock)
m_holdedBlock->setCenterPos(m_block->getCenterPos());
m_holdedBlock.swap(m_block);
if (!m_block)
popFrontNewBlock();
m_data->sounds[3].playMulti();
}
m_predictedBlock = Block(blockDefinitions[m_block->getGridState()]);
m_predictedBlock->setCenterPos(m_block->getCenterPos());
m_predictedBlock->setAngle(m_block->getAngle());
while (m_predictedBlock->move(m_data->worldGrid, 0, 1));
++m_timerCount;
if (m_timerCount > Max(0, 60 - static_cast<int>(m_data->currentScore * 0.01)) || (Input::KeyDown.clicked || (Input::KeyDown.pressedDuration > 300 && m_timerCount % 6 == 0)))
!m_block->move(m_data->worldGrid, 0, 1) ? putBlock() : m_timerCount = 0;
if (m_nextBlocks.size() < 7)
addNewBlocks();
if (checkGameOver())
changeScene(L"Result");
Erase_if(m_particleEffects, [&](Vec4& e) { return (e.z += 0.01) > 1.0; });
}
void draw() const override
{
const Point offset(150, -cellSize.y * 0.3);
const RectF backRect(offset + cellSize + Vec2::Down * cellSize.y * 0.5, (worldSize - Vec2(2.0, 2.5)) * cellSize);
const RoundRect baseRoundRect(backRect.stretched(cellSize), 5.0);
baseRoundRect.drawShadow(Vec2::Zero, 20.0);
baseRoundRect.draw(Color(0, 183, 227));
baseRoundRect.drawFrame(2.0, 2.0, Palette::White);
Graphics2D::SetStencilState(StencilState::Replace);
Graphics2D::SetStencilValue(1);
backRect.draw();
Graphics2D::SetStencilState(StencilState::Test(StencilFunc::Equal));
backRect.draw(Color(20, 20, 20));
for (auto p : step(Point::One, worldSize - Point(2, 2)))
{
if (m_data->worldGrid[p.y][p.x] == GridState::Null)
Rect(offset + p * cellSize, cellSize).stretched(-1).draw(gridStateColors[m_data->worldGrid[p.y][p.x]]);
else
drawBlockRect(offset + p * cellSize, gridStateColors[m_data->worldGrid[p.y][p.x]]);
}
if (m_predictedBlock)
for (const auto& blockPos : m_predictedBlock->getPositions())
Rect(offset + blockPos * cellSize, cellSize).drawFrame(2.0, 0.0, Color(gridStateColors[m_predictedBlock->getGridState()]).setAlpha(128));
for (const auto& blockPos : m_block->getPositions())
drawBlockRect(offset + blockPos * cellSize, gridStateColors[m_block->getGridState()]);
Graphics2D::SetStencilState(StencilState::Default);
for (auto i : step(3))
for (const auto& blockPos : blockDefinitions[m_nextBlocks[i]].figure)
drawBlockRect(offset + Point(300, 90 + i * 100) + blockPos * cellSize, gridStateColors[m_nextBlocks[i]]);
if (m_holdedBlock)
for (const auto& blockPos : blockDefinitions[m_holdedBlock->getGridState()].figure)
drawBlockRect(Point(50, 90) + blockPos * cellSize, gridStateColors[m_holdedBlock->getGridState()]);
FontAsset(L"H24")(Pad(m_data->currentScore, { 7, L'0' })).drawCenter((backRect.bl + backRect.br) / 2.0 + Vec2::Down * cellSize.y * 0.5);
FontAsset(L"H24")(L"NEXT").drawCenter(Vec2(offset.x + 320, 40));
FontAsset(L"H24")(L"HOLD").drawCenter(Vec2(70, 40));
Graphics2D::SetBlendState(BlendState::Additive);
for (const auto& effect : m_particleEffects)
Circle(offset + effect.xy() + Vec2::Up * effect.w * 300.0 * (1.0 - Exp(-5 * effect.z)), cellSize.x * 0.3).drawFrame(cellSize.x * 1.0, 0.0, ColorF(1.0, Random(0.15 * Exp(-5 * effect.z))));
Graphics2D::SetBlendState(BlendState::Default);
if (m_isPausing)
{
Window::ClientRect().draw(ColorF(0.0, 0.3));
FontAsset(L"H24")(L"PAUSE").drawCenter(Window::Center());
}
}
private:
void putBlock()
{
for (const auto& blockPos : m_block->getPositions())
m_data->worldGrid[blockPos.y][blockPos.x] = m_block->getGridState();
updateScore();
}
void updateScore()
{
int count = 0;
for (int y = 3; y < worldSize.y - 1; ++y)
{
if (AllOf(getGridLine(y, 1, worldSize.x - 2), [&](GridState state) { return state != GridState::Null; }))
{
for (int x = 1; x < worldSize.x; ++x)
m_particleEffects.emplace_back((Vec2(x, y) + Vec2::One * 0.5) * cellSize, 0.0, 0.0);
for (int i = 1; i < y - 1; ++i)
for (int x = 1; x < worldSize.x - 1; ++x)
m_data->worldGrid[y - i + 1][x] = (i != y - 2) ? m_data->worldGrid[y - i][x] : GridState::Null;
++count;
}
}
m_data->currentScore += scoringFormula[count];
popFrontNewBlock();
m_data->sounds[(count ? 0 : 1)].playMulti();
}
void addNewBlocks()
{
Array<GridState> newBlocks = { GridState::I, GridState::O, GridState::S, GridState::Z, GridState::J, GridState::L, GridState::T };
Shuffle(newBlocks.begin(), newBlocks.end());
m_nextBlocks.insert(m_nextBlocks.end(), newBlocks.begin(), newBlocks.end());
}
void popFrontNewBlock()
{
m_block = Block(blockDefinitions[static_cast<int>(m_nextBlocks.front())]);
m_nextBlocks.erase(m_nextBlocks.begin());
}
bool checkGameOver()
{
return AnyOf(getGridLine(2, 1, worldSize.x - 2), [&](GridState state) { return state != GridState::Null; });
}
const Array<GridState>& getGridLine(int y, int startX, int endX)
{
m_line.clear();
for (int x = startX; x <= endX; ++x)
m_line.push_back(m_data->worldGrid[y][x]);
return m_line;
}
void drawBlockRect(const Point& pos, const Color& color) const
{
const auto rect = RectF(pos, cellSize).stretched(-1);
rect.draw(color);
rect.stretched(-1, -cellSize.y * 0.4).movedBy(0, -cellSize.y * 0.4).draw(ColorF(1.0, 0.4));
rect.stretched(-1, -cellSize.y * 0.3).movedBy(0, cellSize.y * 0.3).draw(ColorF(1.0, 0.2));
rect.drawFrame(1.0, 1.0, ColorF(0.0, 0.2));
}
Optional<Block> m_block, m_holdedBlock, m_predictedBlock;
Array<GridState> m_line, m_nextBlocks;
Array<Vec4> m_particleEffects;
int m_timerCount = 0;
bool m_isPausing = false;
};
class Result : public MyApp::Scene
{
public:
void init() override
{
m_data->highScores.push_back(m_data->currentScore);
std::sort(m_data->highScores.begin(), m_data->highScores.end(), std::greater<int>());
m_data->highScores.pop_back();
m_timer.start();
}
void update() override
{
if (Input::AnyKeyClicked())
changeScene(L"GameMain");
}
void draw() const override
{
const double offsetY = static_cast<double>(Window::Height() - 100) / m_data->highScores.size();
const double startY = Window::Height() * 0.5 - (m_data->highScores.size() / 2.0) * offsetY;
FontAsset(L"H24")(L"RESULTS").drawCenter(Vec2(Window::Width()* 0.5, startY));
for (auto i : step(m_data->highScores.size()))
{
const Vec2 pos = Vec2(Window::Width() * 0.5 * (1.0 - Exp(-0.007 * (m_timer.elapsed().count() - i * 100.0))), startY + offsetY * (i + 1));
const auto backRoundRect = RoundRect(RectF(Window::Width() * 0.8, offsetY * 0.9).setCenter(pos), 10.0);
backRoundRect.draw((m_data->currentScore == m_data->highScores[i] && m_data->currentScore != 0) ? Palette::White : Color(0, 183, 227));
backRoundRect.drawFrame(2.0, 2.0);
FontAsset(L"H24")(i + 1).drawCenter(pos + Vec2::Left * 200.0);
FontAsset(L"H24")(Pad(m_data->highScores[i], { 7, L'0' })).drawCenter(pos + Vec2::Right * 20.0);
}
}
Stopwatch m_timer;
};
void Main()
{
Window::SetTitle(L"落ち物ゲーム Z,X:回転 C:ホールド 左右下:移動 上:ドロップ");
Graphics::SetBackground(Color(0, 183, 227));
FontAsset::Register(L"H40", 40, Typeface::Heavy, FontStyle::Outline);
FontAsset::Register(L"H24", 24, Typeface::Heavy, FontStyle::Outline);
MyApp sceneManager;
sceneManager.add<Title>(L"Title");
sceneManager.add<GameMain>(L"GameMain");
sceneManager.add<Result>(L"Result");
while (System::Update())
{
for (auto& s : step((Window::Size() / 50) + Point::One * 2))
if ((s.x + s.y) % 2)
RectF(50).setCenter(50 * s).moveBy(Vec2(50, 50) * (System::FrameCount() % 120 / 120.0 - 1.0)).draw(AlphaF(0.1));
sceneManager.updateAndDraw();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment