Skip to content

Instantly share code, notes, and snippets.

@tks2shimizu
Created December 15, 2013 06:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tks2shimizu/691c1d95de32699623f5 to your computer and use it in GitHub Desktop.
Save tks2shimizu/691c1d95de32699623f5 to your computer and use it in GitHub Desktop.
Puzzle Game (6) 玉を作成する
/****************************************************************************
Copyright (c) 2013 TKS2
http://tks2.net
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#include "HelloWorldScene.h"
USING_NS_CC;
#define BALL_NUM_X 6
#define BALL_NUM_Y 5
#define BALL_SIZE 53
#define ONE_ACTION_TIME 0.2
#pragma mark - BallSprite class
BallSprite::BallSprite()
: _removedNo(0)
, _checkedX(false)
, _checkedY(false)
, _fallCount(0)
, _PositionIndex(0, 0)
{
}
BallSprite::~BallSprite()
{
}
BallSprite* BallSprite::create(BallType type, bool visible)
{
BallSprite *pRet = new BallSprite();
if (pRet && pRet->init(type, visible))
{
pRet->autorelease();
return pRet;
}
else
{
delete pRet;
pRet = nullptr;
return nullptr;
}
}
bool BallSprite::init(BallType type, bool visible)
{
if (!Sprite::initWithFile(getBallImageFilePath(type)))
return false;
_ballType = type;
setVisible(visible);
return true;
}
const char* BallSprite::getBallImageFilePath(BallType type)
{
// タイプに適した画像を返す
switch (type)
{
case BallType::Red: return "red.png";
case BallType::Blue: return "blue.png";
case BallType::Yellow: return "yellow.png";
case BallType::Green: return "green.png";
case BallType::Purple: return "purple.png";
default: return "pink.png";
}
}
void BallSprite::resetParams()
{
_removedNo = 0;
_checkedX = false;
_checkedY = false;
_fallCount = 0;
}
// 位置をm_PositionIndexの位置に表示する
void BallSprite::resetPosition()
{
// 位置を変更する
setPosition(getPositionForPositionIndex(_PositionIndex));
}
// インデックスとタグを変更する
void BallSprite::setPositionIndex(PositionIndex positionIndex)
{
// インデックスを保持する
_PositionIndex = positionIndex;
// タグをセットする
setTag(generateTag(_PositionIndex));
}
// インデックスとタグと位置を変更する
void BallSprite::setPositionIndexAndChangePosition(PositionIndex positionIndex)
{
// インデックスとタグを変更する
setPositionIndex(positionIndex);
// 位置を変更する
resetPosition();
}
Point BallSprite::getPositionForPositionIndex(PositionIndex positionIndex)
{
return Point(BALL_SIZE * (positionIndex.x - 0.5) + 1,
BALL_SIZE * (positionIndex.y - 0.5) + 1);
}
int BallSprite::generateTag(PositionIndex positionIndex)
{
return positionIndex.x * 10 + positionIndex.y;
}
void BallSprite::removingAndFallingAnimation(int maxRemovedNo)
{
// 玉を消すアニメーション
removingAnimation(maxRemovedNo);
// 玉を落とすアニメーション
fallingAnimation(maxRemovedNo);
}
void BallSprite::removingAnimation(int maxRemovedNo)
{
if (_removedNo > 0)
{
// 玉を消すアニメーション
auto delay1 = DelayTime::create(ONE_ACTION_TIME * (_removedNo - 1));
auto fade = FadeTo::create(ONE_ACTION_TIME, 0);
auto delay2 = DelayTime::create(ONE_ACTION_TIME * (maxRemovedNo - _removedNo));
auto removeSelf = RemoveSelf::create(false);
// アニメーション実行
runAction(Sequence::create(delay1, fade, delay2, removeSelf, nullptr));
}
}
void BallSprite::fallingAnimation(int maxRemovedNo)
{
if (_fallCount > 0)
{
// 玉を落とすアニメーション
setPositionIndex(PositionIndex(_PositionIndex.x, _PositionIndex.y - _fallCount));
auto delay = DelayTime::create(ONE_ACTION_TIME * maxRemovedNo);
auto show = Show::create();
auto move = MoveTo::create(ONE_ACTION_TIME, getPositionForPositionIndex(getPositionIndex()));
runAction(Sequence::create(delay, show, move, nullptr));
}
}
#pragma mark - HelloWorld class
Scene* HelloWorld::createScene()
{
auto scene = Scene::create();
auto layer = HelloWorld::create();
scene->addChild(layer);
return scene;
}
bool HelloWorld::init()
{
if (!Layer::init())
return false;
// 乱数初期化
srand((unsigned int)time(nullptr));
// 変数初期化
_movingBall = nullptr;
_touchable = true;
// 玉の表示
initBalls();
// シングルタップイベントの取得
EventListenerTouchOneByOne* _touchListener = EventListenerTouchOneByOne::create();
_touchListener->onTouchBegan = CC_CALLBACK_2(HelloWorld::onTouchBegan, this);
_touchListener->onTouchMoved = CC_CALLBACK_2(HelloWorld::onTouchMoved, this);
_touchListener->onTouchEnded = CC_CALLBACK_2(HelloWorld::onTouchEnded, this);
_touchListener->onTouchCancelled = CC_CALLBACK_2(HelloWorld::onTouchCancelled, this);
_eventDispatcher->addEventListenerWithSceneGraphPriority(_touchListener, this);
return true;
}
void HelloWorld::initBalls()
{
auto allBalls = Array::create();
for (int x = 1; x <= BALL_NUM_X; x++)
{
for (int y = 1; y <= BALL_NUM_Y; y++)
{
// 玉を生成する
auto newBall = newBalls(BallSprite::PositionIndex(x, y), allBalls);
allBalls->addObject(newBall);
}
}
}
// 玉を生成する
BallSprite* HelloWorld::newBalls(BallSprite::PositionIndex positionIndex, Array* allBalls)
{
// 乱数を元に、ランダムでタイプを取得
int ballType;
while (true)
{
ballType = rand() % (int)BallSprite::BallType::SIZE;
if (!allBalls)
break;
// 妥当性のチェック(ボールが隣り合わせにならないようにする)
// 左隣のボール
auto ballX1 = (BallSprite*)(getChildByTag(BallSprite::generateTag(BallSprite::PositionIndex(positionIndex.x - 1, positionIndex.y))));
auto ballX2 = (BallSprite*)(getChildByTag(BallSprite::generateTag(BallSprite::PositionIndex(positionIndex.x - 2, positionIndex.y))));
if (!(ballX1 && ballType == (int)ballX1->getBallType()) ||
!(ballX2 && ballType == (int)ballX2->getBallType()))
{
// 下隣のボール
auto ballY1 = (BallSprite*)(getChildByTag(BallSprite::generateTag(BallSprite::PositionIndex(positionIndex.x, positionIndex.y - 1))));
auto ballY2 = (BallSprite*)(getChildByTag(BallSprite::generateTag(BallSprite::PositionIndex(positionIndex.x, positionIndex.y - 2))));
if (!(ballY1 && ballType == (int)ballY1->getBallType()) ||
!(ballY2 && ballType == (int)ballY2->getBallType()))
{
break;
}
}
}
// ボールの表示
bool visible = (allBalls == nullptr)? false : true;
auto ball = BallSprite::create((BallSprite::BallType)ballType, visible);
ball->setPositionIndexAndChangePosition(positionIndex);
addChild(ball);
return ball;
}
// タップした位置のチェック
BallSprite* HelloWorld::getTouchBall(Point touchPos, BallSprite::PositionIndex withoutPosIndex)
{
for (int x = 1; x <= BALL_NUM_X; x++)
{
for (int y = 1; y <= BALL_NUM_Y; y++)
{
if (x == withoutPosIndex.x && y == withoutPosIndex.y)
{
// 指定位置の玉の場合は、以下の処理を行わない
continue;
}
// タップ位置にある玉かどうかを判断する
auto ball = (BallSprite*)(getChildByTag(BallSprite::generateTag(BallSprite::PositionIndex(x, y))));
if (ball)
{
// 2点間の距離を求める
float distance = ball->getPosition().getDistance(touchPos);
if (distance <= BALL_SIZE / 2)
return ball;
}
}
}
return nullptr;
}
// タップ操作による玉の移動完了時処理
void HelloWorld::movedBall()
{
// 移動している玉を本来の位置に戻す
_movingBall->resetPosition();
_movingBall = nullptr;
// 一列に並んだ玉があるかチェックする
_chainNumber = 0;
_removeNumbers.clear();
checksLinedBalls();
}
#pragma mark - 玉の移動処理
bool HelloWorld::onTouchBegan(Touch* touch, Event* unused_event)
{
if (!_touchable)
return false;
_movingBall = getTouchBall(touch->getLocation());
if (_movingBall)
return true;
else
return false;
}
void HelloWorld::onTouchMoved(Touch* touch, Event* unused_event)
{
_movingBall->setPosition(_movingBall->getPosition() + touch->getDelta());
BallSprite* touchBall = getTouchBall(touch->getLocation(), _movingBall->getPositionIndex());
if (touchBall && _movingBall != touchBall)
{
// 別の玉のタグを取得
BallSprite::PositionIndex touchBallPositionIndex = touchBall->getPositionIndex();
// 別の玉を移動している玉の元の位置へ移動する
touchBall->setPositionIndexAndChangePosition(_movingBall->getPositionIndex());
// 移動している玉の情報を変更
_movingBall->setPositionIndex(touchBallPositionIndex);
}
}
void HelloWorld::onTouchEnded(Touch* touch, Event* unused_event)
{
movedBall();
}
void HelloWorld::onTouchCancelled(Touch* touch, Event* unused_event)
{
movedBall();
}
#pragma mark - 玉の並びチェック
// 一列に並んだ玉があるかチェックする
void HelloWorld::checksLinedBalls()
{
if (existsLinedBalls())
{
// 3個以上並んだ玉の存在する場合
// 画面をタップ不可とする
_touchable = false;
// 連鎖カウントアップ
_chainNumber++;
// ボールの削除と生成
removeAndGenerateBalls();
// アニメーション後に再チェック
auto delay = DelayTime::create(ONE_ACTION_TIME * (_maxRemovedNo + 1));
auto func = CallFunc::create(CC_CALLBACK_0(HelloWorld::checksLinedBalls, this));
auto seq = Sequence::create(delay, func, nullptr);
runAction(seq);
}
else
{
endAnimation();
}
}
// 3個以上並んだ玉の存在チェック
bool HelloWorld::existsLinedBalls()
{
// 全ての玉のBallTypeを取得
auto allBalls = getAllBalls();
// 玉のパラメータを初期化する
initBallParams(allBalls);
// 消去される順番の初期化
_maxRemovedNo = 0;
for (int x = 1; x <= BALL_NUM_X; x++)
{
for (int y = 1; y <= BALL_NUM_Y; y++)
{
// x方向の玉をチェック
checkedBall(BallSprite::PositionIndex(x, y), Direction::x, allBalls);
// y方向の玉をチェック
checkedBall(BallSprite::PositionIndex(x, y), Direction::y, allBalls);
}
}
// 戻り値の決定
return _maxRemovedNo > 0;
}
// 全ての玉のBallTypeを取得
Dictionary* HelloWorld::getAllBalls()
{
auto balls = Dictionary::create();
Object* object = nullptr;
CCARRAY_FOREACH(getChildren(), object)
{
auto ball = dynamic_cast<BallSprite*>(object);
if (ball)
balls->setObject(ball, ball->getTag());
}
return balls;
}
// 指定方向の玉と同じ色かチェックする
bool HelloWorld::isSameBallType(BallSprite::PositionIndex current, Direction direction, Dictionary* allBalls)
{
if (direction == Direction::x)
{
if (current.x + 1 > BALL_NUM_X)
return false;
}
else
{
if (current.y + 1 > BALL_NUM_Y)
return false;
}
// 現在の玉を取得
int currentTag = BallSprite::generateTag(BallSprite::PositionIndex(current.x, current.y));
BallSprite* currentBall = (BallSprite*)allBalls->objectForKey(currentTag);
// 次の玉を取得
int nextTag;
if (direction == Direction::x)
nextTag = BallSprite::generateTag(BallSprite::PositionIndex(current.x + 1, current.y));
else
nextTag = BallSprite::generateTag(BallSprite::PositionIndex(current.x, current.y + 1));
auto nextBall = (BallSprite*)allBalls->objectForKey(nextTag);
if (currentBall->getBallType() == nextBall->getBallType())
// 次の玉が同じBallTypeである
return true;
return false;
}
// 玉のパラメータを初期化する
void HelloWorld::initBallParams(Dictionary* allBalls)
{
DictElement* element = nullptr;
CCDICT_FOREACH(allBalls, element)
{
auto ball = (BallSprite*)allBalls->objectForKey(element->getIntKey());
ball->resetParams();
}
}
void HelloWorld::checkedBall(BallSprite::PositionIndex current, Direction direction, Dictionary* allBalls)
{
// 検索するタグの生成
int tag = BallSprite::generateTag(BallSprite::PositionIndex(current.x, current.y));
BallSprite* ball = (BallSprite*)allBalls->objectForKey(tag);
// 指定方向のチェック済みフラグを取得
bool checked;
if (direction == Direction::x)
checked = ball->getCheckedX();
else
checked = ball->getCheckedY();
if (!checked)
{
int num = 0;
while (true)
{
// 次の玉の位置を取得
BallSprite::PositionIndex nextPosition;
if (direction == Direction::x)
nextPosition = BallSprite::PositionIndex(current.x + num, current.y);
else
nextPosition = BallSprite::PositionIndex(current.x, current.y + num);
// 次の玉と同じballTypeかチェックする
if (isSameBallType(nextPosition, direction, allBalls))
{
// 次の玉と同じballType
int nextTag = BallSprite::generateTag(nextPosition);
auto nextBall = (BallSprite*)allBalls->objectForKey(nextTag);
// チェックした玉のチェック済みフラグを立てる
if (direction == Direction::x)
nextBall->setCheckedX(true);
else
nextBall->setCheckedY(true);
num++;
}
else
{
// 次の玉が異なるballType
if (num >= 2)
{
int removedNo = 0;
// 消去するボールのカウント
if (_removeNumbers.size() <= _chainNumber)
{
// 配列が存在しない場合は追加する
std::map<BallSprite::BallType, int> removeNumber;
_removeNumbers.push_back(removeNumber);
}
_removeNumbers[_chainNumber][ball->getBallType()] += num + 1;
// すでにRemovedNoがあるものが存在するかチェック
for (int i = 0; i <= num; i++)
{
BallSprite::PositionIndex linedPosition;
if (direction == Direction::x)
linedPosition = BallSprite::PositionIndex(current.x + i, current.y);
else
linedPosition = BallSprite::PositionIndex(current.x, current.y + i);
int linedBallTag = BallSprite::generateTag(linedPosition);
auto linedBall = (BallSprite*)allBalls->objectForKey(linedBallTag);
if (linedBall->getRemovedNo() > 0)
{
// すでにRemovedNoがあるものが存在するので、removedNoを取得し次の処理を行う
removedNo = linedBall->getRemovedNo();
break;
}
}
// 消去する順番のカウントアップ
if (removedNo == 0)
removedNo = ++_maxRemovedNo;
// 3個以上並んでいた場合は、linedをtrueとする
for (int i = 0; i <= num; i++)
{
BallSprite::PositionIndex linedPosition;
if (direction == Direction::x)
linedPosition = BallSprite::PositionIndex(current.x + i, current.y);
else
linedPosition = BallSprite::PositionIndex(current.x, current.y + i);
int linedBallTag = BallSprite::generateTag(linedPosition);
auto linedBall = (BallSprite*)allBalls->objectForKey(linedBallTag);
linedBall->setRemovedNo(removedNo);
}
}
break;
}
};
// 指定方向をチェック済みとする
if (direction == Direction::x)
ball->setCheckedX(true);
else
ball->setCheckedY(true);
}
}
void HelloWorld::removeAndGenerateBalls()
{
// 全ての玉のBallTypeを取得
auto allBalls = getAllBalls();
int maxRemovedNo = 0;
for (int x = 1; x <= BALL_NUM_X; x++)
{
int fallCount = 0;
for (int y = 1; y <= BALL_NUM_Y; y++)
{
int tag = BallSprite::generateTag(BallSprite::PositionIndex(x, y));
auto ball = (BallSprite*)allBalls->objectForKey(tag);
if (ball) {
int removedNoForBall = ball->getRemovedNo();
if (removedNoForBall > 0)
{
fallCount++;
if (removedNoForBall > maxRemovedNo)
maxRemovedNo = removedNoForBall;
}
else
{
// 落ちる段数をセット
ball->setFallCount(fallCount);
}
}
}
// 玉を生成する
generateBalls(x, fallCount, allBalls);
}
// 玉の消去&落下アニメーション
animationBalls(allBalls);
}
// 玉を生成する
void HelloWorld::generateBalls(int xLineNum, int fallCount, Dictionary* allBalls)
{
for (int i = 1; i <= fallCount; i++)
{
// 玉を生成する
auto positionIndex = BallSprite::PositionIndex(xLineNum, BALL_NUM_Y + i);
auto ball = newBalls(positionIndex, nullptr);
ball->setFallCount(fallCount);
allBalls->setObject(ball, BallSprite::generateTag(positionIndex));
}
}
// 玉の消去&落下アニメーション
void HelloWorld::animationBalls(Dictionary* allBalls)
{
DictElement* element = nullptr;
CCDICT_FOREACH(allBalls, element)
{
// 玉のアニメーションを実行する
auto ball = (BallSprite*)allBalls->objectForKey(element->getIntKey());
ball->removingAndFallingAnimation(_maxRemovedNo);
}
}
// アニメーション終了時処理
void HelloWorld::endAnimation()
{
// タップを有効にする
_touchable = true;
}
/****************************************************************************
Copyright (c) 2013 TKS2
http://tks2.net
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#ifndef __HELLOWORLD_SCENE_H__
#define __HELLOWORLD_SCENE_H__
#include "cocos2d.h"
class BallSprite : public cocos2d::Sprite
{
public:
enum class BallType
{
Blue,
Red,
Green,
Yellow,
Purple,
Pink,
SIZE,
};
struct PositionIndex
{
PositionIndex()
{
x = 0;
y = 0;
};
PositionIndex(int posX, int posY)
{
x = posX;
y = posY;
};
int x;
int y;
};
protected:
const char* getBallImageFilePath(BallType type);
void removingAnimation(int maxRemovedNo);
void fallingAnimation(int maxRemovedNo);
public:
BallSprite();
virtual ~BallSprite();
static BallSprite* create(BallType type, bool visible);
virtual bool init(BallType type, bool visible);
CC_SYNTHESIZE(int, _removedNo, RemovedNo);
CC_SYNTHESIZE(int, _checkedX, CheckedX);
CC_SYNTHESIZE(int, _checkedY, CheckedY);
CC_SYNTHESIZE(int, _fallCount, FallCount);
CC_SYNTHESIZE_READONLY(BallType, _ballType, BallType);
CC_SYNTHESIZE_READONLY(PositionIndex, _PositionIndex, PositionIndex);
void setPositionIndex(PositionIndex positionIndex);
void setPositionIndexAndChangePosition(PositionIndex positionIndex);
void resetPosition();
void resetParams();
void removingAndFallingAnimation(int maxRemovedNo);
static cocos2d::Point getPositionForPositionIndex(PositionIndex positionIndex);
static int generateTag(PositionIndex positionIndex);
};
class HelloWorld : public cocos2d::Layer
{
protected:
enum class Direction
{
x,
y,
};
BallSprite* _movingBall;
bool _touchable;
int _maxRemovedNo;
int _chainNumber;
std::vector<std::map<BallSprite::BallType, int> > _removeNumbers;
void initBalls();
BallSprite* newBalls(BallSprite::PositionIndex positionIndex, cocos2d::Array* allBalls);
BallSprite* getTouchBall(cocos2d::Point touchPos,
BallSprite::PositionIndex withoutPosIndex = BallSprite::PositionIndex(0, 0));
void movedBall();
void checksLinedBalls();
bool existsLinedBalls();
cocos2d::Dictionary* getAllBalls();
bool isSameBallType(BallSprite::PositionIndex current, Direction direction, cocos2d::Dictionary* allBalls);
void initBallParams(cocos2d::Dictionary* allBalls);
void checkedBall(BallSprite::PositionIndex current, Direction direction, cocos2d::Dictionary* allBalls);
void removeAndGenerateBalls();
void generateBalls(int xLineNum, int fallCount, cocos2d::Dictionary* allBalls);
void animationBalls(cocos2d::Dictionary* allBalls);
void endAnimation();
public:
static cocos2d::Scene* createScene();
virtual bool init();
CREATE_FUNC(HelloWorld);
// シングルタップイベント
virtual bool onTouchBegan(cocos2d::Touch* touch, cocos2d::Event* unused_event);
virtual void onTouchMoved(cocos2d::Touch* touch, cocos2d::Event* unused_event);
virtual void onTouchEnded(cocos2d::Touch* touch, cocos2d::Event* unused_event);
virtual void onTouchCancelled(cocos2d::Touch* touch, cocos2d::Event* unused_event);
};
#endif // __HELLOWORLD_SCENE_H__
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment