KotlinのDouble型

はじめに

本記事ではKotlinのDouble型の仕様に関して確認したことをまとめます.

等価演算子

import kotlin.Double.Companion.NaN

fun main() {
    println(NaN == NaN) // false
    println(NaN != NaN) // true
    println(NaN as Any == NaN) // true
    println(NaN as Any != NaN) // false

    println(0.0 == -0.0) // true
    println(0.0 as Any == -0.0) // false
}

Kotlinの等価演算子は両辺が静的にDouble型であるときはIEEE 754に従います. そのため,NaN == NaNfalseとなります. また,NaN != NaNtrueとなります.

しかし,NaN as Any == NaNは左辺がAny型であるため,equalsメソッドによってtrueとなります. また,NaN as Any != NaNは左辺がAny型であるため,falseとなります.

同様に,0.0 == -0.0IEEE 754に従ってtrueとなりますが,0.0 as Any == -0.0equalsメソッドによってfalseとなります.

関係演算子

import kotlin.Double.Companion.NEGATIVE_INFINITY as nInf
import kotlin.Double.Companion.POSITIVE_INFINITY as pInf
import kotlin.Double.Companion.NaN

fun main() {
    println(NaN < NaN) // false
    println(NaN > NaN) // false
    println(NaN < pInf) // false
    println(NaN > pInf) // false
    println(NaN < nInf) // false
    println(NaN > nInf) // false

    println(NaN <= NaN) // false
    println(NaN as Comparable<Double> <= NaN) // true

    println(-0.0 < 0.0) // false
    println(-0.0 as Comparable<Double> < 0.0) // true
}

Kotlinの関係演算子は両辺が静的にDouble型であるときはIEEE 754に従います. そのため,右辺または左辺がNaNであるときは,関係演算子の結果はfalseとなります. また,-0.0 < 0.0falseとなります. しかし,NaN as Comparable<Double> <= NaNは左辺がComparable<Double>型であるため,compareToメソッドを用いて判定され,結果はtrueとなります. 同様に,-0.0 as Comparable<Double> < 0.0は左辺がComparable<Double>型であるため,compareToメソッドを用いて判定され,結果はtrueとなります.

四則演算

import kotlin.Double.Companion.NEGATIVE_INFINITY as nInf
import kotlin.Double.Companion.POSITIVE_INFINITY as pInf

fun main() {

    // plus
    println(pInf + 1.0) // Infinity
    println(nInf + 1.0) // -Infinity

    println(pInf + pInf) // Infinity
    println(pInf + nInf) // NaN
    println(nInf + nInf) // -Infinity

    // times
    println(pInf * pInf) // Infinity
    println(pInf * 2.0) // Infinity
    println(pInf * 0.0) // NaN
    println(pInf * -0.0) // NaN
    println(pInf * -2.0) // -Infinity
    println(pInf * nInf) // -Infinity

    println(nInf * pInf) // -Infinity
    println(nInf * 2.0) // -Infinity
    println(nInf * 0.0) // NaN
    println(nInf * -0.0) // NaN
    println(nInf * -2.0) // Infinity
    println(nInf * nInf) // Infinity

    // minus
    println(pInf - 1.0) // Infinity
    println(nInf - 1.0) // -Infinity

    println(pInf - pInf) // NaN
    println(pInf - nInf) // Infinity
    println(nInf - pInf) // -Infinity
    println(nInf - nInf) // NaN

    // div
    println(pInf / pInf) // NaN
    println(pInf / 2.0) // Infinity
    println(pInf / 0.0) // Infinity
    println(pInf / -0.0) // -Infinity
    println(pInf / -2.0) // -Infinity
    println(pInf / nInf) // NaN

    println(pInf / pInf) // NaN
    println(2.0 / pInf) // 0.0
    println(0.0 / pInf) // 0.0
    println(-0.0 / pInf) // -0.0
    println(-2.0 / pInf) // -0.0
    println(nInf / pInf) // NaN

    println(pInf / nInf) // NaN
    println(2.0 / nInf) // -0.0
    println(0.0 / nInf) // -0.0
    println(-0.0 / nInf) // 0.0
    println(-2.0 / nInf) // 0.0
    println(nInf / nInf) // NaN
}

