Gaming Life

一日24時間、ゲームは10時間

それでも小学生にプログラミングの授業はあってほしい

某バズった刺身タンポポ記事、大学生時分の私には身に覚えがある事が多すぎて余りに頭の痛い話だった。

note.mu

運良く地方の国立理系に通わせて貰っているが、プログラミングの講義の質は本当に先生次第。基本に忠実に、かつ最新の規格に沿って教えてくれる先生もいれば、まだC言語しか触っていない学科で、第二回で世界に挨拶し、第三回で説明もなくjavaでクラスを作らされ、置いてけぼりの学生をよそに講義予定の30分前に「もういいよね?」帰宅する先生もいる。

他大の話だが、リストを自作し、それも含めて課題を提出したのに「ライブラリに頼るな」というありがたいフィードバックを頂いたというお話をTLで観測したこともある(出来が良すぎたからどこかからコピペしたとでも思われたんだろうか)。

教えられる学生側もまぁ問題はあって、エラーメッセージ読まない、サンプルを丸写しだけして的はずれなことをやってる、質問の意図が意味不明など……元記事の通り色々問題はある。(もしこの記事をガチ同級生が読んでたらスマン。これは特定個人宛のメッセージではないし、私の知る同級生は皆私にないもの沢山持ってて羨ましいって日々思ってる)

大学でさえこの有様だから、政府が進める「小学校でのプログラミング教育」なんぞ上手くいくわけがなく。断言できる。

文科省の考えるプログラミング教育

文科省の資料によると、プログラミング教育の目的は、「プログラミング的思考を育成」らしい。

まぁそんなの無理だと思う。

バイトで小学生に算数を教えることがあるが、絶望的に出来ない子はいる。掛け算すらままならないような彼らに「プログラミング的思考を育成」なんて目的でやるプログラミング教育は絶対にうまくいかない。彼らにプログラミング・コンピュータに対する苦手意識を植え込むだけ。彼らはいずれ「圏外でLINEが繋がらないバグをどうにかしろ!」とクレームを入れるリテラシー皆無のモンスタークレーマーとなるのだろう。

結局のところ、伝統などと理由をつけて元号公表を必死に遅らせようとする政治が主導するプログラミング教育に期待してはならないのである。

だが小学校のプログラミング教育に賛成

悲観的な話ばかりしてきたが、私は小学校でのプログラミング教育は是非してほしい、と考えている。ただし、プログラミング的思考の習得などという高尚な理由ではなく、プログラミングに夢を抱く純真無垢な少年少女の「その幻想をぶち壊す」目的でなら、である。

閑話休題

私が本格的にプログラミングの勉強を始めたのは2年半前。小学校の頃から漠然と抱き、高校生時に読んだ「社長が訊く」で絶対になりたいと決めたゲームプログラマーという夢。夢自体は持っていたがその夢に向けて動き始めたのはつい最近になってから。理由は単純で「プログラミングの学び方」を知らなかったから。

田舎者で小心者の私は身近にプログラマーがおらず、探すことも出来ず。結局大学進学で親元を離れるまでプログラミングはしなかった。それでもパソコンは大好きで、実家のオンボロPCでOfficeを弄ったり、ブラウザゲームを遊んだり、Firefoxにアドオンを大量に突っ込んでマルウェアをインストールしてしまい大目玉を食らったりしてきた。

大学で漸くプログラミングが学べそうな情報系に進学した。あとで聞くと、大学の同期もざっくりと「プログラミングが学べそうだから」という理由で弊学科に進学している方が多いらしい。

入学した時はこの学科に来るような人はプログラミングの経験の有無はあれど、皆PCスキルは一定以上持っていると思っていた。

しかし実際は物理化学では私より遥かにいい成績を取れる人がexcelをロクに使えない という現実。プログラミングは出来なくともOfficeが使えたaiくんはいつの間にやら「パソコン詳しいオタク」扱いされるようになったのである。

変に持ち上げられてしまった以上、それに見合うスキルを得たいと、何冊も技術書を買って、独学でプログラミングを学ぶようになった。ここでようやく私は、交友関係、社会性を生贄に捧げることで「プログラミング書いたことない」から「プログラミングかけなくはない」にクラスチェンジすることができたのだ。

プログラミングの向き不向きは早めに判断したほうがいい

長々自分語りしてしまったが、言いたい事は一つ。

