name_untitledのエッセイ

IT関係で知ったことなどを記事としてあげていきます。内容に不備や質問などあればぜひコメントをよせてください。

FirebaseでトランプのドボンのWebアプリを作る。(レイアウト)

色々と手を出し過ぎなんだけど、今FirebaseでWebアプリを作ろうとしている。トランプのドボンが出来るアプリ。

ステップはこんな感じと思う。

1.FirebaseのWebアプリのサンプルの確認

2.Firebaseから手札のデータを取得して表示する

3.選んだ手札をFirebaseDBに登録する

4.新しいゲームのボタンを押したら、カードをランダムに配る

5.順番に手札を台札に乗せる

6.手札が出せる札かチェックする

7.得点計算をする

 

ちょっとずつ更新する予定

1.FirebaseのWebアプリのサンプルの確認

 

Firebaseのチャットアプリのサンプルの動作や作りを一通り眺める。

Firebase Web Codelab

 

2.Firebaseから手札のデータを取得して表示する

これはコードを眺めて対応するところをコピペした。

Firebaseの方で手札データを登録して、それを表示させる。

 

3.選んだ手札をFirebaseに登録する

ここで、bindしないとつらそうな気がしたので、ちょっとググる

firebase.googleblog.com

これを参考にVueを導入してみた。まずは、クライアントでrequireを使うので、browserifyが必要らしい。

>npm install -g browserify

>npm install --save promise-polyfill

>browserify ./public/scripts/main.js -o ./public/scripts/bundle.js

>firebase serve

で、これ以降はmain.jsではなく、browserifyで生成したbundle.jsをindex.htmlで読み込んで使う。

arata.hatenadiary.com

動かしてみたがエラー。メッセージからすると、エレメントが見つからないからエラー。2行目は分かりにくいけどたぶん関係ない。

[Vue warn]: Cannot find element: #cards

[Vue warn]: You are using the runtime-only build of Vue where the template compiler is not available. Either pre-compile the templates into render functions, or use the compiler-included build.
(found in <Root>)

stackoverflow.com

確認して、bundle.jsを作り直して、もう一度表示。

bundle.js:2302 [Vue warn]: You are using the runtime-only build of Vue where the template compiler is not available. Either pre-compile the templates into render functions, or use the compiler-included build. (found in <Root>) 

やっぱりまだエラー。

これVueのバージョンアップに絡んでいそうなので、ちょっとここでいったんストップ。同じ感じで表示だけ行う処理を組み込んだ。

f:id:name_untitled:20170414151028p:plain

 

2017/04/23 更新

Vueはちょっとお預け。

データはまずは以下の形で作っている自分のカードを表示して、こんな感じにした。

root

┣messages
┣current

┃┣mark

┃┗num

┗cards

 ┗id

┣mark

 ┃┗num

 ┣id

 

手札を出す=cardsから1つ削除&currentを更新。

main.js →browserify → bundle.js

this.cardsRef.child(key).remove().then(function() {
    // Clear message text field and SEND button state.
    Dobon.resetMaterialTextfield(this.messageInput);
    this.toggleButton();
    }.bind(this)).catch(function(error) {
        console.error('Error writing new message to Firebase Database', error);
});

このコードでカードデータを削除。

Dobon.prototype.loadCards = function() {
    // // Reference to the /messages/ database path.
    this.cardsRef = this.database.ref('cards');

    // Make sure we remove all previous listeners.
    this.cardsRef.off();

    var remCard = function(data) {
        var val = data.val();
        var elm = document.getElementById(data.key);
        elm.parentNode.removeChild(elm);
    }.bind(this);
    // Loads the last 12 messages and listen for new ones.
    var setCard = function(data) {
        var val = data.val();
        this.displayCard(data.key, val.mark, val.num);
    }.bind(this);
    this.cardsRef.limitToLast(12).on('child_added', setCard);
    this.cardsRef.limitToLast(12).on('child_changed', setCard);
    this.cardsRef.limitToLast(12).on('child_removed', remCard);
};

上記はDB変更時のイベントの設定。元々のサンプルはchild_added, child_changedだけだけど、child_removedを追加。

 

次に、FirebaseのFunctionを追加する。

index.js

exports.makeUppercase = functions.database.ref('/cards/{pushId}')
    .onWrite(event => {

    // Only edit data when it is deleted
    if *1
        || event.data.exists()) {
        console.log('end trigger');
        return;
    }
    // Grab the current value of what was written to the Realtime Database.
    var mark = event.data.previous.val().mark;
    var num = event.data.previous.val().num;
    event.data.ref.parent.parent.child('current').child('mark').set(mark);
    return event.data.ref.parent.parent.child('current').child('num').set(num);
});

previousにあって、今のデータにない=削除されたデータ。

