nikeeshi のコーディング記

コーディングの成果をはっつけるとこ。このブログにあるソースコードはNYSL Version 0.9982に従い公開します(2014/06/18)。

個人開発におけるmonorepoのメリット

今日の駄文です。

僕はゲーム作りたいんだけど、UnityとかUEとかの大きな開発環境を勉強する気も起きないし、勉強してる間にやる気が尽きちゃうし、pixi.jsあたりの小さな描画ライブラリを使ってかるーく作りたいっていう気持ちがある。そうするとpixi.jsなんかはもちろん足りない機能いっぱいあるし、新しいゲームを作ろうと思ったときに共通のコードをなんどもなんども再実装する羽目になる。

で!その共通のコードをライブラリ化したらいいのかなとか思うんだけど、レポジトリがいっぱいあると、どんどんどのレポジトリで開発してたかなんか半年もほったらかせばすっかり忘れてしまう。

という動機で僕はmonorepoをします。

というか僕が書く全部のコードを1つのrepogitoryにぶち込みます。そうすれば忘れる心配なし(たぶん)

monorepo のメリット

IDEを開くときにどのプロジェクト選べばいいかなとか考える必要がなくなります。とにかく全部 packages/ 以下に入れてgitでまとめて管理します。

ライブラリとか作りたくなったときもそのままpackages/ 以下に入れておく。

で!ライブラリとしてリリースしたくなったらリリース簡単にできそうなのでOK。

monorepo のめんどくさいところ

monorepoをサポートするツールが沢山ありすぎて勉強が追いつきません。とりあえず、リリースのことは後で考えるとして、Nxを手元で使っておけばよいのではないかな?ビルド速いらしいし。

おわりに

ゲーム制作のモチベは単調減少するものなので、いかに浪費しないで手ごたえ得られるところまで進めるかのマラソン的なところがあるので、とにかくモチベを減らす余計な作業を減らすのにmonorepoはいいのかなって思いました。まる。

今日の駄文でした。

Webpack 一番基本の設定

注意

この記事の質は保証しません。自分向けメモ。

やること

webpack を使ってJavaScriptをバンドルしてHTMLに読み込む。

CSSはとりあえず無視。

ディレクトリ構成

JSファイルはsrc以下に置く。 ビルドが成功すると、dist以下にHTMLとJSファイルが生成される。

必要なファイル

package.json

npm init (yarn init)でひな型を作る。その状態から設定に必要ないくつかの項目を追加する。

必要な項目

これらを追加。ライブラリのバージョンは適当に直して。

{
  "private": true,
  "main": "src/index.js",
  "scripts": {
    "start": "webpack-dev-server",
    "build": "webpack",
  },
  "dependencies": {
    "lodash": "^4.17.15" // これはライブラリが正しくロードできるかのテスト用
  },
  "devDependencies": {
    "html-webpack-plugin": "^3.2.0",
    "webpack": "^4.41.6",
    "webpack-cli": "^3.3.11",
    "webpack-dev-server": "^3.10.3"
  }
}

webpack.config.js

const path = require("path");
var HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
  entry: "./src/index.js",
  mode: "none",
  output: {
    path: path.resolve(__dirname, "./dist"),
    publicPath: "/",
  },
  plugins: [
    new HtmlWebpackPlugin()
  ],
  devServer: {
    contentBase: path.join(__dirname, "dist"),
    compress: true,
    port: 9000,
    open: true
  }
};

サンプルプログラム

index.js

import { appendStrings } from "./appendStrings";

document.body.appendChild(
  document.createTextNode(appendStrings("Hello", "world!"))
);

appendStrings.js

import _ from "lodash";
export function appendStrings(a, b) {
  return _.join([a, b], " ");
}

実行の仕方

ブラウザで見る方法:

npmならnpm start, yarnならyarn start

ビルド単体なら

npmならnpm run build, yarnならyarn build

React練習はじめました

特に内容はないんですが、React-drillっていう名前でレポジトリを作ってReactの練習をしていこうかなって思ってます。 まあ仕事では使ってるんですが、好きなもの作れないので、好きなものを作る欲はこっちで満たしたいですね。

レポジトリ GitHub - nikeeshi/react-drill: Let's practice React.

ホームページ React App

まだ中身は空っぽ

ちょっとずつ追加していくつもりなんですが、どこまで続くかな

