kazasiki's blog

プログラミングとかVRゲームとか

Slidesファイルをpdfに変換するGoogleAppsScript

仕様書やワイヤーフレームをGoogleSlidesでよく作るor作ってもらうんだけど、地味に開くのに時間がかかるのでpdfかするgasを書きました。 destDir.createFile(file.getBlob()) だけで変換自体はできるんだけど、同名のファイルが既にあれば消したり、前回変換したときから更新されてるやつだけ対象にしたら結構なスクリプトになったので公開します。

function myFunction() {
  var srcDir = DriveApp.getFolderById("slides folder");
  var destDir = DriveApp.getFolderById("pdf folder");
  var files = srcDir.getFiles();
  while (files.hasNext()) {
    var file = files.next();
    if (file.getMimeType() != "application/vnd.google-apps.presentation") {
      continue
    }
    destName = file.getName() + ".pdf";
    var exist = false;
    var dFile;
    destFiles = destDir.getFilesByName(destName); // 毎回getFilesByNameをするの効率悪い...
    while (destFiles.hasNext()) {
      var destFile = destFiles.next()
      if (destFile.getName() == destName) {
        exist = true;
        dFile = destFile
        break;
      }
    }
    if (!exist){
      destDir.createFile(file.getBlob())
    }
    if (exist && dFile.getLastUpdated() < file.getLastUpdated()) {
      dFile.setTrashed(true)
      destDir.createFile(file.getBlob())
    }
  }
}

自分が重宝してるGolangのtesthelper

golangに限らず、テストを書いてると同じようなパターンを何回も書くことが多いです。 そこで私が重宝してるヘルパー関数を紹介します。

golangの関数を書く場合、戻り値にerrorと何かを返すパターンが多いですね。 そこで以下のヘルパー関数を用意します。

// CheckErr エラーの存在チェック
func CheckErr(t *testing.T, i int, ok bool, err error) bool {
    t.Helper()
    if ok {
        if err != nil {
            t.Errorf("[%d] err should be nil, but got:%s", i, err)
            return true
        }
        return false
    }
    if err == nil {
        t.Errorf("[%d] err should not be nil, but got:%s", i, err)
        return true
    }
    return true
}

err以外のレスポンスをチェックする場合はfalseを返します。
iはTDDのループのindexを入れます。TDDしてるとどこででたエラーが出たかわからなくなることがあるので...
okはerrが出るテストケースかどうかをboolで入れます(trueならエラーがない)
errはテスト対象の関数のerror戻り値を入れます

例えばこんな感じで使います。
errorを返す場合は、他の戻り値はどうせゼロ値なのでチェックしないでしょう。

func Something(x int) (string, error){
    if x < 0 {
        return "", errors.New("")
    } else {
        return strconv.Itoa(x), nil
    }
}

func TestSomething(t *testing.T){
    testTable := []struct{
        x int
        res string
        isErr bool
    }{
        {x: 1, res: "1", isErr: false},
        {x: -1, isErr: true},
    }
    for i, tt := range testTable {
        res, err := Something(i)
        if !CheckErr(t, i, tt.isErr, err) {
            if w, g := tt.x, res; w != g {
                t.Errof("unmatch w: %s, g: %s", w, g)
            }
        }
    }
}

さらに例えばerrorがcodeを返すように拡張(?)してる場合もあるでしょう。

// ErrCodeを返すMyErrorがあるとする
type MyError interface{
    Error() string
    ErrCode() uint32
}

type HogeError struct{}

func (e HogeError) Error() string {
    return "error"
}

func (e HogeError) ErrCode() uint32 {
    return 1001
}

その場合はCheckErrも以下のように拡張します。

// CheckErrCode 想定されるエラーコードがあればそれを確認する
func CheckErrCode(t *testing.T, i int, code uint32, err error) bool {
    t.Helper()
    noErr := code == 0
    if !noErr && err != nil {
        if terr, ok := errors.Cause(err).(MyError); ok {
            if w, g := terr.ErrCode(), code; w != g {
                t.Errorf("[%d] err_code unmached. want:%d, but got %d", i, w, g)
            }
        } else {
            t.Errorf("[%d] err type unmached. %T", i, terr)
        }
    }
    return CheckErr(t, i, noErr, err)
}

