「技術」カテゴリーアーカイブ

【COCOS2D-X】クエストリストをスクロールさせてみる。

こんな感じになりました。

今まではタッチしたときの処理のみを実装していましたが、

タッチ処理のイベントハンドラは三つありまして、

    // タッチアニメーション
    auto listener1 = EventListenerTouchOneByOne::create();
    listener1->onTouchBegan = CC_CALLBACK_2(HomeScene::onTouchBegan, this);
    listener1->onTouchMoved = CC_CALLBACK_2(HomeScene::onTouchMoved, this);
    listener1->onTouchEnded = CC_CALLBACK_2(HomeScene::onTouchEnded, this);
    _eventDispatcher->addEventListenerWithSceneGraphPriority(listener1, this);

onTouchBeganはタッチ開始の処理、

onTouchMovedはタッチ中に動かしたとき、

onTouchEndedは画面から指をリリースときに動作します。

まずは、タッチ処理とスライド処理を分ける必要があるので、

タッチ判定は、タッチ時とリリース時のポジションが同じパーツならばタッチと判定します。

なぜ変更する必要があるのかというと、タッチ開始時に判定しちゃうとスライドの判定ができないのでリリース時にタッチ判定をする必要があります。

これはとりあえず仮の処理です。後で変えるかもしれません。

今回重要なのはスクロール処理。

タッチ開始時にその位置を記憶しておきます。


    if(questListMenu.isShow)
    {
        keepPosition = touch->getLocation();
    }

そして、指移動処理で、移動前と移動後の位置の差分をとり、その分だけパーツを移動させます。

void HomeScene::onTouchMoved(cocos2d::Touch *touch, cocos2d::Event *event)
{
    log("move(%f, %f)", touch->getLocation().x, touch->getLocation().y);

    if(questListMenu.isShow)
    {
        // Yの差分だけメニューを動かす
        float divY = touch->getLocation().y - keepPosition.y;
        for(int i = 0; i < QUEST_NUM; i++)
        {
            questListMenu.questListMenu[i].parts.sprite->setPosition(questListMenu.questListMenu[i].parts.point.x, questListMenu.questListMenu[i].parts.point.y + divY);
            questListMenu.questListMenu[i].parts.point.y = questListMenu.questListMenu[i].parts.point.y + divY;
            questListMenu.questListMenu[i].questName.label->setPosition(questListMenu.questListMenu[i].questName.point.x, questListMenu.questListMenu[i].questName.point.y + divY);
            questListMenu.questListMenu[i].questName.point.y = questListMenu.questListMenu[i].questName.point.y + divY;
        }
        keepPosition.y += divY;
    }
}

思った以上に滑らかなスクロールができました。

「これ以上スクロールさせない」といった処理も必要になりますが、

まぁ、今回はこれで良いでしょう。

デザインパターン】Proxyパターン

Proxyパターンのサンプルコードです。

package org.example.proxy;

public interface Subject {
    void request1();
    void request2();
}
package org.example.proxy;

public class RealSubject implements Subject {
    @Override
    public void request1() {
        // 時間のかかる処理
    }

    @Override
    public void request2() {
        // 時間のかかる処理
    }
}
package org.example.proxy;

public class Proxy implements Subject{
    private RealSubject real;

    @Override
    public synchronized void request1() {
        realize();
        real.request1();
    }

    @Override
    public synchronized void request2() {
        realize();
        real.request2();
    }

    private void realize() {
        if(real == null) {
            real = new RealSubject();
        }
    }
}
package org.example.proxy;

public class Main {
    public static void main(String[] args) {
        Subject subject = new Proxy();
        subject.request1();
        subject.request2();
    }
}

Proxyパターンは時間のかかる処理を実行する際、クラスを一つ噛ませて一部の処理を代行(Proxy)するパターンです。

上のサンプルコードではRealSubjectが時間のかかる処理を行い、それを実行する前にProxyが実行されます。

ProxyとReadSubjectはSubjectという共通のインターフェースを持っています。

この例では、「直接ReadSubjectを実行してもいいんじゃね?」って思いますが、

実際に利用されるパターンとしては、ReadSubjectでは時間のかかる処理のみを実装し、それ以外の処理をProxyで処理させる、というのが一般的のようです。

【北海道大戦2021】都市データを表示。

都市データの入力が終わりまして、

画面に都市の情報を表示させてみたいと思います。

今までの実装では

こんな感じになってました。

テキストだけを表示させている、と言う状態です。

