Unity 勉強メモ

ゲームエンジンのUnityを勉強するブログです。

Elmを使ってみた時のメモ (その1)

Elmとは

とりあえず使ってみた

Hello, World.

Elmはサンプルプログラムが非常に充実している。とりあえずHello, World.プログラムを作ってみた。

import Text(plainText)
main = plainText "Hello, World."

オンライン環境でコンパイルしたら、Hello, World.と表示された。

plainTextは以下の型の関数でTextモジュールに定義されている。

plainText : String -> Element

型を明記する

以下のようにプログラムの型を明示することもできる。

import Graphics.Element(Element)
import Text(plainText)

main : Element
main = plainText "Hello, World."

式の計算をする

import Graphics.Element(Element)
import Text(..)

main : Element
main = plainText <| toString <| 2 + 3 * 4

toStringBasicsモジュールの関数で任意の値を文字列に変換する。

toString : a -> String

(<|)は関数適用の演算子で、Basicsモジュールで定義されている。

(<|) : (a -> b) -> a -> b

ここら辺はF#と同じ。|><<>>も定義されている。

Basicsモジュールはデフォルトでインポートされるので明示的なインポートは不要。 デフォルトでは以下がインポートされるらしい。

import Basics (..)
import List ( List )
import Maybe ( Maybe( Just, Nothing ) )
import Result ( Result( Ok, Err ) )
import Signal ( Signal )

関数を定義する

階乗計算

まずは定番の階乗計算から。

import Graphics.Element(Element)
import Text(..)

main : Element
main = plainText <| "fact 5 = " ++ toString (fact 5)

fact : Int -> Int
fact n =
  if | n == 0    -> 1
     | otherwise -> n * fact (n - 1)

ifthenの部分に->を使う所にとても親近感がわく。良くできたシンタックスだと思う。

実行すると、

fact 5 = 120

と表示される。

階乗の表(1)

次は階乗の表を表示してみたいと思う。

import Graphics.Element(..)
import Text(..)
import List(..)

main : Element
main = flow down <| map factElement [1..10]

factElement : Int -> Element
factElement n =
  plainText <| "fact " ++ toString n ++
               " = " ++ toString (fact n)

fact : Int -> Int
fact n =
  if | n == 0    -> 1
     | otherwise -> n * fact (n - 1)

Graphics.Elementモジュールのflow関数でElementを配置できるらしいのでそれを使った。

flow : Direction -> List Element -> Element

階乗の表(2)

せっかくなので、=のところを揃えて表示させたいと思う。ついでに等幅フォントにしてみた。

import Graphics.Element(..)
import Text(..)
import List(..)

main : Element
main =
  let range = [1..10] in
  flow right <| map (flow down) <|
    map (\f -> map f range) [expr, equal, value]

expr : Int -> Element
expr n = container 60 18 midRight << leftAligned
  << monospace << fromString <| "fact " ++ toString n

equal : Int -> Element
equal n = container 30 18 middle << leftAligned
  << monospace <| fromString "="

value : Int -> Element
value n = container 60 18 midLeft << asText <| fact n

makeContainer : Int -> Position -> Element -> Element
makeContainer w pos elem =
  container w 12 pos elem

fact : Int -> Int
fact n =
  if | n == 0    -> 1
     | otherwise -> n * fact (n - 1)

以下のようにそれらしい表示が出た。

f:id:lambdataro:20150314162951p:plain

マウス座標の表示

マウスのX座標の値を表示するプログラムを作ってみた。

import Graphics.Element(..)
import Text(..)
import Signal
import Mouse

main : Signal Element
main = Signal.map asText Mouse.x

mainの型がこれまでのElementからSignal Elementに変わった。 Signalというのは要素の無限列というイメージだろうか。 例えばMouseモジュールの値xは、

x : Signal Int

という型を持っている。 これをSignalモジュールのmap関数で、Signal Elementにすると、リアクティブなプログラムができるらしい。

map : (a -> result) -> Signal a -> Signal result

なんだ、意外と簡単ではないかと思った。 要はこれを組み合わせてプログラミングしていけば良いということなのだろう。

時間を扱ってみた

とりあえず1秒に1ずつカウントするプログラムを作ってみた。

import Graphics.Element(..)
import Text(..)
import Signal
import Time(..)

main : Signal Element
main = Signal.map asText << Signal.foldp update initial
  <| every second

initial : Time
initial = 0

update : Time -> Time -> Time
update v st = st + 1

状態を扱うためには、Signalモジュールのfoldp関数を使うらしい。

ボールが移動するプログラム

左右に移動する

import Graphics.Element(..)
import Graphics.Collage(..)
import Color(..)
import Signal(..)
import Time(..)

main = draw <~ foldp update init (fps 30)

draw st =
  collage 300 200 [
    moveX st.x (filled blue (circle 10)),
    outlined (solid black) (rect 300 200)
  ]

init = { x = 0, vx = 5 }

update v st =
  let new_x = st.x + st.vx in
  { st | x <- new_x,
         vx <- if new_x > 150 || new_x < -150
                 then negate st.vx
                 else st.vx }

座標系を理解するのに少し時間がかかった。レコードを初めて使ってみた。

ウインドウ内をボールが飛び跳ねる

import Graphics.Element(..)
import Graphics.Collage(..)
import Color(..)
import Signal(..)
import Time(..)
import Window(..)

type Event = Tick | Resize Int Int

type alias State = {
  x : Float, y : Float,
  vx : Float, vy : Float,
  w : Int, h : Int
}

main : Signal Element
main = map draw <| foldp update init event

event : Signal Event
event = delay 1 (sampleOn tick resize) `merge` tick

tick : Signal Event
tick = always Tick <~ fps 30

resize : Signal Event
resize = uncurry Resize <~ dimensions

init : State
init = { x = 0, y = 0, vx = 5, vy = 5, w = 500, h = 500 }

draw : State -> Element
draw st =
  collage st.w st.h [
    move (st.x, st.y) (filled blue (circle 10)),
    outlined (solid black) (rect (toFloat st.w) (toFloat st.h))
  ]

update : Event -> State -> State
update ev st =
  case ev of
    Tick ->
      let new_x = st.x + st.vx
          new_y = st.y + st.vy in
      { st | x <- new_x, y <- new_y,
             vx <- new_v st.x st.vx st.w,
             vy <- new_v st.y st.vy st.h }
    Resize w h ->
      { st | w <- w, h <- h }

new_v : Float -> Float -> Int -> Float
new_v x vx w =
  if | x > toFloat w / 2   -> -(abs vx)
     | x < -(toFloat w / 2) -> abs vx
     | otherwise            -> vx

ウインドウの初期サイズを取得するところで非常に苦労した。 時間0のウインドウサイズの通知と同時にfpsのイベントも発生するため、 片方を遅延させる必要があった。 もっとエレガントな解法があるのかもしれないが、現時点では分からない。

今日はここまでにしよう。