読者です 読者をやめる 読者になる 読者になる

いものやま。

雑多な知識の寄せ集め

Javaで動画作成ツールを作った話。(その1)

技術 Java ニコニコ動画

自分はニコニコ動画にいくつかボードゲーム動画を上げてるんだけど、動画を作るときに使っている自作ツールの話を少ししようかなと。

ちなみに、ツールの名前はMSC
Movie-create-tool by Script and Commandの頭文字をとってMSCーーというのは建前で、実際には大好きなGRANRODEOのModern Strange Cowboyからw

特徴

さて、このMSCがどんなツールなのかというと、以下のとおり:

  • Javaで書かれたコンソールアプリ
  • 独自仕様のスクリプトを書いて読み込ませることで、動画ファイル(Quick Time Movie形式)を生成する
    • ノード(画像、文字、プレイスホルダー)の表示をサポート
    • ノードのアクション(レイヤーへの追加・削除、前面・背面への移動、移動、フェード、文字列のセット、待ち)をサポート
    • ノードのグループ化をサポート
    • アクションの直列化、並列化をサポート
    • 複数レイヤーをサポート
    • 音声は未サポート
  • プレビュー機能あり
  • フレームワークはJMF(Java Media Framework)を使用

スクリプトを書いて動画を作るので、作業は基本的にテキストを打ち込むだけ。
あとは、プレビューで表示を確認したり、出来上がったらファイルに書き出したりすればいい。

この方法のいいところは、テキストエディタさえあればどこでも作業が出来るというところ。
それと、ボードゲーム動画のように、細かい要素を細かく動かす必要がある場合、プログラムを書くように動きをつけることが出来るので、比較的簡単に作ることが出来る。
(ただ、サブルーチンとかはサポートしていないので、そこまで使い勝手はよくない・・・DSLとして使えるようにするのが一番いいのだけど)

ちなみに、音声とQuick Time Movie形式以外の出力形式をサポートしていないので、実際には出力された動画をiMovieに読み込ませて、BGMやSEをつけたあと、MPEG4形式(コーデックはH.264)にエンコードして、アップをしている。

iMovieを使ったニコニコ動画向けのエンコードについては、自分の作った以下の動画を参照:

実例

例えば、自分がボードゲーム動画として最初にアップした動画が、以下:

この動画は、以下のようなスクリプトを読み込ませて作っている:

まず、全体的な設定をする、config.txt:

# コンフィギュレーション

> movie()
width: 800;
height: 600;
frame_rate: 25.0;
temp_dir: tmp;
<

> font(丸ゴ)
name: Hiragino Maru Gothic Pro;
size: 24;
color: ffffff;
<

> font(丸ゴ黒)
name: Hiragino Maru Gothic Pro;
size: 24;
color: 000000;
<

これで、以下を設定したことになる:

  • 動画の設定
    • 動画のサイズは800x600
    • フレームレートは25.0
    • 一時出力ディレクトリはtmp
  • フォントの設定
    • 「丸ゴ」というフォントとして、ヒラギノ丸ゴシックPro、サイズ24pt、色は白を使用する
    • 「丸ゴ黒」というフォントとして、ヒラギノ丸ゴシックPro、サイズ24pt、黒は黒を使用する

次に、動画に出現させるアイテムを定義する、item.txt:

# アイテム

#----------------------------------------
# レイヤー
#----------------------------------------
> layer(背景レイヤー) <
> layer(人物レイヤー) <
> layer(ボードレイヤー) <
> layer(移動レイヤー) <
> layer(効果レイヤー) <
> layer(会話背景レイヤー) <
> layer(会話レイヤー) <

#----------------------------------------
# 画像
#----------------------------------------

#------------------------------
# 一般
#------------------------------
> image(背景)
file: 画像/穂波部屋.png;
x: 0; y: 0; visible: true;
<

〜省略〜

> draw(黒塗り)
type: rect;
x: 0; y: 0; visible: true;
width: 800; height: 600;
color: 000000;
<

〜省略〜

#------------------------------
# 矢印
# x方向の間隔は53 / y方向の間隔は43
#------------------------------
> image(上矢印)
file: 画像/上矢印.png;
x: 582; y: 419; visible: false; <

〜省略〜

#------------------------------
# コマ
# x方向の間隔は53 / y方向の間隔は43
# 移動するときは、-5x-5で浮かせる
#------------------------------
> image(o1)
file: 画像/コマ/o1.png;
x: 582; y: 32; visible: false; <

〜省略〜

> group(コマ)
item: o1, o2, o3, o4, o5, b1, b2, b3, b4, b5; <

> place(カウント)
font: 丸ゴ黒;
x: 541; y: 18; visible: true;
<

#------------------------------
# サイコロ
#------------------------------
> image(dl_1)
file: 画像/サイコロ/サイコロ1.png;
x: 372; y: 314; visible: true; <

〜省略〜

> group(dice)
item: dl_1, dl_2, dl_3, dl_4, dl_5, dl_6, 
    dr_2, dr_3, dr_4, dr_5, dr_6, dr_1; <

〜省略〜

#------------------------------
# 穂波アップ1
# x: 172; y: 0; / x: 12; y:0;
#------------------------------
> image(hu01)    # 真顔
file: 画像/穂波アップ/HOBDA01.png;
x: 172; y: 0; visible: false; <

> image(hu02)    # 微笑み
file: 画像/穂波アップ/HOBDA02.png;
x: 172; y: 0; visible: false; <

> image(hu03)    # 柔らかい笑み
file: 画像/穂波アップ/HOBDA04.png;
x: 172; y: 0; visible: false; <

〜省略〜

#----------------------------------------
# テキスト、プレースホルダー
#----------------------------------------
> text(会話穂波)
text: 穂波;
font: 丸ゴ;
x: 100; y: 426; visible: true;
<

> text(会話拓也)
text: 拓也;
font: 丸ゴ;
x: 100; y: 426; visible: true;
<

> text(会話ゆのは)
text: ゆのは;
font: 丸ゴ;
x: 95; y: 426; visible: true;
<

> text(会話前回)
text: 〜前回のおさらい〜;
font: 丸ゴ;
x: 30; y: 426; visible: false;
<

# 30文字、3段までOK
> place(会話)
font: 丸ゴ;
x: 10; y: 494; visible: true;
duration: 0.04;
<

そして最後に、実際のシナリオと動作を記述する、scenario.txt:

# シナリオ

> scene(アイテム追加)
背景レイヤー: > add() <;
人物レイヤー: > add() <;
ボードレイヤー: > add() <;
移動レイヤー: > add() <;
効果レイヤー: > add() <;
会話背景レイヤー: > add() <;
会話レイヤー: > add() <;

背景: > add() layer: 背景レイヤー; <;
黒塗り: > add() layer: 効果レイヤー; <;
会話バー:
    > add() layer: 会話背景レイヤー; < ,
    > fade() type: in; sec: 0.5; < ;
名前バー:
    > add() layer: 会話背景レイヤー; < ,
    > fade() type: in; sec: 0.5; <;
会話:
    > add() layer: 会話レイヤー; <;
<

> scene(intro1)
before: アイテム追加;
会話穂波:
    > add() layer: 会話レイヤー; <;
会話:
    > set()
        text: 「……拓也くん。;
        text:  …………拓也くん。」;
    <,
    > wait() sec: 0.5; <,
    > act() sec: 0.32; <,
    > wait() sec: 2; <,
    > act() <,
    > wait() sec: 2; <;
<

> scene(intro2)
before: intro1;
会話穂波: > remove() <;
会話拓也: > add() layer: 会話レイヤー; <;
会話:
    > set()
        text:  穂波のやわらかい声が聞こえてくる。;
        text:  昨日はたしか、雪かきのバイトでヘトヘトになって;
        text:  わかばちゃんの家に帰って寝たはず。;
    <,
    > wait() sec: 0.5; <,
    > act() sec: 0.76; <,
    > wait() sec: 2; <,
    > act() <,
    > wait() sec: 4; <,
    > set()
        text:  夢でも見てるのかな?;
    <,
    > act() <,
    > wait() sec: 4; <;
<