これでは見づらいので、表示ウィンドウを作りたいと思います。

    class InfomationWindow
    {
        private TextObject2D _valueText;
        private GeometryObject2D _windowBox;
        private GeometryObject2D[] _geometryObj = new GeometryObject2D[4];
        private RectangleShape _rect;
        private LineShape[] _line = new LineShape[4];
        private const int rectWidth = 250;
        private const int rectHeight = 70;
        private const int xPositionOffset = 10;

        public InfomationWindow()
        {
        }

        public void AddLayer(Layer2D layer)
        {
            _windowBox = new GeometryObject2D();
            _windowBox.DrawingPriority = 10;
            _windowBox.Color = new Color(255, 255, 255, 255);
            _rect = new RectangleShape();
            layer.AddObject(_windowBox);

            for(int i = 0; i < 4; i++)
            {
                _geometryObj[i] = new GeometryObject2D();
                _geometryObj[i].DrawingPriority = 10;
                _geometryObj[i].Color = new Color(0, 0, 0, 255);
                _line[i] = new LineShape();
                _line[i].Thickness = 5;
                _geometryObj[i].Shape = _line[i];
                layer.AddObject(_geometryObj[i]);
            }

            _valueText = new TextObject2D();
            _valueText.Font = Singleton.Font;
            _valueText.DrawingPriority = 20;
            layer.AddObject(_valueText);
        }

        public void ShowText(Vector2DF pos, string text)
        {
            _rect.DrawingArea = new RectF(pos.X, pos.Y, rectWidth, rectHeight);
            _windowBox.Shape = _rect;
            _line[0].StartingPosition = new Vector2DF(pos.X, pos.Y);
            _line[0].EndingPosition = new Vector2DF(pos.X + rectWidth, pos.Y);
            _line[1].StartingPosition = new Vector2DF(pos.X + rectWidth, pos.Y);
            _line[1].EndingPosition = new Vector2DF(pos.X + rectWidth, pos.Y + rectHeight);
            _line[2].StartingPosition = new Vector2DF(pos.X + rectWidth, pos.Y + rectHeight);
            _line[2].EndingPosition = new Vector2DF(pos.X, pos.Y + rectHeight);
            _line[3].StartingPosition = new Vector2DF(pos.X, pos.Y + rectHeight);
            _line[3].EndingPosition = new Vector2DF(pos.X, pos.Y);
            _valueText.Text = text;
            _valueText.Position = new Vector2DF(pos.X + xPositionOffset, pos.Y);
        }

        public void AppendText(Vector2DF pos, string text)
        {
            _valueText.Text += text;
            _valueText.Position = new Vector2DF(pos.X + xPositionOffset, pos.Y);
        }
    }

画面に白い四角形を描画し、それに追加して、四辺に線を書きました。

あ、そうだ、タイトル画面作らないと。

次回はタイトル画面作ります。

【ALEXAスキル開発】どうも、今のままではAlexa連携はできないっぽい。

あれからAlexaからWebAPIを実行する方法を探ってみたのですが、

結論から言うと、今のままでは無理っぽいです。

理由は、WebAPIを実行するためには、インターネットからHTTPSでアクセスできることが条件と言うことです。

https://developer.amazon.com/ja-JP/docs/alexa/custom-skills/host-a-custom-skill-as-a-web-service.html

今の環境下では、テレビの赤外線を使用しているラズパイはローカルネットワーク環境下でのみ実行可能で、インターネットからアクセスしようにも、プロバイダー(ビッグローブ)側で自宅IPへの直アクセスもブロックされているっぽいのです。

外からアクセスする手段が無い以上、どうすることもできません。

Arduinoだとこんな感じで使用できるみたいです。

https://dev.classmethod.jp/articles/smart-home-skill/

なので、Alexaスキル開発は一旦中断。

ただ、テレビの赤外線通信はこのままではもったいないので、別の活用法を考えてみたいと思います。

リモコンが無くても、PCやスマホからコントロールできるようにするとか。

ちょっとそっちの方で進めてみましょうか。

他にやることないし。

【ダイエット支援】アップロード処理(Vue.js側)

こちらの記事を参考にしました。

                <div id="mobile">
                    <p>
                        <input @change="onSelectedFile" type="file" name="file" />
                    </p>
                    <p>
                        <button @click="onUpload">送信</button>
                    </p>
        onSelectedFile: function(e) {
            e.preventDefault();
            let files = e.target.files;
            this.uploadFile = files[0];
        },
        onUpload: function() {
            let formData = new FormData();
            formData.append('picture', this.uploadFile);
            let config = {
                headers: {
                    'content-type': 'multipart/form-data'
                }
            };
            axios
                .post('/api/eating/upload', formData, config)
                .then(function(response) {
                })
                .catch(function(error) {
                })
        },

これの動作確認のために、Laravel側にWebAPIを作成します。

Route::post('api/eating/upload', 'Eating\ApiController@upload');
    public function upload(Request $request)
    {
        $file = $request->picture;
        logger(dump($file->getClientOriginalName()));
        return response()->json();
    }

