Skip to content

Instantly share code, notes, and snippets.

@galanggg
Forked from rtfeldman/GameState.elm
Created September 11, 2023 07:22
Show Gist options
  • Save galanggg/f45fe61485bbe33c648176ece6667d3d to your computer and use it in GitHub Desktop.
Save galanggg/f45fe61485bbe33c648176ece6667d3d to your computer and use it in GitHub Desktop.

Revisions

  1. @rupertlssmith rupertlssmith revised this gist Jan 12, 2018. 1 changed file with 6 additions and 0 deletions.
    6 changes: 6 additions & 0 deletions readme.md
    Original file line number Diff line number Diff line change
    @@ -14,3 +14,9 @@ certain states are valid:
    InPlay (State { definition = { boardSize = 100 }, play = { score = 0, position = [] } })
    GameOver (State { definition = { boardSize = 100 }, finalScore = 123 })
    Ready (State { definition = { boardSize = 100 } })

    The state machine exported by this module can only be manipulated in legal ways by code consuming the module. A
    constructor function is provided to create the initial state 'loading' but from there the 'toX' functions must be used
    to move through states without bypassing the rules of the state machine.

    Note: The 'State' type is fully exposed in the the-sett/elm-state-machines package here: [ https://github.com/the-sett/elm-state-machines/blob/1.0.0/src/StateMachine.elm#L27 ]. This means that it is possible to create illegal states and use the exposed Game constructors to build them. The alternative would be to not expose the 'State' type, but that would require that each state machine define its own private version of it with 'map' and 'untag' functions. In order to cheat in this way, the consumer of the state machine would need to import the StateMachine module, and use it to build illegal states.
  2. @rupertlssmith rupertlssmith revised this gist Jan 12, 2018. 2 changed files with 127 additions and 232 deletions.
    232 changes: 0 additions & 232 deletions Game.elm
    Original file line number Diff line number Diff line change
    @@ -1,232 +0,0 @@
    module Game exposing (..)

    import Html exposing (Html)
    import Task


    -- An example of it in action.


    type Msg
    = Loaded GameDefinition
    | StartGame
    | Die Int
    | AnotherGo


    type alias Model =
    { game : Game
    , previous : List Game
    , count : Int
    }


    msgToCmd msg =
    Task.perform (\() -> msg) (Task.succeed ())


    main =
    Html.program
    { init = init
    , subscriptions = \_ -> Sub.none
    , update = update
    , view = view
    }


    init : ( Model, Cmd Msg )
    init =
    ( { game = loading
    , previous = []
    , count = 5
    }
    , msgToCmd <| Loaded { boardSize = 100 }
    )


    update : Msg -> Model -> ( Model, Cmd Msg )
    update msg model =
    let
    noop =
    ( model, Cmd.none )

    ( nextGame, cmd ) =
    case ( model.game, (Debug.log "update" msg) ) of
    ( Loading loading, Loaded gameDefinition ) ->
    ( { model | game = toReadyWithGameDefinition gameDefinition loading }
    , msgToCmd StartGame
    )

    ( Ready ready, StartGame ) ->
    ( { model | game = toInPlay { score = 0, position = [] } ready }
    , msgToCmd <| Die 123
    )

    ( InPlay inPlay, Die finalScore ) ->
    ( { model | game = toGameOver <| (updatePlayState <| updateScore finalScore) inPlay }
    , msgToCmd AnotherGo
    )

    ( GameOver gameOver, AnotherGo ) ->
    ( { model | game = toReady gameOver }
    , msgToCmd StartGame
    )

    ( _, _ ) ->
    noop
    in
    if model.count > 0 then
    ( { nextGame
    | previous = model.game :: model.previous
    , count = model.count - 1
    }
    , cmd
    )
    else
    noop


    view : Model -> Html Msg
    view model =
    Html.div [] <|
    List.map (\game -> Html.p [] [ Html.text (toString game) ]) (List.reverse model.previous)



    -- Reusable state machine concepts.


    type Allowed
    = Allowed


    type State trans model
    = State model



    -- An Example model for a game of some kind.


    type alias GameDefinition =
    { boardSize : Int
    }


    type alias PlayState =
    { score : Int
    , position : List Int
    }


    updateScore : Int -> PlayState -> PlayState
    updateScore score play =
    { play | score = score }


    type Game
    = Loading Loading
    | Ready Ready
    | InPlay InPlay
    | GameOver GameOver



    -- The state definitions with enough typing information to enforce matching
    -- states against legal state transitions, and against the available data model
    -- in the state.


    type alias Loading =
    State { ready : Allowed } {}


    type alias Ready =
    State { inPlay : Allowed } { definition : GameDefinition }


    type alias InPlay =
    State { gameOver : Allowed } { definition : GameDefinition, play : PlayState }


    type alias GameOver =
    State { ready : Allowed } { definition : GameDefinition, finalScore : Int }



    -- State constructors.


    loading : Game
    loading =
    State {} |> Loading


    ready : GameDefinition -> Game
    ready definition =
    State { definition = definition } |> Ready


    inPlay : GameDefinition -> PlayState -> Game
    inPlay definition play =
    State { definition = definition, play = play } |> InPlay


    gameOver : GameDefinition -> Int -> Game
    gameOver definition score =
    State { definition = definition, finalScore = score } |> GameOver



    -- Map functions that can be applied when parts of the model are present.


    mapGameDefinition : (GameDefinition -> a) -> State p { m | definition : GameDefinition } -> a
    mapGameDefinition func (State model) =
    func model.definition



    -- ... more mapping functions
    -- Update functions that can be applied when parts of the model are present.


    updateGameDefinition :
    (GameDefinition -> GameDefinition)
    -> State p { m | definition : GameDefinition }
    -> State p { m | definition : GameDefinition }
    updateGameDefinition func (State model) =
    State { model | definition = func model.definition }


    updatePlayState :
    (PlayState -> PlayState)
    -> State p { m | play : PlayState }
    -> State p { m | play : PlayState }
    updatePlayState func (State model) =
    State { model | play = func model.play }



    -- State transition functions that can be applied only to states that are permitted
    -- to make a transition.


    toReady : State { a | ready : Allowed } { m | definition : GameDefinition } -> Game
    toReady (State model) =
    ready model.definition


    toReadyWithGameDefinition : GameDefinition -> State { a | ready : Allowed } m -> Game
    toReadyWithGameDefinition definition game =
    ready definition


    toInPlay : PlayState -> State { a | inPlay : Allowed } { m | definition : GameDefinition } -> Game
    toInPlay play (State model) =
    inPlay model.definition play


    toGameOver : State { a | gameOver : Allowed } { m | definition : GameDefinition, play : PlayState } -> Game
    toGameOver (State model) =
    gameOver model.definition model.play.score
    127 changes: 127 additions & 0 deletions GameState.elm
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,127 @@
    module GameState
    exposing
    ( Game(..)
    , GameDefinition
    , PlayState
    , loading
    , updateGameDefinition
    , updatePlayState
    , updateScore
    , toReady
    , toReadyWithGameDefinition
    , toInPlayWithPlayState
    , toGameOver
    )

    import StateMachine exposing (..)


    -- An Example model for a game of some kind.


    type alias GameDefinition =
    { boardSize : Int
    }


    type alias PlayState =
    { score : Int
    , position : List Int
    }



    -- The state definitions have enough typing information to enforce matching
    -- states against legal state transitions, and against the available data model
    -- in the state.


    type Game
    = Loading (State { ready : Allowed } {})
    | Ready (State { inPlay : Allowed } { definition : GameDefinition })
    | InPlay (State { gameOver : Allowed } { definition : GameDefinition, play : PlayState })
    | GameOver (State { ready : Allowed } { definition : GameDefinition, finalScore : Int })



    -- State constructors.


    loading : Game
    loading =
    State {} |> Loading


    ready : GameDefinition -> Game
    ready definition =
    State { definition = definition } |> Ready


    inPlay : GameDefinition -> PlayState -> Game
    inPlay definition play =
    State { definition = definition, play = play } |> InPlay


    gameOver : GameDefinition -> Int -> Game
    gameOver definition score =
    State { definition = definition, finalScore = score } |> GameOver



    -- Update functions that can be applied when parts of the model are present.


    mapDefinition : (a -> b) -> ({ m | definition : a } -> { m | definition : b })
    mapDefinition func =
    \model -> { model | definition = func model.definition }


    mapPlay : (a -> b) -> ({ m | play : a } -> { m | play : b })
    mapPlay func =
    \model -> { model | play = func model.play }


    updateGameDefinition :
    (GameDefinition -> GameDefinition)
    -> State p { m | definition : GameDefinition }
    -> State p { m | definition : GameDefinition }
    updateGameDefinition func state =
    map (mapDefinition func) state


    updatePlayState :
    (PlayState -> PlayState)
    -> State p { m | play : PlayState }
    -> State p { m | play : PlayState }
    updatePlayState func state =
    map (mapPlay func) state


    updateScore : Int -> PlayState -> PlayState
    updateScore score play =
    { play | score = score }



    -- State transition functions that can be applied only to states that are permitted
    -- to make a transition.


    toReady : State { a | ready : Allowed } { m | definition : GameDefinition } -> Game
    toReady (State model) =
    ready model.definition


    toReadyWithGameDefinition : GameDefinition -> State { a | ready : Allowed } m -> Game
    toReadyWithGameDefinition definition game =
    ready definition


    toInPlayWithPlayState : PlayState -> State { a | inPlay : Allowed } { m | definition : GameDefinition } -> Game
    toInPlayWithPlayState play (State model) =
    inPlay model.definition play


    toGameOver : State { a | gameOver : Allowed } { m | definition : GameDefinition, play : PlayState } -> Game
    toGameOver (State model) =
    gameOver model.definition model.play.score
  3. @rupertlssmith rupertlssmith revised this gist Jan 11, 2018. 1 changed file with 7 additions and 1 deletion.
    8 changes: 7 additions & 1 deletion readme.md
    Original file line number Diff line number Diff line change
    @@ -7,4 +7,10 @@ and functions need to be mapped over that data or updates to it made, rather tha
    As the example runs it prints out the states, showing how the shape of the model varies as the state machine runs. This is
    the point of using the state machine; it only makes available fields in the model that need to exist in any given state. This
    removes the need for lots of fields in the model to by 'Maybe's, or to have lots of 'Bool' flags in the model to indicate when
    certain states are valid.
    certain states are valid:

    Loading (State {})
    Ready (State { definition = { boardSize = 100 } })
    InPlay (State { definition = { boardSize = 100 }, play = { score = 0, position = [] } })
    GameOver (State { definition = { boardSize = 100 }, finalScore = 123 })
    Ready (State { definition = { boardSize = 100 } })
  4. @rupertlssmith rupertlssmith revised this gist Jan 11, 2018. 1 changed file with 6 additions and 2 deletions.
    8 changes: 6 additions & 2 deletions readme.md
    Original file line number Diff line number Diff line change
    @@ -2,5 +2,9 @@ This Gist explores the idea of using phantom types to encode the possible states
    some other state in a state machine.

    This also demonstrates how this can be used in a more real world setting where states in the machine may have addition data,
    and functions need to be mapped over that data or updates to it made whilst remaining in the current state, rather than just
    a pure state machine.
    and functions need to be mapped over that data or updates to it made, rather than just a pure state machine.

    As the example runs it prints out the states, showing how the shape of the model varies as the state machine runs. This is
    the point of using the state machine; it only makes available fields in the model that need to exist in any given state. This
    removes the need for lots of fields in the model to by 'Maybe's, or to have lots of 'Bool' flags in the model to indicate when
    certain states are valid.
  5. @rupertlssmith rupertlssmith revised this gist Jan 11, 2018. 1 changed file with 18 additions and 22 deletions.
    40 changes: 18 additions & 22 deletions Game.elm
    Original file line number Diff line number Diff line change
    @@ -36,7 +36,7 @@ main =

    init : ( Model, Cmd Msg )
    init =
    ( { game = Loading loading
    ( { game = loading
    , previous = []
    , count = 5
    }
    @@ -63,13 +63,9 @@ update msg model =
    )

    ( InPlay inPlay, Die finalScore ) ->
    let
    newPlayState =
    (updatePlayState <| updateScore finalScore) inPlay
    in
    ( { model | game = toGameOver newPlayState }
    , msgToCmd AnotherGo
    )
    ( { model | game = toGameOver <| (updatePlayState <| updateScore finalScore) inPlay }
    , msgToCmd AnotherGo
    )

    ( GameOver gameOver, AnotherGo ) ->
    ( { model | game = toReady gameOver }
    @@ -137,8 +133,8 @@ type Game


    -- The state definitions with enough typing information to enforce matching
    -- states against states that can make transitions into the state, and against
    -- the available data model in the state.
    -- states against legal state transitions, and against the available data model
    -- in the state.


    type alias Loading =
    @@ -161,24 +157,24 @@ type alias GameOver =
    -- State constructors.


    loading : Loading
    loading : Game
    loading =
    State {}
    State {} |> Loading


    ready : GameDefinition -> Ready
    ready : GameDefinition -> Game
    ready definition =
    State { definition = definition }
    State { definition = definition } |> Ready


    inPlay : GameDefinition -> PlayState -> InPlay
    inPlay : GameDefinition -> PlayState -> Game
    inPlay definition play =
    State { definition = definition, play = play }
    State { definition = definition, play = play } |> InPlay


    gameOver : GameDefinition -> Int -> GameOver
    gameOver : GameDefinition -> Int -> Game
    gameOver definition score =
    State { definition = definition, finalScore = score }
    State { definition = definition, finalScore = score } |> GameOver



    @@ -218,19 +214,19 @@ updatePlayState func (State model) =

    toReady : State { a | ready : Allowed } { m | definition : GameDefinition } -> Game
    toReady (State model) =
    ready model.definition |> Ready
    ready model.definition


    toReadyWithGameDefinition : GameDefinition -> State { a | ready : Allowed } m -> Game
    toReadyWithGameDefinition definition game =
    ready definition |> Ready
    ready definition


    toInPlay : PlayState -> State { a | inPlay : Allowed } { m | definition : GameDefinition } -> Game
    toInPlay play (State model) =
    inPlay model.definition play |> InPlay
    inPlay model.definition play


    toGameOver : State { a | gameOver : Allowed } { m | definition : GameDefinition, play : PlayState } -> Game
    toGameOver (State model) =
    gameOver model.definition model.play.score |> GameOver
    gameOver model.definition model.play.score
  6. @rupertlssmith rupertlssmith revised this gist Jan 11, 2018. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion Game.elm
    Original file line number Diff line number Diff line change
    @@ -104,7 +104,7 @@ type Allowed
    = Allowed


    type State pre model
    type State trans model
    = State model


  7. @rupertlssmith rupertlssmith revised this gist Jan 11, 2018. 1 changed file with 155 additions and 44 deletions.
    199 changes: 155 additions & 44 deletions Game.elm
    Original file line number Diff line number Diff line change
    @@ -1,9 +1,117 @@
    module Game exposing (..)

    import Html exposing (Html)
    import Task


    -- An example of it in action.


    type Msg
    = Loaded GameDefinition
    | StartGame
    | Die Int
    | AnotherGo


    type alias Model =
    { game : Game
    , previous : List Game
    , count : Int
    }


    msgToCmd msg =
    Task.perform (\() -> msg) (Task.succeed ())


    main =
    Html.program
    { init = init
    , subscriptions = \_ -> Sub.none
    , update = update
    , view = view
    }


    init : ( Model, Cmd Msg )
    init =
    ( { game = Loading loading
    , previous = []
    , count = 5
    }
    , msgToCmd <| Loaded { boardSize = 100 }
    )


    update : Msg -> Model -> ( Model, Cmd Msg )
    update msg model =
    let
    noop =
    ( model, Cmd.none )

    ( nextGame, cmd ) =
    case ( model.game, (Debug.log "update" msg) ) of
    ( Loading loading, Loaded gameDefinition ) ->
    ( { model | game = toReadyWithGameDefinition gameDefinition loading }
    , msgToCmd StartGame
    )

    ( Ready ready, StartGame ) ->
    ( { model | game = toInPlay { score = 0, position = [] } ready }
    , msgToCmd <| Die 123
    )

    ( InPlay inPlay, Die finalScore ) ->
    let
    newPlayState =
    (updatePlayState <| updateScore finalScore) inPlay
    in
    ( { model | game = toGameOver newPlayState }
    , msgToCmd AnotherGo
    )

    ( GameOver gameOver, AnotherGo ) ->
    ( { model | game = toReady gameOver }
    , msgToCmd StartGame
    )

    ( _, _ ) ->
    noop
    in
    if model.count > 0 then
    ( { nextGame
    | previous = model.game :: model.previous
    , count = model.count - 1
    }
    , cmd
    )
    else
    noop


    view : Model -> Html Msg
    view model =
    Html.div [] <|
    List.map (\game -> Html.p [] [ Html.text (toString game) ]) (List.reverse model.previous)



    -- Reusable state machine concepts.


    type Allowed
    = Allowed


    type State pre model
    = State model



    -- An Example model for a game of some kind.


    type alias GameDefinition =
    { boardSize : Int
    }
    @@ -15,31 +123,38 @@ type alias PlayState =
    }


    type State
    = Loading
    | Ready
    | InPlay
    | GameOver
    updateScore : Int -> PlayState -> PlayState
    updateScore score play =
    { play | score = score }


    type Game
    = Loading Loading
    | Ready Ready
    | InPlay InPlay
    | GameOver GameOver

    type Game pre model
    = Game model


    -- The state definitions with enough typing information to enforce matching
    -- states against states that can make transitions into the state, and against
    -- the available data model in the state.


    type alias Loading =
    Game {} { state : State }
    State { ready : Allowed } {}


    type alias Ready =
    Game { loading : Allowed, gameOver : Allowed } { state : State, definition : GameDefinition }
    State { inPlay : Allowed } { definition : GameDefinition }


    type alias InPlay =
    Game { ready : Allowed } { state : State, definition : GameDefinition, play : PlayState }
    State { gameOver : Allowed } { definition : GameDefinition, play : PlayState }


    type alias GameOver =
    Game { inPlay : Allowed } { state : State, definition : GameDefinition, finalScore : Int }
    State { ready : Allowed } { definition : GameDefinition, finalScore : Int }



    @@ -48,41 +163,30 @@ type alias GameOver =

    loading : Loading
    loading =
    Game { state = Loading }
    State {}


    ready : GameDefinition -> Ready
    ready definition =
    Game
    { state = Ready
    , definition = definition
    }
    State { definition = definition }


    inPlay : GameDefinition -> PlayState -> InPlay
    inPlay definition play =
    Game
    { state = InPlay
    , definition = definition
    , play = play
    }
    State { definition = definition, play = play }


    gameOver : GameDefinition -> Int -> GameOver
    gameOver definition score =
    Game
    { state = GameOver
    , definition = definition
    , finalScore = score
    }
    State { definition = definition, finalScore = score }



    -- Map functions that can be applied when parts of the model are present.


    mapGameDefinition : (GameDefinition -> a) -> Game p { m | definition : GameDefinition } -> a
    mapGameDefinition func (Game model) =
    mapGameDefinition : (GameDefinition -> a) -> State p { m | definition : GameDefinition } -> a
    mapGameDefinition func (State model) =
    func model.definition


    @@ -93,33 +197,40 @@ mapGameDefinition func (Game model) =

    updateGameDefinition :
    (GameDefinition -> GameDefinition)
    -> Game p { m | definition : GameDefinition }
    -> Game p { m | definition : GameDefinition }
    updateGameDefinition func (Game model) =
    Game { model | definition = func model.definition }
    -> State p { m | definition : GameDefinition }
    -> State p { m | definition : GameDefinition }
    updateGameDefinition func (State model) =
    State { model | definition = func model.definition }


    updatePlayState :
    (PlayState -> PlayState)
    -> State p { m | play : PlayState }
    -> State p { m | play : PlayState }
    updatePlayState func (State model) =
    State { model | play = func model.play }



    -- ... more update functions
    -- State transition functions that can be applied only to states that are permitted
    -- to make a transition.


    toReady : Game { a | ready : Allowed } { m | definition : GameDefinition } -> Ready
    toReady (Game model) =
    ready model.definition
    toReady : State { a | ready : Allowed } { m | definition : GameDefinition } -> Game
    toReady (State model) =
    ready model.definition |> Ready


    toReadyWithGameDefinition : GameDefinition -> Game { a | ready : Allowed } m -> Ready
    toReadyWithGameDefinition : GameDefinition -> State { a | ready : Allowed } m -> Game
    toReadyWithGameDefinition definition game =
    ready definition
    ready definition |> Ready


    toInPlay : PlayState -> Game { a | inPlay : Allowed } { m | definition : GameDefinition } -> InPlay
    toInPlay play (Game model) =
    inPlay model.definition play
    toInPlay : PlayState -> State { a | inPlay : Allowed } { m | definition : GameDefinition } -> Game
    toInPlay play (State model) =
    inPlay model.definition play |> InPlay


    toGameOver : Game { a | gameOver : Allowed } { m | definition : GameDefinition, play : PlayState } -> GameOver
    toGameOver (Game model) =
    gameOver model.definition model.play.score
    toGameOver : State { a | gameOver : Allowed } { m | definition : GameDefinition, play : PlayState } -> Game
    toGameOver (State model) =
    gameOver model.definition model.play.score |> GameOver
  8. @rupertlssmith rupertlssmith revised this gist Jan 11, 2018. 1 changed file with 1 addition and 0 deletions.
    1 change: 1 addition & 0 deletions readme.md
    Original file line number Diff line number Diff line change
    @@ -1,5 +1,6 @@
    This Gist explores the idea of using phantom types to encode the possible states that are allowed to make transitions into
    some other state in a state machine.

    This also demonstrates how this can be used in a more real world setting where states in the machine may have addition data,
    and functions need to be mapped over that data or updates to it made whilst remaining in the current state, rather than just
    a pure state machine.
  9. @rupertlssmith rupertlssmith revised this gist Jan 11, 2018. 1 changed file with 0 additions and 0 deletions.
    Empty file removed intro.md
    Empty file.
  10. @rupertlssmith rupertlssmith revised this gist Jan 11, 2018. 2 changed files with 5 additions and 5 deletions.
    5 changes: 0 additions & 5 deletions intro.md
    Original file line number Diff line number Diff line change
    @@ -1,5 +0,0 @@
    This Gist explores the idea of using phantom types to encode the possible states that are allowed to make transitions into
    some other state in a state machine.
    This also demonstrates how this can be used in a more real world setting where states in the machine may have addition data,
    and functions need to be mapped over that data or updates to it made whilst remaining in the current state, rather than just
    a pure state machine.
    5 changes: 5 additions & 0 deletions readme.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,5 @@
    This Gist explores the idea of using phantom types to encode the possible states that are allowed to make transitions into
    some other state in a state machine.
    This also demonstrates how this can be used in a more real world setting where states in the machine may have addition data,
    and functions need to be mapped over that data or updates to it made whilst remaining in the current state, rather than just
    a pure state machine.
  11. @rupertlssmith rupertlssmith revised this gist Jan 11, 2018. 1 changed file with 125 additions and 0 deletions.
    125 changes: 125 additions & 0 deletions Game.elm
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,125 @@
    module Game exposing (..)

    type Allowed
    = Allowed


    type alias GameDefinition =
    { boardSize : Int
    }


    type alias PlayState =
    { score : Int
    , position : List Int
    }


    type State
    = Loading
    | Ready
    | InPlay
    | GameOver


    type Game pre model
    = Game model


    type alias Loading =
    Game {} { state : State }


    type alias Ready =
    Game { loading : Allowed, gameOver : Allowed } { state : State, definition : GameDefinition }


    type alias InPlay =
    Game { ready : Allowed } { state : State, definition : GameDefinition, play : PlayState }


    type alias GameOver =
    Game { inPlay : Allowed } { state : State, definition : GameDefinition, finalScore : Int }



    -- State constructors.


    loading : Loading
    loading =
    Game { state = Loading }


    ready : GameDefinition -> Ready
    ready definition =
    Game
    { state = Ready
    , definition = definition
    }


    inPlay : GameDefinition -> PlayState -> InPlay
    inPlay definition play =
    Game
    { state = InPlay
    , definition = definition
    , play = play
    }


    gameOver : GameDefinition -> Int -> GameOver
    gameOver definition score =
    Game
    { state = GameOver
    , definition = definition
    , finalScore = score
    }



    -- Map functions that can be applied when parts of the model are present.


    mapGameDefinition : (GameDefinition -> a) -> Game p { m | definition : GameDefinition } -> a
    mapGameDefinition func (Game model) =
    func model.definition



    -- ... more mapping functions
    -- Update functions that can be applied when parts of the model are present.


    updateGameDefinition :
    (GameDefinition -> GameDefinition)
    -> Game p { m | definition : GameDefinition }
    -> Game p { m | definition : GameDefinition }
    updateGameDefinition func (Game model) =
    Game { model | definition = func model.definition }



    -- ... more update functions
    -- State transition functions that can be applied only to states that are permitted
    -- to make a transition.


    toReady : Game { a | ready : Allowed } { m | definition : GameDefinition } -> Ready
    toReady (Game model) =
    ready model.definition


    toReadyWithGameDefinition : GameDefinition -> Game { a | ready : Allowed } m -> Ready
    toReadyWithGameDefinition definition game =
    ready definition


    toInPlay : PlayState -> Game { a | inPlay : Allowed } { m | definition : GameDefinition } -> InPlay
    toInPlay play (Game model) =
    inPlay model.definition play


    toGameOver : Game { a | gameOver : Allowed } { m | definition : GameDefinition, play : PlayState } -> GameOver
    toGameOver (Game model) =
    gameOver model.definition model.play.score
  12. @rupertlssmith rupertlssmith revised this gist Jan 11, 2018. 1 changed file with 4 additions and 1 deletion.
    5 changes: 4 additions & 1 deletion intro.md
    Original file line number Diff line number Diff line change
    @@ -1,2 +1,5 @@
    This Gist explores the idea of using phantom types to encode the possible states that are allowed to make transitions into
    some other state in a state machine.
    some other state in a state machine.
    This also demonstrates how this can be used in a more real world setting where states in the machine may have addition data,
    and functions need to be mapped over that data or updates to it made whilst remaining in the current state, rather than just
    a pure state machine.
  13. @rupertlssmith rupertlssmith created this gist Jan 11, 2018.
    2 changes: 2 additions & 0 deletions intro.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,2 @@
    This Gist explores the idea of using phantom types to encode the possible states that are allowed to make transitions into
    some other state in a state machine.