func渡したり構造体作ったりもせずシンプルな関数なので適当に拡張できて便利です。

私的 VR Game of the Year 2018

2018年ももう12月ですね。2018年は皆さんにとってどういう年でしたでしょうか。私は年中VRゲームを遊んでいる年でした。
昨年の12月くらいにOculus Riftを買ってようやく本格的に遊び始めたのが1月ごろ。今でも定期的にVRゲームのストアを漁って、買って、プレイしています。
The Game Awardの様々な発表を見て刺激を受けたので、自分も今年遊んだVRゲームのいろいろをまとめてみようと思います。(今年発売のゲームには限ってません、私的なので^-^;)

Best VR Game & Best Sports Game

Racket: NX

簡単に紹介すると、360度が壁に囲われてる状態でやるスカッシュ+的あてという感じです。一人プレイモードでは主に制限時間内に次々と出現する的のすべてにボールを当てらるかを競います。
VRゲームが通常のゲームより優れているところはたくさんありますが、このゲームでは特に360度見渡せるところと、手が自由に使えることをうまく生かしています。 最初は慣れない操作に苦労しますが、慣れてくるとちょうどテニスのボレーに近いようなフォームで遊ぶようになります。

Best Narrative

Virtual Virtual Realiy

本作のストーリーを一言でいうと、「ほぼすべての労働がAIによって自動化された社会で、あなたは人間としてAI様のために労働をしよう」というものです。
世界観や登場するキャラクター(AI達)が独特で、勝手にしゃべってるのを聞くだけで結構面白いです。VRアドベンチャーゲームとして操作性が非常に良く、日本語字幕も見やすい位置に表示され、体験として全く粗がありません。

Best Action Game & Best Shooting Game

OVERTURN

目を覚ますと謎の研究所に幽閉された主人公がそこからの脱出と報復を目指すアクションシューティングゲームです。ヒロインは同じく研究所に幽閉されていた少女で、謎の超能力(ビームとか)が使えるのでそれでサポートしてくれます。
特にステージの構成が良くできていて、最初から最後まで手ごたえのあるゲームです。また、ストーリーもよくできており、先が気になる展開や、主人公やヒロインに感情移入してしまうシーンも多いです。

Best Rythem Game

Airtone

VR音ゲーです。操作としては前方から飛んでくるノーツに合わせてコントローラを動かすだけで、音ゲーとしてはとても素直な形式です。
楽曲数が30曲とVR界隈の音ゲーにしては多めで、難易度的にも幅広いので誰でも遊べる良いゲームだと思います。ガイド役の女の子のキャラが可愛く、ストーリー要素もほんのりあります。最近PSVRでも発売しました。

Best Puzzle Game

星のかけらの物語、ひとかけら版

宇宙空間のような世界の特異点に迷い込んだ少女と協力してそこからの脱出を図る謎解きアドベンチャー。主人公はただ見ることしかできず、物を動かすのもスイッチを押すのも、ヒロインに目線で指示してやってもらいます。
このゲームは自分が本当にその空間にいるような観察能力が求められます。見るべきところが平面にまとめってはくれないですし、特定のタイミングで後ろを振りむいたり下からのぞき込むような操作に意味があることもあります。
謎解きの難易度が全体的に高めで好みが分かれるとは思いますが、謎が解けたときの達成感も素晴らしく、ぜひ頑張って最後まで自力で進んでほしい作品です。

次点

以上に紹介したゲーム以外で面白かったものをずらっとあげておきます。「こいつの趣味わりと近いな」と思ったかたは参考までにどうぞ。

www.oculus.com www.oculus.com

GoogleHomeに「今日は何のゴミの日」か教えてもらえるようにした

ついにGoogleHomeのルーティン機能が日本でも使えるようになりました。 メニュー上ではルーティン機能という見出しですが、実際にはタイマー機能もありますし、いろいろできるようになりました。 詳細は以下の記事をご覧ください。

japanese.engadget.com

そこでルーティン機能を使って、GoogleHomeに「今日は何のゴミの日」か教えてもらえるようにしました

仕組み

