自動トーナメント作成無料Webサービス Challonge と kintone を連携してみました

皆さんこんにちは！こちらkintone Advent Calendar 2016 18日目の記事になります（少し遅くなってすみません。。。）。
初記事投稿ということでよろしくお願いします！m(_ _)m

私は普段 kintone のデベロッパーでありつつも兼業プロゲーマーとしても活動しております。
プロゲーマーとしての活動の一貫としてゲームの大会イベントを主催することも多いのですが
大会イベントを企画する中で私はある悩みを持ってました。

そう。**トーナメントの作成作業がとてもめんどくさかった**のです。

Excelで作ったトーナメントや図に参加者の名前を手書きで書き込んでそれを回してコールで呼び出して・・・。
大会が終わったらそれを集計してHPに載せて・・・。

そんな悩みを解決してくれたとても便利なトーナメント作成サービス、[Challonge](http://challonge.com/ja) と kintone の
連携に挑戦してみたので紹介させていただきます。
忘年会シーズンでちょっとしたイベントを開く際には是非ご利用ください！

#事前準備
##Challongeとは？
Challongeは上述の通り、トーナメント作成サービスです。
主にゲームシーンで活用されており、海外を中心に徐々に認知が広がっているようです。
サンプル http://challonge.com/ja/battlegateway13
![challonge2.png](https://qiita-image-store.s3.amazonaws.com/0/149983/44480c7a-efa8-8334-49e9-01132c8e628d.png)
トーナメントの形式はもちろん、細かいスコアや参加者のシード権や並び順も自由に変更できます。
詳しくはこちらのブログで紹介されておりますので是非お試しください。
https://keykakko.wordpress.com/2013/03/14/challonge-%E3%81%AE%E4%BD%BF%E3%81%84%E6%96%B9/

##やりたいこと
[Challonge APIドキュメント](https://api.challonge.com/ja/api)より色々できることがわかりますが
今回はkintoneのアプリでトーナメントを作れるアプリを作成し、kintoneから

- トーナメントの作成
- 参加者の追加&ランダム並び替え
- トーナメントの開始
- トーナメントの終了
- トーナメントの結果集計

をやってみます。

##Challongeのアカウントを取得する
今回はChallongeのAPIをkintoneから呼び出すために、アカウントを取得してAPIトークンを確認します。
1.「Sign Up」からアカウント作成します。ユーザ名、メールアドレス、パスワード入力で簡単に作成できます。
![challonge1.png](https://qiita-image-store.s3.amazonaws.com/0/149983/063fe587-4360-03e7-637e-1a23202c1288.png)

![challonge3.png](https://qiita-image-store.s3.amazonaws.com/0/149983/20f435c1-5991-3cdf-bac7-19f7037e1f15.png)

2.自分のアカウントにログインし、設定画面からAPIキーを発行し、APIキーを控えておきます。
![challonge4.png](https://qiita-image-store.s3.amazonaws.com/0/149983/dc3250eb-2685-df05-9dd8-ec928670c91e.png)

##kintoneのアプリを作成する
下記のアプリを新規作成します。フィールドコードがtournament_から始まるものはこのあとのJSカスタマイズで利用します。

| フィールド名 | フィールドコード | フィールドタイプ |
|:-----------|------------:|:------------:|
| 運営者|        作成者|     作成者     |
| トーナメント開催日時|      tournament_start_at|    日時|
| 公開設定|        tournament_private|     ラジオボタン|
| トーナメント名|          tournament_name|      文字列一行      |
| トーナメントurl(半角英数、アンダースコアのみ)|       tournament_url|    リンク（Webアドレス）|
| トーナメントフルURL（編集不要）|     tournament_full_challonge_url|   リンク（Webアドレス）|
| トーナメントタイプ       |        tournament_type|     ドロップダウン|
| トーナメント説明|          tournament_description|      文字列一行|
| トーナメント参加者|       tournament_participants|    ユーザー選択|
| テーブル|     Table|   テーブル|
| テーブル「ランク」|     tournament_rank|   数値|
| テーブル「参加者」|     tournament_user |   aligned    |

![challonge5.png](https://qiita-image-store.s3.amazonaws.com/0/149983/718ea58a-324a-0774-1140-353951f1603e.png)

最後にプロセス管理を設定して完了です。
![プロセス管理.PNG](https://qiita-image-store.s3.amazonaws.com/0/149983/68348a09-1f0d-ac1b-710d-9e6a81cc97fe.png)


これで準備は整いました。あとはJavaScriptカスタマイズをやるだけです！

#JSカスタマイズ
##トーナメントの作成

レコード保存前イベントを使ってトーナメントを作成します。外部のAPIの呼び出しになるので、kintone.proxy()を使っています。
ここから登場する「api_key」項目はすべてChallongeで取得したAPI_TOKENを入力します。
利用API
POST https://api.challonge.com/v1/tournaments.json
https://api.challonge.com/ja/v1/documents/tournaments/create

```
    //レコード保存前イベント
    kintone.events.on('app.record.create.submit', function(event) {
        var record = event.record;
        var private_value = "";
        if (record.tournament_private.value === "公開") {
            private_value = false;
        } else if (record.tournament_private.value === "非公開") {
            private_value = true;
        }
        var params = {
            'api_key': INPUT_YOUR_API_TOKEN,
            'tournament': {
                'name': record.tournament_name.value,
                'tournament_type': record.tournament_type.value,
                'private': private_value,
                'url': record.tournament_url.value,
                'description': record.tournament_description.value,
                'start_at': record.tournament_start_at.value
            }
        };
        var headers = {
            'Content-Type': 'application/json'
        };
        var url = 'https://api.challonge.com/v1/tournaments.json';
        return kintone.proxy(url, 'POST', headers, params).then(function(args) {
            //success
            if (JSON.parse(args[0]).errors) {
                alert(JSON.parse(args[0]).errors);
                return false;
            }
            record['tournament_full_challonge_url']['value'] = JSON.parse(args[0]).tournament.full_challonge_url;
            return event;
        }, function(error) {
            //error
            return false;
        });
    });
```

##参加者の追加&ランダム並び替え

続いてaddParticipants()関数とrandomizeParticipants()関数を作成します。
これらはボタンからの入力で操作できるようにいじります。

利用API
POST https://api.challonge.com/v1/tournaments/{tournament}/participants/bulk_add.json
https://api.challonge.com/ja/v1/documents/participants/bulk_add

POST https://api.challonge.com/v1/tournaments/{tournament}/participants/randomize.json
https://api.challonge.com/ja/v1/documents/participants/randomize

```
    //参加者を加える
    function addParticipants() {
        var record = kintone.app.record.get();
        var participants_array = [];
        for (var i = 0; i < record.record.tournament_participants.value.length; i++) {
            participants_array.push(
                {
                    'name': record.record.tournament_participants.value[i].name
                }
            );
        }

        var params = {
            'api_key': INPUT_YOUR_API_TOKEN,
            'participants': participants_array
        };
        var headers = {
            'Content-Type': 'application/json'
        };
        var url = 'https://api.challonge.com/v1/tournaments/' + record.record.tournament_url.value +
                    '/participants/bulk_add.json';
        return kintone.proxy(url, 'POST', headers, params).then(function(args) {
            //success
            if (JSON.parse(args[0]).errors) {
                alert(JSON.parse(args[0]).errors);
                return false;
            }
            alert("参加者を追加しました。");
            window.location.reload();
        }, function(error) {
            //error
            return false;
        });
    }
    //参加者を並び替える
    function randomizeParticipants() {
        var record = kintone.app.record.get();
        var params = {
            'api_key': INPUT_YOUR_API_TOKEN
        };
        var headers = {
            'Content-Type': 'application/json'
        };
        var url = 'https://api.challonge.com/v1/tournaments/' + record.record.tournament_url.value +
                    '/participants/randomize.json';
        return kintone.proxy(url, 'POST', headers, params).then(function(args) {
            //success
            if (JSON.parse(args[0]).errors) {
                alert(JSON.parse(args[0]).errors);
                return false;
            }
            alert("並び替えが完了しました。");
            window.location.reload();
        }, function(error) {
            //error
            return false;
        });
    }
```

## トーナメントの開始/終了

次にトーナメントの開始の関数startTournament()とトーナメントの終了の関数endTournament()を作ります。
これらの関数はプロセス管理のステータスを回したときに動作することを想定しています。

POST https://api.challonge.com/v1/tournaments/{tournament}/start.json
https://api.challonge.com/ja/v1/documents/tournaments/start

POST https://api.challonge.com/v1/tournaments/{tournament}/finalize.json
https://api.challonge.com/ja/v1/documents/tournaments/finalize

```
    //トーナメントを開始する
    function startTournament(record) {
        var params = {
            'api_key': INPUT_YOUR_API_TOKEN
        };
        var headers = {
            'Content-Type': 'application/json'
        };
        var url = 'https://api.challonge.com/v1/tournaments/' + record.tournament_url.value + '/start.json';
        return kintone.proxy(url, 'POST', headers, params).then(function(args) {
            //success
            if (JSON.parse(args[0]).errors) {
                alert(JSON.parse(args[0]).errors);
                return false;
            }
            alert("トーナメントを開始しました");
        }, function(error) {
            //error
            return kintone.Promise.reject(error);
        });
    }

    //トーナメントを終了する
    function endTournament(record) {
        var params = {
            'api_key': INPUT_YOUR_API_TOKEN
        };
        var headers = {
            'Content-Type': 'application/json'
        };
        var url = 'https://api.challonge.com/v1/tournaments/' + record.tournament_url.value + '/finalize.json';
        return kintone.proxy(url, 'POST', headers, params).then(function(args) {
            //success
            if (JSON.parse(args[0]).errors) {
                alert(JSON.parse(args[0]).errors);
                return false;
            }
            alert("トーナメントが終了しました。");
        }, function(error) {
            //error
            return kintone.Promise.reject(error);
        });
    }
```

## トーナメントの結果集計
最後に集計用の関数getFinalRank()関数で取得した値をputRecordResult()関数でテーブルに更新をかけます。
これらもボタンからの入力で操作できるようにいじります。

利用API
GET https://api.challonge.com/v1/tournaments/{tournament}/participants.{json|xml}
https://api.challonge.com/ja/v1/documents/participants/bulk_add

```
    //最終結果を取得する。
    function getFinalRank() {
        var record = kintone.app.record.get();
        var api_key = '?api_key=' + INPUT_YOUR_API_TOKEN;
        var headers = {
            'Content-Type': 'application/json'
        };
        var url = 'https://api.challonge.com/v1/tournaments/' + record.record.tournament_url.value +
                    '/participants.json' + api_key;
        return kintone.proxy(url, 'GET', headers, {}).then(function(args) {
            //success
            if (JSON.parse(args[0]).errors) {
                alert(JSON.parse(args[0]).errors);
                return false;
            }
            return JSON.parse(args[0]);
        }, function(error) {
            //error
            return kintone.Promise.reject(error);
        });
    }

    //最終結果をレコードに保存する。
    function putRecordResult(result_data) {
        var result_array = [];
        for (var i = 0; i < result_data.length; i++) {
            result_array.push(
                {
                    "value": {
                        "tournament_user": {
                            "value": result_data[i].participant.name
                        },
                        "tournament_rank": {
                            "value": result_data[i].participant.final_rank
                        }
                    }
                }
            );
        }
        //配列をソート
        result_array.sort(
            function(a, b) {
                var aName = a['value']["tournament_rank"]['value'];
                var bName = b['value']["tournament_rank"]['value'];
                if (aName < bName) return -1;
                if (aName > bName) return 1;
                return 0;
            }
        );
        var body = {
            "app": kintone.app.getId(),
            "id": kintone.app.record.getId(),
            "record": {
                "Table": {
                    "value": result_array
                }
            }
        };
        return kintone.api(kintone.api.url('/k/v1/record', true), 'PUT', body).then(function(resp) {
            // success
            alert("トーナメントの最終結果をレコードに保存しました。");
            window.location.reload();
        }, function(error) {
            // error
            return kintone.Promise.reject(error);
        });
    }
```

## その他
レコード詳細イベント、プロセス管理のイベントに上記の関数を書いていきます。
レコード詳細画面ではせっかくなのでボタンで埋め込む以外にiframeで画面操作をできるようにしました。

```
    //レコード詳細画面が表示された時のイベント
    kintone.events.on('app.record.detail.show', function(event) {
        if (document.getElementById("addParticipants") !== null) {
            return;
        }
        //参加者を追加するボタン
        var menuButton = document.createElement("button");
        menuButton.id = "addParticipants";
        menuButton.innerHTML = "参加者を追加する";
        menuButton.onclick = function() {
            if (window.confirm("参加者を追加しますか？")) {
                addParticipants()
                    .catch(function(error) {
                        alert(error);
                    });
            }
        };

        //参加者をランダムに並び替えるボタン
        var menuButton2 = document.createElement("button");
        menuButton2.id = "randomizeParticipants";
        menuButton2.innerHTML = "ランダム振分";
        menuButton2.onclick = function() {
            if (window.confirm("参加者の位置をランダムに変えますか？")) {
                randomizeParticipants()
                    .catch(function(error) {
                        alert(error);
                    });
            }
        };

        //結果をレコードに追加するボタン
        var menuButton3 = document.createElement("button");
        menuButton3.id = "getFinalRank";
        menuButton3.innerHTML = "結果をレコードに追加する";
        menuButton3.onclick = function() {
            if (window.confirm("結果をレコード内のサブテーブルに追加しますか？")) {
                getFinalRank()
                    .then(putRecordResult)
                    .catch(function(error) {
                        alert(error);
                    });
            }
        };

        var el = kintone.app.record.getHeaderMenuSpaceElement();
        var tournament_element = '<iframe src="https://challonge.com/ja/' + event.record.tournament_url.value +
                                    '/module?show_final_results=1" width="100%"' +
                                    'height="500" frameborder="0" scrolling="auto" allowtransparency="true"></iframe>';
        el.innerHTML = tournament_element;
        kintone.app.record.getHeaderMenuSpaceElement().appendChild(menuButton);
        kintone.app.record.getHeaderMenuSpaceElement().appendChild(menuButton2);
        kintone.app.record.getHeaderMenuSpaceElement().appendChild(menuButton3);
        return;
    });

    //プロセス管理のアクションイベント
    kintone.events.on('app.record.detail.process.proceed', function(event) {
        var record = event.record;
        return new kintone.Promise(function(resolve, reject) {
            if (event.nextStatus.value === "進行中") {
                resolve(startTournament(record));
            } else if (event.nextStatus.value === "完了") {
                resolve(endTournament(record));
            } else {
                resolve(event);
            }
        }).catch(function(error) {
            alert(error);
            return false;
        });
    });
```


#実際に使ってみた
それでは実際に上のカスタマイズを追加したアプリを見ていきましょう！

##新規レコード作成
参加者やトーナメント名称などを入力していきます。
トーナメントフルURLとテーブルは入力不要です。
![App_1.PNG](https://qiita-image-store.s3.amazonaws.com/0/149983/0906c2a5-b02d-43b6-7d39-8d5ca2a6e240.png)

保存時にトーナメントが作成されます。この時点では参加者がいないので何も表示されていません。
![App_2.PNG](https://qiita-image-store.s3.amazonaws.com/0/149983/94ce892f-3a6c-9216-b6b2-36006aa17694.png)

##参加者を追加する
「参加者を追加する」ボタンをクリックしてユーザー選択フィールドのメンバーを参加者に追加します。
ユーザーの表示名を使っています。参加者を追加したらプロセス管理を進めましょう！
![App_3.PNG](https://qiita-image-store.s3.amazonaws.com/0/149983/c7b8023b-0f70-70d0-2352-d331a025d0e9.png)

##結果を入力する
入力画面はiframeで埋め込んでいるだけなので、Challongeでログインしていれば結果入力もこの画面からできてしまいます！
コメント等で結果報告してもらいながら運営者で進めていきましょう。
![App_4.PNG](https://qiita-image-store.s3.amazonaws.com/0/149983/968eb89a-0af0-bdbc-bb90-3c16d18474c4.png)

##トーナメント終了して結果報告
結果をすべて入力し終わったら最後にプロセス管理を進めて「結果をレコードに追加する」ボタンをクリックして終了です。
![App_5.PNG](https://qiita-image-store.s3.amazonaws.com/0/149983/a0d4c72c-aca0-9c67-2599-90310cd6fe0d.png)
![App_6.PNG](https://qiita-image-store.s3.amazonaws.com/0/149983/fc59cfe8-eeef-74d4-8fd4-e3215059480d.png)

ちゃんと通知も行く設定にしてみました。
![App_7.PNG](https://qiita-image-store.s3.amazonaws.com/0/149983/a0e23795-18d2-a5af-32ab-93ff11aae1e9.png)

#終わりに
思ったより長くなってしまい遅くなりました。記事を書くってなかなか大変です・・・。
余談ですが、参加者の一括登録のAPIのプロパティがリファレンスだと「participant」なんですが
実際は「participants」が正しいようでなかなかハマりました。。。要注意。
また、Challongeで作成したトーナメントは基本全公開なのですが、プライベート設定をするとリンクを直接叩かない限り
見えなくなるようです。

細かいところをまだまだいじっていないので今後バージョンアップしていきたいと思います。

今回のサンプルで作ったトーナメント
http://challonge.com/ja/kintone_top