今日のHello World「React と GitHub Pagesを使って、静的ページを立ち上げるまで。」

追記

github のデフォルトのブランチが master ではなく main に変わったので、新しく作ったレポジトリに関しては読み替えてください。

create-react-app が npm ではなく yarn をパッケージマネージャとして使うようになったので、npm 関連のコマンドは逐次 yarn のコマンドに読み替えてください。npxはそのままで大丈夫です。仮に npm を無理矢理使っても、設定ファイルである package.json は変わらないので問題ないです。その場合は不要になった yarn.lock は消してください。代わりに npm によって package-lock.json が生成されます。

npm から yarn への読み替え

npmのコマンド yarnのコマンド
パッケージをpackage.jsonに追記してインストール npm i (package name) yarn add (package name)
package.jsonに書いてある必要なパッケージを全てインストール npm i yarn
start スクリプトを実行 npm start yarn run start
スクリプトを実行 npm run (script name) yarn run (script name)

静的ページを作ろう

今回作るものは静的ページです。静的ページは、自分の理解で説明すると、クライアント(閲覧者)に応じて見せるものを変えられないWebページのことです。 自分の日常を載せるだけのブログとか自分の成果物を見せるだけのポートフォリオとかを作る分には、毎回見せるものを変える必要はないので、静的ページで充分ですね。

React は、Facebook が 開発した、HTML を描画時に生成する Javascript (JS) のライブラリです。React は設定が少し煩わしいのですが、Facebook が提供する create-react-app を使えば、コマンドを一回走らせるだけで簡単に設定ができます。create-react-app は複雑な設定を全て react-scripts に隠蔽しているので、プロジェクトの設定も簡潔になります。 ここでは React の詳しい説明はしません。

GitHub PagesGitHub のレポジトリを Web ページとして使える静的ページホスティングサービスです。作れるページの種類が二つあることに、僕は混乱したので、注意してください。それについての公式のガイドはここです。後でちゃんと説明します。

まず GitHubリポジトリの設定

前提条件

  1. GitHub のアカウントを持っている。(ない人は Join GitHub · GitHub で登録してね。)

まず GitHub でページ専用のリポジトリを作ります。リポジトリの名前は作りたいページの種類によって決まります。公式の説明はここです。

User Pages (もしくは Organization Pages)

各ユーザー(もしくは団体)につき一つだけ作れるページです。 URL は (username).github.io になります。 リポジトリの名前は (username).github.io で設定しなければなりません。必ず master ブランチのルートがページを読み込むときに参照される位置になります。制限がややこしいため、最初に User Page を作るのはあんまり推奨しません。

  • URL (username).github.io
  • repository (username).github.io
  • ページのルート master ブランチ

Project Pages

何個でも作れるページです。 URL は (username).github.io/(projectname) になります。 リポジトリの名前は作りたいページの URL の最後の部分になります。 デフォルトで gh-pages ブランチのルートがページを読むこむときに参照される位置になります。この設定で充分です。(この設定になっていない場合は GitHubリポジトリのページから設定を変更してください。Setting > Options > GitHub Pages > Source の部分です。)

  • URL (username).github.io/(projectname)
  • repository projectname
  • ページのルート gh-pages ブランチ、(master ブランチ、master ブランチの docs 以下に変更できる)

リポジトリを作ったら、空っぽのままにしておいてください。中身はローカルで作ります。

次に create-react-app でプロジェクトを作る

前提条件

  1. npm (npx が使える バージョン8 以上がいい)をインストールしている。npm は直接インストールするのではなく、nvm を使って管理するのがお勧めです。(Windows の場合は nvm-windows っていうのがある。)npm の代わりに yarn でもいいけど僕はわからないので説明はしません。
  2. git をインストールしている。(Windows ユーザーは問題を避けるため、一応アップデートしておいてね。chocolatey を使っている人はChocolatey Software | Git 2.34.1

これから create-react-app を使用してプロジェクトを作ります。公式の説明はここです。まずコマンドラインインタプリタWindows ならコマンドプロンプトMacならターミナル)を起動して、プロジェクトを置きたい場所の親ディレクトリに移動します。例えば、Project ディレクトリ 以下にプロジェクトを置いてる場合、$ cd Projectnpx create-react-app (my-project-name) を走らせると my-project-name というディレクトリができ、中に React プロジェクトが用意されます。ためしに、cd my-project-nameでプロジェクト内に移動してから、npm startを実行してみましょう。URL が localhost:3000 で React のロゴがぐるぐる回転しているページが表示されたはずです。ページに新しい機能を追加するときにはnpm startを実行した状態で描画された結果を確認しながら、コードを変更する形になります。

