-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathMain.elm
232 lines (178 loc) · 7.92 KB
/
Main.elm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
module Main exposing (main)
{-| This is the main module of the application. It delegates control to a number of submodules and handles transitioning
between them. The main modules are:
- `WelcomeScreen`: used when the value of this module's model is `Welcome`. Shows the Welcome screen to the user.
- `UserGame`: used when the value of this module's model is `Playing`. Shows the actual game.
- `GameOver`: used when the value of this module's model is `GameOver`. Shows the "Game Over" message to the user for
a few seconds then moves back to the Welcome screen.
-}
import Browser
import Element exposing (Element)
import Element.Background
import GameOver
import HighScores exposing (HighScores)
import Html exposing (Html)
import Json.Encode as JE
import Settings exposing (Settings)
import UIHelpers exposing (edges)
import UserGame
import WelcomeScreen
-- MAIN
main =
Browser.element
{ init = init
, view = view
, update = update
, subscriptions = subscriptions
}
{-| The flags passed into the application from the hosting JS. Contains the `settings` (which define such things as the
key bindings, and the stored high scores).
-}
type alias Flags =
{ settings : JE.Value, highScores : JE.Value }
init : Flags -> ( Model, Cmd Msg )
init { settings, highScores } =
initAtWelcomeScreen (Settings.fromJson settings) (HighScores.fromJson highScores) Cmd.none
{-| Initialises the model at the welcome screen. Used when the site is first loaded, and at the end of a game (after the
"Game Over" animation.
-}
initAtWelcomeScreen : Settings -> HighScores -> Cmd Msg -> ( Model, Cmd Msg )
initAtWelcomeScreen settings highScores cmd =
let
( subModel, subCmd ) =
WelcomeScreen.init settings highScores
in
( Welcome { model = subModel }
, Cmd.batch [ cmd, Cmd.map GotWelcomeScreenMsg subCmd ]
)
-- MODEL
{-| The model for this app. There is some state which is persisted in local storage (namely the high scores and settings).
This is read in at the start of the app, then retained in memory thereafter for the duration of the app. In order to retain
it, we keep that data against every variant below as a separate field (and don't pass it into the models of the variants
that don't need that data). For variants whose associated model _does_ need access to that data, the variant's model
(e.g. `WelcomeScreen.Model`) stores it, and we then don't store it as a separate field.
-}
type Model
= Welcome { model : WelcomeScreen.Model } -- No game being played - showing the user some welcome/introductory info.
| Playing { model : UserGame.Model, highScores : HighScores } -- Game is currently being played
| GameOver { model : GameOver.Model, settings : Settings } -- Game has ended
type Msg
= GotWelcomeScreenMsg WelcomeScreen.Msg
| GotPlayingGameMsg UserGame.Msg
| GotGameOverMsg GameOver.Msg
-- UPDATE
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case ( model, msg ) of
( Welcome welcome, GotWelcomeScreenMsg welcomeMsg ) ->
handleWelcomeScreenMsg welcomeMsg welcome
( _, GotWelcomeScreenMsg _ ) ->
( model, Cmd.none )
( Playing playing, GotPlayingGameMsg playingMsg ) ->
handlePlayingGameMsg playingMsg playing
( _, GotPlayingGameMsg _ ) ->
( model, Cmd.none )
( GameOver gameOver, GotGameOverMsg gameOverMsg ) ->
handleGameOverMsg gameOverMsg gameOver
( _, GotGameOverMsg _ ) ->
( model, Cmd.none )
handleWelcomeScreenMsg : WelcomeScreen.Msg -> { model : WelcomeScreen.Model } -> ( Model, Cmd Msg )
handleWelcomeScreenMsg welcomeMsg welcome =
let
( welcomeModel, welcomeCmd, welcomeUpdateResult ) =
WelcomeScreen.update welcomeMsg welcome.model
in
case welcomeUpdateResult of
WelcomeScreen.Stay ->
( Welcome { welcome | model = welcomeModel }
, Cmd.map GotWelcomeScreenMsg welcomeCmd
)
WelcomeScreen.StartGame ->
WelcomeScreen.getSettings welcomeModel
|> UserGame.init
|> (\( gameModel, gameCmd ) ->
( Playing { model = gameModel, highScores = WelcomeScreen.getHighScores welcomeModel }
, Cmd.map GotPlayingGameMsg gameCmd
)
)
handlePlayingGameMsg : UserGame.Msg -> { model : UserGame.Model, highScores : HighScores } -> ( Model, Cmd Msg )
handlePlayingGameMsg gameMsg playing =
case UserGame.update gameMsg playing.model of
UserGame.Continue ( subModel, subCmd ) ->
( Playing { playing | model = subModel }
, Cmd.map GotPlayingGameMsg subCmd
)
UserGame.GameOver game ->
( GameOver { model = GameOver.init playing.highScores game, settings = UserGame.getSettings playing.model }
, Cmd.none
)
handleGameOverMsg : GameOver.Msg -> { model : GameOver.Model, settings : Settings } -> ( Model, Cmd Msg )
handleGameOverMsg gameOverMsg gameOver =
case GameOver.update gameOverMsg gameOver.model of
GameOver.Continue ( subModel, subCmd ) ->
( GameOver { gameOver | model = subModel }, Cmd.map GotGameOverMsg subCmd )
GameOver.Done ( subModel, subCmd ) ->
initAtWelcomeScreen gameOver.settings (GameOver.getHighScores subModel) <| Cmd.map GotGameOverMsg subCmd
-- VIEW
view : Model -> Html Msg
view model =
let
contents : Element Msg
contents =
case model of
Welcome welcome ->
WelcomeScreen.view welcome.model |> Element.map GotWelcomeScreenMsg
Playing playing ->
UserGame.view playing.model |> Element.map GotPlayingGameMsg |> wrapBoardView
GameOver gameOver ->
-- TODO: the below assumes there are no highlighted blocks when the game ends, but the type system doesn't
-- currently guarantee that (Game.handleDroppingShapeLanded can result in GameOver even when its state is
-- RowRemovalGameState, even though it's not currently ever called like that). Revisit maybe.
GameOver.view gameOver.model |> Element.map GotGameOverMsg |> wrapBoardView
in
Element.layoutWith
{ options =
[ Element.focusStyle
{ borderColor = Just UIHelpers.mainForegroundColour
, backgroundColor = Nothing
, shadow =
Just
{ color = Element.rgba255 198 195 195 0.6
, offset = ( 0, 0 )
, blur = 3
, size = 2
}
}
]
}
[ Element.width Element.fill
, Element.height Element.fill
, Element.Background.color UIHelpers.mainBackgroundColour
, Element.scrollbarY
, Element.inFront <|
Element.link [ Element.alignBottom, Element.alignRight, Element.padding 20 ]
{ url = "https://github.com/yonigibbs/yaet"
, label =
Element.image []
{ src = "./github.png", description = "Source code on GitHub" }
}
]
contents
wrapBoardView : Element msg -> Element msg
wrapBoardView boardView =
Element.el
[ Element.centerX
, Element.paddingEach { edges | top = 25 }
, Element.height Element.fill
, Element.width Element.fill
]
boardView
subscriptions : Model -> Sub Msg
subscriptions model =
case model of
Welcome welcome ->
WelcomeScreen.subscriptions welcome.model |> Sub.map GotWelcomeScreenMsg
Playing playing ->
UserGame.subscriptions playing.model |> Sub.map GotPlayingGameMsg
GameOver gameOver ->
GameOver.subscriptions gameOver.model |> Sub.map GotGameOverMsg