で、上記の処理でノードを更新。

Realtime Database Triggers  |  Firebase

このサイトがとても詳しく書かれていてありがたい。

Read and Write Data on the Web  |  Firebase

 

2017/04/24 追記

4.新しいゲームのボタンを押したら、カードをランダムに配る

カードデータを作成して、ランダムにシャッフルする処理は以下のような感じ。

ランダムに並べ替えるアルゴリズムがあるそうで助かりました。

h2ham.net

このサイトが見ててきれい。

Fisher–Yates Shuffle

function getCards(){
    var marks = ["spade","clover","heart","diamond"];
    var cards = [];
    for(var i = 0; i < marks.length; i++){
        for(var j = 1; j <= 13; j++){
            var node = {"mark":marks[i], "num": j};
            cards.push(node);
        }
    }
    return cards;
}
function shuffle(array) {
    var n = array.length, t, i;

    while (n) {
        i = Math.floor(Math.random() * n--);
        t = array[n];
        array[n] = array[i];
        array[i] = t;
    }

    return array;
}

var test_array = getCards();
var cards =shuffle(test_array);
console.log(cards.length);
console.log(cards);

 2017/04/25 追記

4.新しいゲームのボタンを押したら、カードをランダムに配る

の続き。

Firebaseで手札を配る際に、RESTでGETしたら手札を配るようにしたいため、FirebaseのFunctionsという機能を利用する。これはREST APIっぽいことが出来るみたい。index.jsでの書き方はこんな感じ。

exports.date = functions.https.onRequest((req, res) => {
 
// ...
});

ここで、dataがHTTPのTriggerの名前。reqからJSONデータを取ることもできるらしい。詳しくは公式をご覧ください。

HTTP Triggersの書き方はこんな感じ。

HTTP Triggers  |  Firebase

Databaseへの登録の書き方はこんな感じ。

Read and Write Data on the Web  |  Firebase

var functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);

 

exports.newgame = functions.https.onRequest((req, res) => {
    var getCards = function(){
        var marks = ["spade","clover","heart","diamond"];
        var cards = [];
        for(var i = 0; i < marks.length; i++){
            for(var j = 1; j <= 13; j++){
                var node = {"mark":marks[i], "num": j};
                cards.push(node);
            }
        }
        return cards;
    };
    var shuffle = function(array) {
        var n = array.length, t, i;

        while (n) {
            i = Math.floor(Math.random() * n--);
            t = array[n];
            array[n] = array[i];
            array[i] = t;
        }
        return array;
    };
    var newGame = function(cards){
        for(var i = 0; i < cards.length; i++){
            // A card entry.
            var cardData = {
                mark: cards[i].mark,
                num: cards[i].num
            };

            var newCardKey = admin.database().ref().child('cards').push().key;

            var updates = {};
            updates['/cards/' + newCardKey] = cardData;
            admin.database().ref().update(updates);
        }
    };
    newGame(shuffle(getCards()));
    res.send(200);
});

JSっぽくない書き方になってるのでどう直したらいいか教えてください!

これをdeployして、GETリクエストを投げたらこんな感じでデータが出来ました。

f:id:name_untitled:20170426004141p:plain

うれしい、、、。

 

2017/04/27 追記

カードを配る前に今のカードを全部削除する処理を追加した。

上の処理を1行追加してこんな感じ。

admin.database().ref().child('cards').remove();
newGame(shuffle(getCards()));
res.send(200);

removeで配下のものを全部削除してくれるらしい。

 

2017/04/28 追記

ユーザーごとにカードを配る処理を実装したが、アクティブユーザー一覧が取れなさそうなので、ゲームの流れとしては、開始を呼びかける→他のユーザーが参加表明→ゲーム開始という流れにして、表明のタイミングでUIDを登録するようにする。

参加表明はクライアントからIDを送る。ちょっと微妙な気もするが、HTTP Requestのトリガーではuidが取れなさそう。

Dobon.prototype.enrollGame = function() {

    this.playersRef = this.database.ref('players');
    // Make sure we remove all previous listeners.
    var user = firebase.auth().currentUser;
    var data = {
        uid: user.uid
        ,name: user.displayName
    };
    this.playersRef.push(data);
};

ゲーム開始時の処理も修正。カードをユーザーごとに配る。