健全な市民である読者諸兄はおそらく毎日決まった曜日に決まった種類のゴミを出していると思います。 なので、それをGoogleHomeに聞いたら答えてくれるようにしました。 といっても、曜日を使った条件分岐なんて器用なことはルーティン機能ではできません。なので単に曜日分だけルーティンを登録します。

メニューから ルーティン を選んで、一覧画面をひらくとこんな感じ。曜日分ずらっと並んでます。

f:id:kazasiki:20180907190020p:plain:w230 f:id:kazasiki:20180907185651p:plain:w230

例として月曜日のルーティンを見せるとこんな感じ。カスタムメッセージを使って、キーワード(曜日)に対応したメッセージ(ゴミの種類)を返答するように1つ1つ設定します。いつも同じ時間に家を出てるのであれば、その少し前の時間をタイマーに設定しておくと良いでしょう。

f:id:kazasiki:20180907185314p:plain:w200 f:id:kazasiki:20180907185318p:plain:w200

仕組みとしてはこれだけです。簡単でしょう?

最初の入力は手間ですが、一度入力すれば基本的にずっと使えますし、曜日は7つしかないので手間といってもたかが知れてます。これでゴミ出しを忘れたりすることもなくなり、よりストレスフリーな生活になりました。

みなさんも快適なgoogle homeライフを。

期限が複数ある場合のバーンダウンチャートの書き方

この記事では、プロジェクトにおいて期限が近いタスク群Aと期限が遠いタスク群Bがあるときにどのようにバーンダウンチャートを運用すると良いかを書く。 あくまでも提案のようなもので、こうすると必ずうまくいくという類いのものではないので注意してほしい。

バーンダウンチャートとは?

バーンダウンチャートをご存知だろうか。驚いたことに日本語のwikipediaの記事がなかったのだが、backlogの解説記事を読むと、以下のように書いてある。

「期限までに全ての作業を消化できるのか?」ということが一目で分かるグラフです。 バーンダウンチャートでは縦軸に「作業量」、横軸に「時間」を割り当てて残りの作業量がグラフ表示されます。時間が進むと残りの作業量が減っていくので、右肩下がりのグラフになります。

私がspreadsheetで書いた雑な図がこちら。この図では幸運にも期日通りに作業が完了している。

f:id:kazasiki:20180720231451p:plain:w600

進捗が芳しくない場合は 実線が理想線よりも上にある という形でひと目で分かるようになる。作業が終わらない不幸なプロジェクトは以下のようになる。

f:id:kazasiki:20180720232238p:plain:w600

複数の期限がある場合にどうするか

さて、バーンダウンチャートの紹介はここまでにして本題に入ろう。 基本的にバーンダウンチャートはチームに対して大きなタスクが1つだけあり、それには単一の締切(もしくはマイルストーン)があるという前提で書かれるものだ。 その大きなタスクを細かいタスクに分けて、その経過を見ることで全体の進捗を推し量る手法だ。 ただ、現実にはそんなにわかりやすい状況はあまりない。チームとして複数のタスクが同時進行し、そのタスクにはそれぞれ違う締切が切られている場合も多いだろう。

最初に述べたとおり、期限が近いタスク群Aと期限が遠いタスク群Bがあるときにどのようにバーンダウンチャートを書くと良いだろう? 前提として、タスク群の優先度は考慮しない。基本的にはA群とB群のどちらも必ず終わらせる必要があるし、進捗が悪くなれば残業なり、人を増やして対応するとする。

まず、A群とB群でそれぞれバーンダウンチャートを書くというのはどうだろう?これはうまく機能しない。図を書くまでもない。 例えば、A群の締切が5日後で、残りの作業量が4日分だったとする。また、B群の締切が10日後で、残りの作業量が8日分だったとする。 どちらも単体であれば、終わりそうに見えるが、A群→B群で順序で作業した場合、総作業量は12日分であり、B群の締切には2日分間に合わない。 別々にバーンダウンチャートを書いている場合、このことには気づきにくい。

最も簡単な解決策は、A群のバーンダウンチャートの上に、B群のバーンダウンチャート積み上げることだ。要するに、期限が近い方を下に置き、遠い方を上に積み上げていくグラフにする。 例えば以下の図のようにする。この図は作業Aは間に合ってるが作業Bは間に合っていない。

f:id:kazasiki:20180721002526p:plain:w600

