Cocos2d-xとFirebaseでゲームランキングを作る #3

[前回](http://qiita.com/h-takauma/items/42c898a548e663306f4a)はスコアを送信する所までを実装しました。
今回は送信したスコアを取得して降順に並べる処理を行います。

#ランククラス作成
次にランククラスを作成します。
ClassesフォルダのFirebaseフォルダに`FIBRankBridge.h`を作成しましょう。

![s190.png](https://qiita-image-store.s3.amazonaws.com/0/154355/b6ac01b2-a5c5-ae8f-ed7d-c205f933b5c0.png)

```cpp:FIBRankBridge.h
#include "cocos2d.h"
#include "FIBUserBridge.h"

USING_NS_CC;

class FIBRankBridge;

using FIBRankCallback = std::function<void (std::vector<FIBRankBridge*> ranks, const std::string &)>;

class FIBRankBridge
{
    public :
    long long int rank;
    long long int score;
    FIBUserBridge* user;
    
    static FIBRankBridge* rankBridge(const void* rank);
    static void fetchTopRanking(int cnt, FIBRankCallback callback);
};
```
次にiOSフォルダのFirebaseフォルダに下記を新規作成しましょう。
こちらも拡張子が`.mm`なので気をつけましょう。

- FIBRankBridge.mm
- FIBRank.mm
- FIBRank.h

![s191.png](https://qiita-image-store.s3.amazonaws.com/0/154355/60c795e3-8528-dfb3-e15f-cd70d2129d42.png)

```objc:FIBRankBridge.mm
#import "FIBRankBridge.h"
#import "FIBRank.h"

void FIBRankBridge::fetchTopRanking(int cnt, FIBRankCallback callback)
{
    [FIBRank fetchTopRanking:cnt completion:^(NSArray *ranking, NSError *error) {
        __block std::vector<FIBRankBridge*> rankBridges;
        
        if (error != nil) {
            callback(rankBridges, "error");
        } else {
            
            [ranking enumerateObjectsUsingBlock:^(FIBRank *rank, NSUInteger idx, BOOL * _Nonnull stop)
             {
                 NSLog(@"fetchTopRanking=%@", rank.user.userName);
                 FIBRankBridge *rankBridge = FIBRankBridge::rankBridge(rank);
                 rankBridges.push_back(rankBridge);
             }];
            callback(rankBridges, "");
            
        }
    }];
}

FIBRankBridge* FIBRankBridge::rankBridge(const void*/*FIBRank*/ rank)
{
    FIBRank *aRank = (FIBRank *)rank;
    FIBRankBridge* rankBridge = new FIBRankBridge();
    
    rankBridge->score = [aRank.score longLongValue];
    log("rankBridge=%s", [aRank.user.userName UTF8String]);
    rankBridge->user = FIBUserBridge::userBridge(aRank.user);
    
    return rankBridge;
}
```

```objc:FIBRank.mm
#import "FIBRank.h"
#import "Firebase.h"

@implementation FIBRank

-(instancetype) initWithRankUser:(FIBUser *)user
                            rank:(NSString *)rank
                           score:(NSString *)score
{
    if ((self = [super init])) {
        _user = user;
        _rank = rank;
        _score = score;
        
    }
    
    return self;
}

+(void) fetchTopRanking:(int)cnt
             completion:(FIBRankCompletionHandler)completion
{
    FIRDatabaseReference* ref = [[FIRDatabase database] reference].ref;
    
    [[[[ref child:@"score_ranking"] queryOrderedByChild:@"score"] queryLimitedToLast:cnt]
     observeSingleEventOfType:FIRDataEventTypeValue
     withBlock:^(FIRDataSnapshot * _Nonnull snapshotScore)
     {
         if (snapshotScore.value != [NSNull null]) {
             NSDictionary *scoreList = snapshotScore.value;
             NSMutableArray *rankList = [NSMutableArray array];
             
             for (id key in [scoreList keyEnumerator]) {
                 NSDictionary* dict = [scoreList valueForKey:key];
                 
                 [[[ref child:@"users"] child:key] observeSingleEventOfType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot * _Nonnull snapshotUser) {
                     if (snapshotUser.value != [NSNull null]) {
                         NSDictionary *snapshotData = snapshotUser.value;
                         NSString* aUserID = [snapshotUser key];
                         NSString* aUserName = [snapshotData valueForKey:@"name"];
                         NSLog(@"name=%@", aUserName);
                         
                         NSString* aCreateTM = [snapshotData valueForKey:@"createTM"];
                         
                         FIBUser* fibUser = [[FIBUser alloc] initWithUserID:aUserID
                                                                   userName:aUserName
                                                                   createTM:aCreateTM];
                         FIBRank* fibRank = [[FIBRank alloc] initWithRankUser:fibUser rank:@"1" score:[dict valueForKey:@"score"]];
                         [rankList addObject:fibRank];
                         
                         if (snapshotScore.childrenCount == rankList.count) {
                             completion(rankList, nil);
                         }
                         
                     }
                 }];
             }
             
             
         } else {
             // スコア未登録
             completion(nil, nil);
         }
     }];
}
@end 
```
```objc:FIBRank.h
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import "FIBUser.h"

@class FIBRank;
typedef void (^FIBRankCompletionHandler)(NSArray/*<FIBRank>*/ *ranking, NSError *error);

@interface FIBRank : NSObject

@property (nonatomic, readonly) NSString* rank;
@property (nonatomic, readonly) NSString* score;
@property (nonatomic, readonly) FIBUser *user;

-(instancetype) initWithRankUser:(FIBUser *)user
                            rank:(NSString *)rank
                           score:(NSString *)score;

+ (void)fetchTopRanking:(int)cnt
             completion:(FIBRankCompletionHandler)completion;

@end
```
#スコア取得処理を記述する
実際の処理を`HelloWorldScene.cpp`に記述しましょう

![s150.png](https://qiita-image-store.s3.amazonaws.com/0/154355/e9e107de-b5f8-5b5a-1071-66fc1d3f6495.png)

```cpp:HelloWorldScene.cpp
#include "HelloWorldScene.h"
#include "GSUtilCom.h"
#include "FIBUserBridge.h"
#include "FIBAccountBridge.h"
#include "FIBScoreBridge.h"
#include "FIBRankBridge.h"

#define WINSIZE Director::getInstance()->getWinSize()
#define WINCENTER Point(WINSIZE.width*0.5, WINSIZE.height*0.5)

HelloWorld::HelloWorld()
: _userID("")
, _tableView(nullptr)
{
}

HelloWorld::~HelloWorld()
{
    
}

Scene* HelloWorld::createScene()
{
    // 'scene' is an autorelease object
    auto scene = Scene::create();
    
    // 'layer' is an autorelease object
    auto layer = HelloWorld::create();

    // add layer as a child to scene
    scene->addChild(layer);

    // return the scene
    return scene;
}

// on "init" you need to initialize your instance
bool HelloWorld::init()
{
    //////////////////////////////
    // 1. super init first
    if ( !Layer::init() )
    {
        return false;
    }
    
    // セットアップ
    this->setup();
    
    return true;
}

// セットアップ処理
void HelloWorld::setup()
{
    // ユーザー作成
    Sprite* btnCreateUser_ON = Sprite::create();
    btnCreateUser_ON->setTextureRect(Rect(0, 0, 200, 100));
    btnCreateUser_ON->setColor(Color3B::GRAY);
    
    Sprite* btnCreateUser_OFF = Sprite::create();
    btnCreateUser_OFF->setTextureRect(Rect(0, 0, 200, 100));
    btnCreateUser_OFF->setColor(Color3B(0,0,200));
    
    MenuItemSprite* btnCreateUser = MenuItemSprite::create(btnCreateUser_OFF,
                                                           btnCreateUser_ON,
                                                           [this](Ref* sender) {this->createUserTapped((MenuItemSprite*)sender);});
    Label* lblCreateUser = Label::createWithSystemFont("ユーザー作成", "arial", 26);
    lblCreateUser->enableOutline(Color4B::BLACK, 1);
    lblCreateUser->setPosition(Vec2(btnCreateUser->getContentSize().width*0.5, btnCreateUser->getContentSize().height*0.5));
    btnCreateUser->addChild(lblCreateUser);

    // スコア送信
    Sprite* btnSendScore_ON = Sprite::create();
    btnSendScore_ON->setTextureRect(Rect(0, 0, 200, 100));
    btnSendScore_ON->setColor(Color3B::GRAY);
    
    Sprite* btnSendScore_OFF = Sprite::create();
    btnSendScore_OFF->setTextureRect(Rect(0, 0, 200, 100));
    btnSendScore_OFF->setColor(Color3B(200,0,0));
    
    MenuItemSprite* btnSendScore = MenuItemSprite::create(btnSendScore_OFF,
                                                          btnSendScore_ON,
                                                          [this](Ref* sender) {this->sendScoreTapped((MenuItemSprite*)sender);});
    Label* lblSendScore = Label::createWithSystemFont("スコア送信", "arial", 26);
    lblSendScore->enableOutline(Color4B::BLACK, 1);
    lblSendScore->setPosition(Vec2(btnSendScore->getContentSize().width*0.5, btnSendScore->getContentSize().height*0.5));
    btnSendScore->addChild(lblSendScore);

    // ランキング取得
    Sprite* btnRank_ON = Sprite::create();
    btnRank_ON->setTextureRect(Rect(0, 0, 200, 100));
    btnRank_ON->setColor(Color3B::GRAY);
    
    Sprite* btnRank_OFF = Sprite::create();
    btnRank_OFF->setTextureRect(Rect(0, 0, 200, 100));
    btnRank_OFF->setColor(Color3B(200,0,0));
    
    MenuItemSprite* btnRank = MenuItemSprite::create(btnRank_OFF,
                                                     btnRank_ON,
                                                     [this](Ref* sender) {this->rankTapped((MenuItemSprite*)sender);});
    
    Label* lblRank = Label::createWithSystemFont("ランキング\n取得", "arial", 26);
    lblRank->setAlignment(TextHAlignment::CENTER);
    lblRank->enableOutline(Color4B::BLACK, 1);
    lblRank->setPosition(Vec2(btnRank->getContentSize().width*0.5, btnRank->getContentSize().height*0.5));
    btnRank->addChild(lblRank);
    
    Menu* menu = Menu::create(btnCreateUser, btnSendScore, btnRank, NULL);
    menu->alignItemsHorizontallyWithPadding(50);
    menu->setPosition(Vec2(WINCENTER.x, WINSIZE.height*0.9));
    this->addChild(menu);
}

// ユーザー作成
void HelloWorld::createUserTapped(cocos2d::MenuItemSprite *menuSpr)
{
    std::string aUserID = GSUtilCom::getUserID();
    std::string userName = "テスト";
    FIBUserBridge* fibUser = FIBUserBridge::create(aUserID, userName);
    FIBAccountBridge::signupUser(fibUser, [=](FIBUserBridge* user, std::string error)
    {
        if (error.length()) {
            log("登録失敗=%s", error.c_str());
        } else {
            if (user) {
                log("登録しました！");
                log("id=%s", user->userID.c_str());
                log("name=%s", user->userName.c_str());
                log("createTM=%lld", user->createTM);
                
                _userID = user->userID;
            }
        }
    });

}

// スコア送信
void HelloWorld::sendScoreTapped(cocos2d::MenuItemSprite *menuSpr)
{
    // ユーザー登録されていればスコア送信
    if (_userID.length() != 0) {
        
        FIBScoreBridge::submitScore(_userID, 100, [=](std::string error)
        {
            if (error.length() == 0) {
                log("スコア送信完了!");
            } else {
                log("スコア送信失敗");
            }
        });

    }
}

// ランキング取得
void HelloWorld::rankTapped(cocos2d::MenuItemSprite *menuSpr)
{
    this->dispTable();
}

// テーブル表示処理
void HelloWorld::dispTable()
{
    _rankList.clear();

    if (_tableView != NULL) {
        _tableView->removeFromParentAndCleanup(true);
    }

    // ランキング取得
    FIBRankBridge::fetchTopRanking(10, [=](std::vector<FIBRankBridge*> ranks, const std::string error)
    {
       
       if (error.length() == 0) {
           if (ranks.size() != 0) {
               ValueVector rankVector;
               
               // スコア並び替え(降順)
               std::sort(ranks.begin(), ranks.end(), [=] (const FIBRankBridge* aa, const FIBRankBridge* bb) {
                   long long score1 = aa->score;
                   long long score2 = bb->score;
                   
                   if (score1 == score2) {
                       long long createTM1 = aa->user->createTM;
                       long long createTM2 = bb->user->createTM;
                       
                       return createTM1 < createTM2;
                       
                   } else {
                       return score1 > score2;
                   }
               });
               
               long long int rankCnt = 1;

               // 配列に格納
               for (auto data: ranks) {
                   ValueMap map;
                   
                   long long int score = data->score;
                   FIBUserBridge*  dataUser = data->user;
                   
                   if (score) {
                       map["rank"] = Value(StringUtils::format("%lld", rankCnt));
                       map["score"] = Value(StringUtils::format("%lld", score));
                       map["user_id"] = Value(dataUser->userID.c_str());
                       map["createTM"] = Value(StringUtils::format("%lld", dataUser->createTM));
                       map["name"] = Value(dataUser->userName);
                       rankVector.push_back(Value(map));
                       
                       _rankList.push_back(Value(map));
                       
                       rankCnt++;
                   }
                   
               }
               
               _tableView = TableView::create(this, Size(WINSIZE.width, WINSIZE.height*0.8));
               //縦表示
               _tableView->setDirection(cocos2d::extension::ScrollView::Direction::VERTICAL);
               //表示順番
               _tableView->setVerticalFillOrder(TableView::VerticalFillOrder::TOP_DOWN);
               _tableView->setPosition(Vec2(0, 0));
               _tableView->setDelegate(this);
               this->addChild(_tableView);
               
           }
       }
    });
}

// セルのサイズを設定する
Size HelloWorld::cellSizeForTable(TableView* table) {
    return Size(600, 100);
}

// セルの中身を作成する
TableViewCell* HelloWorld::tableCellAtIndex (TableView* table,ssize_t idx)
{
    TableViewCell* cell = table->dequeueCell();
    
    if (!cell) {
        
        cell = new TableViewCell;
        cell->autorelease();
        
        Sprite* bk = Sprite::create();
        bk->setTextureRect(Rect(0, 0, 600, 90));
        bk->setColor(Color3B(200,200,200));

        bk->setPosition(Vec2(WINCENTER.x, 50));
        bk->setTag(100);
        cell->addChild(bk);
        
        ValueMap map = _rankList.at(idx).asValueMap();
        Label* labelRank = Label::createWithSystemFont(StringUtils::format("%s位:", map["rank"].asString().c_str()), "Arial", 25);
        labelRank->setTextColor(Color4B(67, 67, 67, 255));
        labelRank->setAnchorPoint(Vec2(0, 1));
        labelRank->setPosition(Vec2(10, bk->getContentSize().height*0.5+(25*0.5)));
        labelRank->setTag(1);
        bk->addChild(labelRank);
        
        Label* labelName = Label::createWithSystemFont(StringUtils::format("%s さん", map["name"].asString().c_str()), "Arial", 25);
        labelName->setAnchorPoint(Vec2(0, 1));
        labelName->setTextColor(Color4B(67, 67, 67, 255));
        labelName->setPosition(Vec2(70, bk->getContentSize().height*0.5+(25*0.5)));
        labelName->setTag(2);
        bk->addChild(labelName);
        
        Label* labelScore = Label::createWithSystemFont(StringUtils::format("%s点", map["score"].asString().c_str()), "Arial", 25);
        labelScore->setTextColor(Color4B(67, 67, 67, 255));
        labelScore->setAnchorPoint(Vec2(1, 0.5));
        labelScore->setPosition(Vec2(bk->getContentSize().width-20, bk->getContentSize().height*0.5));
        labelScore->setTag(3);
        bk->addChild(labelScore);
        
    } else {
        Sprite *bk = (Sprite*)cell->getChildByTag(100);
        bk->setVisible(true);
        
        ValueMap map = _rankList.at(idx).asValueMap();
        
        Label *labelRank = (Label*)bk->getChildByTag(1);
        labelRank->setString(StringUtils::format("%s位:", map["rank"].asString().c_str()));
        
        Label *labelName = (Label*)bk->getChildByTag(2);
        labelName->setString(StringUtils::format("%s さん", map["name"].asString().c_str()));
        
        Label *labelScore = (Label*)bk->getChildByTag(3);
        labelScore->setString(StringUtils::format("%s点", map["score"].asString().c_str()));
        
    }
    
    return cell;
}
void HelloWorld::tableCellHighlight(cocos2d::extension::TableView *table, cocos2d::extension::TableViewCell *cell)
{
}
void HelloWorld::tableCellUnhighlight(cocos2d::extension::TableView *table, cocos2d::extension::TableViewCell *cell)
{
}
// セルの数を設定する
ssize_t HelloWorld::numberOfCellsInTableView (TableView* table)
{
    // 10位以上は表示させない
    return (_rankList.size()>10)?10:_rankList.size();
}
// テーブルにタッチした時に発生するイベント
void HelloWorld::tableCellTouched(TableView* table, TableViewCell* cell)
{
    
}
```

```cpp:HelloWorldScene.h
#include "cocos2d.h"
#include "cocos-ext.h"

USING_NS_CC;
USING_NS_CC_EXT;

class HelloWorld : public cocos2d::Layer, public cocos2d::extension::TableViewDataSource, public cocos2d::extension::TableViewDelegate
{
public:
    static cocos2d::Scene* createScene();

    HelloWorld();
    ~HelloWorld();
    
    virtual bool init();
    
    // implement the "static create()" method manually
    CREATE_FUNC(HelloWorld);
    
    //TableViewDataSourceに必要な関数
    virtual Size cellSizeForTable(TableView* table);
    virtual TableViewCell* tableCellAtIndex(TableView* table,ssize_t idx);
    virtual ssize_t numberOfCellsInTableView(TableView* table);
    
    //TableViewDelegateに必要な関数
    virtual void tableCellTouched(TableView* table, TableViewCell* cell);
    virtual void tableCellHighlight(TableView* table, TableViewCell* cell);
    virtual void tableCellUnhighlight(TableView* table, TableViewCell* cell);
    
private:
    // ユーザーID保持
    CC_SYNTHESIZE(std::string, _userID, UserID);
    
    // テーブルビュー
    CC_SYNTHESIZE(TableView*, _tableView, TableView);
    
    // ランキング情報格納用
    CC_SYNTHESIZE(ValueVector, _rankList, RankList);
    
    // セットアップ
    void setup();
    
    // ユーザー作成ボタンタップ処理
    void createUserTapped(MenuItemSprite* menuSpr);
    
    // スコア送信処理
    void sendScoreTapped(MenuItemSprite* menuSpr);

    // ランキング取得処理
    void rankTapped(MenuItemSprite* menuSpr);
    
    // テーブル表示処理
    void dispTable();
    
};
```
ビルドして実行してください。ランキング取得ボタンを押すと、
Firebaseに登録されているスコアトップ10位が取得出来ます。
テーブルで表示されているので、指で下位をスクロールして表示出来ます。

![s200.PNG](https://qiita-image-store.s3.amazonaws.com/0/154355/1c1efb55-0079-3cd1-a675-bd9492a1e65e.png)

![s201.PNG](https://qiita-image-store.s3.amazonaws.com/0/154355/7b2be3c8-43e8-3f0d-be82-ff6936619438.png)

#まとめ
Firebaseを使うと簡単なランキングシステムならすぐに作成する事ができました。
index化されているので、10万件あったとしても一瞬でランキング情報を取得する事ができます。

では、素敵なCocos2d-xとFirebaseライフを！