exports.newgame = functions.https.onRequest((req, res) => {
  var getCards = function(){
    var marks = ["spade","clover","heart","diamond"];
    var cards = [];
    for(var i = 0; i < marks.length; i++){
      for(var j = 1; j <= 13; j++){
        var node = {"mark":marks[i], "num": j};
        cards.push(node);
      }
    }
    return cards;
  };
  var shuffle = function(array) {
    var n = array.length, t, i;

    while (n) {
      i = Math.floor(Math.random() * n--);
      t = array[n];
      array[n] = array[i];
      array[i] = t;
    }
    return array;
  };
  var newGame = function(cards){
    //TODO: get list of players
    admin.database().ref().child('players').once('value').then(function(snapshot) {
      //TODO: deal cards to each players(hands)
      var users = snapshot.val();
      for(key in users){
        var user = users[key];
        for(var j = 0; j < 5; j++){
          var last = cards.pop();
          var cardData = {
            mark: last.mark,
            num: last.num
          };
          admin.database().ref().child('hands').child(user.uid).child('cards').push(cardData);
       }
      }

      //TODO: first card will be upcard
      cards.pop();
      //TODO: the rest will be talon
      var card = cards.pop();
      while(cards != undefined){
        var cardData = {
          mark: card.mark,
          num: card.num
        };

         var newCardKey = admin.database().ref().child('cards').push().key;
         var updates = {};
         updates['/cards/' + newCardKey] = cardData;
         admin.database().ref().update(updates);
        card = cards.pop();
      }
    });
  };
  admin.database().ref().child('cards').remove();
  newGame(shuffle(getCards()));
  res.send(200);
});

ユーザーIDのJSONが配列じゃなかったから結構はまってしまったし、探し方が悪かった。やりたいことをきっちり言語化すべきだった。ここだと、JSONのキーを取得して、そこからそれぞれの要素を取得する、だった。

for(key in users){
}

でできちゃうんだ、、、。

 

2017/05/05 追記

ちょっと飽きたのでレイアウトをいじることにした。

HTML見てると、classにmdl~とかいう記述があったけど、どうもそれは、Material Design LiteというGoogle謹製のライブラリらしい。これを使うと、WebでもAndroid同様に面とインクのメタファーに倣ったデザインができるらしい。ちょっと触ってみたけど、影を出すのが精いっぱいでリップルは出せなかった。とりあえず、台とカードは出た。

f:id:name_untitled:20170505210314p:plain

クラスを複数指定することによりデザインをきれいに出来る。でもそもそもCSSが全然わかってないし、MDLもよく分かってないのでなんでこんな書き方になるのか説明できない。1行めはグリッド表示用、2行目が各カードの親DIVのクラス。

<div class="mdl-grid">

<div class="mdl-play-card mdl_supporting-text mdl-color--white mdl-shadow--2dp">

youtu.be

getmdl.io

概要や使い方は上記で。

色の指定はこのサイトがいろいろと見本を載せてくれていて良い。

blog2.gods.holy.jp

 

2017/05/27 追記

カードを引く処理を以下の流れで想定していたがうまく行かなかった。

JavascriptでuidをPOSTして、Functionsでuidを受け取って、カードを1件取得して、uidをキーにカードをpushし直す。

問題は2つあった。1.クロスドメインNG。2.1件データが取得できない。

まず1.についてはHostingとFunctionsのドメインが異なるため。ただGETでは問題ないため、メソッドをGETに変更し、クエリにuidを含めるよう変更。
2.については何か書き方間違えているかもだけど分からないからとりあえず力技でしのいだ。

exports.drawcard = functions.https.onRequest((req, res) => {
    admin.database().ref().child('cards').once('value').then(function(snapshot) {
        var allData = snapshot.val();
        var keys = Object.keys(allData);
        var val = allData[keys[0]];
        var uid = req.query.uid;
        var data = {"mark": val.mark, "num": val.num};
        admin.database().ref('hands').child(uid).child('cards').push(data);
        admin.database().ref('cards').child( keys[0]).remove();
    });

    res.status(200).send();
});

developer.mozilla.org

www.aipacommander.com

 

2017/06/18 追記

8を出したときにマークを選ぶダイアログを表示させるようにした。

Dialogはまだ一般的じゃないみたいだけどChrome限定だから良しとする。

getmdl.io

マークを選ぶのはラジオボタンから。

www.tutorialspoint.com

最近はブラウザでdialogがサポートされるようになってきている。

HTML DOM Dialog showModal() Method

 

ドキュメントとかでPromiseというのがでてきたけど、まだちょっと内容が分かってない。

developer.mozilla.org

 

まだ機能足りてないけどとりあえずGitHubに上げました。

github.com

