(音もエフェクトも何もないですが…)
- ぷよぷよ風ゲーム.zip
- (1.26 MiB) ダウンロード数: 186 回
設計の話から、push_backをemplace_backにするというような点まで何でも構いません。
個人的にはGame::BlockDrop()関数の前半の処理がやや醜く感じるのと、Draw()で現在のぷよとボードのぷよのインデックスの指定がややこしくなってしまっているのが気がかりです。
ソースは添付ファイルにも同梱しましたが、こちらにも載せます。
► スポイラーを表示
#include <utility>
#include <list>
#include <cassert>
#include "../../dxlib_vc/Dxlib.h"
// 警告の抑制
#pragma warning(disable:4351) // warning C4351: 新しい動作: 配列 'Key::mKeyState' の要素は既定で初期化されます => zero-filled
// Macroes
// #define DEBUG
#define SHOULD_NOT_REACH_HERE assert(false && "should not reach here")
#define DISALLOW_COPY_AND_MOVE(className) \
className(const className&) = delete; \
className(const className&&) = delete; \
className& operator=(const className&) = delete; \
className& operator=(const className&&) = delete;
// Grobal Constants
enum{
PIECEX = 32,
PIECEY = 32,
FIELDX = 8, // 実際のボード
FIELDY = 14,
BOARDX = 6, // 目に見えるボード
BOARDY = 12,
};
// Structs
struct Block{
Block() :x(0), y(0), color(0) {}
Block(int ax, int ay, int acolor) :x(ax), y(ay), color(acolor) {}
int x, y, color; // colorは[1, 4]
};
// Alias
using uint = unsigned int;
//---------------------------------------------------------
// キーボード関連処理クラス
//---------------------------------------------------------
class Key final{
private:
Key()
:mKeyState{}
{
}
public:
~Key() = default;
DISALLOW_COPY_AND_MOVE(Key);
static Key* get()
{
static Key inst;
return &inst;
}
// 状態更新
void Update()
{
static char keystateBuf[KEYMAX] = {};
// keystate準備
GetHitKeyStateAll(keystateBuf);
for (int i = 0; i < KEYMAX; ++i){
mKeyState[i] = keystateBuf[i] ? ++mKeyState[i] : 0;
}
}
bool Hit(const int keycode) const { return mKeyState[keycode] == 1; } // 押した瞬間かどうか
uint Hold(const int keycode) const { return mKeyState[keycode]; } // 長押ししたフレーム数を返す
private:
enum{
KEYMAX = 256,
};
uint mKeyState[KEYMAX];
};
//---------------------------------------------------------
// ゲームメインクラス
//---------------------------------------------------------
class Game final{
private:
Game()
:mField{}, mNow{}
{
this->LoadImg();
this->InitParams();
}
public:
~Game() = default;
DISALLOW_COPY_AND_MOVE(Game);
static Game* get()
{
static Game inst;
return &inst;
}
void Run(); // ゲームの実行
private:
void LoadImg(); // 画像読み込み
void InitParams(); // 変数初期化
void NewBlock(); // 新しいブロックを降らせる
void KeyProcess(); // キー入力処理
void BlockMove(const int dir); // ブロックを左右に動かす
void Rotate(); // 回転処理
void BlockDown(); // ブロックを落とす
void BlockDrop(); // ブロックを一気に落とす
void EraseAll(); // 4つ以上つながったブロックを全て消去する
std::list<Block> EnumAdjoin(); // 消去すべきものの座標の一覧を返す
int CountAdjoin(const int x, const int y); // 消去判定
void CountAdjoinImpl(int fieldBuf[][FIELDX], const int x, const int y, uint* countBuf); // 再帰的に隣接同色ブロックを数える
void EraseAdjoin(const int x, const int y); // 隣接したものを消去する
void FieldDrop(); // 全体を落下させる
void Draw() const; // 描画処理
private:
// Constants
enum class GameFlag{
Normal,
BlockDelete,
FieldDrop,
};
enum BoardState{
Board_Wall = -1,
Board_Empty = 0,
};
enum{
DXLIB_ERROR = -1, // DXライブラリでエラーが発生したときの戻り値
APPEAR_X = 3, // ブロックが降りてくるX座標
BLOCK_DOWN_FRAME = 30, // 無操作でブロックが下りてくる間隔
};
// Local Variables
int mFrame = 0; // 経過フレーム
int mField[FIELDY][FIELDX]; // ゲームボードの状態
int mLastStateFrame = 0; // 最後の状態の時間(消去・落下を一定時間ごとにさせる用)
bool mIsOver = false; // ゲームオーバーフラグ
Block mNow[2]; // 現在の2個のブロック
GameFlag mGameFlag = GameFlag::Normal; // 通常・ブロック消去・落下のどれかの状態
// Images
using ImageHandle = int;
ImageHandle mImgPiece[5];
ImageHandle mImgBg = -1;
};
//---------------------------------
// 画像読み込み
void Game::LoadImg()
{
const char* failedToLoadImgMsg = "画像の読み込みに失敗しました。ゲームを終了します。";
if (DXLIB_ERROR == LoadDivGraph("img/puyo.png", 5, 5, 1, PIECEX, PIECEY, mImgPiece))
throw failedToLoadImgMsg;
if (DXLIB_ERROR == (mImgBg = LoadGraph("img/background.png")))
throw failedToLoadImgMsg;
}
//---------------------------------
// 変数初期化
void Game::InitParams()
{
this->NewBlock();
// 壁を作る
for (int i = 0; i < FIELDY; ++i){
mField[i][0] = mField[i][FIELDX - 1] = Board_Wall;
}
for (int i = 0; i < FIELDX; ++i){
mField[FIELDY - 1][i] = Board_Wall;
}
}
//---------------------------------
// 新しいブロックを降らせる
void Game::NewBlock()
{
mNow[0] = Block(APPEAR_X, 0, GetRand(3) + 1);
mNow[1] = Block(APPEAR_X, 1, GetRand(3) + 1);
}
//---------------------------------
// キー入力処理
void Game::KeyProcess()
{
if (Key::get()->Hit(KEY_INPUT_A))
this->BlockMove(-1);
else if (Key::get()->Hit(KEY_INPUT_D))
this->BlockMove(+1);
else if (Key::get()->Hit(KEY_INPUT_J))
this->Rotate();
else if (Key::get()->Hit(KEY_INPUT_S))
this->BlockDown();
else if (Key::get()->Hit(KEY_INPUT_K))
this->BlockDrop();
}
//---------------------------------
// ブロックを左右に動かす
// arg: +1 = right -1 = left
void Game::BlockMove(const int dir)
{
// 何もなければ移動、あれば動けない
if (!mField[mNow[0].y][mNow[0].x + dir] &&
!mField[mNow[1].y][mNow[1].x + dir]){
mNow[0].x += dir;
mNow[1].x += dir;
}
}
//---------------------------------
// 回転処理
void Game::Rotate()
{
int rot;
// yに差がある => x(左右)への回転
if (rot = mNow[1].y - mNow[0].y){
mNow[1].y -= rot;
mNow[1].x -= rot;
// ぶつかるなら
if (mField[mNow[1].y][mNow[1].x]){
// 反対もぶつかるならもう1回転
if (mField[mNow[1].y][mNow[1].x + rot * 2]){
this->Rotate();
// y座標調整
mNow[0].y += rot;
mNow[1].y += rot;
return;
}
// 空いてればずらす
else{
mNow[0].x += rot;
mNow[1].x += rot;
}
}
}
// xに差がある => y(縦)への回転
else if (rot = mNow[1].x - mNow[0].x){
mNow[1].y += rot;
mNow[1].x -= rot;
// 下にぶつかれば1つ上げる
if (mNow[1].y >= FIELDY - 1){
--mNow[0].y;
--mNow[1].y;
}
}
}
//---------------------------------
// ブロックを落とす
void Game::BlockDown()
{
++mNow[0].y;
++mNow[1].y;
// ブロックにぶつかれば止める
if (mField[mNow[0].y][mNow[0].x] ||
mField[mNow[1].y][mNow[1].x]){
// 個別に切り離して落とす
this->BlockDrop();
}
}
//---------------------------------
// ブロックを一気に落とす
void Game::BlockDrop()
{
// 下にあるブロックを先に落とす さもなくば 上にある奴も同じ座標まで落ちてしまう
const int lower = mNow[0].y < mNow[1].y;
const int upper = !lower;
// ぶつかるまで下降・1個戻して確定
while (mField[mNow[lower].y][mNow[lower].x] == Board_Empty) ++mNow[lower].y;
mField[--mNow[lower].y][mNow[lower].x] = mNow[lower].color;
while (mField[mNow[upper].y][mNow[upper].x] == Board_Empty) ++mNow[upper].y;
mField[--mNow[upper].y][mNow[upper].x] = mNow[upper].color;
// 消せれば消去フラグを立てる
if (this->CountAdjoin(mNow[0].x, mNow[0].y) >= 4 ||
this->CountAdjoin(mNow[1].x, mNow[1].y) >= 4){
mGameFlag = GameFlag::BlockDelete;
mLastStateFrame = GetNowCount();
}
else{
// 所定の座標にブロックが乗ってしまったらゲームオーバー
if (mField[1][APPEAR_X]){
mIsOver = true;
return;
}
this->NewBlock();
}
}
//---------------------------------
// 4つ以上つながったブロックを全て消去する
void Game::EraseAll()
{
auto ignitionPoints = this->EnumAdjoin();
if (!ignitionPoints.empty()){
for (const auto& elem : ignitionPoints){
this->EraseAdjoin(elem.x, elem.y);
}
mGameFlag = GameFlag::FieldDrop;
mLastStateFrame = GetNowCount();
return;
}
// 間隔調整のため区切りまで待つ
if (mFrame % BLOCK_DOWN_FRAME == 0){
this->NewBlock();
mGameFlag = GameFlag::Normal;
}
}
//---------------------------------
// 消去すべきものの着火点の座標の一覧を返す
std::list<Block> Game::EnumAdjoin()
{
// 判定用に複製
int fieldBuf[FIELDY][FIELDX] = {};
for (int i = 0; i < FIELDY; ++i){
for (int j = 0; j < FIELDX; ++j){
fieldBuf[i][j] = mField[i][j];
}
}
// 着火点を列挙
std::list<Block> ignitionPoints;
for (int i = 1; i < FIELDY - 1; ++i){
for (int j = 1; j < FIELDX - 1; ++j){
uint count = 0;
this->CountAdjoinImpl(fieldBuf, j, i, &count);
if (count >= 4){
ignitionPoints.emplace_back(j, i, fieldBuf[i][j]);
}
}
}
return std::move(ignitionPoints);
}
//---------------------------------
// 消去判定
int Game::CountAdjoin(const int x, const int y)
{
// 判定用に複製
int fieldBuf[FIELDY][FIELDX] = {};
for (int i = 0; i < FIELDY; ++i){
for (int j = 0; j < FIELDX; ++j){
fieldBuf[i][j] = mField[i][j];
}
}
// 判定
uint count = 0;
this->CountAdjoinImpl(fieldBuf, x, y, &count);
return count;
}
//---------------------------------
// 再帰的に隣接同色ブロックを数える
// 実際のボードを渡せば実際に消す
// arg: フィールド情報, x, y, 隣接カウント情報変数のアドレス
void Game::CountAdjoinImpl(int fieldBuf[][FIELDX], const int x, const int y, uint* countBuf)
{
const int color = fieldBuf[y][x];
// 壁か空白ならおしまい
if (color <= Board_Empty)
return;
fieldBuf[y][x] = Board_Empty;
if (countBuf != nullptr)
++*countBuf;
if (fieldBuf[y - 1][x] == color) this->CountAdjoinImpl(fieldBuf, x, y - 1, countBuf);
if (fieldBuf[y + 1][x] == color) this->CountAdjoinImpl(fieldBuf, x, y + 1, countBuf);
if (fieldBuf[y][x - 1] == color) this->CountAdjoinImpl(fieldBuf, x - 1, y, countBuf);
if (fieldBuf[y][x + 1] == color) this->CountAdjoinImpl(fieldBuf, x + 1, y, countBuf);
}
//---------------------------------
// 隣接したものを消去する
void Game::EraseAdjoin(const int x, const int y)
{
this->CountAdjoinImpl(mField, x, y, nullptr);
}
//---------------------------------
// 全体を落下させる
void Game::FieldDrop()
{
for (int i = 1; i < FIELDX; ++i){
int tmp = FIELDY - 1;
for (int j = FIELDY - 1; j >= 1; --j){
if (mField[j][i]){
mField[tmp--][i] = mField[j][i];
}
}
// 残りをEmptyに
while (tmp >= 0)
mField[tmp--][i] = Board_Empty;
}
mGameFlag = GameFlag::BlockDelete;
mLastStateFrame = GetNowCount();
}
//---------------------------------
// 描画処理
void Game::Draw() const
{
// 背景
DrawGraph(0, 0, mImgBg, true);
// 現在のブロックを描画(消去中は自分を描画したらゾンビのように残ってしまう)
if (mGameFlag == GameFlag::Normal){
for (const auto& elem : mNow){
DrawGraph((elem.x - 1) * PIECEX, (elem.y - 1) * PIECEY, mImgPiece[elem.color], true);
}
}
// 今までのブロックを描画(番兵があるためFIELDに問い合わせるときは1を加算する)
for (int i = 0; i < BOARDY; ++i){
for (int j = 0; j < BOARDX; ++j){
if (mField[i + 1][j + 1]){
DrawGraph(PIECEX * j, PIECEY * i, mImgPiece[mField[i + 1][j + 1]], true);
}
}
}
// ゲームオーバー表示
if (mIsOver){
DrawFormatString(60, 100, GetColor(0, 0, 0), "GAME OVER");
}
#ifdef DEBUG
DrawFormatString(0, 0, GetColor(0, 0, 0), "1:(%d,%d)", mNow[1].x, mNow[1].y);
#endif
}
//---------------------------------
// ゲーム実行処理
void Game::Run()
{
enum{
INTERVAL_FRAME = 400,
};
++mFrame;
if (!mIsOver){
switch (mGameFlag){
case GameFlag::Normal:
this->KeyProcess();
// 一定フレームに1回自動的にブロック落下
if (mFrame % BLOCK_DOWN_FRAME == 0)
this->BlockDown();
break;
case GameFlag::BlockDelete:
if (GetNowCount() > mLastStateFrame + INTERVAL_FRAME){
this->EraseAll();
}
break;
case GameFlag::FieldDrop:
if (GetNowCount() > mLastStateFrame + INTERVAL_FRAME){
this->FieldDrop();
}
break;
default:
SHOULD_NOT_REACH_HERE;
}
}
this->Draw();
}
//---------------------------------
// 毎フレームの処理
bool LoopProc()
{
return !ScreenFlip() && !ProcessMessage() && !ClearDrawScreen() && !CheckHitKey(KEY_INPUT_ESCAPE);
}
//---------------------------------------------------------
// エントリポイント
//---------------------------------------------------------
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
ChangeWindowMode(true);
SetGraphMode(PIECEX * BOARDX, PIECEY * BOARDY, 32);
SetWindowText("Game");
SetOutApplicationLogValidFlag(false);
if (DxLib_Init()) return -1;
try{
while (LoopProc()){
Key::get()->Update();
Game::get()->Run();
}
}
catch (const char* s){
::MessageBox(nullptr, s, "Error", MB_OK | MB_ICONASTERISK);
}
catch (...){
::MessageBox(nullptr, "unexpected exception raised", "Error", MB_OK | MB_ICONASTERISK);
}
DxLib_End();
return 0;
}