リモートの設定

GitHub の設定に戻ります。リポジトリを作成した際に書いてあった説明に従ってローカルのプロジェクトを GitHub と連携します。これをローカルで実行すればOKです。(コマンド内の username と repositoryname は適当に設定してください。)

git remote add origin https://github.com/username/repositoryname.git
git push -u origin master

ローカル(自分のPC)の master ブランチがリモートの master と連携できました。

注意: User Page を作りたい場合、ページの静的ファイルを置くために master ブランチを使うので、別のブランチでソースコードを管理してください。Project Page の場合はデフォルトで gh-pages ブランチを静的ファイル用、 master ブランチをソースコード用に使うようになっています。

gh-pages でデプロイしよう

Create React App の公式ページにデプロイ用の解説が詳しくのっています。読んで勝手に進めてください。

Project Page の人は、

  1. package.json の中に、"homepage": "https://myusername.github.io/my-app", を追加して、
  2. npm i gh-pagesgh-pages をインストールして、
  3. package.json の script 以下に"predeploy": "npm run build", "deploy": "gh-pages -d build",の二つのコマンドを登録したら準備完了です。

User Page の人は、

  1. package.json の中に、"homepage": "https://myusername.github.io", を追加して、
  2. npm i gh-pagesgh-pages をインストールして、
  3. package.json の script 以下に"predeploy": "npm run build", "deploy": "gh-pages -d build -b master",の二つのコマンドを登録したら準備完了です。-b で静的ファイルをおくためのブランチを master に指定しています。

次のコマンドを実行すると、GitHub の静的ファイル用のブランチが更新され、作ったページが見れるようになります。

npm run deploy

試しに、GitHub で gh-pages ブランチがどうなっているか見てみるといいですよ。

注意: gh-pages ブランチが既に存在すると怒られてデプロイできない場合は、gh-pages のキャッシュを消すとうまくいく場合があるらしいです。参考 GitHub - tschaub/gh-pages: General purpose task for publishing files to a gh-pages branch on GitHub

サンプルページ

試しに、Project Page を作ってみました。参考にしてみてください。

github.com

https://nikeeshi.github.io/sample-project-pages/

Googleの検索エンジンに期待を抱きすぎているのかもしれない

愚痴を書きます。

今日僕はこんな問題に直面しました。

問題

研究室のPC上のgvimTeXで書かれたファイルを開くとTeXのコマンドの先頭はバックスラッシュで表示されます。

しかし、家のPC上のgvimで同じファイルを開くとコマンドの先頭は円マークで表示されます。

そこで、家のPCでもバックスラッシュを表示されるようにしたいのですが、どうしたらよいでしょうか。


円マークとバックスラッシュはWindowsでは同じ文字コードを使っているはずなので、見る側の問題であると推測できます。

見る側、つまり2つのマシンに乗っているvimの設定が違うのではないかと推測しました。

ここでGoogle検索エンジンにご登場ねがおうではないか!!

さぞ当意即妙な解決策を提示してくれたまえ!!!!!

Googleの検索窓に検索クエリを打ち込みます。

一回目のトライ「vim backslash」

f:id:nikeeshi:20160701123836p:plain

さあ英語のページばかり出てきました。

この問題は日本語と韓国語(たぶん)特有の問題なので、英語のページには解決策はないと予測できます。

ここで二つの選択肢

  1. 日本語のページしかでてこない設定にする

  2. 日本語の検索語を使う

今回は2番を使いました。

二回目のトライ「vim バックスラッシュ」

f:id:nikeeshi:20160701124220p:plain

さてと、バックスラッシュを軸に考えると二種類の話題があることがわかります。

  1. バックスラッシュの入力方法

  2. バックスラッシュのコマンドとしての使い方

(実はここで4番目のサイトが正解なわけですが、見落としてました。人間はミスをする生き物です。)

1番目について、「Mac」の話であることが分かります。