*1:!event.data.previous.exists(

教育サイト用のソフトウェア Moodle。

この間Moodleというソフトを知った。

https://moodle.org/

これはLinux上でオンラインコースのWebサイトを作成できる、CMSソフトらしい。Linux上に、Apache2、DB(MySQLMariaDB、PostGresDB)、PHPを入れて、Moodleのモジュールを入れると出来るとか。

f:id:name_untitled:20170414125510p:plain

機能を色々と見てみたけどなかなか良い感じ。

テストだけじゃなく、学習も出来るみたいで、字幕付きのVideoを入れたりもできる。またランダムな出題も出来るみたいで、一斉テストでも使えそう。

こんなアプリがあるのね。

今まで全く知らなかったけどこんなアプリがあるんだと思った話。

マチコミというAndroidiPhoneアプリがあって、幼稚園とかと保護者の情報共有をするアプリ。

f:id:name_untitled:20170411114940p:plain

多分、元々Webサービスがあって、それをionicとかのフレームワークで移植してアプリとして公開してるんだと思う。

ユーザーは施設運営者と保護者の2パターン。施設運営者は事前にマチコミに登録が必要と思われる。で、学年ごととかでグループを作成する。保護者はユーザー登録をした後で、施設運営者から学年ごとに振られるIDを教えてもらい、こどもの教室を登録する。

SNS、ニュースなども見ることが出来て、広告が入っている。

収入は広告らしい。利用者はアンケートに答える必要があるらしく、これもデータを売ってたりするのかな。意外とダウンロードされててびっくり。

クローズなコミュニティ向けのサービスって意外と需要があるのね。

Androidでテストを導入したい。Contextの受け渡し。

Androidでちっちゃいアプリを作っている。機能を追加する前にユニットテストを組み込みたいと思ってお勉強中。

Googleのサンプルに従って導入しているところだけどデータアクセスのところでつまづいた。辞書機能を作るのにSQLiteを使っているのだが、DBアクセスにContextが必要。ただDBアクセス部はコードを分離したい、、、。ちょっと探してみると2つくらい選択肢があるっぽい。1、Dagger2というDIライブラリを使う。2、Contextをアプリ内で参照できるようにする。

1、を選ぶと収集がつかなくなりそうだったので、2、のContextをアプリ内で参照できるように修正。テスト用のコードとリリースとで実装を分けて、テストしやすくなりそうな気がする。

qiita.com

 

ありがとうございました。

Androidのサンプルプロジェクトで、Gradle version 2.2 is required. Current version is 2.10っていう感じのエラー。無駄にはまった。

stackoverflow.com

AndroidのサンプルプロジェクトをGitHubから落として、開いたらこんなエラー。BuildToolsのバージョンとかを書き換えればいいと思ってたかをくくってたら、なかなか解決しなかった。

原因は、サンプルプロジェクトは複数のアプリを含んだやつで、プロジェクトレベルの設定を読んでバージョン違うとメッセージが出てたのに、アプリレベルのbuild.gradleを一生懸命修正してた、、、。

1階層上のbuild.gradleを修正して、開き直してOK。

UTMグリッドのデータを作りたい。

国土地理院の資料でもUTMが紹介されているように、特定地域の情報を統合するためにUTMの利用が増えているらしい。

http://www.gsi.go.jp/common/000090488.pdf

で、これを地雷の除去に利用するという話があったのだが、UTMデータの用意をどうしたらいいかわからなかったのでしばらくそっとしていた。で、今日思い出してちょっと調べたら作り方があったのでメモがてら。

このサイトでは主要な地理情報データベースでのUTMデータの作り方が紹介されている。どうもちょこちょこと原点を調整する必要があるらしい。原点を元にして、グリッドを切っていくが、地球は丸いのでUTMの各原点も2次元とは違うんだと思う。それで、原点がずれるとグリッドもずれる。多分そういうわけで、このサイトでは原点を1,000mごとに振り直すことを推奨している。

www.chuogeomatics.jp

地理院中部地方測量部が中部地方のUTMデータを公開している。

中部地方測量部管内及びその周辺のUTMグリッドデータ(kml形式)|国土地理院

Androidでテスト導入。R.txtがないと怒られる。

昨日に引き続いて、EspressoのUIテストを試してみた。

Android Testing Codelab

で、書いてある通りになるよう、フォルダ構成を整えて、テストメソッドを実行したら、エラーになった。

Error:Execution failed for task ':app:processMockDebugAndroidTestResources'.
> java.io.FileNotFoundException: C:\Users\<approot>\app\build\intermediates\symbols\androidTest\mock\debug\R.txt (指定されたファイルが見つかりません。)

 

サンプルと見比べてみても違いが分からない。また、androidTest\mock\debug\にはR.txtがないものの、mock\debug\にはR.txtがあるため、リソースファイルがビルド出来ないからという訳ではなさそう。とりあえずStackOverflowを漁ったら、androidTestの下に、res/values/strings.xmlを置いたら解決したよってあったので、さんざん「これはないわ」と思ってたけど、そのとおりにしてみた。

stackoverflow.com

解決、ではないけど、とりあえずテストメソッドは走るようになった。

リソース系のファイルがコピーされないのはビルドのタイミングでlayoutファイルがstrings.xmlを参照してないからコピーされないのか、とか考えたけどちょっとすっきりしない。