来世から頑張る!!

技術ブログを目指して

ボードゲームのハコについて

このエントリーはボドゲ紹介 Advent Calendar 2017の7日目です。

前日: 推理ゲーム『ワトソン&ホームズ』 - kano-e no memo
翌日: テラフォーミング・マーズの魅力: うぃりあむの「ヴァリアントの森」

タイトルの通り、ボドゲのハコを紹介したいと思います。

先日ゲームマーケット2017が実施されましたが、そのなかでとある出展者さんが「ダイスタワーという言葉を知らない人が検索フォームからダイスタワーに出会う可能性は限りなく低い」というようなお話をしており、私も知らなければ出会う可能性がすごく低いものを紹介しようとこのエントリーにしました。

ここではBox insertと呼ばれるアクセサリーを紹介致します。

Box insertとは

ボードゲームの箱って内容物に対して小さすぎて、中身を小分けするとフタが閉まらなかったりしますよね?
拡張も一緒に持ち運びたいのに、スペースが足りずに諦めたりしますよね?

f:id:kazzna:20171207221826j:plain
ボードゲームコンポーネントと入りきらない拡張

空気が主なコンポーネント? そういうボードゲームの事は一旦忘れましょう。

こういった大量のコンポーネントをうまく詰め込めるように作られた箱がbox insertです。

Box insertを使うと、先程のようなコンポーネントがうまく箱に収まります。 f:id:kazzna:20171207222041j:plain f:id:kazzna:20171209001801j:plain

f:id:kazzna:20171207222116j:plain
拡張とともに1箱へ
通常はスペースなく箱の中身がきっちり埋まるように作られているので、蓋が開かないように留めてしまえば縦置きも可能です。

注文と組み立て

自分で組み立てする形態で売られていることがほとんどで、注文すると数週間程度で下の写真のような板が届きます。 f:id:kazzna:20171207222315j:plain

組み立てには以下のものが必要になるので100均で揃えてください。

  • 木工用ボンド
  • 紙ヤスリ(120番と400番あたりがあると大丈夫です。)
  • 軍手
  • 綿棒(大量にあるといい感じ)
  • ゴムハンマー(なくても大丈夫かも)

基本的にはプラモデルと同じで、枠から取り外して説明書の通りにくっつけるだけです。
ただ、木製なので細いパーツが折れやすかったり、トゲが手に刺さったりするのでそこは注意が必要です。 万が一パーツが折れた場合は、要らないフレーム部分をヤスリで削って出てきた粉とボンドを混ぜてくっつけることが可能です。

1時間ぐらいで完成すると書かれていますが、そこはボードゲームと同じで大体半日はかかります。
慣れた人が回せば早いんでしょうね。

ニスとかはよく分からないので詳しい人教えてください。

販売サイト紹介

box insertは大きな会社から個人の副業っぽいところまで、多数のサイトで販売されています。
色々と探し回ってあっちがいいこっちがいいと比べて回るのが楽しさの醍醐味です!!!

個人的に気になるところをいくつかピックアップしてご紹介致します。

Meeple Realty

Think inside the box(箱の中について考える)がテーマのサイトです。
ただの木箱ではなくて、ゲームのテーマに合った形に加工されているのが特徴です。 その分使い勝手は他に少し劣ることもありますが、インサートなしよりは確実に使いやすいのでデザインを重視する人にオススメです。

Daedalus Productions

冒頭のテラミスティカのインサートを販売しているところです。
Quick-Start Insertという取り出してすぐにプレイできるようにと作られたシリーズの使い勝手が素晴らしい出来です。

組み立ての説明書がサイト上で公開されていますので、イメージをつかみたい場合にどうぞ。

The Broken Token

インサートだけでなく、オーガナイザー(organizer)やコインなども販売しているサイトです。
プラスチック性の蓋などの木製だけじゃないコンポーネントがついていたり、カードのケースがそのままカードフィーダーになったりと色々素敵なものが沢山あります。

最近追加されたテラミスティカのインサートの出来も良さそうなので、もし既に持っていなければここのを買っていたと思います。

