Elmでぷよぷよ
前提として、Javascriptも関数型言語もほとんどやったことないので、慣れてない部分が多くコードも綺麗じゃないのはご勘弁いただきたい。
また、途中まで qiita.com の丸パクリなので、最初は↑読んだ方が良いかも。
とりあえずコードは
に上げる。
盤面を定義
まずは、Elmでどうやって盤面を定義・表示するかを考えてみる。
ElmではModel(アプリの状態)、Veiw(画面表示)、Update(状態の更新)の3つの要素で構成されるらしい。 The Elm Architecture · An Introduction to Elm まずはわかりやすいようにUpdateは考えずに、「Modelを定義して、初期値入れて、Viewで表示する」ってところまでやってみたい。
Modelの定義します。後々の処理をわかりやすくするために、盤面は2次元配列的な感じで定義する。 下のような感じ。
type alias Model = { board: Board } type alias Board = Array Row type alias Row = Array Cell type Cell = Tile Int | Empty
Boardは2次元配列になっていて、各配列にはCell情報が入っている。
CellはTileとEmptyの2つの状態を持っていて、Emptyはその名の通り何もないCell、Tileは何らかのぷよがある状態。
Tile Int
とすることで、Intの部分でぷよの種類を識別するようにしている。
次にModelに初期値をセットする。
init : () -> ( Model, Cmd Msg ) init _ = let board = Array.repeat 10 <| Array.repeat 8<| Empty in ( Model board , Cmd.none )
let 〜 in
の部分で初期値となる2次元配列(board)を生成する。Array.repeat
はelm coreで用意されている関数で、指定した長さの配列を自動で生成してくれる。
Elmでは処理内容に応じて多くのmoduleが用意されているのでどんどん使っていく。
続いてView
先ほどModelで定義した2次元配列をひたすらdiv
囲って表示することで何となく盤面っぽいのを作る。
-- VIEW view : Model -> Html Msg view model = div [] [ div [] [ viewBoard model.board ] ] viewBoard : Board -> Html Msg viewBoard board = div Styles.boardStyle <| Array.toList (Array.map viewRow board) viewRow : Row -> Html Msg viewRow row = div [] <| Array.toList (Array.map viewCell row) viewCell : Cell -> Html Msg viewCell cell = case cell of Tile number -> div Styles.cellStyle [ text t ] Empty -> div Styles.cellStyle [ text "" ]
少しわかりにくいが、viewBoardでboardをRowに切り分けてviewRowへ。viewRowではRowをCellに分けてviewCellへ。 viewCellではboardに定義されたcellの状態をcaseで分類して、何を表示するか決めている。
Style.cellStyle
は外出ししたスタイルシートを読み込んでいるだけなので、今は無視でOK。
最後に(最初に書くべきだが)main文を書く
-- MAIN main = Browser.element { init = init , update = update , subscriptions = subscriptions , view = view }
もはや呪文に近いが、副作用のあるコードを書く場合はBrowser.element
というBrowserモジュールを利用する。
Browserモジュールについては
がわかりやすかった。
今の状態では副作用の無いコードだが、後々副作用を入れてくのでこれにする。
subscriptions
はまだ使わないので側だけ定義
-- SUB subscriptions model = Sub.none
ここまでのコードをまとめ
import Browser import Array exposing (Array) import Html exposing (..) import Html.Attributes exposing (..) import Styles -- Model type alias Model = { board: Board } type alias Board = Array Row type alias Row = Array Cell type Cell = Tile Int | Empty init : () -> ( Model, Cmd Msg ) init _ = let board = Array.repeat 10 <| Array.repeat 8<| Empty in ( Model board , Cmd.none ) -- Update type Msg = Demo update : Msg -> Model -> ( Model, Cmd Msg ) update msg model = case msg of Demo -> ( model , Cmd.none ) -- VIEW view : Model -> Html Msg view model = div [] [ div [] [ viewBoard model.board ] ] viewBoard : Board -> Html Msg viewBoard board = div Styles.boardStyle <| Array.toList (Array.map viewRow board) viewRow : Row -> Html Msg viewRow row = div [] <| Array.toList (Array.map viewCell row) viewCell : Cell -> Html Msg viewCell cell = case cell of Tile number -> div Styles.cellStyle [ text "foo" ] Empty -> div Styles.cellStyle [ text "" ] -- SUB subscriptions model = Sub.none -- MAIN main = Browser.element { init = init , update = update , subscriptions = subscriptions , view = view }
とりあえず盤面はこれで完成。
盤面の変更
次に盤面の変更を行う。
今は全部Emptyなので、盤面を変更する。
やることは、2次元配列に値をセットするだけ。ただし、Elmで配列を参照する際は、必ず結果がMaybeで帰ってくるので、そこだけ注意して書く必要がある。
setBoard : (Int, Int) -> Cell -> Board -> Board setBoard ( x, y ) cell board = Array.get y board |> Maybe.map (\oldRow -> Array.set x cell oldRow) |> Maybe.map (\newRow -> Array.set y newRow board) |> Maybe.withDefault board
最初のArray.get y
で縦軸のRowをgetし、Maybe.mapでMaybe Row
を受け取る。
(\oldRow -> Array.set x cell oldRow)
はコールバック関数で、バックスラッシュは無名関数の意味を持つ。ということでここではcellを新しい値に更新し、新しいRowを作成。
2こ目のMaybe.map
で更新されたRowをBoardにセットし、更新されたBoardが完成。
Maybe.withDefault
ではMaybeの値がNothingになった場合の値をセットしている。ここではNotingになったら元のboardを返すという処理になる。
Maybeについては
がわかりやすかった。
setBoard
関数をinitで使うと
board = setBoard (0,0) (Tile 1) <| Array.repeat 10 <| Array.repeat 8<| Empty
こんな感じで盤面の特定のCellを更新できる。
次はいよいよぷよぷよ的な盤面の動きを実装していく。