KotlinのDouble型の演算でオーバーフローが発生する場合は演算の結果はInfinityとなります. アンダーフローが発生する場合は演算の結果は-Infinityとなります. また,Int型の除算と異なり,Double型の除算では割る数が0.0であっても例外が発生しません.

pow

import kotlin.math.pow
import kotlin.Double.Companion.NEGATIVE_INFINITY as nInf
import kotlin.Double.Companion.POSITIVE_INFINITY as pInf
import kotlin.Double.Companion.NaN

fun main() {
    // a^0.0 == 1.0
    println(0.0.pow(0.0)) // 1.0
    println(NaN.pow(0.0)) // 1.0
    println(pInf.pow(0.0)) // 1.0
    println(nInf.pow(0.0)) // 1.0

    // a^1.0 == a
    println(0.0.pow(1.0)) // 0.0
    println(NaN.pow(1.0)) // NaN
    println(pInf.pow(1.0)) // Infinity
    println(nInf.pow(1.0)) // -Infinity
    
    // a^Infinity
    println(pInf.pow(pInf)) // Infinity
    println(2.0.pow(pInf)) // Infinity
    println(1.0.pow(pInf)) // NaN
    println(0.5.pow(pInf)) // 0.0
    println(0.0.pow(pInf)) // 0.0
    println((-0.0).pow(pInf)) // 0.0
    println((-0.5).pow(pInf)) // 0.0
    println((-1.0).pow(pInf)) // NaN
    println((-2.0).pow(pInf)) // Infinity
    println(nInf.pow(pInf)) // Infinity

    // a^-Infinity
    println(pInf.pow(nInf)) // 0.0
    println(2.0.pow(nInf)) // 0.0
    println(1.0.pow(nInf)) // NaN
    println(0.5.pow(nInf)) // Infinity
    println(0.0.pow(nInf)) // Infinity
    println((-0.0).pow(nInf)) // Infinity
    println((-0.5).pow(nInf)) // Infinity
    println((-1.0).pow(nInf)) // NaN
    println((-2.0).pow(nInf)) // 0.0
    println(nInf.pow(nInf)) // 0.0

    // a^b (a < 0.0)
    println((-2.0).pow(2.0)) // 4.0
    println((-2.0).pow(2.5)) // NaN
    println((-2.0).pow(3.0)) // -8.0
}

NaN.pow(0.0)NaNではなく1.01.0.pow(pInf)および1.0.pow(nInf)1.0ではなくNaN(-2.0).pow(pInf)nInf.pow(pInf)および(-0.5).pow(nInf)NaNではなくInfinitypInf.pow(nInf)NaNではなく0.0となりました. これらはJavaMath.powの規定に合致するものですが私の直感に反するものだったため気をつけなければいけないと感じました.

log

import kotlin.math.log
import kotlin.Double.Companion.NEGATIVE_INFINITY as nInf
import kotlin.Double.Companion.POSITIVE_INFINITY as pInf

fun main() {
    println(log(pInf, pInf)) // NaN
    println(log(2.0, pInf)) // 0.0
    println(log(1.0, pInf)) // 0.0
    println(log(0.5, pInf)) // -0.0
    println(log(0.0, pInf)) // NaN
    println(log(-0.0, pInf)) // NaN

    println(log(pInf, 2.0)) // Infinity
    println(log(2.0, 2.0)) // 1.0
    println(log(1.0, 2.0)) // 0.0
    println(log(0.5, 2.0)) // -1.0
    println(log(0.0, 2.0)) // -Infinity
    println(log(-0.0, 2.0)) // -Infinity

    println(log(pInf, 0.5)) // -Infinity
    println(log(2.0, 0.5)) // -1.0
    println(log(1.0, 0.5)) // -0.0
    println(log(0.5, 0.5)) // 1.0
    println(log(0.0, 0.5)) // Infinity
    println(log(-0.0, 0.5)) // Infinity
}

Kotlinのlogに関して,特に気になる結果はありませんでした.

リファレンス

学年割り電卓2のリリース

学年割り電卓2(スマホ用Webアプリ)をすぐに使ってみる --> https://jumpaku.github.io/GakunenWari/

はじめに

