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

【デザインパターン】Chain of Responsibilityパターン

Chain of Responsibilityのサンプルコードです。

package org.example.chainofresponsibility;

public class Question {
    public int level;
    public Question(int level){
        this.level = level;
    }
}
package org.example.chainofresponsibility;

public abstract class Handler {
    private Handler next;

    public Handler setNext(Handler next)
    {
        this.next = next;
        return next;
    }

    public final void request(Question question)
    {
        if(judge(question)) {
            // 処理完了
        } else if(next != null) {
            next.request(question);
        } else {
            // 処理不可能
        }
    }

    protected abstract boolean judge(Question question);
}
package org.example.chainofresponsibility;

public class ConcreteHandler1 extends Handler {
    @Override
    protected boolean judge(Question question) {
        if(question.level <= 1) {
            return true;
        } else {
            return false;
        }
    }
}
package org.example.chainofresponsibility;

public class ConcreteHandler2 extends Handler{
    @Override
    protected boolean judge(Question question) {
        if(question.level <= 2) {
            return true;
        } else {
            return false;
        }
    }
}
package org.example.chainofresponsibility;

public class Main {
    public static void main(String[] args)
    {
        Handler handler1 = new ConcreteHandler1();
        Handler handler2 = new ConcreteHandler2();

        handler1.setNext(handler2);

        Question question = new Question(1);

        handler1.request(question);
    }
}

Chain of Responsibilityは処理を行う人(Handler)を数珠つなぎに配列しておき、リクエスト(Question)に対して先頭のHandlerから処理が可能かどうかを判定(judge)し、処理できない物であれば後ろのHandlerにまる投げする、という仕組みです。

なので、あらかじめHandlerを継承しているConcreteHandlerを作成して数珠つなぎを作っておく必要があります。

ConcreteHandlerにてjudge=trueならば、渡されたquestionを適切に処理し、judge=falseならば、そのquestionを後ろのConcreteHanderに渡します。

【北海道大戦2021】タイトル画面の作成

ロードボタンは何も実装していないので、何も置きませんが、

新規ゲームを選択すると、フェードイン、フェードアウトがかかって前回まで作成した画面に遷移します。

    class TitleScene : asd.Scene
    {
        private asd.Layer2D layer = null;
        private asd.TextureObject2D _newgame = null;
        private asd.TextureObject2D _load = null;

        private asd.Texture2D newgame1Image = asd.Engine.Graphics.CreateTexture2D("newgame1.png");
        private asd.Texture2D newgame2Image = asd.Engine.Graphics.CreateTexture2D("newgame2.png");
        private asd.Texture2D load1Image = asd.Engine.Graphics.CreateTexture2D("load1.png");
        private asd.Texture2D load2Image = asd.Engine.Graphics.CreateTexture2D("load2.png");

        private const int buttonWidth = 330;
        private const int buttonHeight = 80;

        public TitleScene()
        {
        }

        protected override void OnRegistered()
        {
            layer = new asd.Layer2D();
            AddLayer(layer);

            // 下地
            var background = new asd.GeometryObject2D();
            layer.AddObject(background);
            var bgRect = new asd.RectangleShape();
            bgRect.DrawingArea = new asd.RectF(0, 0, 1900, 1000);
            background.Shape = bgRect;

            // 北海道の背景
            var hokkaido = new asd.TextureObject2D();
            hokkaido.Texture = asd.Engine.Graphics.CreateTexture2D("101.png");
            hokkaido.Scale = new asd.Vector2DF(1.5f, 1.5f);
            layer.AddObject(hokkaido);

            // タイトル
            var title = new asd.TextureObject2D();
            title.Texture = asd.Engine.Graphics.CreateTexture2D("title.png");
            title.Position = new asd.Vector2DF(250, 200);
            layer.AddObject(title);

            // 新規ゲームボタン
            _newgame = new asd.TextureObject2D();
            _newgame.Texture = newgame1Image;
            _newgame.Position = new asd.Vector2DF(150, 450);
            layer.AddObject(_newgame);

            // ロードボタン
            _load = new asd.TextureObject2D();
            _load.Texture = load1Image;
            _load.Position = new asd.Vector2DF(500, 450);
            layer.AddObject(_load);
        }

        protected override void OnUpdated()
        {
            asd.Vector2DF pos = asd.Engine.Mouse.Position;

            if(isOnMouse(pos, _newgame))
            {
                _newgame.Texture = newgame2Image;
            }
            else
            {
                _newgame.Texture = newgame1Image;
            }

            if (isOnMouse(pos, _load))
            {
                _load.Texture = load2Image;
            }
            else
            {
                _load.Texture = load1Image;
            }

            if (asd.Engine.Mouse.LeftButton.ButtonState == asd.ButtonState.Push)
            {
                if (isOnMouse(pos, _newgame))
                {
                    var scene = new MainScene();
                    asd.Engine.ChangeSceneWithTransition(scene, new asd.TransitionFade(1.5f, 1.5f));
                }
            }
        }

        private bool isOnMouse(asd.Vector2DF pos, asd.TextureObject2D button)
        {
            if (pos.X > button.Position.X && pos.X < button.Position.X + buttonWidth
                && pos.Y > button.Position.Y && pos.Y < button.Position.Y + buttonHeight)
            {
                return true;
            }
            return false;
        }
    }

Tesseract-OCRを試してみる

どうやらLinuxにはオープンソースのOCRソフト

Tesseract-OCRというものがあるようです。

これを試してみます。

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

https://kitakantech.com/tesseract-basic/

インストールは、

$ sudo apt-get install tesseract-ocr
$ sudo apt-get install tesseract-ocr-jpn

1つ目はTesseract-OCRの本体、2つ目は言語対応モジュールです。

使用方法は、こんな感じ。

$ tesseract test.jpg output -l jpn

これに対して、

読み込ませてみました。

うーん、読み込んでほしい部分が読み込まれてない。

写真だからダメなのだろうか。

たぶんスキャナで読み取った画像ならうまく読み込めるのかもしれないが。

栄養成分表をスキャナで読み取るなんて手間はありえないので、

ちょっと、この機能の実装を続けるかどうかは微妙ですな。

【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など)でも使用できます。

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