> scene(intro3)
before: intro2;
会話穂波: > add() layer: 会話レイヤー; <;
会話拓也: > remove() <;
会話:
    > set()
        text: 「……拓也くん?;
        text:  ………………起きないといたずらしちゃいますよ?」;
    <,
    > wait() sec: 0.5; <,
    > act() sec: 0.32; <,
    > wait() sec: 2; <,
    > act() <,
    > wait() sec: 4; <;
<

> scene(intro4)
before: intro3;
名前バー: > remove() <;
会話穂波: > remove() <;
会話:
    > set()
        text:  ガバッ!;
        text:  慌てて飛び起きる。;
    <,
    > wait() sec: 0.5; <,
    > act() sec: 0.2; <,
    > wait() sec: 1; <,
    > act() <,
    > wait() sec: 2; <;
<

> scene(intro5)
before: intro4;
hb02: > add() layer: 背景レイヤー; <;
会話バー: > remove() <;
会話: > remove() <;
黒塗り:
    > fade() type: out; sec: 1; <,
    > wait() sec: 2; <,
    > remove() <;
<

> scene(intro6)
before: intro5;
名前バー:
    > add()
        layer: 会話背景レイヤー;
        visible: false; <,
    > fade() type: in; sec: 0.5; <;
会話バー:
    > add()
        layer: 会話背景レイヤー;
        visible: false;
    <,
    > fade() type: in; sec: 0.5;
    <;
会話:
    > add() layer: 会話レイヤー; < +
    > set() <;
<

> scene(intro7)
before: intro6;
会話穂波: > add() layer: 会話レイヤー; <;
会話:
    > set()
        text: 「拓也くん、おはよう。」;
    <,
    > wait() sec: 0.5; <,
    > act() <,
    > wait() sec: 4; <;
<

〜以下略〜

ちなみに、ルール説明のために矢印をスタートからゴールへ動かしたり(3:35付近〜)、サイコロを振ったり(4:35付近〜など)、ゲーム中に駒を動かしたり(11:55付近〜など)というのは、以下のような感じ:

# 矢印をスタートからゴールへ動かす

> scene(move_explain)
before: rule1;
矢印:
    > add() layer: 移動レイヤー; <,
    > wait() sec: 0.7; <,
    > move() dx: 0; dy: -387; sec: 1; <,
    > wait() sec: 0.2; <,
    > move() dx: 53; dy: 0; sec: 0.5; <,
    > wait() sec: 0.2; <,
    > move() dx: 0; dy: 387; sec: 1; <,
    > wait() sec: 0.2; <,
    > move() dx: 53; dy: 0; sec: 0.5; <,
    > wait() sec: 0.2; <,
    > move() dx: 0; dy: -387; sec: 1; <,
    > wait() sec: 0.2; <,
    > move() dx: 40; dy: 0; sec: 0.5; <,
    > wait() sec: 3; <,
    > remove() <,
    > wait() sec: 1; <;
上矢印:
    > fade() type: in; sec: 0.2; <,
    > wait() sec: 1.5; <,
    > fade() type: out; sec: 0.2; <,
    > wait() sec: 2.4; <, # 0.5+0.2+1+0.2+0.5
    > fade() type: in; sec: 0.2; <,
    > wait() sec: 1; <,
    > fade() type: out; sec: 0.2; <;
右矢印:
    > wait() sec: 1.7; <,
    > fade() type: in; sec: 0.2; <,
    > wait() sec: 0.5; <,
    > fade() type: out; sec: 0.2; <,
    > wait() sec: 1; <,
    > fade() type: in; sec: 0.2; <,
    > wait() sec: 0.5; <,
    > fade() type: out; sec: 0.2; <,
    > wait() sec: 1; <,
    > fade() type: in; sec: 0.2; <,
    > wait() sec: 2; <,
    > fade() type: out; sec: 0.2; <;
下矢印:
    > wait() sec: 2.4; <, # 0.2+0.5+1+0.2+0.5
    > fade() type: in; sec: 0.5; <,
    > wait() sec: 1; <,
    > fade() type: out; sec: 0.2; <;
<
# ダイスロール

> scene(rule10)
before: rule9;
dice: > add() layer: 効果レイヤー; <;
dl_1: > wait() sec: 0.1; <, > add() layer: 効果レイヤー; <;
dl_2: > wait() sec: 0.2; <, > add() layer: 効果レイヤー; <;
dl_3: > wait() sec: 0.5; <, > add() layer: 効果レイヤー; <;
dl_4: > wait() sec: 0.4; <, > add() layer: 効果レイヤー; <;
dl_5: > wait() sec: 0.6; <, > add() layer: 効果レイヤー; <;
dl_6: > wait() sec: 0.3; <, > add() layer: 効果レイヤー; <;
dr_1: > wait() sec: 0.3; <, > add() layer: 効果レイヤー; <;
dr_2: > wait() sec: 0.1; <, > add() layer: 効果レイヤー; <;
dr_3: > wait() sec: 0.6; <, > add() layer: 効果レイヤー; <;
dr_4: > wait() sec: 0.2; <, > add() layer: 効果レイヤー; <;
dr_5: > wait() sec: 0.4; <, > add() layer: 効果レイヤー; <;
dr_6: > wait() sec: 0.5; <, > add() layer: 効果レイヤー; <;
<
### 駒を動かす

> scene(replay14)
before: replay13;
o3:
    > remove() < + > add() layer: 移動レイヤー; <,
    > move() dx: -5; dy: -5; sec: 0.4; <, > wait() sec: 0.2; <,
    > move() dx: 0; dy: -43; sec: 0.4; <, > wait() sec: 0.2; <,
    > move() dx: 5; dy: 5; sec: 0.4; <, > wait() sec: 0.2; <;
カウント:
    > wait() sec: 1; <,
    > set() text: 1; < + > wait() sec: 0.6; <,
    > set() text:  ; <;
b4:
    > wait() sec: 1.6; <,
    > remove() < + > add() layer: 移動レイヤー; <,
    > move() x: 582; y: 419; sec: 0.4; <,
    > remove() < + > add() layer: ボードレイヤー; <;
b5:
    > wait() sec: 2; <,
    > remove() < + > add() layer: 移動レイヤー; <,
    > move() x: 542; y: 419; sec: 0.4; <
        + > fade() type: to; sec: 0.4; alpha: 0.5; <,
    > remove() < + > add() layer: ボードレイヤー; <,
    > wait() sec: 2; <;
<

> scene(replay15)
before: replay14;
会話:
    > act() <,
    > wait() sec: 4; <;
<

> scene(replay16)
before: replay15;
o2:
    > remove() < + > add() layer: 移動レイヤー; <,
    > move() dx: -5; dy: -5; sec: 0.4; <, > wait() sec: 0.2; <,
    > move() dx: 0; dy: -43; sec: 0.4; <, > wait() sec: 0.2; <,
    > move() dx: 0; dy: -43; sec: 0.4; <, > wait() sec: 0.2; <,
    > move() dx: 53; dy: 0; sec: 0.4; <, > wait() sec: 0.2; <,
    > move() dx: 5; dy: 5; sec: 0.4; <, > wait() sec: 0.2; <;
カウント:
    > wait() sec: 1; <,
    > set() text: 1; < + > wait() sec: 0.6; <,
    > set() text: 2; < + > wait() sec: 0.6; <,
    > set() text: 3; < + > wait() sec: 0.6; <,
    > set() text:  ; <;
b2:
    > wait() sec: 2.8; <,
    > remove() < + > add() layer: 移動レイヤー; <,
    > move() x: 582; y: 419; sec: 0.4; <,
    > remove() < + > add() layer: ボードレイヤー; <;
b4:
    > wait() sec: 3.2; <,
    > remove() < + > add() layer: 移動レイヤー; <,
    > move() x: 542; y: 376; sec: 0.4; <
        + > fade() type: to; sec: 0.4; alpha: 0.5; <,
    > remove() < + > add() layer: ボードレイヤー; <,
    > wait() sec: 2; <;
<

GUIでチマチマと動きをつけるとかなり大変だけど、スクリプトならサクッと書けるので、(比較的)楽チン。
(まぁ、いろいろと問題もあるんだけど・・・例えば、会話表示の適切な待ち時間を自分で指定しないといけなかったりとか)

ちなみに、scenario.txtは約3,000行。

そして、スクリプトを書いたら、あとはコマンドで動画を出力:

$ java -jar msc_console.jar -o output.mov config.txt item.txt scenario.txt
compile...
  configuration... OK
  item... OK
  scenario... OK