この図は先程の説明とほぼ同じ状態になっている。作業想定は50h/日で、A群は4日分の200hを5日で、B群は8日分の400hを10日でやろうとしている。全体として600hを10日でやろうとする。 A群をこなすだけなら速度は想定より低い40h/日でも足りる。だが、A+B群をこなそうとすると、60h/日は必要だ。(わかりすくするために50h/日の補助線を緑で引いた)

この図ではお行儀よくA群を全て終わらせてからB群に取り掛かってるが、そうでなくてもこの図は同じように機能する。 これを通常のバーンダウンチャートと同じようにチーム単位や、個人単位で見ることが出来れば、速度の遅れやキャパシティの超過は同じように追えるだろう。

察しのいい人はわかると思うが、B群単体の理想線は引いても意味がない。なぜならA群の作業をしている間、B群の作業は進捗しないからである。A群と並列で作業している間は、B実線がB理想線よりも上にくるのは当然であり、そんな図を見ても意味がない。A群とB群にどのくらい作業量を振り分けるかを計画できているなら計画線(理想線とは別に実績の計画を書く線)は引けるが、そんな芸当ができるケースは正直そんなにないと思う。

あなたが使っているバーンダウンチャート作成ツールがこういった形式の出力にうまく対応できることを祈る。

私と、競馬と、美穂という女性について

私と競馬について

私の父は競馬を愛している。私が実家を出てからは実際には見ていないが、たぶん今でも気になるレースでは馬券を買っているだろう。ギャンブルに身を滅ぼず様な賭け方ではなく、1000円程度の慎ましい賭け事を楽しんでいた(筈だ)。競馬場には行かずに、家で携帯やパソコンから馬券を注文していた。それもあって、家でスポーツ新聞の競馬の面を開いてよく唸っていたし、テレビでレースを見ては時折リビングでわーわーと騒いでいた。

f:id:kazasiki:20090307115000j:plain:w400

私が小学生くらいの頃は父がよくゲームセンターに連れていってくれて、競馬のメダルゲームを遊んでいた。父いわく「あれが一番1円あたり長く遊べる」らしい。小さいバケツのようなメダル入れ一杯分くらいのメダルを買って、私と父で半分に分けてゲームを始める。私は(幼かったのもあって)馬券の種類すらよくわからないまま遊んでいたが、なんとなく楽しかった記憶がある。おそらくミニチュアの馬たちが競馬場を模した盤面を走っているのが良かったのだろう。実際はモーターで動いていたわけだが、子供の私にはそれで十分だった。

ルールもよくわかってないからすぐに私の分のメダルが尽きてしまうのだが、そういったときは父から少しメダル分けて貰って延命を施した。それを繰り返し、二人ともメダルが尽きたら、その日はもう家に帰るという具合だった。

美穂という女性について

小学生の頃の私の友人に、美穂という名前の年上の女性がいた。姉の同級生であり、また美穂の妹も私の同級生だったので偶然接点があったのだ。子供同士が仲良くしてたのもあって、授業参観か運動会かなんかで父親同士も話す機会があったらしく、そこで彼女の父親も競馬好きであることを知った。今考えると大した偶然だと思うが、当時の私は大人の男性はみんな競馬が好きなものだと思っていたのでそんなものかと思っていた。

ある日、父が晩酌しながら「ちょっと面白い話があって」と話をしてくれた、曰く「美穂という名前は実は競走馬であるミホシンザンにちなんでつけた」と美穂の父親が言っていたらしい。競馬に詳しくない読者に伝えておくと、ミホシンザンというのは大体1985〜1987年に活躍した競走馬で、1985年の皐月賞菊花賞に優勝し、中央競馬クラシック二冠を達成している。私にもこの凄さはよくわからないがとにかく凄い馬だそうだ。確かに、美穂は1987年生まれなので時期的には一致している。私の姉も同じ年度の生まれだが、私の父はミホシンザンがそこまで好きではなかったようだ。

もともと日本人の女性に美穂という名前は多い。日本の女性の名前ランキング(生まれ年別) を参照すると、1973~1984年でも7~10位にはたまに入っている。ただ、やはりミホシンザンの効果なのか1986年は2位、続く1987年は5位、さらに翌年の1988年は3位、1989年も3位である。その年代のタレントに中山美穂氏もいるので、その影響もあるかもしれないが、どちらにしても子供につける名前とはそういうものということだろう。