Mac上では円マークとバックスラッシュは打ち分けることができるという知識はあらかじめありましたので、これは違うと思いました。

2番目も、vim上でバックスラッシュを使うとキーワード検索ができるのですが、これも求めているのと違います。

きっとこのふたつの話題に埋もれてるのではないかと考え検索語を追加していきます。

まず、「バックスラッシュ」だけではなく「円マーク」も検索語に入れれば、2番目ははじけます。

それに「Mac」の話ではなく「Windows」の話が知りたいので、「-Mac」を検索語に入れます。

三回目のトライ「vim 円マーク バックスラッシュ -mac

f:id:nikeeshi:20160701125512p:plain

聞いてください。Googleが検索語を無視し始めました。

上から順に見ていきますね。

  1. メタ文字の話が知りたいわけではないですので、却下!

  2. vim」を無視されました!却下!(ただし文字コードの知識を入れるのには役にたちますね)

  3. 「円マーク」の「円」を無視されました!そんな無視の仕方があるのでしょうか?新発見です。却下!

  4. どうやら同じような問題に引っかかっている人らしいですね。中を覗いてみると、なんとMacの話でした!!Macはじいたやろ!!却下!!

  5. vim」の類似語である「vi」がタグ一覧のところに引っかかっています!本文と関係ないところに引っ掛けるのやめて!!却下!!(中を覗いてみるとこれが正解なんですが、外からじゃわかりません)

  6. vim」どこ行った。却下!!

全然だめでした。検索語をいれすぎると無視され始めるんですね。

さて、やけくそで4回目のトライ行ってみましょうか!

四回目のトライ「windows10の上でgvimでバックスラッシュが円マークになる」

結果省略。

もうだめだあああああああ。

言いたいこと

一部の人はすぐ「ググれ」だの「ググれカス」だの言いますが、上でみたようにGoogle検索エンジンは万能ではないです。

検索エンジンを使って目的のページを出すには、調べたい分野の知識が絶対必要になります。

答えがわかってる人ほど目的のページを見つけやすく、答えが知りたい人には目的のページは見つからないんだよ

「ググれカス」とかいうやつくたばれ!!!!!!!!

あとがき

ちなみにたぶんこの問題はvimのフォントを変えると解決すると思われるので、フォントの設定を見直しましょう。

コンビネーションとか二項分布とか三分探索とか

まえがき

ちょっとハースストーンで遊んでいたら検証したいことが出てきたので、尤度を計算したよ。

そのときのコードです。何やってたかしりたかったらこっちも見てね ヨグ=サロンは果たして自分のヒーローの呪文を多く選ぶのか? - ふらふらHearthstone

コンビネーションの計算とか二項分布の計算とか三分探索とかやってます。

コンビネーションのdoubleバージョンに関してはある程度大きくなると誤差がでてきます。誤差保証はしていないので、注意してください。

使いたかったらご自由にコピペしてどうぞ。

以下スパゲッティーなコード

#include <iostream>
#include <vector>
#include <map>
#include <algorithm>
#include <cassert>
#include <functional>
#include <iomanip>
using namespace std;
class combination
{
    map<pair<int,int>,long long> memo;
    map<pair<int,int>,double> memo_f;
public:
    long long combi(int n, int x) {
        assert(n>=0);
        assert(x>=0&&x<=n);
        if(n==x)return 1;
        else if(x==0) return 1;
        else{
            auto f=memo.find(make_pair(n,x));
            if(f!=memo.end()){
                return (*f).second;
            }else{
                auto res= combi(n-1, x)+combi(n-1, x-1);
                assert(res>=0);
                memo[make_pair(n,x)]=res;
                return res;
            }
        }
    }
    double combi_f(int n, int x) {
        assert(n>=0);
        assert(x>=0&&x<=n);
        if(n==x)return 1;
        else if(x==0) return 1;
        else{
            auto f=memo_f.find(make_pair(n,x));
            if(f!=memo_f.end()){
                return (*f).second;
            }else{
                auto res= combi_f(n-1, x)+combi_f(n-1, x-1);
                assert(res>=0);
                memo_f[make_pair(n,x)]=res;
                return res;
            }
        }
    }
};
long long combi(long long n,long long x){
    static combination c;
    return c.combi(n,x);
}
double combi_f(long long n,long long x){
    static combination c;
    return c.combi_f(n,x);
}
// 確率pのベルヌーイ試行をn回したときにx回出る確率
double nikou(int x,int n,double p){
    return combi_f(n,x)*pow(p,x)*pow(1-p,n-x);
}
struct Yogdata{int x;int n;int myhero;int all;};
double likelihood(vector<Yogdata> data,double mass){
    double ans=1;
    for(auto item:data){
        ans*=nikou(item.x,item.n,item.myhero*mass/(item.myhero*mass+item.all-item.myhero));
    }
    return ans;
}

