게임 규칙 구현하기
Consts.h
#pragma once
#include <string>
namespace Consts
{
const std::string paths[]{ // 이미지 파일 절대 경로
R"(C:\\Users\\4rest\\Desktop\\Shin\\QtGame\\threeMatchImage\\PNG\\ico\\1.png)",
R"(C:\\Users\\4rest\\Desktop\\Shin\\QtGame\\threeMatchImage\\PNG\\ico\\2.png)",
R"(C:\\Users\\4rest\\Desktop\\Shin\\QtGame\\threeMatchImage\\PNG\\ico\\3.png)",
R"(C:\\Users\\4rest\\Desktop\\Shin\\QtGame\\threeMatchImage\\PNG\\ico\\4.png)",
R"(C:\\Users\\4rest\\Desktop\\Shin\\QtGame\\threeMatchImage\\PNG\\ico\\5.png)",
R"(C:\\Users\\4rest\\Desktop\\Shin\\QtGame\\threeMatchImage\\PNG\\ico\\6.png)",
R"(C:\\Users\\4rest\\Desktop\\Shin\\QtGame\\threeMatchImage\\PNG\\ico\\7.png)",
R"(C:\\Users\\4rest\\Desktop\\Shin\\QtGame\\threeMatchImage\\PNG\\ico\\8.png)",
R"(C:\\Users\\4rest\\Desktop\\Shin\\QtGame\\threeMatchImage\\PNG\\ico\\9.png)",
R"(C:\\Users\\4rest\\Desktop\\Shin\\QtGame\\threeMatchImage\\PNG\\ico\\10.png)",
R"(C:\\Users\\4rest\\Desktop\\Shin\\QtGame\\threeMatchImage\\PNG\\ico\\11.png)",
R"(C:\\Users\\4rest\\Desktop\\Shin\\QtGame\\threeMatchImage\\PNG\\ico\\12.png)",
R"(C:\\Users\\4rest\\Desktop\\Shin\\QtGame\\threeMatchImage\\PNG\\ico\\13.png)",
R"(C:\\Users\\4rest\\Desktop\\Shin\\QtGame\\threeMatchImage\\PNG\\ico\\14.png)",
};
const int BOARD_LENGTH = 12;
const int BOARD_ITEM_SIZE = 60;
const int MAX_ITEM_TYPES = 11;
}
아이템 종류 개수를 제어하기 위한 상수로 MAX_ITEM_TYPES를 추가하였다.
Board.h
#pragma once
#include <QtWidgets/QGraphicsScene>
#include <QtWidgets/QGraphicsPixmapItem>
#include <QtWidgets/QGraphicsRectItem>
#include <vector>
#include <random>
#include <set>
#include "Item.h"
using MatchPair = std::pair<int, int>;
using MatchSet = std::set<MatchPair>;
class Board : public Item::EventListener // EventListener로 제어하기 위해 인터페이스 상속
{
private:
QGraphicsScene* _scene;
QGraphicsRectItem _root; // 보드판 묶음
std::vector<std::vector<Item*>> _items; // item 배열
std::random_device _device; // 난수 생성
std::mt19937 _gen;
public:
Board(QGraphicsScene* scene);
~Board();
void addItem(int row, int column);
void removeItem(int row, int column);
void moveItem(int fromRow, int fromColumn, int toRow, int toColumn);
void moveItem(Item* item, int toRow, int toColumn); // 아이템 위치 이동
void exchangeItems(int row0, int column0, int row1, int column1); // 아이템 교환
bool refresh();
MatchSet matchedItems() const;
MatchSet matchedItems(int row, int column) const;
MatchSet matchedItemsHorizontal(int row, int column) const;
MatchSet matchedItemsVertical(int row, int column) const;
virtual void itemDragEvent(Item* item, Item::Direction dir) override; // 오버라이딩
};
/*
items
1 2 3 -> _items[0], row0
4 5 6 -> _items[1], row1
7 8 9 -> _items[2], row2
_items[0][0] -> 1
_items[0][1] -> 2
_items[0][2] -> 3
*/
타입 앨리어스 설정, moveItem 오버로딩, refresh, matched 함수 정의
Board.cpp
#include <random>
#include "Board.h"
#include "Consts.h"
#include "Item.h"
Board::Board(QGraphicsScene* scene)
: _scene(scene) // scene 설정
, _gen(_device()) // 난수 생성
{
_scene->addItem(&_root); // scene에 보드판 묶음 추가
_root.setX(_scene->sceneRect().width() / 2 // 보드판 위치 설정
- Consts::BOARD_ITEM_SIZE * Consts::BOARD_LENGTH / 2);
_root.setY(_scene->sceneRect().height() / 2
- Consts::BOARD_ITEM_SIZE * Consts::BOARD_LENGTH / 2);
for (int row = 0; row < Consts::BOARD_LENGTH; ++row) // vector에 item 넣기
{
std::vector<Item*> rowItems(Consts::BOARD_LENGTH);
_items.push_back(rowItems); // 행 삽입
for (int column = 0; column < Consts::BOARD_LENGTH; ++column)
{
addItem(row, column); // 각 열에 item 추가
}
}
while(refresh()); // 처음에 생기는 3match 제거
}
Board::~Board()
{
for (int row = 0; row < _items.size(); ++row)
{
for (int column = 0; column < _items[row].size(); ++column)
{
removeItem(row, column);
}
}
}
void Board::addItem(int row, int column)
{
std::uniform_int_distribution<int> dis(0, Consts::MAX_ITEM_TYPES); // 난수를 균등하게 생성
const std::string& path = Consts::paths[dis(_gen)];
Item* item = new Item(this, path, row, column, &_root); // EventListener로 자신을 전달, 오버라이딩한 함수가 호출가능해짐.
item->setPos(column * Consts::BOARD_ITEM_SIZE, row * Consts::BOARD_ITEM_SIZE); // 부모위치를 기준으로 item 위치 설정
_items[row][column] = item; // 배열에 item 삽입
}
void Board::removeItem(int row, int column)
{
auto* item = _items[row][column];
if (item == nullptr) // 존재하지 않는다면 종료
return;
_items[row][column] = nullptr; // 벡터 원소 해제
item->setParentItem(nullptr); // 부모 정보 해제
_scene->removeItem(item); // scene에서 해제
delete item; // 동적할당 해제
}
void Board::moveItem(int fromRow, int fromColumn, int toRow, int toColumn)
{
Item* item = _items[fromRow][fromColumn];
if (item == nullptr)
return;
moveItem(item, toRow, toColumn);
}
void Board::moveItem(Item* item, int toRow, int toColumn)
{
item->setRow(toRow); // 좌표값 변경
item->setColumn(toColumn);
item->setPos(toColumn * Consts::BOARD_ITEM_SIZE, toRow * Consts::BOARD_ITEM_SIZE);
_items[toRow][toColumn] = item; // 벡터내에서 변경
}
void Board::exchangeItems(int row0, int column0, int row1, int column1)
{
Item* item0 = _items[row0][column0];
Item* item1 = _items[row1][column1];
moveItem(item0, row1, column1); // 교환
moveItem(item1, row0, column0);
}
bool Board::refresh()
{
MatchSet matched = matchedItems(); // 3개 이상 연속된 아이템 모음
if (matched.size() < 3) // 3개 이하라면 종료
return false;
for (const auto& pair : matched) // 3개 이상이라면 아이템 제거
{
removeItem(pair.first, pair.second);
}
// 빈곳에 아이템 내리기
for (int column = 0; column < _items[0].size(); ++column)
{
for (int row = _items.size() - 1; row >= 0; --row)
{
if (_items[row][column] != nullptr)
{
continue;
}
for (int i = row - 1; i >= 0; --i)
{
if (_items[i][column] != nullptr)
{
moveItem(i, column, row, column);
_items[i][column] = nullptr;
break;
}
}
}
}
// 새 아이템 채우기
for (int column = 0; column < _items[0].size(); ++column)
{
for (int row = 0; row < _items.size(); ++row)
{
if (_items[row][column] == nullptr)
{
addItem(row, column);
}
else
{
break;
}
}
}
return true;
}
// 3개이상 연속된 아이템을 찾아서 리턴하는 함수
MatchSet Board::matchedItems() const
{
MatchSet matched;
for (int row = 0; row < _items.size(); ++row)
{
for (int column = 0; column < _items[row].size(); ++column)
{
MatchSet m = matchedItems(row, column);
if (m.size() >= 3)
{
matched.insert(m.begin(), m.end());
}
}
}
return matched;
}
// 한개의 아이템을 기준으로 가로세로 검사한다.
MatchSet Board::matchedItems(int row, int column) const
{
MatchSet horizontalMatched = matchedItemsHorizontal(row, column);
MatchSet verticalMatched = matchedItemsVertical(row, column);
MatchSet matched;
if (horizontalMatched.size() >= 3)
matched.insert(horizontalMatched.begin(), horizontalMatched.end());
if (verticalMatched.size() >= 3)
matched.insert(verticalMatched.begin(), verticalMatched.end());
return matched;
}
// 가로 검사
MatchSet Board::matchedItemsHorizontal(int row, int column) const
{
Item* item = _items[row][column];
if (item == nullptr)
return {};
MatchSet leftMatched;
for (int i = column - 1; i >= 0; --i)
{
if (_items[row][i] && _items[row][i]->path() == item->path())
{
leftMatched.insert({ row,i });
}
else
{
break;
}
}
MatchSet rightMatched;
for (int i = column + 1; i < _items[row].size(); ++i)
{
if (_items[row][i] && _items[row][i]->path() == item->path())
{
rightMatched.insert({ row,i });
}
else
{
break;
}
}
if (leftMatched.size() + rightMatched.size() + 1 >= 3)
{
leftMatched.insert(rightMatched.begin(), rightMatched.end());
leftMatched.insert({ row,column });
return leftMatched;
}
else
{
return {};
}
}
// 세로 검사
MatchSet Board::matchedItemsVertical(int row, int column) const
{
Item* item = _items[row][column];
if (item == nullptr)
return {};
MatchSet upMatched;
for (int i = row - 1; i >= 0; --i)
{
if (_items[i][column] && _items[i][column]->path() == item->path())
{
upMatched.insert({ i,column });
}
else
{
break;
}
}
MatchSet downMatched;
for (int i = row + 1; i < _items.size(); ++i)
{
if (_items[i][column] && _items[i][column]->path() == item->path())
{
downMatched.insert({ i,column });
}
else
{
break;
}
}
if (upMatched.size() + downMatched.size() + 1 >= 3)
{
upMatched.insert(downMatched.begin(), downMatched.end());
upMatched.insert({ row,column });
return upMatched;
}
else
{
return {};
}
}
void Board::itemDragEvent(Item* item, Item::Direction dir)
{
int row0 = item->row(); // 기존 아이템
int column0 = item->column();
int row1 = row0; // 교환할 아이템
int column1 = column0;
switch (dir) // 방향
{
case Item::Direction::Left:
column1--;
break;
case Item::Direction::Right:
column1++;
break;
case Item::Direction::Up:
row1--;
break;
case Item::Direction::Down:
row1++;
break;
}
if (row1 < 0 || column1 < 0) // 보드판 바깥 예외처리
return;
if (row1 >= Consts::BOARD_LENGTH || column1 >= Consts::BOARD_LENGTH)
return;
Item* item1 = _items[row1][column1];
if (item1 == nullptr)
return;
exchangeItems(row0, column0, row1, column1); // 교환
while (refresh());
}
같은 아이템 3개가 모이면 사라지고 위에서 블록이 내려와 채우는 로직을 구현했다. 한번 리프레쉬된 이후에 또 3개가 생길 수 있기 때문에 3개가 만들어지지 않을 때까지 반복한다.
Item.h, Item.cpp, main.cpp 는 큰 변화가 없었다.
마치며
![]() |
![]() |
가장 오른쪽 열에 노란 사탕 세개가 만들어 질 수 있다. | 노란 사탕이 사라지면서 위에 있던 아이템이 내려왔다 |
사실 오늘 애니메이션 구현하는 것까지 했었는데 Qt 버전차이 때문에 버그가 너무 많이 나와서 일단 게임 규칙 구현까지 기록했습니다. 쉽지 않네요 ㅠㅠ
'게임개발 > Qt' 카테고리의 다른 글
[QT] 애니팡 - 점수 UI 만들기 (완성) (2) | 2024.08.28 |
---|---|
[QT] 애니팡 - 애니메이션 넣기 (2) | 2024.08.28 |
[QT] 애니팡 - 마우스 이벤트 받기 (0) | 2024.08.20 |
[QT] 애니팡 - 이미지 배치하기 (0) | 2024.08.16 |