「もっと早くプログラミング勉強したかった!!!!!!」   私はゲームを作るためなら大学でのキラキラな交友関係や社会性を投げ捨ても構わないと割り切り、独学で(勿論講義で貰った資料、課題も参考にしたが)プログラミングを学び、一人でミニゲームを作れるくらいにはなれた。

しかしもっと早くから勉強できていれば今頃とんでもないゲームが作れていたかもしれない。今頃ゲーム会社のスーパープログラマーになれていたかもしれない。そんな後悔は尽きない。(まあまあ長いこと勉強してるのにその程度かって指摘はやめて。心折れる)

大学に入るまでプログラミングをしてこなかったのは、結局のところきっかけがなかったから。そして学び方が分からなかったからなのだろう。

もし小学校でプログラミング教育が実現すれば、プログラミングの才能、面白さに目覚めた小学生が私の様な後悔をすることなくとんでもない3Dゲームを作るかもしれないし、その若さ故の柔軟な発想で世界を変えるサービスを開発するかもしれない。

逆に今のままであれば、プログラミングで薔薇色の未来が待っているという幻想を抱き情報系の大学、専門学校に進み、そこで自分には向いていないと悟り四年(二年)をつまらなく過ごしてしまう人が生産され続けるだろう。

悲しいことにプログラミングは向き不向きがはっきり出る技能。大人になってそれを判断するよりかは、もっと早い段階で判断させ、向かない人にはプログラミングへの幻想を捨ててもらい別の道に迷いなく進んでもらう。向いている人には授業を足がかりにその才能を早くに開花させてもらう。

これならばプログラミング教育に賛成できる。

締め

随分元記事と趣旨がずれた話をしてしまった。

こんな記事を書くことにしたのは(打ち消し) 自分のツイートがバズってブログ告知ぶら下げたらアクセス伸びたから乗っかって記事書こうとおもった 元記事にプログラミング教育なんて無理、といった話があったから。TLで稀に見かける、真剣に小・中学生向けのプログラミング教育機材開発に取り組んでいる方を見ていると、そんなに悲観しなくてもと思う。

いつかこういったプログラミング教育機材でプログラミングに目覚めた子から産まれた傑作ゲームが遊んでみたい。

2018振り返りと来年の話

本当はしっかりした記事を書きたかったのだが年末時間が取れなかったので振り返りのツイートで。年明け時間があればちゃんとした記事を書きたい。

来年は就活に卒研色々忙しくなりそうで、ブログ更新頻度も下がると思われ。それでも最低月1ブログ更新、そして大学卒業までにゲームリリースはしたいなあ。

Python 連番テキストファイルを結合する

test_0.txt, test_1.txt, test_2.txt, test_3.txt...と連番で用意したテキストファイルを順番通りに結合して、test.txtという一つのファイルとして書き出すプログラム。

import glob
import os.path


# ファイルの結合
def join_file(filePath):
    fileList = create_filelist(filePath)
    with open(filePath, 'wb') as saveFile:
        for f in fileList:
            data = open(f, "rb").read()
            saveFile.write(data)
            saveFile.flush()


# 連番ファイルのリスト作成
def create_filelist(filePath):
    pathList = []
    for index in range(100000):
        filename = file_indexed(filePath, index)
        # ファイルが存在しなければ終了
        if not os.path.exists(filename):
            break
        else:
            pathList.append(filename)

    return pathList


# ファイル名に指定のindex値をふる
def file_indexed(filePath, index):
    name, ext = os.path.splitext(filePath)

    return "{0}_{1}{2}".format(name, index, ext)


if __name__ == "__main__":
    join_file("testData/test.txt")  # 相対パス

まとめ

catでいいやん。

Ubuntu + VSCodeでAsciiDocを書く環境を整える

以前の記事ではWindows上でAsciidocを書く環境を整えたが、Ubuntuでも同じことがしたくなったので導入してみた。今回はその作業メモ。

検証環境

Rubyの導入

このサイトを参考にRubyを導入

qiita.com

Asciidoctorパッケージをインストール

Rubyを導入できればgemコマンドが叩ける。というわけで以下実行。

$ gem install asciidoctor
$ gem install --pre asciidoctor-pdf
$ gem install asciidoctor-pdf-cjk
$ gem install asciidoctor-diagram

VSCodeで.adocのリアルタイムプレビューを有効にする