日本には「先輩は後輩よりも偉い」という意味不明の謎の文化があるらしい. 実際,私も大学生活において次のような場面に遭遇したことがある.

  1. サークル内でお喋りをしていたとき,後輩が先輩に対して敬語を使わなかったため空気が張り詰めた.
  2. サークルで食事をして割り勘をするときに,先輩が後輩よりも多く支払った.

ここで,2. のように先輩の支払額が後輩のものよりも多くなるように割り勘することを学年割りという. 学年割りは学年が上がるに従って支払額も増加させる必要があるが,暗算だけで「丁度良い」増加量を設定するのは困難である. そこで,これまでに学年割り電卓 GakunenWari Calculator - Jumpaku’s blog[1],学年割電卓に新機能 GakunenWari Calculator updated - Jumpaku’s blog[2]が開発され,学年割りを行うスマホ用Webアプリによって,暗算に頼らない学年割りが実現された. しかし,これらのスマホ用Webアプリでは「丁度良い」増加量を見つけるために,学年間の支払金額の差をテキストフィールドに入力し,計算ボタンを押す,という作業を繰り返しながら試行錯誤する必要があり,手間がかかった. また,端数の処理していなかったため,学年割り計算後の集金にも手間がかかった.

以下では,端数処理を含めた学年割り計算法を示した上で,これを[1], [2]に組み込み,新たに「学年割り電卓2」を開発する.

端数処理を含めた学年割り計算法

学年割りは学年\(i \ (i \in I, I = \{0, \ldots, 6\})\)が上がるに従ってその学年の参加者一人当たりの支払額\(x_i\)が増加するように\(x_i\)を計算するものである. ただし,学年\(i\)は以下のように定める.

学部1年 学部2年 学部3年 学部4年 修士1年 修士2年 博士以上
\(0\) \(1\) \(2\) \(3\) \(4\) \(5\) \(6\)

ここでは\(x_i\)を単調増加する等差数列として求めることとする. また,切捨て端数の上限を\(u\)を設定し,\(x_i\)に次の制約を与えることとする.

$$ \forall i \in I, \ x_i は u で割り切れる. $$

この端数処理より,\(x_i\)が切りの良い値となり,集金の際の手間が軽減される.

合計支払金額を\(T\),学年が\(i\)である参加者の人数を\(c_i\)とし, 参加者からそれぞれ\(x_i\)だけ集金しそれを合計した合計集金額を \( T' = \sum_{i \in I} x_i c_i \) とするとき, 端数処理によって必ずしも \(T = T'\) とはならない. ここでは\(T'\)が\(T\)を超えない限り最大になるように\(x_i\)を求めて,不足額\(r = T - T'\)とともに出力するものとする.

\(x_i\)は初項\(x_0\),公差\(d\)を用いて

$$ x_i = x_0 + id $$

と表される. \(x_i\)は\(u\)の倍数となるため,\(x_0 = a u\),\(d = b u\)を満たすパラメータ\(a \in \mathrm{Z}\),\(b \in \mathrm{Z}\)が存在し,\(x_i\)は

$$ x_i = u(a + ib) $$