Etsy

ハンドメイド作品を気軽に販売できるサイトです。
アクセサリーや服から大型家具まで幅広く売られており、ゲーム関連アクセサリーも大量にあります。

販売者は個人の場合も多数あるようで、普通のお店の感覚では挑まない方が無難かもしれません。
コンポーネントの質はきっとピンきりなんでしょうけど、お値段の割に非常に良いものも多いので探す価値は大です。

システム的にはカートに入れるボタンを押して支払いを済ませれば買えるのですが、他の海外ショップでお問い合わせぐらいはできるようになってからの利用をオススメします。

Insert here

フォームコアによるインサートを作っているサイトです。 家族でやっている?らしいです。
フォームコアはプラスチックっぽい謎の物体で、ネットで調べる限りでは木の箱よりも軽そうです。

木製のインサートはそれなりの重量になるので、軽いならいつかチャレンジしてみたいと思っています。

その他の気になるサイト

インサートじゃなかったりもしますが、気になるサイトをいくつか。

選び方のポイント

正直、箱に何を求めるかなんて人それぞれだと思いますが、私の経験と好みからの選ぶ基準を記載しておきます。

カードがスリーブ付きで入るのかどうか

トークンの多いゲームでは特に、スペースの都合でカードはスリーブ無しでしか格納できないものもあります。
別のサイトで売られているものはスリーブ付きでも入るということもままあるので、スリーブの有無は確認しましょう。

蓋は閉まらなくてもそこまで困らない

インサートによっては箱の上端より数センチ上まで飛び出ているものもありますが、蓋が一番下までいかなくても意外に困らないものです。
見た目にこだわる場合は避けるべきでしょうし一概には言えませんが、うまくバランスの取れているものを選びたいものです。

みんなに配る駒は別々のトレイに

それぞれのプレイヤー用トークンや開始時の手札など、配ってしまうものはそれぞれ別のトレイに入っていてそのまま配れるものの方が便利です。
また、コインなどの共通のストックに置いておくトレイが2つに分かれていると、それぞれをボードの左右に置いたりなど便利さが上がるのでチェックしておくとよいと思います。

何が格納できるのか

拡張なども一緒に格納できるのかどうかの確認は重要です。
拡張を持っていない場合拡張入れは空っぽになるものの、そのトレイを入れないと中で崩れるので空のまま入れておかなければ傾けられません。
中には「プロモボードがないと隙間ができるから縦向けにはできないよ」というものもあるので注意してください。

お気に入りの箱を探す際の参考程度にどうぞ。

以上、良いボードゲームライフをお過ごしください。

Scalaでの🍣の数え方

最近のおっさんはビールもきちんと数えられないことに驚愕したので、ScalaですがJavaの話をします。 昔と違ってUTF-8の半角カナが3バイトだと信じてくれないおじさんとは出会わなくなってきたなと思ってたのに、世の中そう甘くはなかったようです。

Javaの話なのでClojureだろうがKotlinだろうがJasminだろうが基本的におんなじです。

文字の数え方

🍺🍺🍺 <- これ、いくつかと聞かれたら3と答えて欲しいわけですよ!

scala> val beers = "🍺🍺🍺"
beers: String = 🍺🍺🍺

scala> print(beers.length)
6

当然、emojiなのでサロゲートペアですよ。 codePointCountを使ってくださいねという話です。

scala> print(beers.codePointCount(0, beers.length))
3

せっかくなのでScalaらしくpimpしちゃいましょう。 メソッド名はStringOpsとかとかぶらないように・・・

implicit class StringCount(val s: String) extends AnyVal {
  def letterCount: Int = s.codePointCount(0, s.length)
}

これでbeers.letterCountで文字数が出せるわけですね!

合成文字にも対応しておく

せっかくなので合成文字の数え方にも対応しておきましょう。
参考: Java SE 6 バラバラにして組み立てて - Normalizer

Java(というかUnicode)の正規化には2種類(2段階)あって、Å("\u0041\u030a")のような合成文字をÅ("\u00c5")に圧縮するだけの正規化Canonical Composeと、同じ意味を表すもっと(だいたい英語圏民に)一般的そうな文字に置き換えるCompatibility Composeがあります。