拡張機能からAsciiDocと検索して最初に表示されるAsciiDoc pluginをインストール。

続いて、Settingを開き(ファイル→基本設定→設定)、検索欄にasciidocと入力。

Use_asciidoctor_jsのチェックを外す。(最近のAsciiDoc pluginの更新でこの設定の名前が変更になった。以前の設定のままでプレビューが効かなくなったらまずここを疑うべき。)

VSCodeを再起動する。

Ctrl + Shift + P → AsciiDoc Open Preview to the side を実行すればプレビューが出来るようになる。

AsciiDoc + VSCodeでかっこいい文書作る

某方に布教されてAsciiDocにめっちゃハマった。

AsciiDocって

markdownのような軽量マークアップ言語の一種。

markdownより優れている点

  • markdownより高機能。
    • 困ったらHTMLタグ直書きとかしなくていい。
    • 表セルを結合出来る。
    • ソースコード、PlantUMLなどをincludeできる。
    • 目次が自動で作られる。
    • 使用可能なアイコンが大量にある。
    • PDF書き出しした時の見た目が安定&綺麗。

(plantUMLについては過去記事参照)

ai-gaminglife.hatenablog.com

markdownに劣る点

  • 環境構築がダルい
  • 普及してない
  • はてなブログがasciidocに対応していない

環境構築

windowsを想定。macでも大体変わらない(と思う)。

  • ruby installerをDLし、実行環境を整える
  • asciidoctorとエクステンションを追加する
    • 以下をコマンドプロンプトで実行
    • asciidoctor-diagramはplantUML等をasciidocで利用するためのエクステンション
    • asciidoctor-pdfはPDF変換に必要。
gem install asciidoctor
gem install asciidoctor-diagram
gem install --pre asciidoctor-pdf
  • VSCodeにエクステンションを追加する

  • VSCodeのsetting.jsonを変更する

    • ctrl + ,で基本設定ウインドウを表示。
    • setting.jsonに以下を追加。
"AsciiDoc.use_asciidoctor_js": false,
 "AsciiDoc.asciidoctor_command": "asciidoctor -a outdir=tmp -a imagesdir=tmp -a imagesoutdir=tmp -r asciidoctor-diagram -o-",

これで環境構築完了。.adocフォルダを作って編集していく。

ctrl + K + V もしくは Ctrl + Shift + P → AsciiDoc: Open preview to the side でサイドパネルにリアルタイムプレビューが表示される。

f:id:ai_gaminglife:20181113232211p:plain

HTML&PDF書き出し

  • HTMLに書き出す時
asciidoctor [ファイル名].adoc
  • PDFに書き出す時
asciidoctor-pdf [ファイル名].adoc
  • asciidoctor-pdf等を使う時
asciidoctor -r asciidoctor-diagram [ファイル名].adoc
asciidoctor-pdf -r asciidoctor-diagram [ファイル名].adoc

文法

ここで説明するより既にあるリファレンスを見てもらうほうが確実。

  • 超基本的な機能に絞って紹介されている記事

qiita.com

  • 公式リファレンス

takumon.github.io

iconについて

  • 冒頭に :icons: font を追加すればアイコンが使用できる。
NOTE: NOTE

TIP: TIP

IMPORTANT: IMPORTANT

WARNING: WARNING

CAUTION: CAUTION

レンダリング結果

f:id:ai_gaminglife:20181113232225p:plain

  • 他にも色々なアイコンが使用できる。有名企業のアイコンもある。
icon:font[]
icon:fire[] 
icon:hand-stop-o[] 
icon:amazon[]
[aqua]#icon:twitter[]#

レンダリング結果

f:id:ai_gaminglife:20181113232234p:plain

  • 使えるフォントはFont-Awesomeに依存。以下のサイトで調べることが出来る。

Icons from Font Awesome, Bootstrap and Google

HTML/PDF書き出しのテーマの変更

  • 書き出し時、ヘッダーフッターを設定したり、文字のフォーマットを指定したり、色々な事ができる。 f:id:ai_gaminglife:20181113232319p:plain
    • PDF書き出しのフォーマットを変更したければ、デフォルト設定なら以下のテーマを書き換えることで実現。 C:\Ruby25-x64\lib\ruby\gems\2.5.0\gems\asciidoctor-pdf-1.5.0.alpha.16\data\themes
    • 参考サイト

