普段競技プログラミングばっかりやってる僕がSiv3Dを触ってみた話

この記事はSiv3D Advent Calendar 2015 - Qiitaの14日目の記事です。


普段はプログラミングといえば競技プログラミングというレベルで競技プログラミング大好きな僕のSiv3D体験記です。

Siv3Dとは

Siv3D は C++ で楽しく簡単にゲームやメディアアートを作れるライブラリ

play-siv3d.hateblo.jp

インストール

まずはインストールから

  1. Siv3Dを使うにはVisual Studio 2013が必要らしい。
  2. Visual Studio 2013をインストール(途中で言語パックがおかしいと言われ4時間位書けて完了)
  3. Siv3Dにはインストーラー(Siv3D_Install.wsf)が付いている!
  4. Siv3D_Install.wsfをダブルクリックすると"Visual Studio 2013 の Project Template フォルダが見つかりませんでした。手動でのセットアップを試してください。"と言われる。
  5. 絶望
  6. Siv3D_Install.wsfの中を読む。->"Visual Studio 2013Templates\ProjectTemplates"にzipをコピーすればええんちゃう?
  7. 動く

円とか線とか

Siv3DはすごいのでCircle().draw()とか書くだけで円が表示できちゃいます。 Siv3DはすごいのでLine().draw()とか書くだけで線が表示できちゃいます。 Siv3DはすごいのでCircle().leftClickedとか書くだけで領域が左クリックされたか判定できちゃいます。 Siv3Dはすごすぎるので今回はこの3つをなんとなくごちゃごちゃしてるうちに日をまたぎこんなのが出来上がりました。 f:id:matsu7874:20151215022649p:plain

頂点数120のグラフを表示し、右クリックで始点を、左クリックで終点を選択して、最短経路を赤く表示するコードです。 ダイクストラで距離を求めて、始点から到達できない頂点は緑色に表示します。2000頂点くらいまでならさくさく動きます。

#define N_NODE 120

# include <Siv3D.hpp>
# include <vector>

struct Node{
    int x, y;
    Color color;
    std::vector<int> to;
    std::vector<double> cost;
    bool visited;
    double minCost;
    int from;
};

void dijkstra(int n, int start, struct Node *node){
    for (int i = 0; i < n; i++){
        node[i].visited = false;
        node[i].minCost = -1;
        node[i].from = -1;
    }
    node[start].minCost = 0;
    node[start].from = start;
    while (1){
        int visitedNode = -1;
        for (int i = 0; i < n; i++){
            if (node[i].visited == true){
                continue;
            }
            if (node[i].minCost < 0){
                continue;
            }
            if (visitedNode < 0 || node[i].minCost < node[visitedNode].minCost){
                visitedNode = i;
            }
        }

        if (visitedNode == -1){
            break;
        }

        node[visitedNode].visited = true;

        for (unsigned int i = 0; i < node[visitedNode].to.size(); i++){
            int to = node[visitedNode].to[i];
            double cost = node[visitedNode].minCost + node[visitedNode].cost[i];
            if (node[to].minCost < 0 || cost < node[to].minCost){
                node[to].minCost = cost;
                node[to].from = visitedNode;
            }
        }
    }
}

std::vector<int> CreatePath(int start, int end, struct Node *node){
    std::vector<int> path;
    if (node[end].minCost > 0){
        for (int i = end; i != start; i = node[i].from){
            if (i < 0){
                return std::vector<int>{start};
            }
            path.push_back(i);
        }
        path.push_back(start);
    }
    else{
        path.push_back(start);
    }
    return path;
}

void Main()
{
    const Font font(20);
    struct Node node[N_NODE];
    for (int i = 0; i < N_NODE; ++i){
        node[i].x = Random(10, 620);
        node[i].y = Random(40, 460);
        node[i].color = Color({ 255, 255, 255 });
    }
    for (int i = 0; i < N_NODE + 10; i++){
        int a = Random(0, N_NODE - 1);
        int b = Random(0, N_NODE - 1);
        while (a == b){
            b = Random(0, N_NODE - 1);
        }
        node[b].to.push_back(a);
        node[a].to.push_back(b);
        double dist = (node[a].x - node[b].x)*(node[a].x - node[b].x);
        dist += (node[a].y - node[b].y)*(node[a].y - node[b].y);
        node[a].cost.push_back(Sqrt(dist));
        node[b].cost.push_back(Sqrt(dist));
    }
    int root = 0;
    int target = 1;
    dijkstra(N_NODE, root, node);
    std::vector<int> path = { 0 };
    node[root].color = Palette::Blue;
    node[target].color = Palette::Red;
    while (System::Update())
    {
        for (int i = 0; i < N_NODE; ++i){
            if (Circle(node[i].x, node[i].y, 2).leftClicked){
                node[target].color = Palette::White;
                target = i;
                node[target].color = Palette::Red;
                path = CreatePath(root, target, node);
            }
            else if (Circle(node[i].x, node[i].y, 2).rightClicked){
                node[root].color = Palette::White;
                root = i;
                node[root].color = Palette::Blue;
                dijkstra(N_NODE, root, node);
                path = CreatePath(root, target, node);
            }
        }
        for (int i = 0; i < N_NODE; ++i){
            for (int j = 0; j < node[i].to.size(); ++j){
                Line(node[i].x, node[i].y, node[node[i].to[j]].x, node[node[i].to[j]].y).draw(Palette::White);
            }
        }
        for (int i = 0; i < path.size() - 1; ++i){
            Line(node[path[i]].x, node[path[i]].y, node[path[i + 1]].x, node[path[i + 1]].y).draw(2.0, Palette::Red);
        }
        for (int i = 0; i < N_NODE; ++i){
            if (node[i].from == -1){
                Circle(node[i].x, node[i].y, 3).draw(Palette::Green);
                if (i == target){
                    Circle(node[i].x, node[i].y, 2).draw(node[i].color);
                }
            }
            else{
                Circle(node[i].x, node[i].y, 2).draw(node[i].color);
            }
        }
        font(node[target].minCost).draw(0, 0, Palette::Gray);
    }
}

ハマった所

  • 関数なのか変数なのかCircle(node[i].x, node[i].y, 2).leftClickedとか関数っぽくないですか?変数です。
  • アイテムが重なっている場合、画面に表示されるのは一番最後に描写したアイテムです。大事なものから描きたいやんけ!慣れない。

疑問点

  • 辺描いて、特殊な辺を上書きして、頂点描いてとループを3つに分けているのがキモくないだろうか。
  • アルゴリズム部分の状態管理と画面への表示状態を上手く管理する方法。

結論

Siv3Dはやばい。C++の練習のためにももっと書き込まなば。学ばねば。