Home

Awesome

Whack a Fraudster

A simple game in Elm and javascript

This week we'll be discussing and comparing a javascript implementation for a simple whack-a-mole style game and the implementation for the same game in Elm. After going over the pros, cons and general observations of each implementation we'll get our hands dirty extending the elm implementation.

You can find a link to the game on ellie below: https://ellie-app.com/j3zdd4RvQa1/5

Get started

Feel free to extend the game in anyway you like, if nothing comes to mind then why not build the functionality for a super customer rather than a super fraudster and accidentally clicking the super customer could be a huge point penalty or even end the game?

#So what do we need to look at?

Adding an entry to the ContentType

type ContentType
    = Empty
    | Fraudster
    | SuperFraudster
    | Client

Handle clicking the new content type

We'll need to add a new entry in the case statement as was done for the SuperFraudster. There we can either update the score accordingly to how we want this to influence the game or we can call update with the GameEnded function to have a 'Game Over' style action.

ClickBox index ->
    let
        ( fraudsters, customers, superbadGuy ) =
            model.score
    in
    case Dict.get index model.gridContents of
        Just SuperFraudster ->
            ( { model
                | score = ( fraudsters, customers, superbadGuy + 1 )
                , gridContents =
                    Dict.update
                        index
                        (Maybe.map (\previousContentTypes -> Empty))
                        model.gridContents
              }
            , Cmd.none
            )

Add to the model and initialise in StartGame

StartGame ->
    let
        ( updatedState, updatedCmd ) =
            update
                ApplyTick
                { model
                    | gameState = Playing
                    , level = Just Level1
                    , score = ( 0, 0, 0 )
                    , tickCount = 0
                    , superBadGuyTick = Nothing
                }
    in
    ( updatedState, Cmd.batch [ updatedCmd, Task.perform StartedTime Time.now ] )

Re-use or create a new function to randomly allocate when the super customer appears

StartedTime time ->
    ( { model
        | startedTime = Just time
        , superBadGuyTick = Just (randomTick (floor time))
      }
    , Cmd.none
    )

--

randomTick : Int -> Int
randomTick seed =
    Tuple.first <| Random.step (Random.int 1 12) (Random.initialSeed seed)

Check if the the current tick is the super customer tick

isSuperBadGuyTick : Maybe Int -> Int -> Bool
isSuperBadGuyTick superBadGuyTick tickCount =
    case superBadGuyTick of
        Just superBadGuyTick ->
            superBadGuyTick == tickCount

        Nothing ->
            False

To persist the super customer over several ticks: Check and extract from the grid

getSuperBadGuyFromGrid : Maybe Int -> Int -> Dict Int ContentType -> List ( Int, ContentType )
getSuperBadGuyFromGrid superBadGuyTick tickCount gridContents =
    case superBadGuyTick of
        Just superBadGuyTick ->
            if (superBadGuyTick + 2) >= tickCount then
                Dict.toList gridContents
                    |> List.filter
                        (\( index, cellType ) ->
                            cellType == SuperFraudster
                        )
            else
                []

        Nothing ->
            []

Reduce the number of empty slots by 1 if the super customer is carried over from previous tick

emptySpaces =
    if List.isEmpty superBadGuyRemains then
        (rows * rows) - 1
    else
        (rows * rows) - 2

Adding the super customer to the content building function

createContentList : Int -> Int -> Int -> Bool -> List ContentType
createContentList emptySpaces numberOfFraudsters numberOfClients isSuperBadGuyTick =
    let
        fraudsters =
            List.range 1 numberOfFraudsters
                |> List.map (\_ -> Fraudster)

        clients =
            List.range 1 numberOfClients
                |> List.map (\_ -> Client)

        superbadGuy =
            if isSuperBadGuyTick then
                [ SuperFraudster ]
            else
                []

        empties =
            List.range 1 (emptySpaces - numberOfFraudsters - numberOfClients)
                |> List.map (\_ -> Empty)
    in
    fraudsters
        ++ clients
        ++ superbadGuy
        ++ (if isSuperBadGuyTick then
                List.take (List.length empties - 1) empties
            else
                empties
           )