done.
output...
  scene progress... OK
  file output... OK
done.

これでoutput.movという名前で動画が書き出される。

ちなみに、JMFでの動画の出力方法がよく分からなかったので、かなり効率の悪い出力の仕方をしていて、そのせいでoutput.movのファイルサイズは1.7GBとかあったり(^^;
まぁ、実際にはH.264エンコードしてしまうので、そこまでアップ時のサイズはそれほどでもないんだけど。

今日はここまで!

コミックマーケット90の3日目に一般参加してきた。

その他

2日目に引き続き、3日目も一般参加してきたので、その話を。

f:id:yamaimo0625:20160816194712p:plain

なお、2日目の様子は以下から:

再び国際展示場へ!

せっかくなので、始発で行って、何時ぐらいに入れるのかをもう一度確認してみようと思ったのだけど、普通に起きれなかった(^^;
ということで、2日目と大差なく、11時半ちょっと前に国際展示場駅に到着。

3日目ということで、さすがに2日目ほどすんなりと入れるわけではなく、一応入場列に並んだ。

f:id:yamaimo0625:20160816194211p:plain

といっても、10分程度で普通に入場できた。
やはり、「これだ!」というお目当てのものがないのであれば、これくらいの時間に行くのがいいと思う。

自転車関係エリア

今日のお目当ては、西1ホールの自転車関係、西3, 4ホールのソフトウェア関係、東5ホールの創作関係。
あと、俗に言う薄い本がいくつかw
もっとも、薄い本は人気のサークルさんなので、入手できればラッキーかなくらいの感覚だけど。

どうやって回ろうかなと考えたのだけど、西から入場したので、2日目と同じように、西→東という順番で回ることにした。

ということで、まずは自転車関係エリアから。

この自転車関係エリアの配置が普段とちょっと違っていて、「れ」になっていた。
この「れ」というのは、島じゃなくて壁。
壁は列形成がしやすいので、人気の高いサークルが(あまりジャンル関係なく)配置されていくというのが普通なのだけど、今回はこの壁の一部のエリアに自転車関係が配置されていた。
これにはちょっとビックリ。
もっとも、そのせいなのか、配置されたサークル数自体は島に割り当てられたときよりも少なかったように思う・・・

自転車関係では、以下の同人誌をゲット。

f:id:yamaimo0625:20160816194736p:plain

  • 自転車女子はじめてみたらね、しまなみ自転車一人旅 総集編(CUTIE KIDS CLUBさん)
  • 自転車女子はじめてみたらね、沖縄八重山自転車旅(同上)
  • びわっこ三姉妹自転車旅行記 佐渡ロングライド2016(大塚志郎さん)
  • 腐ったオタクの自転車ライフ3 〜腐女子台湾最高峰の山を登る〜(脂華さん)
  • Spot Rider Vol.3(FlowerParkさん)※小笠原諸島ポタリングガイド

西1, 2ホールをブラブラ

自転車関係を一通り見たので、その周辺の島をブラブラと見たり。

ここで心惹かれたのが、数々の聖地巡礼レポート。
聖地巡礼レポートを見ると、そこへ自転車で行ってみたいなぁと思ったりするw

そんな中、「これは!」と思って手に取ったのが、これ。

f:id:yamaimo0625:20160816194756p:plain

「地元が聖地になった LocoDol Leaks 2016」(Wipe Out!さん)

アニメにもなった「ろこどる」は流山市(自宅からも結構近いw)がモデルになっているのだけど、そのときに実際に地元で起こったいろいろな問題(?)を地元民の実体験として書かれている。
非常に興味深かったので、購入。

他にも、歩いていたら、こんなステキ写真が。

f:id:yamaimo0625:20160816194809p:plain

「街角画像集 Vol.12 東京の夕景・夜景 2016 1st」(四条烏丸屋本舗さん)

思わず買ってしまった・・・w

他にも廃墟の写真集だとか、いろいろあって、素晴らしい。
こういったいろんな同人の存在をもっと多くの人が知ってくれればいいんだけど。

ただ、このままだと財布のお金がなくなるということで、切り上げて西2ホールへ。

西2ホールで目指したのは、skyclaymanさんの同人誌。

ただ、行く途中で、こんなすごいポスターが。

f:id:yamaimo0625:20160816194837p:plain

なんという書き込み・・・

ということで、1冊購入。

f:id:yamaimo0625:20160816194847p:plain

「TWINS」(マイナスガーデンさん)

これ、タイトルの通り、よく見ると全部顔が2つずつあるのね。

そして、skyclaymanさんのブースへ。
以下を購入:

f:id:yamaimo0625:20160816194859p:plain

  • I love I' 総集編(vol.1〜vol.4 + vol.5〜vol.6 + Hairy Heaven)
  • Lingeric Hand II

この人はホントに変態天才だと思うので、大好き。

ソフトウェア関係エリア

ここで西2ホールを離れて、西3, 4ホールのソフトウェア関係エリアへ。

いろいろ興味深いんだけど、全部をやれるわけではないので、当初の予定どおり、まつらリッチ研究所さんへ。

まつらリッチ研究所さんでは、以下をゲット:

f:id:yamaimo0625:20160816194916p:plain

  • Shell Script ライトクックブック 2014-2016 ーーWindows, Mac, UNIX, すべてで動くプログラムを書くーー
  • 解説Coreutils(第7開発セクションさん)
  • バブみある技術(第7開発セクションさん)

最近、Shell Scriptを書くことがあって、癖は強いけど、やはりコマンドを普通に使えるというのがめちゃくちゃ便利だと実感したので、必要な技術だなぁ、と。

あと、新刊が到着してなかったので、軽く眺めただけだったんだけど、ヲタMacさんが出展してたのには驚いたり。
マンガの更新がずっと昔に止まって、もうすっかり活動を止めたんだと思っていたのだけど、まだ活動してたのか・・・

創作関係エリア

このあとは東へ移動。

余談だけど、東への移動が時間かかった・・・
3日目はやはり人が多いから大変。

東ではまず薄い本をいくつかゲット。
壁サークルなので手に入れられるか分からなかったけど、意外となんとかなるものw
(ちなみに、東に着いたのが13時ちょっと前)

そして、東5ホールの創作関係のエリアへ。

創作関係エリアでは、哲学を扱ったものがあるかなぁと島を巡ったのだけど、ほとんど見つからず。
やはり哲学関係のサークルは少ない・・・
一つ見つけたサークルも、ウィトゲンシュタイン論理哲学論考を読むという同人誌を出してたんだけど、レジュメみたいな冊子で、自分の求めてるものとは違う感じ。
そういう「思想を勉強しました」っていうのじゃなくて、「哲学をしました」っていう本が読みたいんだけどなぁ。

そんな中、ICUさんのブースへ。
ICUさんは河村塔王さんのサークルで、趣向を凝らした小説などを出されている。
これが非常に面白い。

今回は、以下のものをゲット:

f:id:yamaimo0625:20160816194934p:plain

「星芒の消息」には、ビンに入った手紙が付属してきて、これが本文と関係してたりする。
そして、†は変わった構造の作りになっている本で、その構造自体が文章と非常に関係を持っている。

f:id:yamaimo0625:20160816194950p:plain

よくこういうのを思いつくなぁと脱帽w

あと、ちょっと変わったものも見つけたり。
何かというと、「語りべ少女ほのか」というアニメと、そのメイキングの冊子。

f:id:yamaimo0625:20160816195018p:plain

最初に目を取られたのは百合な冊子だったのだけど、その横にこのアニメも置いてあって、見れば、声優がなんと桑島法子さんとのこと!
このアニメは岩手県は遠野を舞台にしたもので、語りべ(見習い?)のほのかという少女が主人公になってる。
その縁なのかどうなのか、この主人公の声を桑島法子さんがやられている、と。
これはゲットせねばということで、早速ゲット。

ちなみに、動画自体はニコニコ動画にアップされてたw

普通にいい話なので、オススメ。


そんな感じで、コミックマーケット90は終了。
哲学関係の本に出会えなかったのは残念だったけど、それでもたくさん収穫があってよかったw

今日はここまで!

コミックマーケット90の2日目に一般参加してきた。

その他

コミックマーケット90の2日目に一般参加してきたので、そのレポート(?)

f:id:yamaimo0625:20160813180643p:plain

国際展示場へ!

さて、コミケというと、朝早くから待機列に並んで開場を待つもの、というイメージの人も多いだろうけど、必ずしもそんなことはない。
「大人気のサークル/企業さんの、これは絶対欲しいんだ!」というのがないのであれば、後からまったりと行くのがオススメだったりする。

例えば、今回、自分は11時半に国際展示場駅に到着したのだけど、待ち時間0分で入場できたw
いろんな時間から並び始めたことがあるけど、(ホントはアウトだけど)始発前の4時くらいからだと、確か10:05〜10:10くらい、始発で行って6時半くらいからだと、確か10:20〜30くらい、8時〜9時くらいから並び始めると、10:30〜11:00くらいで入れた気がする。
なので、時間対効果を考えるなら、11時とか11時半くらいになって待機列が落ち着いてから行った方が、断然いい。
どうせ、早く売り切れてしまうようなところは、早く行ってもシャッター組に勝てるかどうか分からないし、大手の企業だったら、1時間や2時間は誤差の内。
もちろん、あまりに遅いと、それはそれでダメだけど。

今回の導線

今回の導線でちょっと気になっていたのは、企業ブースへの行き方。

今回は、西ホール奥の屋外エリアが使えないということで、企業ブースは狭い西3, 4ホールではなく、広い西1, 2ホールを使うようになり、代わりに1, 2日目だけの出展となっていた。
そうなると、当然、西1, 2ホールへの太い導線が必要になってくる。
そこで、入口から入って西1, 2ホールへ直接向かうエスカレーターを利用するのではなく、西3, 4ホールへ向かう階段を一度登り、西3, 4ホールの周りをグルッと回って、スロープを下りていくような導線になっていたみたい。

f:id:yamaimo0625:20160813193004p:plain

ただ、行ったときにはちょうどその導線を切り替える作業をしていて(立て看板の矢印の向きを変えてた)、普通に入口から入ってエスカレーターを使って西1, 2ホールへ。
なので、自分は特に困ることはなかったんだけど、周りはどうだったのかな?

企業ブース

さて、そんなこんなで、さっそく企業ブースへ。

今回の自分のお目当ては、SHOW BY ROCK!!関係とろんぐらいだぁす!関係。
プラプラといろんなブースの展示を眺めつつ、それぞれのブースへと向かっていく。

まずは、ろんぐらいだぁす!のブースへ。
そこでは、もうイベントではお馴染みとなった、咲耶パカ店長がお出迎えw

f:id:yamaimo0625:20160813183822p:plain

可愛い!

その次は、SHOW BY ROCK!!関係ということで、サンリオ男子のブースへ。
ここではシアンがお出迎えw

f:id:yamaimo0625:20160813193206p:plain

可愛いなぁ!

そのあとは、これまたSHOW BY ROCK!!関係で、ポニーキャニオンへ。

とまぁ、自分がちゃんと見たのは、それくらい。

で、歩いてたら、いろんなコンパニオンさんがいるんだけど、すーぱーそに子のコスプレをしたコンパニオンさんがめちゃエロかった可愛かったので、これまた一枚パシャリと撮らせてもらった。

f:id:yamaimo0625:20160813193811p:plain

うーん、堪りませんなw

他、ちょっと気になったのは、これ。

f:id:yamaimo0625:20160813194257p:plain

えっ、丸井!?

なんかカードの宣伝をしてたみたい。
これにはちょっと驚き。

あと、すみぺこと上坂さんのブースが真っ赤だったり(※上坂さんはロシア大好き)、

f:id:yamaimo0625:20160813194418p:plain

真空管ドールズが企業出展してたり、

f:id:yamaimo0625:20160813194607p:plain

ばなにゃがいたりw

f:id:yamaimo0625:20160813194625p:plain

いろいろ面白いw

非電源ゲームエリア

そんな感じで企業ブースを一通り回ったら、東4ホールの非電源ゲームのエリアへ。

ちなみに、西3, 4ホールでは東方関係、東1ホールでは艦これ関係が集まってたのだけど、華麗にスルーw
昔だったら行ってただろうけど、資金も限られてるからねぇ・・・

非電源ゲームのエリアでは、いくつか知ってるサークルさんも出展してたけど、それほど数は多くなく。
島を一通り歩いてみたけど、やはりTRPGTCG、ウォーゲーム関係がコミケでは大多数を占めるようだった。
やはり冊子で展開できる方がコミケにはあってるのかもしれない。

そういう意味で、実は伝統ゲーム(もしくは、伝統ゲーム寄りの創作ゲーム)の方が、コミケには親和性が高いのかも。
なんせ、コンポーネントは市販されている一般的なものを使うので、ルールが書かれた冊子があれば頒布可能だから。
実際、今回、ドミノのルール集を出しているサークルさんがいて(だらだら部ゲーム分科会さん)、思わず買ってしまったw
(※狂道化さんの出した『ドミノたおさず。』とはまた別の冊子)

そんな中、大盛況だったのがEJIN研究所さん。
新作のサバイバルホラーボードゲーム「ハコオンナ」を引っさげての出展だったのだけど、自分が行ったときには島中ながら人混みが出来るほどの人気!
思わず、その盛況ぶりを写真に撮ってしまったw

f:id:yamaimo0625:20160813200707p:plain

いやー、「ハコオンナ」のポスターの迫力がとんでもない!
これは嫌でも気になるw

なお、自分は買ってないんだけど・・・
気にはなるけど、自分が「これ遊びましょうよ」と提案するかというと「?」なので、そういう場合、買わないようにしている・・・

まぁ、そんな感じで一通り回って、撤退。

戦利品

ということで、今回の戦利品はこちら。

f:id:yamaimo0625:20160813201400p:plain

ろんぐらいだぁす!のフェイスタオル、SHOW BY ROCK!!の冊子、CD(プラズマジカル★ミュージカル♪ by 魔法少女プラズマジカwww)、倒さない!ドミノルール集(無印、トリックテイキング編)と、数は少ないけど、厳選して購入したので、大満足!

あ、あと、コミックマーケット91のサークル参加申込書も買ってきたので、冬コミにはサークル参加する予定。

今日はここまで!

宝川温泉に輪行で行ってみた。

自転車

群馬県の水上にある、宝川温泉

とっても大きな露天風呂があるということで、すごく気になってた。

ただ、自宅から自走で行くとなると、かなり遠い。
片道160km強とか。
そいつはキツイなということで、輪行で行ってみることにした。

ルート

今回走ったのは、水上〜宝川温泉の約20km。

距離的には全然たいしたことない。
山を登るとはいえ、(ルートを見る限りでは)そんなにキツくなさそうだし、1時間くらいかな、と。

ちなみに、最初は、高崎とか渋川くらいまで電車で行き、そこから自転車を使うことを考えてた。
けど、例えば渋川〜水上を自転車で行くとなると、距離が約35kmなので、自分の脚だと2時間弱、追加で必要になる。
ところが、電車を使った場合、渋川〜水上は約40分で着いてしまうw

今回は温泉でまったりしたいと思っていたので、時間優先で水上まで電車を使うことにした。

自宅〜水上

最寄りの駅で自転車を輪行状態にするのが面倒だったので、自宅で輪行状態にして、最寄り駅までは車で。
そして、8時半頃に電車に乗って、いざ出発。

ここで早速のミスw
8:21の電車に乗っていれば、鈍行だけで11:35に水上に到着できたんだけど、その電車を逃していたので、そのまま行くと、到着が12:38に。
さすが山の方は電車の本数が少ない(^^;

これはアカンということで、急遽、特急を使うルートに変更。
大宮〜渋川は特急草津に乗ることにした。

ただ、この急行草津がクセ者で、初めて乗ったんだけど、出入り口がめっちゃ狭い
そのせいで、入り口で詰まった。。。
今後、急行や新幹線を使うときには気をつけないと。

そんなこんなで、まずは渋川に到着。
せっかくなので、記念写真w

f:id:yamaimo0625:20160731234614p:plain

そこからは上越線で水上まで。
渋川〜水上は車窓からの風景も素敵なので、もし行くことがぜひ見て欲しいな。

f:id:yamaimo0625:20160731235739p:plain
昔、上牧(かみもく)に行ったときに駅から撮った風景


f:id:yamaimo0625:20160801000310p:plain
上牧もいいところだったなぁ

水上到着

そんなこんなで、11時半頃に水上に到着。

さっそく輪行状態を解いて、いろいろ準備して、12時に水上駅を出発。

f:id:yamaimo0625:20160801000526p:plain

薄い上り下りをしつつ、大穴まで。
なんとここで、4人組のローディーが。
とくに話したりはしなかったけど、(スピードの関係で)道中一緒に行くことになったw

しばらくはほぼ平坦で、これなら何も問題ないかなぁと思ってたのも最初だけ。
栗沢の信号を過ぎたところから、ヤバい区間がw

栗沢〜藤原ダム

栗沢の信号のあとの登りがキツかった。
いや、クライマーなら何てことないんだろうけど、自分のように体重の重いおじさんライダーにはキツイw
脚は全然余裕なんだけど、心拍マックスでもうヘロヘロw
もうちょっと心肺を鍛えないとイカンよね。
(でも、あとでルートラボで見てみたら、部分的に平均斜度10%超えてた・・・)

そんな感じで、ヒーコラ言いながら藤原ダムに到着。

f:id:yamaimo0625:20160801002337p:plain

いやー、いい眺めw
自転車を脇に止めて、一休み。

しかし、ホントにキツイのは、この後だった・・・

藤原ダム〜新立岩トンネル

これは帰りに撮ったものだけど、藤原ダムの写真。

f:id:yamaimo0625:20160801002704p:plain

なんてことないような写真だけど、お分かりだろうか・・・

f:id:yamaimo0625:20160801003021p:plain

この登りである。

どれくらい信用できるか分からないのだけど、ルートラボで該当区間の斜度を見てみると、平均斜度約10%とかになってる。
おぉう・・・
距離こそ短かったので助かったけど、こいつはキツかった・・・

新立岩トンネル〜武尊(ほたか)トンネル

ただ、ホントにキツかったのは、藤原ダム〜新立岩トンネルまでと、さっきの栗沢直後の登りだけで、あとはほぼ平坦or下り。
奥多摩湖に行ったとき、トンネル内でひたすら登らされたので、トンネルがちょっと怖かったんだけど、そんなことはなかったので一安心。

新立岩トンネルを通った後は、ダム湖の横を気持ちよく走る。

平出神社のあたりからまた登り。
ルートラボで見てみると、平均斜度8%くらいあるけど、さっきまでの登りに比べれば大したことないので、余裕。
といっても、自分にはキツイので、一番軽いギヤでゆるゆると回すんだけどw

武尊トンネル〜宝川温泉

武尊トンネルを抜ければ、あとはほぼ下り。
ラーメン武尊の手前、水上藤原郵便局のところで道連れの4人組ローディーとも分かれて、宝川温泉へ。

橋を渡った後、宝川温泉まで最後の登りがあるけど、もうゴールは目前なので、そんなに気にならない。

f:id:yamaimo0625:20160801011527p:plain
めっちゃいい景色


f:id:yamaimo0625:20160801011535p:plain
水もびっくりするくらい透き通ってる

そしてとうとう、宝川温泉に到着!

f:id:yamaimo0625:20160801011641p:plain

着いたのが13時半頃だったので、大体1時間半弱かかったことになる。

宝川温泉

さて、到着したので自転車を止めなきゃということなんだけど、サイクルラックはなし。
適当なところに括り付けようと思ってたんだけど、受付の人が「こっちにどうぞ」って、建物の裏を案内してくれたので、そこに止めることにした。
ちなみに、このあと、温泉に入ってる間にちょっと小雨がふってきたんだけど、そしたら自転車の上に段ボールを掛けておいてくれたw
めちゃくちゃいい人達だったw

日帰り入浴だったので、1,500円。
それと、フェイスタオル(200円)とバスタオル(100円)をレンタル。

到着してお腹も空いてたので、さっそくご飯。
岩魚塩焼き定食(1,200円)を食べてみた。

f:id:yamaimo0625:20160801012816p:plain

いやー、これがまた絶品!
熱々に焼かれた魚が柔らかく、また、塩味もいい感じで、美味しい!

ちなみに、行くことが確定しているなら、日帰りプランもオススメ。
これだと、入浴料とレンタルタオル、それに岩魚塩焼き定食がついて、2,500円で済んでしまう。

お昼も済ませて、いよいよ温泉!

食堂を出て、曲がりくねった道を進むと、橋が見えてくる。

橋の向こうには、立派な宿が。

f:id:yamaimo0625:20160801013735p:plain

透き通った川がめちゃくちゃキレイ。

f:id:yamaimo0625:20160801013800p:plain

温泉はこの川の少し下流にあるので、橋は渡らずに、左に折れて進む。

そして、見えてきましたよ、大露天風呂!
と、それに入ってくつろいでる人々がw

そう、実は宝川温泉温泉と周りを区切るような壁とかは、ほとんどないwww
開放感100%www

というかまぁ、混浴だしね。(さらっと

脱衣所とかの様子を描いてみると、普通の温泉とは全然違うことが分かる。

f:id:yamaimo0625:20160801015433p:plain

普通、脱衣所って、入口と浴場への出口が違うわけだけど、宝川温泉の場合、同じw
入っていったところから出ていって、温泉に入るw
(脱衣所の意味、あるのかな・・・?)

ってなわけで、ぶっちゃけ丸見え。
その分、開放感は抜群で、まさに自然の中で温泉に浸かれる感じはすごくいい。
普通はどんなに景色がよくても壁で遮られちゃってるからね。

ちなみに、混浴ではあるけど、女性はタオルを巻いて入るんで、もろに見えたりはしないw
(そういうタオルを貸し出してるっぽい)
あと、普通は湯船にタオルはご法度だけど、そんな感じなので、男性も腰にタオル巻いてる場合が多い感じ。
別に巻かなくてもいいけどw

この温泉が最高なのなんのって、もう泳ぎたくなるレベルの広さなのね。
周りは自然に囲まれれて、湯船の中にもいい感じに腰掛けられる感じの岩が用意されてたり、ホント最高かよ!
上を見てみれば、モミジっぽかったので、秋に来たらまた格別なんだろうなぁと思った。

ただ、一つ注意が必要で、大自然の中だけあって、アブがすごかった(^^;
時期的なせいもあるだろうけど、夏場はちょっと気をつける必要があるかも。

さてさて、たっぷりと温泉を満喫したあとは、食堂に戻って、水を飲んだり。
この水もね、めちゃくちゃ美味しいの。
ゴクゴクと何杯でも飲めるw
あまりにいい感じだったので、許可をもらってボトルに入れさせてもらったw

帰路へ

少し小雨に降られたんだけど、すぐに止んだので、そこからは帰路へ。

帰りはラクチンなもんで、少し登るところはあったけど、ほとんど下り。
何もしなくても、勝手に進んでいくw
もちろん、雨上がりで、(一応ブレーキだけ105に替えてるけど)制動力にちょっと心配のあるTiagraなので、そんなにスピードは出さなかったけれど。

無事、水上駅に到着して、輪行状態にしたら、あとは電車に乗って、帰宅。
自転車に乗ってる時間は短かったけど、たっぷりと温泉を満喫できたので、とてもよかった。
また行きたいなw

今日はここまで!

トランプゲーム「トレセッテ」(2人用ルール)の紹介。

ゲーム

以前、iOSで遊ぶトリテのススメ。 - いものやま。でも少し紹介したのだけど、イタリアの伝統的なトランプゲームである「トレセッテ」の2人用のルールをちゃんと紹介してみたいと思う。

というのも、日本語で書かれている「トレセッテ」のルールは、4人用、もしくは、4人用のペア戦のものがほとんどだから。
この4人用のルールについては、『トランプゲーム大全』では「ゲーム会ではあまり評判がよくなかった。というのも、単に点数のあるカードをたくさん取ればいいだけだから」と、イマイチの評価。
けど、2人用のルールはめちゃくちゃ面白いと思うので、ぜひ2人用のルールを試してみてほしい。

カードの説明

トレセッテはイタリアの伝統的なトランプゲームなので、イタリアのカード構成を使う。

具体的には、使うカードは各スートの3、2、A、K、Q、J、7、6、5、4で、合計40枚。
カードの強さは、3 > 2 > A > K > Q > J > 7 > 6 > 5 > 4となっている。

カードにはそれぞれポイントがあって、このポイントを得るのがゲームの目的。

  • Aは1点
  • 3、2、K、Q、Jは1/3点(端数切り捨て)
  • 7、6、5、4は0点

一番点数の高いAが一番強いカードではない、というのが肝心なところ。
そこで、このAをいかに取ることが出来るのかという駆け引きが生まれてくる。

ディール

ディーラーはカードをよくシャッフルし、相手と自分に手札として10枚ずつカードを配る。
そして、残りの20枚は山札として裏向きのまま置いておく。

ディーラーの相手プレイヤーが最初のリードプレイヤーになる。

プレイ

リードプレイヤーから順に、カードを1枚ずつプレイする(これをトリックという):

  • リードプレイヤーは、どのカードをプレイしてもいい
  • 相手プレイヤーは、リードプレイヤーのプレイしたカードと同じスートのカードが手札にあれば、必ずそのスートのカードをプレイしなければならない。(マストフォロー)
    もしなければ、どのカードをプレイしてもいい。

カードが1枚ずつプレイされたら、強さを比べてトリックの勝者を決める:

  • 同じスートがプレイされたら、より強いカードをプレイした方がトリックの勝者
  • 違うスートがプレイされたら、リードプレイヤーがトリックの勝者

トリックの勝者は、プレイされた2枚のカードを獲得し、裏向きに伏せて自分の手元に置いておく。

もし、山札が残っている場合、トリックの勝者から順に、1枚ずつ手札の補充を行う。
山札からカードを1枚ドローして、相手プレイヤーにドローしたカードを見せてから、ドローしたカードを手札に加える。

そして、トリックの勝者が次のリードプレイヤーになり、次のトリックを行う。

これを、手札がなくなるまで繰り返す。

得点計算

手札がなくなったら、得点計算を行う。

それぞれのプレイヤーは獲得したカードのポイントを数え、そのポイント分の点数を得る。(端数切り捨て)
また、最後のトリックに勝ったプレイヤーは、追加で1点を得る。

得点は、チップなどで記録しておくといい。

もし、いずれかのプレイヤーの得点が21点以上になったら、ゲーム終了。
より得点の高いプレイヤーが、ゲームの勝者になる。

そうでなければ、ディーラーを交代し、次のディールを行う。


この2人用ルールのキモは、手札を補充するときに、ドローしたカードを相手に見せてから手札に加えるというところ。
このルール(とマストフォローのルール)があるおかげで、相手の手札のどのスートがなくなっているのかという情報が、ずっと保持されることになる。
なので、この情報をうまく使うことで(そして切り札がないということで)、「ずっと俺のターン!」を実現することが出来るというのがすごく面白い。
(逆に、「ずっと俺のターン!」をやられることもあって、そうなると「ぐぬぬ」となるので、それもまた楽しいw)
そして、マストフォローというルールがめちゃくちゃ効いてることも実感できる。

この2人用の「トレセッテ」のルールはホントによく出来ていて、少しでもルールを変えると、途端にほつれが出てくる。

具体的には、以下のとおり:

  • マストフォローをメイフォローに変えた場合
    • 相手の手札に関する情報がさっぱり引き出せなくなる
    • 相手のAを刈り取るといったことが出来なくなる
  • ドローしたカードをそのまま手札に加えるとした場合
    • ドローしたカードが分からないので、相手の手札のどのスートがなくなっているのかという情報が保持されなくなる
  • 切り札ありとした場合
    • 相手の手札の特定のスートがなくなっているという情報を使った「ずっと俺のターン!」が出来なくなる
  • 1点のカードをA以外に変えた場合
    • (ポイントがあってAより強いカード):(ポイントがあってAより弱いカード):(ポイントがないカード) = 2:3:4という絶妙なバランスが崩れる

そんな感じで、すべてのルールがうまい具合に働いているがホントに素晴らしい。

以前紹介したシュナプセンもいいゲームだけど、それよりも圧倒的にシンプルなルールで、ここまでしっかりと考えさせられるというのは、すごいと思う。

ちなみに、以前紹介した通り、この「トレセッテ」はiOSのアプリでも遊ぶことが出来る。

Tressette - Classic Card Games

Tressette - Classic Card Games

  • OutOfTheBit Ltd
  • ゲーム
  • 無料

なので、まずはアプリで試してみるのもありかもしれない。

今日はここまで!

トランプゲーム大全

トランプゲーム大全

ポーカーチップ (中) ゲーム小物

ポーカーチップ (中) ゲーム小物

Mac用とiOS用のフレームワークを作ってみた。

技術 Swift

以前、強化学習用のニューラルネットワークをSwiftで書いていた。

ここで最後に触れた問題が、これ。

NSKeyedArchiver/NSKeyedUnarchiverでオブジェクトをエンコード/デコードすると、クラス名が(モジュール名).(クラス名)となるため、ある実行ファイルで保存したファイルを他の実行ファイルでロードすると、例外が発生する。

この問題があるので、例えば、

  1. Mac用のアプリを作って学習を行う。
  2. 学習で得られたAIをエンコードしてファイルに保存する。
  3. iOS用のアプリで保存されたファイルをデコードしてAIを復元する。

としようとすると、モジュール名が異なってデコードに失敗するという問題が出てくる。

もちろん、保存/復元するときに、NSKeyedArchiver/NSKeyedUnarchiverを使わず、Dictionaryとかにデータを置き直せば、このような問題は起きない。
強化学習用のニューラルネットワークをSwiftで書いてみた。(その6) - いものやま。でちょっと言及してたのは、このこと。
ただ、上記の記事でも言及してる通り、再帰的な構造を持つので、かなり面倒・・・

これを解決するもう一つの方法は、フレームワークを作ること。
フレームワークを作ってMac用のアプリとiOS用のアプリでそれぞれ使えば、フレームワークはアプリとモジュールが異なるので、独立したモジュール名になり、Mac用のアプリでもiOS用のアプリでもクラス名が同じ(フレームワークのモジュール名).(クラス名)になってくれる。

実際にやろうとすると起こる問題

ただ、これをいざやろうとすると、思った以上にいろいろな罠が・・・

まず、同じバイナリのフレームワークMac用のアプリとiOS用のアプリの両方に使うことは出来ない。
これは考えてもみれば当然で、Macx86_64アーキテクチャ、一方iOSARMアーキテクチャなので、機械語のレベルで動作環境が異なってる。
なので、Mac用のフレームワークiOS用のフレームワークは、それぞれ別のバイナリを用意してやらないといけない。

その上で、Mac用とiOS用のフレームワーク名は同じにしないといけない。
というのも、フレームワーク名がそのままモジュール名となるので、それが変わってしまったら、やろうとしていたNSKeyedArchiver/NSKeyedUnarchiverによるエンコード/デコードが出来なくなってしまうから。

このとき壁となるのが、1つのプロジェクトで同名のターゲットを作ることが出来ないという制約。
この制約がなければ、プロジェクトにMac用のフレームワークのターゲットとiOS用のフレームワークのターゲットを同名で用意すればいいだけなんだけど、この制約があるので、このようには出来ない。

解決方法

結果、試行錯誤して得られたのが、次の方法:

  1. 入れ物となるプロジェクトを作る。
  2. 入れ物となるプロジェクトにソースコードを用意する。
  3. ビルド用のサブプロジェクトをMac用とiOS用に用意する。
  4. ビルド用のサブプロジェクトにターゲットを追加する。
  5. ビルド用のサブプロジェクトにソースコードを追加する。
  6. それぞれのサブプロジェクトでビルドを行う。

正直、かなり手間・・・
まぁ、確かにちょっと特殊なユースケースなので、仕方ないのかもしれないけど。

以下で、手順の詳細を見ていく。

入れ物となるプロジェクトを作る

まずは、入れ物となるプロジェクトを作るところから。

その前に。後々アプリを作るプロジェクトも追加するので、最初にワークスペースを用意しておくといい。
ここでは、MultiTargetというフォルダを用意して、その中にMultiTarget.xcworkspaceというワークスペースを用意した。

f:id:yamaimo0625:20160619232551p:plain

まだ中身は空っぽ。
ここに、入れ物となるプロジェクトを作っていく。

まず、左下の"+"ボタンから、"New Project..."を選択。

f:id:yamaimo0625:20160619232921p:plain

ここで、空のプロジェクトを作るために、"Other"の中にある"Empty"という雛形を選ぶ。

f:id:yamaimo0625:20160619233031p:plain

"Empty"という雛形を選んだら、プロジェクト名を入力し(ここでは安直に"Test"とした。実際には"XYZFramework"といったようにした方がいい)、プロジェクトを作成する。
このとき、MultiTargetワークスペースに追加するようにしておく。
バージョン管理を行いたい場合、"Create Git repository on ..."にチェックも忘れずに。

f:id:yamaimo0625:20160619233416p:plain

これで入れ物となるプロジェクトが出来た。

入れ物となるプロジェクトにソースコードを用意する

入れ物となるプロジェクトが出来たら、ソースコードの用意。
Mac用のフレームワークiOS用のフレームワークも、ソースコード自体は共通したものを使うので、ソースコードはこの入れ物となるプロジェクトに用意する。

"Common"というグループ(※名前は任意)を入れ物となるプロジェクトの下に作り、フォルダも作っておく。
そして、そのフォルダに共通で使われるソースコードを用意。

ここでは、以下のような簡単なコードを用意した。

f:id:yamaimo0625:20160619234038p:plain

ビルド用のサブプロジェクトをMac用とiOS用に用意する

さて、ここからが肝心。
ビルドを行うためのサブプロジェクトを、この入れ物のプロジェクトに追加していく。

まず、入れ物となるプロジェクトを選択した状態で、左下の"+"ボタンから、"New Project..."を選択。

f:id:yamaimo0625:20160619234447p:plain

そして、"Other"から"Empty"の雛形を選ぶ
Mac用のフレームワークを作るときも、iOS用のフレームワークを作るときも、"Cocoa Framework"や"Cocoa Touch Framework"を選ばない、というのがポイント。

f:id:yamaimo0625:20160619234846p:plain

なんでこんなことをするのかというと、プロジェクト名とターゲット名を別にしたいから

冒頭で述べた通り、今、ターゲット名はMac用でもiOS用でも同じにしたいという要求がある。
けど、一方で、同じプロジェクト名は使えない。
ここで、もし"Cocoa Framework"や"Cocoa Touch Framework"を選んでしまうと、プロジェクト名と同じターゲット名が自動的に作られてしまう。
そうなると、プロジェクト名を同じに出来ないということから、異なるターゲット名が自動で作られてしまうことになる。
これは都合が悪い。

もちろん、あとでターゲット名を変えたりすることも出来るのだけど、そのためにいろいろと修正が必要になるので、そういった手間が必要になるくらいなら、最初は空のプロジェクトを作って、あとからターゲットを追加した方が簡単。

なお、サブプロジェクトを作るとき、入れ物となるプロジェクト(今回の場合、"Test")のグループに追加するというのを間違えないように。

f:id:yamaimo0625:20160619235558p:plain

さて、ここではMac用のサブプロジェクトを"Test_Mac"という名前、iOS用のサブプロジェクトを"Test_iOS"という名前で、それぞれ用意した。

f:id:yamaimo0625:20160619235656p:plain

見ての通り、まだ中身は空っぽ。
ここにターゲットを追加していく。

ビルド用のサブプロジェクトにターゲットを追加する

ターゲットを追加するプロジェクトを選択した状態で、"TARGETS"の下の方にある"+"ボタンを押して、ターゲットを追加する。

f:id:yamaimo0625:20160620222953p:plain

そして、Mac用のフレームワークを作るなら、"Cocoa Framework"、iOS用のフレームワークを作るなら、"Cocoa Touch Framework"を選択。

f:id:yamaimo0625:20160620223053p:plain

f:id:yamaimo0625:20160620223224p:plain

そのあと、"Product Name"にフレームワーク名にしたい名前をに入力する。

f:id:yamaimo0625:20160620223132p:plain

このとき、Mac用のフレームワークiOS用のフレームワークも同じ名前にしておくのがポイント。

両方にターゲットを追加すると、以下のようになる。

f:id:yamaimo0625:20160620223959p:plain

まだビルドされていないので赤字になっているけど、同名のフレームワークがビルドされるようになっていることが分かると思う。

ビルド用のサブプロジェクトにソースコードを追加する

ターゲットも追加できたので、あとはサブプロジェクトにソースコードを追加するだけ。

ソースコードを追加するサブプロジェクトを選んだ状態で、コンテキストメニューから"Add Files to (サブプロジェクト)..."を選択。

f:id:yamaimo0625:20160620234255p:plain

そして、共通で使用するソースコードが入っているフォルダを選択する。

f:id:yamaimo0625:20160620234841p:plain

追加すると、こんな感じ。

f:id:yamaimo0625:20160620235301p:plain

ちなみに、同じ名前のソースコードが3つあるように見えるけど、実体は1つで、全部同じファイルになっている。
なので、1つのファイルを編集すれば、他のファイル(と言っても同じファイルなんだけど)の内容も同時に変わる。

最終的なフォルダ階層は、以下のような感じ。

f:id:yamaimo0625:20160620235527p:plain

それぞれのサブプロジェクトでビルドを行う

ここまできたら、あとはビルドするだけ。

スキームを切り替えて、"Product"メニューから"Build"を選べば、それぞれのフレームワークがビルドされる。

f:id:yamaimo0625:20160620235646p:plain

アプリの作成

せっかくなので、作ったフレームワークを使って簡単なアプリを作ってみる。

アプリを作る場合、同じワークスペースでプロジェクトを追加すると、作ったフレームワークをそのまま参照できるのでいい。
ということで、ワークスペースにプロジェクトを追加。
"Command Line Tool"の雛形を選ぶ。

f:id:yamaimo0625:20160621000246p:plain

追加するときには、入れ物となるプロジェクトに追加するのではなく、ワークスペースに追加するように注意。

f:id:yamaimo0625:20160621000427p:plain

プロジェクトを作ったら、ターゲットにリンクするフレームワークを追加する。
"TARGETS"の"Build Phases"、"Link Binary with Libraries"の"+"ボタンを押して、リンクするフレームワークを追加。

f:id:yamaimo0625:20160621000653p:plain

ダイアログの"Workspace"のところに作ったフレームワーク候補として出るので、適切なものを選択する。
Mac用とiOS用が同じ名前であるので、間違えないようにw(プロジェクト名でMac用かiOS用かは分かる)

f:id:yamaimo0625:20160621000917p:plain

あとはmain.swiftをちょろちょろっと。

f:id:yamaimo0625:20160621000941p:plain

作ったフレームワークは、import "(作ったフレームワーク名)"とすれば、使うことが出来る。

あとはビルドして実行すればOK。

・・・で終わらせたいんだけど、コマンドラインツールの場合、ちょっと問題があって、もう一手間(^^;

このまま実行すると、ビルドは成功するんだけど、"libswiftXXX.dylib"がないと怒られて、実行時エラーになるかもしれない。
その場合、フレームワークターゲットのビルド設定で、"Build Settings" - "Build Options" - "Embedded Contnt Contains Swift Code"の設定を"YES"に変えてやる必要がある。
こうして実行すれば、実行時に警告がたくさん出る問題が別に起きてくるんだけど、実行自体は出来るようになる。

※これは現状のSwiftの仕様バグだと思う。
現状、Swiftで書かれたコードを動かすには、ランタイムで"libswiftXXX.dylib"という動的ライブラリが必要になっている。
Swiftで普通のアプリを作った場合には、このライブラリがバンドルのFrameworks内に存在するので、問題なくロード出来るのだけど、コマンドラインアプリの場合には、バンドルではないので、これらのライブラリが存在しない。
じゃあ、それらのライブラリが存在しない状態で、コマンドラインアプリがどうして動くのかといえば、コマンドラインアプリの場合、おそらくこのライブラリが静的にリンクされているから。
けど、フレームワークを使おうとした場合には、動的ライブラリはファイル名で検索がかけられてロードされるので、実際にはリンクすべきシンボル自体はコマンドラインアプリのバイナリの中に存在しているのだけど、フレームワークのSwiftのコードからはこのライブラリを見つけることが出来ず、ロードに失敗するのだと思う。
なお、"Embedded Contnt Contains Swift Code"のオプションは、フレームワークObjective-Cなどで書かれたアプリから利用されたときのためのもの。
このオプションをつけると、フレームワークのバンドル内にFrameworksフォルダが用意され、そこにこれらのライブラリがコピーされるようになっている。
なんでそうするのかというと、Objective-Cなどで書かれたアプリの場合、アプリのバンドル内に"libswiftXXX.dylib"などの動的ライブラリが存在しないので、フレームワークは自前でこの動的ライブラリを用意してやらないといけないから。
そこで、このオプションをつけて動的ライブラリのコピーを自前で持っておけば、アプリ側がこれらのライブラリを持っていなくても、ちゃんと動くようになってくれる。
ということで、このような挙動になっているので、このオプションをつけると、コマンドラインアプリの場合もちゃんとロードが出来るようになる。
ただ、今度はロードされたモジュール内と、静的にリンクされたコマンドラインアプリ内に、同じシンボルが2つ存在してしまうことになる。
これが新たに出てくる警告の正体。
なので、本当は"libswiftXXX.dylib"などの動的ライブラリはシステムのロード可能なパスに置かれていて、コマンドラインアプリを作った場合も、ランタイムで動的にロードするとなっていないといけない。

今日はここまで!

ペアーズのオリジナルルール「ヤック・チック・ノック・ダウン」を考えてみた。

ゲーム

テンデイズゲームズから日本語版が発売された、ペアーズ。

ペアーズ日本語版 | テンデイズゲームズ

1が1枚、2が2枚、・・・、10が10枚というカード構成で、ギャンブルゲームを中心とした様々なルールが用意されていて、とても面白い。

そして、今回、日本語版の発売を記念して、テンデイズゲームズで「ペアーズ・ルールデザインコンテスト」が開催されることとなった。

「ペアーズ」ルールデザインコンテスト開催のお知らせ – テンデイズゲームズブログ

なので、自分もさっそくオリジナルルールを考えて応募してみた。

ブログなどで公開してもいいということだったので、公開してみる。
タイトルは、「ヤック・チック・ノック・ダウン」(Yuck, Chick, Knock, Down)

概要

ヤック・チック・ノック・ダウンは、ペアーズデッキ一組を使ったギャンブルゲーム。
各プレイヤーは、山札からカードを引いてくることで、自分の場と手札の合計を出来るだけ大きくし、ポットのチップを獲得することを目指す。
けど、自分の場にペアが出来てしまうと、脱落。
はてさて、蛮勇 (Yuck)、臆病 (Chick)、静観 (Knock)、撤退 (Down) のどれが正しいのか?

  • プレイ人数:4人〜6人
  • 必要なもの:ペアーズデッキ 一組、チップ 一人あたり50枚程度
  • プレイ時間:30分程度

ゲームの準備、進行、目的

全員が同じ枚数のチップを持ってゲームをスタート。

ゲームは複数ラウンド行う。
各ラウンドでラウンドの勝者を決め、ラウンドの勝者はポットのチップをすべて獲得する。

誰かがチップを支払えなくなったら、そのラウンドでゲームは終了。
(もしくは、遊ぶラウンド数を最初に決めておいて、そのラウンド数が終わったら終了)

ゲームが終わったとき、チップをもっとも多く持っていたプレイヤーが、ゲームの勝者となる。

各ラウンドの進行

各ラウンドの最初に、各プレイヤーは参加費として、ポットに1チップずつ支払う。

すべてのカードを裏向きにしてよく混ぜ、各プレイヤーに裏向きで1枚ずつ配り、各プレイヤーの手札とする。
続いて、各プレイヤーの前に表向きで1枚ずつ配り、各プレイヤーの場札とする。
残ったカードは裏向きのまま山札とする。

プレイヤーは自分の手札を見ることが出来るけど、他のプレイヤーには見られないように。

最初のラウンドは、場札の数字がもっとも小さいプレイヤーがスタートプレイヤーになる。
もし複数いた場合、その中から適当な方法でスタートプレイヤーを決める。
2ラウンド目以降は、直前のラウンドの勝者がスタートプレイヤーになる。

スタートプレイヤーから順に、脱落していないプレイヤーは時計回りでアクションを行っていく。
出来るアクションは、次の4つのうち、いずれか1つ:

  • ヤック
  • チック
  • ノック
  • ダウン

各アクションの詳細は後述。

脱落していないプレイヤーが1人だけになったら、そのプレイヤーは即座にラウンドの勝者となる。
もしくは、脱落していないプレイヤー全員が連続でノックを行った場合、ショーダウンを行い、ラウンドの勝者を決定する。
ショーダウンの詳細は後述。

ラウンドの勝者が決まったら、ラウンドの勝者はポットのチップをすべて獲得する。
そして、次のラウンドを行う。

アクションの詳細

ヤック

山札からカードを1枚めくり、自分の場札に加える。

このとき、もし自分の場札にペアが出来てしまったら、脱落。
ペアになったカードの数字分のチップをポットに支払う。
例えば、7のペアが出来てしまったら、ポットに7チップ支払う。
そのあと、脱落したことを示すために、自分の場札と手札をすべて裏向きにする。

チック

まず、自分の手札の枚数と同じだけのチップをポットに支払う。
そして、山札からカードを1枚引き、自分の手札に加える。

このとき、もし自分の手札でペアが出来てしまったり、自分の場札と手札とでペアが出来てしまったとしても、脱落はしない。

ノック

机をノック。
そして、手番が次のプレイヤーに移る。

一度ノックしたあとでも、再び自分の手番が回ってくれば、アクションを行うことが出来る。

ダウン

表向きになっている場札(他のプレイヤーの場札も含む)のうち、好きな1枚を自分の手元に持ってくる。
そのあと、持ってきたカードの数字分のチップをポットに支払い、脱落する。
脱落したことを示すために、持ってきたカードと自分の場札と手札をすべて裏向きにする。

ショーダウンの詳細

脱落していないプレイヤー全員が連続でノックを行った場合、ショーダウンを行う。

最後にノックしたプレイヤーの次の手番のプレイヤーから順に、以下のようにショーダウンしていく:

自分の手札をすべて表向きにし、自分の場札に加える。
このとき、もし自分の場札にペア(3枚以上も含む)が出来てしまったら、脱落。
ペアになったカードの数字分のチップをポットに支払う。
ペアになったカードが複数組あった場合、そのそれぞれについて支払う。
例えば、場札が7, 7, 8, 8, 8, 10になった場合、合計で15チップをポットに支払う。

もし、ショーダウンしたプレイヤーが次々に脱落し、脱落していないプレイヤーがショーダウンしていない1人だけになった場合、そのプレイヤーはショーダウンすることなく、ラウンドの勝者となる。

全員がショーダウンを行ったら、脱落していないプレイヤーで場札の数字の合計を比較する。
場札の数字の合計がもっとも大きいプレイヤーが、ラウンドの勝者となる。
もし、場札の数字の合計がもっとも大きいプレイヤーが複数いた場合、その中でより後にショーダウンしたプレイヤーがラウンドの勝者となる。


漏れ抜けがないようにちょっとカッチリした書き方になっているので、難しそうに感じるかもしれないけど、実際にはすごく簡単なルール。
自分の手番では、場札にカードを追加するか、手札にカードを追加するか、ノックしてパスするか、安くチップを支払って降りるかを選択するだけ。
そして、場にペアが出来てしまったら脱落で、脱落しないようにしながら、出来るだけ場札と手札の合計を大きくなるようにする。

何回かテストプレイしてみたけど、自分自身、このルールはけっこう好きw
チックでペアが出来てしまったとしても、他のプレイヤーが全員脱落してしまえば勝ちだから、うまいこと立ち回ればなんとかなる気がするのが面白い。
まぁ、それで脱落した場合、ダメージが大きいので、ダウンして降りたら、相手も実はペアが出来ていたりとかねw

ちなみに、このゲームを遊ぶにはチップが必要だけど、この前ドンキにふらっと寄って買ったチップがなかなか良かった。

ポーカーチップ (中) ゲーム小物

ポーカーチップ (中) ゲーム小物

もちろん、ちゃんとしたポーカーチップとは比べものにならないけど、ほどよい大きさがあって、枚数もそれなりにあり、そのくせ軽いので、持ち運びに便利だと思う。
2組買って中身を組換え、40枚-40枚-20枚といった構成にしておくと、かなり使い勝手がよさそう。

今日はここまで!