私の元同僚にも1987年生まれの美穂という名前の女性がいる。流石にあなたの名前は競走馬が由来ですかとは聞いてない。

f:id:kazasiki:20150321151246j:plain:w400

私の名前にも馬編が入っている。父は自身が競馬が好きだからお前の名前に馬編の漢字を入れたと豪語していた。私はまだ独身だが、もし今女の子を授かれば鈴香(すずか)と名付けるのだろうか。

貴女の魂が未だ此処にあるうちに【cuphead】

「あ、死んだ。」

私たちはまた唐突に死を迎えた。私と彼女は休日に『Cuphead』をプレイしていた。

f:id:kazasiki:20180519202756j:plain:w400

このゲームはいわゆる2Dのアクションゲームであるが、画面が横や縦にスクロールせず、基本的に1ステージは1つの固定された画面で展開される、ボス戦のみのゲームである。その所為もあってとても難しい。

「また死んだ!」

と彼女は叫んだ。

このゲームはキャラクターが死んでから魂が天に召される(画面の上の方にフェードアウトする)までの間に、味方から魂を触ってもらうと生き返ることができる。なので、「死んだ」と宣言することは「助けて」とお願いすることと同義になる。 魂は天に向かっていくので、上の方で死ぬと助けてもらえる猶予時間が短くなる。また、味方に触ってもらう都合上、離れたところで死ぬと助けられなかったりする。そういった時は

「死んだ!助からない!」

と言う事になる。もしくは生きてる方が

「無理(助けられない)!ごめん!」

と言うことも多い。

味方を助けられなかった場合は、以降の戦いを1人で行う事になり、勝てる見込みが薄くなる。敵にはHPがあり、こちらの攻撃によって少しづつ減るので、手数が多いことはシンプルに火力につながる。また、ボスは相殺可能な攻撃をしてくることが多く、片方が相殺に専念しつつ、もう片方が攻撃に専念するなどのプレイングもある。

f:id:kazasiki:20180519141730j:plain:w400

味方の失敗の予知とその対処こそがこのゲームの本質

ずっと2人で同じボスと戦っていると、ふと気付くことがある。彼女には当たりやすい攻撃とそうでない攻撃があるということだ。ということは即ち、ボスの行動パターンを把握するのと同時に、彼女の行動パターンも把握できてくるということである。いや、この場合は死亡パターンといったほうが正しい。死亡パターンが把握できてくると、彼女の死を予知できるようになる。不思議な表現だが「死ぬ前に助けに行く」ことが可能になる。彼女の生存率が上がれば、ゲームを有利に進められる。味方の失敗の予知とその対処こそがこのゲームの本質であると私は思う。

私達は何度も死に、その度に「死んだ」と「ありがとう」を繰り返した。

このゲームを1人でやる場合、プレイヤーが行うのは回避と射撃のみである。射撃は基本的にひたすら連射してるので、もっと言えば回避だけしてれば良いゲームでもある。だが、2人でやっていると話は別である。彼女の死は、私に助けるか助けないかの選択肢を生む。助けに行くには、ボスの攻撃を避ける以外の行動をすることになり、被弾のリスクを上げる。仲良く一緒に死ぬ、いわゆる「仲よ死」となる可能性もある。その中で一瞬で助けることを選択し、巧みな操作で猛攻をかいくぐり、実際に彼女を助けられればさぞ格好よかろうというものである。実際、こういったシーンは数多くあった。私達は何度も死に、その度に「死んだ」と「ありがとう」を繰り返した。

f:id:kazasiki:20180519142606j:plain:w400

長い戦いの末、私と彼女は遂に悪魔を打ち滅ぼし、インク壺島に安息を取り戻した。
皆から英雄として讃えられ、私たちは清々しい気持ちで紅茶で一服しながら感想を言いあった。

私は愛らしい卵のキャラが描かれたカップを使い、彼女は艶かしい唇が縁に掘られたカップを使った。私達はそれぞれ違うお気に入りのカップがあるのだ。

f:id:kazasiki:20180519202032j:plain:w400