と表すことができる. 従って\(T'\)は\(n = \sum_{i \in I} c_i\),\(C = \sum_{i \in I} i c_i\)を用いて

$$ T' = u a n + u b C $$

と表すことができる. ここで,\(b\)は学年毎の金額差を調節するパラメータであり,ユーザはこれを変更することで丁度良い増加量を見つけることができる. また,\(a\)は初項を決定する未知のパラメータである.

\(r = T - T'\)より

$$ a = \frac{T - r - u b C}{u n} $$

と変形すると\(a\)が整数であり,\(T'\)は\(T\)を超えない限り最大となるため,

$$ r = (T - u b C) \% (u n) $$

と\(r\)が得られ,\(a\)が定まる.

以上より,

  • 合計支払金額\(T\),
  • 端数の上限\(u > 0\),
  • 学年毎の金額差を調節するパラメータ\(b \geq 0\),
  • 学年が\(i\)である参加者の人数\(c_i\)

を入力とし,

  • 学年が\(i\)である参加者の一人当たりの支払額\(x_i\),
  • 不足額\(r\)

を出力とする,端数処理を含めた学年割り計算法が実現される.

学年割り電卓2

上の端数処理を含めた学年割り計算法を用いてスマホ用Webアプリ「学年割り電卓2」を開発した. 学年割り電卓2はHTML,TypeScript,CSSを用いて開発され,現在GitHub Pages上で公開されている(https://jumpaku.github.io/GakunenWari/).

学年割り電卓2のUIを以下に示す.

f:id:Jumpaku:20181224182122j:plain

まず,合計支払金額に\(T\)の値,切捨端数に\(u\)の値,それぞれの学年の人数に\(c_i\)の値を入力し, 次に,下部のスライダーを動かして\(b\)の値を設定すると, 支払額に\(x_i\)の値,不足額に\(r\)の値が表示される. ここで,\(b\)の値を設定するスライダーの変化に応じて,リアルタイムに支払額の表示が変化するため, 丁度良い増加量をすぐに見つけることができる.

また,表示される支払額は設定した端数の上限で割り切れることが保証されているため,集金時に細かい小銭をやり取りする手間もかからない.

学年割り電卓2のソースコードGitHub - Jumpaku/GakunenWari: 学年割り電卓で公開されている.

まとめ

  • 学年が上がるに従って支払額も増加する割り勘,すなわち,学年割りが行われる機会がある.
  • 学年割りを暗算無しに行うためのスマホ用Webアプリ「学年割り電卓2(https://jumpaku.github.io/GakunenWari/)」を開発した.
  • 学年割り電卓2は端数処理を行うため,それぞれ学年の参加者の一人当たりの支払額が切りの良い値となる.
  • 学年間の金額差を設定するスライダーがあり,学年間の金額差を設定しやすい.

謝辞

本記事はMuroran Institute of Technology Advent Calendar 2018 - Adventarの記事です.

環境構築のためのDocker

はじめに

これはMuroran Institute of Technology Advent Calendar 2018 - Adventarの記事です.

  • ホストマシンのOSはWindowsmacOSを使いたい.
  • Linuxも使いたい.(GUIは必要ない.)
  • ホストマシンの環境をできるだけ変更したくない.
  • 同じ開発環境を再現できるようにしたい.
  • できれば賢い誰かが構築した環境を利用したい.

そんな時,ホストマシンにDockerをインストールしておくと,

  • CUIだけの軽量なLinux仮想環境を構築できる.
  • 環境構築の手順はテキストファイルに記述できる.
  • そのファイルから開発環境を自動的に構築できる.
  • Docker Hubにアップロードされた環境を利用できる.

なんて,素晴らしい.

インストール

それぞれのOSにおけるDockerのインストール方法を以下に示します.

Windows 10 64bit : Pro, Enterprise or Education

Install Docker Desktop on Windows | Docker Documentation

それ以外のWindows

Redirecting…

macOS

Install Docker Desktop on Mac | Docker Documentation

Ubuntu

sudo apt install docker
sudo apt install docker-compose

Docker

Dockerはイメージをもとにコンテナを生成し,コンテナ内の環境でアプリケーションを実行します. イメージはアプリケーションとその実行環境の情報をまとめたもので,Docker Hubからpullしてきたり,Dockerfileから作成したりすることで用意します. コンテナはイメージを実体化したもので,実行中のアプリケーションの状態を保持します. Docker Hubは利用可能なイメージが集まったDockerのサービスです. Dockerfileはイメージを作成する手順を記述したテキストファイルです.

Docker Hubからpullしてきたり,Dockerfileから作成したりしたイメージは,ダウンロードしてきたり,ソースコードからビルドしたりしてSSDに保存したプログラムに例えることができると思います. また,イメージをもとに実体化され,実行されるコンテナは,SSDからメモリにロードされ実行されるプログラムに例えることができると思います.

dockerのコマンド

  • docker --help : dockerのヘルプを表示する.
  • docker build -t イメージ名 ディレクトリ : 指定されたディレクトリのDockerfileから指定されたイメージ名のイメージを作成する.
  • docker run イメージ [コマンド] : 指定されたイメージを基にコンテナを起動し,コンテナ内で指定されたコマンドを実行する.
  • docker stop コンテナ : 指定された実行中のコンテナを停止する.
  • docker ps : 実行中のコンテナ一覧を表示する.
  • docker images : ホストマシンにあるイメージ一覧を表示する.
  • docker rm コンテナID : 指定されたコンテナIDを持つコンテナを削除する.
  • docker rmi イメージID : 指定されたイメージIDを持つイメージを削除する.

Dockerfile

Dockerfileはイメージを作成する手順を記述したテキストファイルで,以下のように記述します.

# 基にするイメージを指定する.Docker Hubから探してくることが多い.
FROM ベースイメージ

# 作業ディレクトリを指定する.
WORKDIR /workdir/path

# ホストマシンのファイルやディレクトリをコンテナ内に複製する.
COPY host/path/ container/path

# Shellコマンドを実行する.アプリケーション実行のための準備を行う.
RUN shell command

# コンテナ実行時に実行されるアプリケーション実行コマンドを指定する.
CMD ["アプリケーション実行コマンド", "コマンドライン引数1", "コマンドライン引数2", ...]

使用例

Hello World

次のコマンドを実行すると,

docker run hello-world

dockerはホストマシンに保存されているhello-worldイメージからコンテナを生成し,Hello Worldを出力するアプリケーションをコンテナ内で実行します. ホストマシンにhello-worldイメージがないときは,dockerはDocker Hubからhello-worldイメージをpullしてきてホストマシンに保存します.

Bash

次のコマンドを実行すると,

docker run -i -t ubuntu bash

dockerはホストマシンに保存されているubuntuイメージから(無いときはDocker Hubからpullして保存する.)コンテナを生成し,コンテナ内でBashを実行します. -i, -tオプションはホストマシンのの標準入出力とコンテナの標準入出力を繋いぐためのオプションです.

C++

次の内容のファイルを用意します.

  • ./Dockerfile
FROM ubuntu
WORKDIR /home/app
COPY ./src/main.cpp /home/app
RUN apt update -y && apt upgrade -y && apt install -y g++
RUN g++ -o main main.cpp
CMD ["./main"]
  • ./src/main.cpp
#include<iostream>

int main(int argc, char *argv[])
{
    std::cout << "hello docker-cpp" << std::endl;
}

次のコマンドを実行すると,

docker build -t docker-cpp ./

カレントディレクトリのDockerfileからdocker-cppという名前のイメージが作成されホストマシンに保存されます. docker-cppは具体的にはDockerfileに従い以下のように作成されます.

  1. ubuntuのイメージをダウンロードする.
  2. 作業ディレクトリを/home/appに設定する.
  3. apt update -y && apt upgrade -y && apt install -y g++を実行し,アプリケーションのビルドの準備をする.
  4. g++ -o main main.cppを実行し,アプリケーションをビルドする.
  5. コンテナ起動時に./mainというコマンドでアプリケーションを実行するように設定する.

イメージ作成後に次のコマンドを実行すると,

docker run docker-cpp

ホストマシンに保存されたdocker-cppという名前のイメージからコンテナが生成された後に./mainが実行されて次の実行結果が得られます.

hello docker-cpp

Docker Compose

Docker Composeはdocker-compose.ymlに記述された設定に従って,1つ以上のコンテナを連携させて起動するものです. docker-compose.ymlはコンテナ起動時の設定を記述するテキストファイルです.

docker-composeのコマンド

  • docker-compose --help : docker-composeのヘルプを表示する.
  • docker-compose build : docker-compose.ymlに従ってイメージを作成し,保存する.
  • docker-compose up [-d] : docker-compose.ymlに従ってコンテナを起動する.-dオプションを付けるとコンテナをバックグラウンドで実行する.
  • docker-compose run コンテナ名 コマンド : コンテナ名を持つコンテナを起動し,コンテナ内でアプリケーションの代わりにコマンドを実行する.
  • docker-compose exec コンテナ名 コマンド : コンテナ名を持つ起動中のコンテナ内でコマンドを実行する.
  • docker-compose stop : 起動したコンテナを停止する.

docker-compose.yml

# docker-compose.ymlのバージョンを指定する.'3'を指定する.
version: '3'

# コンテナごとの設定を記述する.
services: 
  コンテナ名:

    # コンテナの名前をコンテナ名に設定する.
    container_name: 'コンテナ名'

    # imageまたはbuildでコンテナのイメージを指定する.
    # イメージを直接指定するときはimageで指定する.
    image: 'イメージ'
    # イメージをDockerfileで指定するときはbuildで指定する.
    build:
      # Dockerfileがあるディレクトリを指定する.
      context: 'Dockerfileのディレクトリ'
      # Dockerfileのファイル名を指定する.
      dockerfile: 'Dockerfile名'

    # ホストマシンのディレクトリをコンテナにマウントする.
    volumes: 
      - 'ホストマシンのディレクトリ:コンテナのディレクトリ'

    # ホストマシンのポートを開放し,コンテナのポートに接続する.
    ports: 
      - 'ホストマシンの開放ポート:コンテナの開放ポート'

    # コンテナ起動時に実行されるコマンドを指定する.
    command: ["コマンド", "コマンドライン引数1", ... ]

使用例

Python

次のファイルを用意します.

  • ./docker-compose.yml
version: '3'

services: 
  docker-py:
    container_name: 'docker-py'
    image: 'python:3'
    working_dir: '/home/app'    
    volumes: 
      - './app:/home/app'
    command: ["python", "main.py"]
  • ./app/main.py
print("hello docker-py")

次のコマンドを実行すると,

docker-compose up

カレントディレクトリのdocker-compose.ymlの設定に従って,docker-pyという名前のコンテナが起動し,次の実行結果が得られます.

hello docker-py

docker-pyは具体的にはdocker-compose.ymlに従い以下のように起動されます.

  1. python:3のイメージをダウンロードする.
  2. 作業ディレクトリを/home/appにする.
  3. ホストマシンの./appをコンテナの/home/appにマウントする.
  4. コンテナを起動してpython main.pyを実行する.

また,次のコマンドを実行して,

docker-compose run docker-py bash

コンテナ内でpythonを実行するとPythonが対話モードで起動します.

Gnuplot

次のファイルを用意します.

  • ./Dockerfile
FROM ubuntu

WORKDIR /home/files
COPY ./files /home/files
RUN apt update -y && apt upgrade -y && apt install -y gnuplot

CMD ["gnuplot", "plot-cos.plt"]
  • ./docker-compose.yml
version: '3'

services: 
  gnuplot:
    build: 
      context: './'
      dockerfile: 'Dockerfile'
    container_name: 'gnuplot'
    volumes: 
      - './files:/home/files/'
  • ./files/plot-cos.plt
set terminal pdfcairo
set output 'plot-cos.pdf'
set xrange [-2*pi:2*pi]
set yrange [-1.5:1.5]
set samples 500
plot cos(x)
  • ./files/plot-sinc.plt
set terminal pdfcairo
set output 'plot-sinc.pdf'
set xrange [-10*pi:10*pi]
set yrange [-1:1.2]
set samples 500
plot sin(x)/x

次のコマンドを実行すると./files/plot-cos.pdfが生成されます.

docker-compose build
docker-compose up

次のコマンドを実行して,

docker-compose build
docker-compose run gnuplot bash

コンテナ内でgnuplot ./plot-sinc.pltを実行すると./files/plot-sinc.pdfが生成されます.

PHP

次のファイルを用意します.

  • ./docker-compose.yml
version: '3'

services: 
  docker-php:
    container_name: 'docker-php'
    image: 'php:7.2-apache'
    volumes: 
      - './html:/var/www/html'
    ports: 
      - '8080:80'
  • ./html/index.php
<?php
echo "hello docker-php"
?>

次のコマンドを実行して,

docker-compose up -d

ブラウザでhttp://localhost:8080にアクセスするとhello docker-phpと表示されます.

まとめ

  • DockerとDocker Composeを利用すると,ホストマシンのOSに依らずに,再現性のある軽量な仮想Linux環境を自動的に構築できます.
  • Dockerはイメージからコンテナを実体化し,アプリケーションを実行します.
  • Docker Composeはdocker-compose.ymlの設定に従ってコンテナを起動します.
  • Dockerfileにはイメージの作成手順を書き,docker-compose.ymlにはコンテナ起動時の設定を書きます.

世界と孤独の説法(エピローグ)

説法系推理アドベンチャシリーズ外伝

ゲーム情報

本ゲームは登場人物の説法を聞くことと,論理クイズを組み合わせた説法系推理アドベンチャシリーズの外伝の推理リンクノベルです.

  • タイトル : 世界と孤独の説法(エピローグ)
  • 読み : せかいとこどくのえぴろーぐ
  • 作者 : Jumpaku
  • ジャンル : 説法系推理リンクノベル
  • プレイ時間 : 30分程度
  • プラットフォーム : PDFビューア
  • リリース : 2018年4月22日
  • 言語 : 日本語
  • 開発環境 : LaTeX

経緯

第9回LOCAL学生部総大会 ヤバい同人誌執筆しようぜというイベントに参加しました.

connpass.com

これはOSC Hokkaidoや技術書展への出展を目指して,LaTeXで同人誌を書くというイベントでした. LaTeXはテキストファイルをPDFファイルに変換する組版システムです. LaTeXチューリング完全であるため,どんなアルゴリズムでも実装できます. そこで,ゲームを作りたいと思いました. ただ,正直,LaTeXでプログラムを書きたくはないし,インタラクティブなゲームを作るビジョンも浮かばないため, 実際にはリンク機能だけを利用したノベルゲームを作成しました. 一応,技術要素として

  • LaTeX によってノベルゲームを作成すること,
  • プログラミングによって効率的に論理クイズを解くこと

がコンセプトとなっています. 物語は説法系推理アドベンチャシリーズ

の外伝となっています. 個人的には,外伝のように後からストーリーが追加される形式を好まないのですが, イベント期間が睡眠,食事を含めて31時間と短いため,全く新しい物語を考えるのではなく,外伝という形式にしました.

このイベントの成果物はLOCAL学生部の「情報ボーイズの寄稿ノート」にまとめられ,技術書展4で紙媒体で頒布されます.

techbookfest.org

当然,紙媒体ではリンク機能を使えないのですが,これを考慮していなかったため,技術書展4で購入しても本ゲームをプレイできないという事態が発生しました. 本ゲームをプレイするにはPDFファイルを手に入れる必要があります. 「情報ボーイズの寄稿ノート」のソースファイルはGitHubリポジトリにあります.

github.com

ここからクローンしてきて,コメントアウトを解除してPDFにコンパイルすればプレイできますが, 手間がかかります. そこで,私の章だけをコンパイルしたPDFを用意しました.

世界と孤独の説法(エピローグ)_v1.pdf - Google ドライブ

「情報ボーイズの寄稿ノート」には私の他にもたくさんの著者がいて, それぞれが自分の得意分野の技術記事を書いています. 是非,そちらも読んでください.

リンク

室工ドライブ Drive around Muroran-IT

f:id:Jumpaku:20180226095745p:plain f:id:Jumpaku:20180226095726p:plain

"安全第一"

概要

本ゲームは室蘭工業大学(室工大)の周囲を安全にドライブするゲームです. 室工大の周囲には路上駐車,道路を横断する歩行者などの様々な障害があります. ドライバはこれらの障害を避けながら安全に室工大を一周します.

情報

  • タイトル : 室工ドライブ
  • 読み : むろこうどらいぶ
  • 作者 : Jumpaku
  • ジャンル : カーアクション
  • プレイ時間 : 1分程度
  • プラットフォーム : macOS
  • リリース : 2018年2月26日
  • 言語 : 日本語
  • 開発環境 : Unity
  • バージョン : 1.1

遊び方

  1. MurokouDrive_v1.appを実行します.
  2. 設定ウィンドウが開くので解像度,画質を設定します.
  3. Playボタンを押してゲームを起動します.
  4. STARTボタンを押してゲームを開始します.
  5. 反時計回りに道路を進みます.
  6. 運転に使用するキーは以下の通りです.
    • アクセルを踏む:上矢印
    • バックする:下矢印
    • ハンドルを右に切る:右矢印
    • ハンドルを左に切る:左矢印
  7. 道路から外れるとGame Overです.
  8. 室工大を反時計回りに一周するとClearです.

ダウンロード

実行ファイルは以下のリンクからダウンロードできます.
室工ドライブ_v1-1.zip - Google ドライブ

ソースコード

ソースコードは以下のリンクから参照できます. https://github.com/Jumpaku/MurokouDrive

開発

前からUnityに興味があり,今回初めてUnityで3Dゲームを作ってみました. 3Dモデル,当たり判定,当たり処理は既存のものを使いました. 工夫した点として,逆走を防ぐためにプッシュダウンオートマトンで状態を管理している点,地形や建造物をできるだけ実物に忠実に作成した点が挙げられます. 現在はmacOS向けのものしかリリースしてません.