前者に使うのがjava.text.Normalizer.Form.NFC、後者に使うのがjava.text.Normalizer.Form.NFKCです。

試してみましょう。

scala> val a = "\u0041\u030a"
a: String = Å

scala> val b = "\u00c5"
b: String = Å

scala> a == b
res0: Boolean = false

scala> val c = Normalizer.normalize(a, Normalizer.Form.NFC)
c: String = Å

scala> b == c
res1: Boolean = true

scala> val d = "㌔㍉"
d: String = ㌔㍉

scala> val e = Normalizer.normalize(d, Normalizer.Form.NFC)
e: String = ㌔㍉

scala> val f = Normalizer.normalize(d, Normalizer.Form.NFKC)
f: String = キロミリ

文字数を数えたいので、使うのはNFCですね。

import java.text.Normalizer
implicit class StringCount(val s: String) extends AnyVal {
  def normalized: String = Normalizer.normalize(s, Normalizer.Form.NFC)
  def letterCount: Int = {
    val n = this.normlized
    n.codePointCount(0, n.length)
  }
}
scala> val x = "🍺\u0041\u030a🍣\u0041\u030a🍺"
x: String = 🍺Å🍣Å🍺

scala> x.length
res0: Int = 10

scala> x.letterCount
res1: Int = 5

寿司が2貫で1個なのかはともかく、酔っぱらっても1つのビールが2つに見えるようにはなりたくないものです。

一般的なShellで空白を含むPathをループしたい願望

まず、Shellって何かがわからないところから入るわけだけど、 いや、自分が打っているのが所謂shなのかbashなのかzshなのかがわからないってことなんだけど、 まあ複数ファイルを順次処理したい時ってあるわけですね。

forを使ったループ