適当な画像ファイルをアップロードしてみましたが、きちんとLaravel側でもファイルを認識しているようです。

次はOCR処理を作成していこうと思います。

【COCOS2D-X】クエストリストの作成を作ってみた。

こんな感じ。

    if(isTouch(touch->getLocation(), &(questButton.parts)))
    {
        auto questList = getQuestList();
        auto questName = questList->begin();
        for(int i = 0; i < questList->size(); i++)
        {
            log("loop");
            questListMenu.questListMenu[i].parts.sprite = Sprite::create("btn02_03_s_bl.png");
            questListMenu.questListMenu[i].parts.sprite->setAnchorPoint(Vec2(0.0, 0.0));
            questListMenu.questListMenu[i].parts.size = Size(questListMenu.questListMenu->parts.sprite->getContentSize().width * questListMenu.scaleRate,
                                                           questListMenu.questListMenu->parts.sprite->getContentSize().height);
            questListMenu.questListMenu[i].parts.point = Vec2(questListMenu.parts.point.x, questListMenu.parts.point.y - questListMenu.questListMenu->parts.sprite->getContentSize().height * (i + 1));
            questListMenu.questListMenu[i].parts.sprite->setPosition(questListMenu.questListMenu[i].parts.point);
            questListMenu.questListMenu[i].parts.sprite->setScale(questListMenu.scaleRate, 1);
            this->addChild(questListMenu.questListMenu[i].parts.sprite, 5);

            questListMenu.questListMenu[i].questName.label = Label::createWithTTF("", "fonts/msgothic.ttc", 18);
            auto str = String();
            str.appendWithFormat("%s", questName.operator*());
            questListMenu.questListMenu[i].questName.label->setString(str.getCString());
            questListMenu.questListMenu[i].questName.label->setAnchorPoint(Vec2(0.0, -0.3));
            questListMenu.questListMenu[i].questName.point = Vec2(questListMenu.questListMenu[i].parts.point.x + questListMenu.questListMenu[i].parts.size.width / 30.0, questListMenu.questListMenu[i].parts.point.y);
            questListMenu.questListMenu[i].questName.label->setPosition(questListMenu.questListMenu[i].questName.point);
            this->addChild(questListMenu.questListMenu[i].questName.label, 6);

            questName++;
        }
    }

あとはこれを指でスクロールさせたいんだけど、

難しそうな気がする。

でもできたらいろいろ応用ができそうだけど。

デザインパターン】Flyweightパターン

Flyweightパターンのサンプルコードです。

package org.example.flyweight;

public class Flyweight {
    public void method1() {

    }

    public void method2() {

    }
}
package org.example.flyweight;

import java.util.HashMap;
import java.util.Map;

public class FlyweightFactory {
    private Map<Integer, Flyweight> pool = new HashMap<>();
    private static FlyweightFactory factory = new FlyweightFactory();

    private FlyweightFactory() {

    }

    public static FlyweightFactory getFactory() {
        return factory;
    }

    public Flyweight getFlyweight(int key) {
        Flyweight result;
        boolean exist = pool.containsKey(key);
        if(exist == true) {
            result = pool.get(key);
        } else {
            result = new Flyweight();
            pool.put(key, result);
        }

        return result;
    }
}
package org.example.flyweight;

public class Main {
    public static void main(String[] args) {
        FlyweightFactory factory = FlyweightFactory.getFactory();
        Flyweight object = factory.getFlyweight(0);
        object.method1();
        object.method2();
    }
}

Flyweightパターンは一度作成したインスタンスを保持して再利用することによってインスタンス作成時の負荷を軽減させるパターンです。

サンプルコードではFlyweightのオブジェクトをFlyweightFactory内のpoolに保持し、一度使用したものはpoolから取得するようにしています。

FlyweightFactoryはSingletonパターンで使用するのが一般的です。

poolは、サンプルではHashMapを使用しましたが、Collectionのインターフェースを持つクラス(Listなど)でも使用できます。

このあたりは実際の設計に合わせて実装していくことになると思います。

【ALEXAスキル開発】テレビスキルを作成開始

前回で大体の仕組みがつかめてきました。

この画面に設定した内容は、スキルの呼び出し名を設定します。

上の設定では、「テレビを開いて」と言えばスキルが起動します。

次にインテント。

「テレビ」スキルに対する命令のようなもの、ですね。

ここでは、VolumeUpIntentを作成して、これに対応するセリフを設定します。

上の場合は「ボリュームを上げて」「ボリュームアップ」と言えば、このインテントが作動します。

次にコード。

const LaunchRequestHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'LaunchRequest';
    },
    handle(handlerInput) {
        const speakOutput = 'テレビをどうしますか?';

        return handlerInput.responseBuilder
            .speak(speakOutput)
            .reprompt(speakOutput)
            .getResponse();
    }
};

const VolumeUpIntentHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
            && Alexa.getIntentName(handlerInput.requestEnvelope) === 'VolumeUpIntent';
    },
    handle(handlerInput) {
        const speakOutput = 'ボリュームを上げました';

        return handlerInput.responseBuilder
            .speak(speakOutput)
            //.reprompt('add a reprompt if you want to keep the session open for the user to respond')
            .getResponse();
    }
};

exports.handler = Alexa.SkillBuilders.custom()
    .addRequestHandlers(
        LaunchRequestHandler,
        VolumeUpIntentHandler,
        HelpIntentHandler,
        CancelAndStopIntentHandler,
        FallbackIntentHandler,
        SessionEndedRequestHandler,
        IntentReflectorHandler)
    .addErrorHandlers(
        ErrorHandler)
    .withCustomUserAgent('sample/hello-world/v1.2')
    .lambda();

まず上のLaunchRequestHandlerはスキル起動時の動作。

ここではスキル起動後、「テレビをどうしますか?」と応答がでます。

そして、VolumeUpIntentHandler。インテントがVolumeUpIntentならば「ボリュームを上げました」を返ります。

そして、最後のexports.handlerに追加したVolumeUpIntentHandlerを追加します。

動作はこんな感じになります。

「テレビ」を起動して「ボリュームを上げて」で設定した内容が返ってきました。

しかし、「テレビのボリュームを上げて」ではダメで、「テレビを開いてボリュームを上げて」と言わなければならないようです。

でも、これで一文でコントロールできそうです。

あとは、どうやってWebAPIを叩くか、ですね。

次回までに調べておきます。

【ダイエット支援】スマホから写真をアップロードできるようにする

さて、スマホのレイアウト崩れも直ったので、

これからやりたいことは、

・カメラで栄養成分表示を撮影してアップロードする

・それをOCRでテキストデータに変換し、数値を入力する

です。

まずは、スマホの画面にだけ、ファイルアップロードのコントロールを表示させます。

div#mobile {
    @include small {
    }
    @include middle {
        display: none;
    }
    @include big {
        display: none;
    }
}
                <div id="mobile">
                    <p>
                        <input type="file" name="file" />
                    </p>
                    <p>
                        <button>送信</button>
                    </p>
                </div>

メディアクエリの使い方覚えたから楽勝。

PCからはこうなってますが、

スマホからはこうなります。

で、ファイルを選択をタップすると、

こんな感じでカメラを起動して撮影したものをすぐにアップロードできるんですね。

とりあえず、今日やりたいことはできた。

次回は実際にOCRを組み込んでみたいと思います。

【COCOS2D-X】クエストリストの作成に着手

こんな感じでウィンドウを作成しました。

「冒険」ボタンをタップすると表示するようになっています。

これを縦に並べてクエストリストを表示させたいのですが、

データ構造はこんな感じで実装しました。

typedef struct _TextLabel {
    cocos2d::Label* label;
    cocos2d::Vec2 point;
} TextLabel;

typedef struct _QuestListMenu {
    Parts parts;
    TextLabel questName;
} QuestListMenu;

typedef struct _QuestList {
    Parts parts;
    QuestListMenu questListMenu[QUEST_NUM];
    float scaleRate;
} QuestList;

シーンの中ではnew演算子禁止なので、listは使えません。

散々悩んだのですが、とりあえず、大きめの配列を用意して使用することにします。

    if(isTouch(touch->getLocation(), &(questButton.parts)))
    {
        log("touch questButton");
        auto questList = getQuestList();
        auto questName = questList->begin();
        questListMenu.questListMenu->parts.sprite = Sprite::create("btn02_03_s_bl.png");
        questListMenu.questListMenu->parts.sprite->setAnchorPoint(Vec2(0.0, 1.0));
        questListMenu.questListMenu->parts.point = questListMenu.parts.point;
        questListMenu.questListMenu->parts.sprite->setPosition(questListMenu.questListMenu->parts.point);
        questListMenu.questListMenu->parts.size = Size(questListMenu.questListMenu->parts.sprite->getContentSize().width * questListMenu.scaleRate,
                                                       questListMenu.questListMenu->parts.sprite->getContentSize().height);
        questListMenu.questListMenu->parts.sprite->setScale(questListMenu.scaleRate, 1);
        this->addChild(questListMenu.questListMenu->parts.sprite, 5);
    }

タップ処理です。

今の段階では上の画面のようなウィンドウを作成追加するだけです。

今後は、数を増やしたり、テキストを追加したりする予定です。