asciidocをPDFに変換してみた(asciidoctor-pdf) – Aimless

OpenSiv3D C++ 反射ベクトルを計算して玉の反射を実現する1

OpenSiv3Dのリファレンスにはブロック崩しのサンプルがある。

https://scrapbox.io/Siv3D/%E3%83%96%E3%83%AD%E3%83%83%E3%82%AF%E3%81%8F%E3%81%9A%E3%81%97

このサンプルでは玉が壁 or ブロックに衝突した時、玉の速度ベクトルのX成分もしくはY成分を反転させることで反射を実現している。

しかし、この方法では斜め壁に対応できていない。

というわけで壁に衝突した時に反射ベクトルを計算して玉を反射させるプログラムを書いた。

f:id:ai_gaminglife:20181110013644g:plain

コード

# include <Siv3D.hpp> // OpenSiv3D v0.2.5

Vec2 calcReflectVec(const Vec2& direction, const Vec2& normal)
{
    auto L = -direction.normalized();
    auto LdotNx2 = 2.0 * L.dot(normal);

    return (LdotNx2 * normal - L).normalized();
}

Vec2 calcLineNormal(const Line& line)
{
    auto dirVec = Vec2(line.begin.x - line.end.x, line.begin.y - line.end.y);
    auto normal = Vec2(-dirVec.y, dirVec.x);

    return normal.normalize();
}

class Ball
{

public:
    Ball() = default;

    Ball(Vec2 pos, Vec2 v)
        :circle(pos, 8)
        ,vec(v.normalize())
    {}

    ~Ball() = default;

    void update()
    {
        circle.moveBy(SPEED * vec.normalize());
    }

    void draw()
    {
        circle.draw();
    }

    Circle getCircle() const
    {
        return circle;
    }

    void bound(const Vec2& normal)
    {
        auto n = (vec.dot(normal) >= 0) ? normal : -normal;

        vec = calcReflectVec(vec, n);
    }

private:

    Circle circle;
    const double SPEED = 8.0;
    Vec2 vec;

};

void Main()
{
    Array<Line> lines;
    lines.push_back(Line(0, 0, 0, Window::Height()));
    lines.push_back(Line(Window::Width(), 0, Window::Width(), Window::Height()));
    lines.push_back(Line(0, 0, Window::Width(), 0));
    lines.push_back(Line(0, Window::Height(), Window::Width(), Window::Height()));
    lines.push_back(Line(550, 250, 420, 420));
    lines.push_back(Line(0, 150, 220, 200));

    Ball ball(Window::Center(), Vec2(-0.7, 0.5));

    while (System::Update())
    {
        ball.update();

        for (const auto& line : lines)
        {
            if (line.intersects(ball.getCircle()))
            {
                ball.bound(calcLineNormal(line));
            }
        }

        ball.draw();
        
        for (const auto& line : lines) 
        {
            line.draw();
        }
    }
}

方針

f:id:ai_gaminglife:20181110013833p:plain

-L: 玉の速度ベクトル

N : 壁の法線ベクトル

R : 壁に反射した時の玉の反射ベクトル

-LとNがわかれば以下の式でRを求めることができる。

\vec{R}=2(\vec{L} \cdot \vec{N})\vec{N}-\vec{L}

玉のクラスを作成する

class Ball
{
    public:
    Ball() = default;

    Ball(Vec2 pos, Vec2 v)
        :circle(pos, 8)
        ,vec(v.normalize())
    {}

    ~Ball() = default;

    void update()
    {
        circle.moveBy(SPEED * vec.normalize());
    }

    void draw()
    {
        circle.draw();
    }

    Circle getCircle() const
    {
        return circle;
    }

    void bound(const Vec2& normal)
    {

    }

private:

    Circle circle;
    const double SPEED = 8.0;
    Vec2 vec;

};

bound()は後で実装する。

壁の法線ベクトルを求める

今回、壁はSiv3DのLineクラスを利用する。

壁の端点の座標を減算し、壁の方向ベクトルtを求めることができる。

\vec{t} = (t_x, t_y)

tと壁の法線ベクトルnは内積を取ると0になる。

\vec{t} \cdot \vec{n}=t_x \times n_x + t_y \times n_y = 0

ここからnは二通りの解が得られる。

\vec{n} = (-t_y,t_x) \vec{n} = (t_y,-t_x)

なぜ法線が二種類得られるかというと、壁には表裏があるから。

Vec2 calcLineNormal(const Line& line)
{
    auto dirVec = Vec2(line.begin.x - line.end.x, line.begin.y - line.end.y);
    auto normal = Vec2(-dirVec.y, dirVec.x);

    return normal.normalize();
}

ここで、図をもう一度見返してみる。

f:id:ai_gaminglife:20181110013833p:plain

正しい法線nは、玉の方向ベクトルと鈍角をなす。鈍角をなす2つのベクトルは内積を取ると負の値を取るので、以下の処理を挟んで正しい法線ベクトルを得る必要がある。

auto n = (vec.dot(normal) >= 0) ? normal : -normal;

反射ベクトルを計算する

-L: 玉の速度ベクトル

N : 壁の法線ベクトル

R : 壁に反射した時の玉の反射ベクトル

とした時、

\vec{R}=2(\vec{L} \cdot \vec{N})\vec{N}-\vec{L}

なので、以下の関数で反射ベクトルを求めることができる。

Vec2 calcReflectVec(const Vec2& direction, const Vec2& normal)
{
    auto L = -direction.normalized();
    auto LdotNx2 = 2.0 * L.dot(normal);

    return (LdotNx2 * normal - L).normalized();
}

ここで求めた反射ベクトルをbound()関数で使用する。

void bound(const Vec2& normal)
{
    auto n = (vec.dot(normal) >= 0) ? normal : -normal;

    vec = calcReflectVec(vec, n);
}

壁を作成する

Siv3Dの動的配列Array<T>を利用してLineを格納する。

 Array<Line> lines;
    //ここからウィンドウ四隅の壁
    lines.push_back(Line(0, 0, 0, Window::Height()));
    lines.push_back(Line(Window::Width(), 0, Window::Width(), Window::Height()));
    lines.push_back(Line(0, 0, Window::Width(), 0));
    lines.push_back(Line(0, Window::Height(), Window::Width(), Window::Height()));
    //ここまでウィンドウ四隅の壁

    lines.push_back(Line(550, 250, 420, 420));
    lines.push_back(Line(0, 150, 220, 200));

玉と壁の当たり判定を取る

今回、壁と玉の当たり判定はintersects()関数を利用する。 壁をRange-based forでArray<Line>に格納した壁を取り出し、総当たりで当たり判定を取り、衝突していればbound()を呼び出す。

ball.update();
for (const auto& line : lines)
    {
        if (line.intersects(ball.getCircle()))
        {
            ball.bound(calcLineNormal(line));
        }
    }

描画する

draw()を呼び出して描画する。

ball.draw();
        
    for (const auto& line : lines) 
    {
        line.draw();
    }

結果

f:id:ai_gaminglife:20181110013644g:plain

未解決の問題

壁には厚みがあるので壁の横から衝突した時におかしな動きになってしまう。

まとめ

実装を始めた当初は数式を見てギョッとしたが、冷静に一つ一つ見ていくとそこまで難しいことはやっていない。未解決の問題だけはまだどうしようも出来ていないが。どなたか解決方法があったら教えてください。

参考サイト

qiita.com

C++で浮動小数点の誤差を考慮して等価比較する

<cfloat>ヘッダ中にあるDBL_EPSILONを使えば誤差も考慮して2つのdouble値の等価比較ができる。

float型ならFLT_EPSILONを使えばよい。

#include <cmath>
#include <cfloat>

//double型のaとbを誤差考慮して比較する
bool NearlyEqual(double a, double b)
{
    return abs(a - b) < DBL_EPSILON;
}

int main()
{
    double a,b;

    if(NearlyEqual(a,b))
    {
        return 0;
    }

    return 1;
}

追記(11/07) 故あってUE4の数学ライブラリ読んでたら似たような関数があったので、それに習って書き換えてみる。

struct Math
{

    static constexpr bool NearlyEqual(const float a, const float b, const float err = FLT_EPSILON)
    {
        return Abs<float>(a - b) <= err;
    }

    static constexpr bool NearlyEqual(const double a, const double b, const float err = DBL_EPSILON)
    {
        return Abs<double>(a - b) <= err;
    }

    template<typename T>
    static constexpr inline T Abs(const T a)
    {
        return (a >= (T)0) ? a : -a;
    }
};