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

【デザインパターン】Commandパターン

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

package org.example.command;

public interface Command {
public void execute() throws Exception;
}
package org.example.command;

public class Receiver {
public void action()
{

}
}
package org.example.command;

public class ConcreteCommand implements Command{
@Override
public void execute() throws Exception {
Receiver receiver = new Receiver();
receiver.action();
}
}
package org.example.command;

public class Main {
public static void main(String[] args)
{
Command command = new ConcreteCommand();
try {
command.execute();
} catch (Exception e) {
e.printStackTrace();
}
}
}

CommandパターンはReceiverに対する命令(Command)をオブジェクト化して使用します。

こうすることによって、命令のバッチ処理、履歴保存ができます。

undo/redo処理を実装する場合もこのパターンを適用する場合が多いです。

【農場経営ゲーム】ボタンを作り始めた。

https://github.com/takishita2nd/FarmGame/tree/develop

ボタンの画像を作成。

ボタンをクラス化して、ボタンの上にマウスを置いたときと、マウスをクリックしたとき、画像を切り替えるようにしました。

次は、下のボタン達は共通している処理なので、ここをまとめて一つのクラスに処理をまとめたいと思います。

それを各シーンで使用して、共通の動作になるようにしたい。

たぶん、シーンの切り替えのみの処理だから。

【北海道大戦2021】リンク情報のチェック

https://github.com/takishita2nd/HokkaidoWar/tree/2021_develop

そういえば、まだデータのチェックやっていなかったな、と思って、

こんなコードを書きました。

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

        var maps = Singleton.FieldMap.GetAllMaps();
        foreach(var map in maps)
        {
            if(map == null)
            {
                continue;
            }

            if(map.IsOnMouse(pos))
            {
                map.GetCity().PaintAttackColor();
                foreach(var linkedMap in map.GetLinkdMap())
                {
                    linkedMap.GetCity().PaintDeffenceColor();
                }
                break;
            }
            else
            {
                map.GetCity().ClearPaint();
            }
        }

都市間のリンクデータを確認するプログラムです。

こんな感じで確認しました。

まぁ、手ででーたを入力していたわけで、

データミスが一箇所見つかりました。

めんどくさいことだけど、放置するともっとめんどくさいことになるから、めんどくさくてもやらなくちゃ行けないのです。

【農場経営ゲーム】画面を作り始めた。

https://github.com/takishita2nd/FarmGame

とりあえず、ボタンを配置してみた。

ボタンは下記リンク先のフリー素材を使用しています。

https://kopacurve.blog.fc2.com

ボタン+テキスト配置で作成したけど、たぶん、文字の入ったボタンを作った方が良いような気がする。

位置合わせがめんどくさい。

あ、そうそう、今回はAltseed2を使用しています。

.NEt Coreで動作するゲーム用ライブラリです。

農場経営ゲームを作ってみる

とりあえず、こんなものをサクッと考えてみた。

メイン画面

  • 各画面への移動
  • 所持金
  • 体力(1日に何回行動できるか)
  • 天気
  • 進捗状況

農場画面

  • 畑に種をまいて、育てて収穫する。
  • 種は、収穫物から回収・市場で購入することで入手。
  • お世話作業をすることで管理を行う
  • 時間経過とともに成長度が増加し、100%を超えたときに収穫できる。
  • 管理状態によって収穫物の品質が変化する。
  • 畑は市場で購入することで拡張できる。

牧場画面

  • 牛や鶏を飼うことができる。
  • 牛や鶏は市場で購入できる。
  • エサを与えることで成長度がアップし、100%に達すると畜産物を収穫できる。収穫後は成長度が0に戻る。
  • エサは市場で買うか、加工場で農作物から作成できる。
  • エサやお世話作業によって畜産物の品質が変化する。

市場画面

  • お金を使用して様々なアイテムを購入することができる。
  • (種、畑の拡張、動物、加工に必要なアイテムなど)

加工場画面

  • 農作物や畜産物を使用して別のアイテム(加工品)を作成する。

お店画面

  • 農作物、畜産物、加工品を商品棚に並べて、販売する。
  • 商品棚に並べた商品は時間経過で自動的に販売される。
  • 販売価格は品物と品質によって変化する。

能力画面

  • プレイヤーの能力値を確認できる。
  • プレイヤーには農業レベル、酪農レベル、加工レベル、経営レベルがある。
  • 農業レベルが高いと、農場でのお世話作業の効率がアップする。
  • 酪農レベルが高いと、牧場でのお世話作業の効率がアップする。
  • 加工レベルが高いと、加工品の品質がアップする。
  • 経営レベルは売り上げによってレベルアップし、作業回数が増える。

こんな感じで、着手開始します。

【COCOS2D-X】クエストリストをスクロールしすぎないように調整する。

https://github.com/takishita2nd/cocos2d-x_sample

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

今回はスクロール処理にもう少し手を加えて、

スクロールしすぎないようにストッパ処理を追加しました。

    if(questListMenu.isShow)
    {
        // Yの差分だけメニューを動かす
        float divY = touch->getLocation().y - keepPosition.y;
        auto visibleSize = Director::getInstance()->getVisibleSize();
        Vec2 origin = Director::getInstance()->getVisibleOrigin();

        if(questListMenu.questListMenu[0].parts.point.y - questListMenu.questListMenu[0].parts.size.height + divY < visibleSize.height)
        {
            for(int i = 0; i < QUEST_NUM; i++)
            {
                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].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);
            }
        }
        else if(questListMenu.questListMenu[QUEST_NUM - 1].parts.point.y + divY >= origin.y)
        {
            int listcount = 0;
            for(int i = QUEST_NUM - 1; i <= 0; i--)
            {
                questListMenu.questListMenu[QUEST_NUM - 1].parts.sprite->setPosition(questListMenu.questListMenu[i].parts.point.x, questListMenu.questListMenu[0].parts.point.y * listcount + origin.y);
                questListMenu.questListMenu[QUEST_NUM - 1].parts.point.y = questListMenu.questListMenu[0].parts.point.y * listcount + origin.y;
                questListMenu.questListMenu[QUEST_NUM - 1].questName.label->setPosition(questListMenu.questListMenu[i].questName.point.x, questListMenu.questListMenu[0].parts.point.y * listcount + origin.y);
                questListMenu.questListMenu[QUEST_NUM - 1].questName.point.y = questListMenu.questListMenu[0].parts.point.y * listcount + origin.y;
                listcount++;
            }
        }
        else
        {
            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;
    }

考え方としては、

リストの先頭が画面上部のサイズから下に移動しそうなときは初期位置に設定する、

リストの最後が画面の下から上にいs¥どうしそうなときは下からリストを並べる、

といった感じです。

じゃあ、次はタップで選択処理だな。

【デザインパターン】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

これに対して、

読み込ませてみました。

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

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

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

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

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