template<class In,class Out>
void test(Out (*f)(In)){
    In x;
    while(true){
        cin>>x;
        cout<<f(x)<<endl;
    }
}
template<class In1,class In2,class Out>
void test(Out (*f)(In1,In2)){
    In1 x;
    In2 y;
    while(true){
        cin>>x>>y;
        cout<<f(x,y)<<endl;
    }
}
template<class In1,class In2,class In3,class Out>
void test(Out (*f)(In1,In2,In3)){
    In1 x;
    In2 y;
    In3 z;
    while(true){
        cin>>x>>y>>z;
        cout<<f(x,y,z)<<endl;
    }
}
template<class In1,class In2,class Out1,class Out2>
void test(Out1 (*f1)(In1,In2),Out2 (*f2)(In1,In2)){
    In1 x;
    In2 y;
    while(true){
        cin>>x>>y;
        cout<<setprecision(20);
        cout<<f1(x,y)<<"\t"<<f2(x,y)<<endl;
    }
}
double sanbutan(double bn,double ed,double f(double)){
    if(abs(bn-ed)<0.0000001)return bn;
    else{
        double le=(bn*2+ed)/3,ri=(bn+ed*2)/3;
        if(f(le)>=f(ri))return sanbutan(bn,ri,f);
        else return sanbutan(le,ed,f);
    }
}

vector<Yogdata> data;
double wrap(double mass){
    return likelihood(data,mass);
}
void run(){
    int N;
    cin>>N;
    data=vector<Yogdata>(N);
    for(int i=0;i<N;i++){
        int a,b,c,d;
        cin>>a>>b>>c>>d;
        data[i]={a,b,c,d};
    }
    for(int i=0;i<5;i++){
        double mass=i/1.0;
        cout<<mass<<"\t"<<likelihood(data,mass)<<endl;
    }
    cout<<sanbutan(0.0,5.0,wrap)<<endl;
}
int main() {
    run();
}

C#のコンストラクタでthisが使える文法は惜しい

C#コンストラクタは引数の組み合わせの異なれば複数定義できる。(関数と同じ)

C#には、あるコンストラクタから別の引数の組み合わせのコンストラクタを呼び出す方法が用意されている。

これは、コンストラクタを再利用できるので、コードの複製をふせぐ文法として多いに期待できそうだ。(この文法がなくても静的なファクトリーメソッドを用意すればいいが、微妙にめんどい)

文法を見てみよう。

class A{
    A(P p,Q q):this(x,y){..}
    A(X x,Y y){..}
}

あーここにthis来ちゃったかー

このコンストラクタの呼び出しから他のコンストラクタに受け渡しするまでに入れられる処理を考えよう。

内側のコンストラクタの引数の数分の「式」の評価である。

これは悲しい。

仮にx=pq,y=p+qを入れたいとすると、this(pq,p+q)と書けばよい。

もっと複雑な計算をしようとして、「変数」が使いたいとする。

this(f(p,q),g(p,q))

として関数をつかえばよい。但し、f内の変数はgで見れないので、同じ計算を何回もやったり、グローバルな領域を駆使したりしないといけない。

これは、引数が複数の場合の問題だ。

引数を無理矢理一つにすると関数が一つにまとまるので好きな計算ができるぞーって思うが。。。

class A{
    A(P p,Q q): this(f(p,q)){..}
    A(X x,Y y){..}
    static x_and_y f(..){..}
}

x_and_yクラスを作らないとね。クラスを一つ作るためにクラスを作ってたらいつまでたっても終わらない気がして萎えるね。

問題はC#のような「文」をつかわないとまともにプログラムが書けない言語で、コンストラクタの再利用が「式」しかかけない文法であること。

こんな文法なかったことにして、とりあえずファクトリーメソッド使うのがいいと思うよ。