[Elm] 懐かしのモン○ターファーム2のアルバイトを作ってみた
最近、スマートフォンや家庭用ゲーム機で移植版が発売されたモン○ターファーム2が発売されましたね!本編もすんごく懐かしくてハマりにハマった記憶がありますが、ポケステでプレイできたアルバイトというミニゲームも懐かしさがありますよね。ということで記念にElmで作ってみました!(移植版でもプレイできるようです。)
こちらのオンライン開発環境でプレイ・変更ができます! ※PC専用です
ミニゲームの内容は、いわゆる脳トレみたいな内容で2つの数値がランダムに表示され、数値の大きさによって矢印キーを押してその正誤によってハイスコアを目指すゲームです。以下ルールです。
上キー: 2つの数値の合計が10
左右キー: 2つの数値のうち大きい方数値
下キー: 2つの数値が同じ(合計が10になる5, 5を除く)
アプリケーションで使用される状態(Model)です。全て、整数で構成されていてシンプルで良いですね。
- スコア(score)
- 2つの数値(numbers)
- 制限時間(time)
- キーを押して得られたポイント(acquiredPoint)
type alias Numbers =
( Int, Int )
type alias Model =
{ score : Int, numbers : Numbers, time : Int, acquiredPoint : Maybe Int }
画面表示の全てです(view)。時間のメーター、2の数値、スコア等を表示しています。これらの表示にはModelが使われます。
view : Model -> Html Msg
view model =
let
( n1, n2 ) =
model.numbers
high =
Attr.attribute "high"
low =
Attr.attribute "low"
optimum =
Attr.attribute "optimum"
acquiredPointText =
Maybe.withDefault "" <|
Maybe.map
(\ap ->
(if ap > 0 then
"+"
else
""
)
++ String.fromInt ap
)
model.acquiredPoint
in
main_ []
[ meter [ Attr.min "0", Attr.max "60", low "20", high "40", optimum "0", value <| String.fromInt <| 60 - model.time ] []
, p [] [ text <| String.fromInt n1 ++ " " ++ String.fromInt n2 ]
, p [] [ text <| "score: " ++ String.fromInt model.score ++ acquiredPointText ]
]
初期値(処理)です。制限時間が60(秒)でセットされ、2の数値を乱数で初期化します。
init : () -> ( Model, Cmd Msg )
init _ =
( { score = 0, numbers = ( 0, 0 ), time = 60, acquiredPoint = Nothing }, Random.generate GotNumbers twoNumbers )
twoNumbers : Random.Generator Numbers
twoNumbers =
Random.pair (Random.int 0 9) (Random.int 0 9)
グローバルで監視されるイベントです(subscriptions)。制限時間を数えるために1秒毎にTick
メッセージを発行。矢印キーを押されたときにPressArrowKey
メッセージを発行しています。onKeyDown
イベントをフックし、"event.key"を取得し独自の型Direction
に変換してメッセージに乗せます。
subscriptions : Model -> Sub Msg
subscriptions { time } =
Sub.batch <|
if time > 0 then
[ onKeyDown (Decode.map PressArrowKey keyDecoder)
, Time.every 1000 Tick
]
else
-- Game Over
[]
type Direction
= Left
| Right
| Up
| Down
toDirection : String -> Decoder Direction
toDirection string =
case string of
"ArrowLeft" ->
Decode.succeed Left
"ArrowRight" ->
Decode.succeed Right
"ArrowUp" ->
Decode.succeed Up
"ArrowDown" ->
Decode.succeed Down
_ ->
Decode.fail "key is not arrow key."
keyDecoder : Decode.Decoder Direction
keyDecoder =
Decode.andThen toDirection (Decode.field "key" Decode.string)
メッセージ(Msg)の定義とメッセージを受信した時の処理(update)です。矢印キーが押されたら、数値と方向を渡して獲得ポイントの算出、スコアに反映、獲得ポイントのセットをしています。最後に乱数を発生させます。発生させた乱数はModelのnumbersにセットされます。1秒おきに制限時間を減らし、獲得ポイントをリセットしています。
type Msg
= PressArrowKey Direction
| GotNumbers Numbers
| Tick Time.Posix
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
PressArrowKey direction ->
let
acquiredPoint =
calcPoint model.numbers direction
in
( { model | score = model.score + acquiredPoint, acquiredPoint = Just acquiredPoint }, Random.generate GotNumbers twoNumbers )
GotNumbers numbers ->
( { model | numbers = numbers }, Cmd.none )
Tick _ ->
( { model | time = model.time - 1, acquiredPoint = Nothing }, Cmd.none )
calcPoint : Numbers -> Direction -> Int
calcPoint ( n1, n2 ) direction =
let
sum =
n1 + n2
direction2Point =
case direction of
Up ->
10
Down ->
5
Left ->
1
Right ->
1
in
if sum == 10 && direction == Up then
direction2Point
else if n1 == n2 && direction == Down then
direction2Point
else if n1 > n2 && sum /= 10 && direction == Left then
direction2Point
else if n1 < n2 && sum /= 10 && direction == Right then
direction2Point
else
negate <| direction2Point
以上がコードの解説となります。なんとなくElmのコードてこんな感じなんだなあ。ちょっと触ってみようかなあ。移植されたゲーム懐かしい or 知らないからやってみようかなあ。そんな気持ちになっていただければうれしいです!
Discussion