シンプルな例だと以下のように書くわけです。

  for f in path/to/target/*.txt
  do
    echo "file is: $f"
  done

このディレクトリーにa.txt, b.txt, c.txtがあれば、以下のように表示されるでしょう。

file is: path/to/target/a.txt
file is: path/to/target/b.txt
file is: path/to/target/c.txt

スペース(など)が含まれたパス

ところが、スペースが含まれているようなディレクトリー名だと、これがうまく動かないのです。

ディレクトリー名をpath/to target with space/だとしてみましょう。

ls "path/to target with space/"
a a.csv      b c d.csv

こんな場合ですね。

同じようなループを実行すると、当然うまく行きません。

for f in path/to target with space/*.csv
do
  echo "file is: $f"
done

結果はこんな感じになったりしてしまいます。

file is: path/to
file is: target
file is: with
file is: space/*.csv

解決方法1: IFS組み込み変数を使用

どうにかスペース入のパスをループできないのかなーと検索した結果見つけたのが、 組み込み変数IFSfindコマンドを組み合わせる方法です。

IFSを変更すると、shellが扱う区切り文字を変更できるとこのことです。

まずfindを実行すると、以下のような結果が取得できます。

> find "path/to target with space" -type f -name "*.csv"
path/to target with space/a a.csv
path/to target with space/b c d.csv

結果が改行されて出てくるので、IFSに改行記号\nを設定後ループするとうまくいくようです。
ただし、IFSを元に戻せるように、退避してから実行した方が良いようです。

IFS_BACK="$IFS"
IFS=$'\n'
for f in `find "path/to target with space" -type f -name "*.csv"`
do
  echo "file is: $f"
done
IFS="$IFS_BACK"
file is: path/to target with space/a a.csv
file is: path/to target with space/b c d.csv

解決方法2: ""でくくる

とりあえず上をコピペして使っていたのですが、 今日もっと簡単にできる方法を発見してしまったのでそちらに書き換えることにしました。

皆さんご存知のように、shellではダブルクォートに囲まれた部分は1つとみなしてくれるのです。
ただし、中にある*は展開してくれません。

まず、ダメな例。

for f in "path/to target with space/*.csv"
do
  echo "file is: $f"
done

これの結果は以下のようになります。

file is: path/to target with space/*.csv

*がそのままですね。

すごく単純すぎて気づかなかったのですが、 *をダブルクォートの外に出してあげれば展開されるんですよね、これ。

for f in "path/to target with space/"*".csv"
do
  echo "file is: $f"
done
file is: path/to target with space/a a.csv
file is: path/to target with space/b c d.csv

どこでダブルクォートを終わらせてもいいみたいなので、変数の部分だけくくるとかが現実的なのかなという感じです。

BASE_PATH="path/to target with space"
for f in "$BASE_PATH"/*.csv
do
  echo "file is: $f"
done

結論

shell難しい。

自分のshell力の低さを痛感させられた。

自分用メモ #scala

アレ用のアレ。

implicit def fs2effectTask: fs2.util.Effect[scalaz.concurrent.Task] = {
  import scalaz.concurrent.Task
  import fs2.util.Effect
  
  new Effect[Task] {
    def fail[A](err: Throwable) = Task.fail(err)
    def attempt[A](t: Task[A]) = t.attempt.map(_.toEither)
    def pure[A](a: A) = Task.now(a)
    def bind[A,B](a: Task[A])(f: A => Task[B]): Task[B] = a flatMap f
    override def delay[A](a: => A) = Task.delay(a)
    def suspend[A](fa: => Task[A]) = Task.suspend(fa)

    override def toString = "fs2.util.Effect[scalaz.concurrent.Task]"
  }
}

SQL99のWITH句について

MySQLしか触った事のない人にとっては全く知らない文法だというとこを最近知ったので。

WITH句とは

WITH句は、ネストされたテーブルに別名をつけて読みやすくするために使用する構文。

PostgreSQL, Oracle, MS SQL Serverなどで使用可能。 MySQLは(少なくとも少し前のバージョンでは)対応していない。

-- ネストされたテーブル
SELECT
    *
FROM
    (
        SELECT
            user_id,
            SUM(payment) AS total_payment
        FROM
            payments
        GROUP BY
            user_id
    ) a
WHERE
    a.total_payment > 3000

この例だとHAVINGすればいいとかはひとまず無視して、WITHを使うとこんな感じ。

WITH a AS (
    SELECT
        user_id,
        SUM(payment) AS total_payment
    FROM
        payments
    GROUP BY
        user_id
)

SELECT
    *
FROM
    a
WHERE
    a.total_payment > 3000

通常の言語の変数宣言と同じ!!便利!!! 変数のように、適切な名前をつけるべき。aとかbとかfとか。

複数の変数を使用する

WITH句で複数のテーブルを取得する場合、,で繋げる。

WITH a AS (SELECT ...),
b AS (SELECT ...),
c AS (SELECT ...)

SELECT * FROM c

自分より上で宣言された別名は利用可能!!

WITH a AS (
    SELECT
        user_id,
        MAX(log_date) AS log_date
    FROM
        some_log
    GROUP BY
        user_id
),
b AS (
    SELECT
        *
    FROM
        some_log s
    WHERE
        EXISTS(
            SELECT
                0
            FROM
                a
            WHERE
                a.user_id = s.user_id
                AND
                a.log_date = s.log_date
        )
),

...

再帰

一部条件を満たせば、自分自身の中でも再帰的に参照が可能。

WITH a AS (
    SELECT 0 AS index
    UNION
    SELECT index + 1 AS index FROM a WHERE index < 2
)
SELECT * FROM a
  • PostgreSQLではWITH RECURSIVE a ASとしないと不可。他のDBは知らない。
    つまり1番目の変数のみ再帰可能。

関数型の再帰と同様に、ループの終了条件書かないと無限ループして返ってこないので注意。

結果はこんな感じ。

index
=====
0
1
2
3

これを利用すれば「n月m日からo日までの毎日、0件なら0を取得する」なども簡単ですよね?

パフォーマンスについて

WITH句で別名をつけた場合、1つの名前につき最大1回しか読み込まれないことが保証される。 また、ほとんどのDBMSではSQLを最後まで読み込んだうえで最適化するので、インデックススキャンで済むものは一時テーブルを作成しない。 なのでネストした表現と最低限同等、読み込み回数によってはWITHの方が圧倒的に効率化が可能。

でも、最後に信じるべきはオプティマイザーですね。過信ダメ、ゼッタイ。

あ、MySQLではCREATE TEMPORARY TABLE 〜 SELECT 〜を使おう。
こっちは自分でインデックス貼れるのでもっと早いよ!!!

AnsibleでDockerとかしたいのにdocker-pyに苦しめられた話

はい。タイトル通りです。

ansibleとはpythonで動いてるけどpython書かなくてもいい不思議な何かという認識。

で、ansibleからdockerコンテナー立ち上げるときのレシピがこちら。

---
- name: Run CentOS container
  docker:
    image: "centos:centos6"
    name: sample001
    tty: yes
    net: host
    docker_api_version: 1.18

net=hostはネット設定はデフォルト不可だよというエラーメッセージ用、 docker_api_version=1.18は本番で動いてるdockerのバージョン違うよ用です。

なのですが、これを実行してもdocker-pyが見つからないよ系エラーが出るのです。

ということでローカルでpip install docker-pyしたりしてみたのですが、 これが一向に消えてなくならない。

virtualenvがダメなのかもとか悩んでいろいろググったり試したりした結果・・・

docker-pyは設定先の環境のデフォルトpythonに必要でした!!

はい。ローカルに入っていようが関係なかったのです。 よって、必要なのは設定先ホストでのpip install

これもansibleに書いてしまいます。

---
- name: Install docker-py
  become: yes
  become_method: sudo
  become_user: root
  pip:
    name: "docker-py"
    version: 1.7.1
    state: present

ansibleの恒等性とかを考えると、state=latestとどっちがいいのかわかりません。

sudoはたぶん必要。と信じている。

このためにはサーバーにもpipが入っていないといけないので、 それはサーバーのディストリビューションに合わせてって感じになるのかと。

私からは以上です。

GolangでShift_JIS(Windows31J)のファイルを読み込み

たどり着くまで時間がかかったのでメモ。

文字コードを意識しないファイルの読み込み

つまりはUTF-8のファイル。BOMはどうなんだろう?(試していない)

os.Openbufio.NewReaderで行ごとに読み込める。

注意点: 読み込み完了時はerrorio.EOFが入る。

package main

import (
    "bufio"
    "fmt"
    "io"
    "os"
)

func main() {
    path := "/path/to/your/file"
    fp, err := os.Open(path)
    if err != nil {
        panic(err)
    }
    defer fp.Close()

    reader := bufio.NewReader(decoder.Reader(fp))
    for {
        line, _, err := reader.ReadLine()
        if err == io.EOF {
            break
        } else if err != nil {
            panic(err)
        }
        fmt.Println(string(line))
    }
}

文字コードを考慮する。

とりあえずgolang decodeとかでググってみるとencodingパッケージが出てくる。

どうやらDecoder.ReaderでラップしたReaderが作れそう。

じゃあDecoderってどうやって作るの?と探したところ、japaneseパッケージShiftJISを発見。 こいつがencoding.Encoding型なので、NewDecoderメソッドを持っているらしい。

とりあえずjapaneseパッケージをゲット

go get golang.org/x/text/encoding/japanese

package main

import (
    "bufio"
    "fmt"
    "golang.org/x/text/encoding/japanese"
    "io"
    "os"
)

func main() {
    path := "/path/to/your/file"
    fp, err := os.Open(path)
    if err != nil {
        panic(err)
    }
    defer fp.Close()

    decoder := japanese.ShiftJIS.NewDecoder()
    reader := bufio.NewReader(decoder.Reader(fp))
    for {
        line, _, err := reader.ReadLine()
        if err == io.EOF {
            break
        } else if err != nil {
            panic(err)
        }
        fmt.Println(string(line))
    }
}

bufio.NewReaderに変換する前にデコードをはさめばOK
わかれば簡単。