今回やること
- n個ずつ点灯させる
- n秒毎に1マス隣に移動する
- 端に辿り着いたら反転させる
完成品はこちら
ellie-app.com
(色々気になる部分があったのでリファクタ予定)
実装解説
いらないものを消した
update
type Msg = InvertLight Box InvertLight { point, lightMode } -> let invert box = if point == box.point then { box | lightMode = invertLightMode box.lightMode } else box in ( { model | boxList = List.map invert model.boxList } , Cmd.none )
invertLightMode : LightMode -> LightMode invertLightMode lightMode = case lightMode of LightOn -> LightOff LightOff -> LightOn
onClick (InvertLight box)
クリックイベントによる操作は不要になったので消した
点滅は全てsubscription
駆動になる
n個ずつ点灯させる
Model
type alias Model = { boxList : List Box , fieldSize : FieldSize , lightPoints : List Point }
光らせる範囲の処理のためにサイズが必要になったのでfieldSize
を追加
光っている座標を保持してy
を加算/減算して移動させたかったので
lightPoints
で光っている座標リストを持つようにした
Point
ではなくList Point
なのはn個光らせるため
Init
initialModel : () -> ( Model, Cmd Msg ) initialModel _ = let fieldSize = { rowSize = 10, columnSize = 7 } pointList = makePointMatrix fieldSize in ( { boxList = List.map makeBox pointList , fieldSize = fieldSize , lightPoints = setStartPoints fieldSize 3 , moveMode = Left } , Cmd.none )
initでfieldSize
を保持するようにして、散らばっていたサイズ定義をまとめた
スタートの個数をsetStartPoints
関数に渡し、最初の光るボックスを生成(後述)
setStartPoints
setStartPoints : FieldSize -> Int -> List Point setStartPoints fieldSize lightCount = let makeStartPoint y = { x = fieldSize.rowSize - 1, y = y } headPoint = fieldSize.columnSize - lightCount tailPoint = fieldSize.columnSize - 1 in List.range headPoint tailPoint |> List.map makeStartPoint
fieldSize
と初期で光らせる個数を引数に受ける
光る範囲の先頭と末尾をfieldSize.columnSize
とlightCount
から計算
List.range
でy
の範囲を生成しmakeStartPoint
でx
一番下の段に固定しつつ
初期の光る範囲を生成する
Update
update : Msg -> Model -> ( Model, Cmd Msg ) update msg model = case msg of Blinking _ -> let blinking box = if List.member box.point model.lightPoints then { box | lightMode = LightOn } else { box | lightMode = LightOff } in ( { model | boxList = List.map blinking model.boxList } , Cmd.none )
予め定義しておいた初期で光る範囲lightPoints
に含まれているか
で光らせるかどうかを判定しlightMode
を切り替える
n秒毎に1マス隣に移動し、端に辿り着いたら向きが反転する
Model
type MoveMode = Left | Right type alias Model = { boxList : List Box , fieldSize : FieldSize , lightPoints : List Point , moveMode : MoveMode }
y-1
していって端にたどり着きy
がマイナスになったらy
の最大値に戻る
と最初考えていたけどSTACKERゲームは端にたどり着くと反転するので
進行方向をカスタム型として定義
Init
, moveMode = Left
開始進行方向は左
進行方向の判定
getMoveMode : MoveMode -> List Point -> MoveMode getMoveMode moveMode lightPoints = let headPoint = case lightPoints of [] -> { x = 0, y = 0 } head :: rest -> head length = List.length lightPoints tailPoint = case List.drop (length - 1) lightPoints of [] -> { x = 0, y = 0 } tail :: rest -> tail in case moveMode of Left -> if tailPoint.y == 1 then invertMoveMode moveMode else moveMode Right -> if headPoint.y == 5 then invertMoveMode moveMode else moveMode
現在の進行方向と光っている範囲を引数に受ける
光っている範囲の先頭と末尾を取得
進行方向が左の場合、光っている範囲の末尾が端っこなら進行方向を反転
進行方向が右の場合、光っている範囲の先頭が端っこなら進行方向を反転
という実装にした(左が先頭で右が末尾になる)
head,tail
取得時の[] ->
のパターンマッチが不要だなーと実装中思っていた
この辺りでリファクタを考え始めた
向きの反転
invertMoveMode : MoveMode -> MoveMode invertMoveMode moveMode = case moveMode of Left -> Right Right -> Left
シンプルイズベスト
AB先生が教えてくれたinvertLight
のパクリ
光っている範囲の移動
moveLightPoints : List Point -> MoveMode -> List Point moveLightPoints lightPoints moveMode = let movePoint { x, y } = { x = x, y = getOperator moveMode y 1 } in lightPoints |> List.map movePoint getOperator : MoveMode -> number -> number -> number getOperator moveMove = case moveMove of Left -> (-) Right -> (+)
光っている範囲と進行方向を引数に受ける
進行方向が左ならy
を減算、進行方向が右ならy
を加算する必要があるので
左なら(-)
、右なら(+)
という形で+-1
する処理を共通化した
中置演算子+ -
が関数として扱える性質を上手く利用できた気がする
update
update : Msg -> Model -> ( Model, Cmd Msg ) update msg model = case msg of Blinking _ -> let blinking box = if List.member box.point model.lightPoints then { box | lightMode = LightOn } else { box | lightMode = LightOff } moveMode = getMoveMode model.moveMode model.lightPoints in ( { model | boxList = List.map blinking model.boxList , lightPoints = moveLightPoints model.lightPoints model.moveMode , moveMode = moveMode } , Cmd.none )
光っている位置に応じて進行方向を更新するためにgetMoveMode
を呼び出し更新
光っている箇所は先程定義したmoveLightPoints
で更新
これで左右に光っているマスを移動できるようになった
まとめ
::
の意味がわかって配列操作がちょっと出来るようになってきた
Listのパターンマッチに使われる中置演算子::やっとわかった
— 🐊すがわに🐊 (@nek0roll) 2020年9月16日
case [1,2,3] of
x::xs ->
の場合xが1でxsが[2,3]なのか
www.amazon.co.jp
コチラの本でバッチリ理解しました、皆さん買いましょう-+
が中置演算子である性質を上手く利用できた気がする
個人的にこれがトップクラスにありがたかった。他の言語でもほしいと本気で思った- 実装中に違和感(よくない部分)が何となく感じ取れるようになってきた気がする
fieldSize
がべた書きで多数出現- 行の
head,tail
が毎回使いづらい - 実装上ありえないはずの
[] ->
のパターンマッチ
この辺りはリファクタで解消予定
subscription
便利だし使いやすい
他の処理に移動速度が紛れ込まないのが特にいい- 実装が楽しい(n回目)
当然ロジックの誤りでバグったりするんだけどjs
や言語として苦しむことが一切ない
全て自分のミスで、かつコンパイラママが全て教えてくれる
Elm
を業務で使えたらコードフォーマットとかルールではなく、もっと本質的なロジックとか仕様について着目して指摘しやすいんだろうなぁ…と妄想した