Created
April 24, 2020 16:54
-
-
Save TarVK/9e2c05cf9e793d594024321381a74ca8 to your computer and use it in GitHub Desktop.
Simple minesweeper game written in elm
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- Minesweeper game for elm, try it out at: https://elm-lang.org/examples/groceries (paste this code there) | |
import Browser | |
import Html exposing (Html, button, div, text, br) | |
import Html.Events as HEV exposing (onClick, custom) | |
import Html.Attributes exposing (style) | |
import Json.Decode as Json | |
import Random | |
width = 10 | |
height = 10 | |
totalBombs = 10 | |
main = | |
Browser.element { init = init, update = update, subscriptions = subscriptions, view = boardView } | |
type alias Cell = {value: Int, isBomb: Bool, revealed: Bool, flagged: Bool} | |
type alias CellID = Int | |
type alias Model = {available: List Int, cells: List Cell, died: Bool, won: Bool} | |
type Msg = Reset | Generate Int | Reveal CellID | Flag CellID | |
emptyCell = {value=0, isBomb=False, revealed=False, flagged=False} | |
-- Updating state | |
update : Msg -> Model -> (Model, Cmd Msg) | |
update msg model = | |
case msg of | |
Reset -> init () | |
Generate id -> ( | |
addBomb model (get id model.available), | |
getBombIndex (List.length model.available - 1)) | |
Reveal id -> (checkWin (revealAll model id), Cmd.none) | |
Flag id -> ( | |
{model | cells= | |
(List.indexedMap | |
(\index cell -> | |
if index == id then | |
{cell | flagged = not cell.flagged} | |
else | |
cell | |
) | |
model.cells | |
) | |
}, | |
Cmd.none) | |
checkWin: Model -> Model | |
checkWin model = | |
{model | won= | |
(List.foldl | |
(\cell won -> | |
won && ((cell.isBomb && not cell.revealed) || cell.revealed) | |
) | |
True | |
model.cells | |
) | |
} | |
-- Cell revealing | |
revealAll: Model -> CellID -> Model | |
revealAll model id = | |
if (getCell model id).value==0 && not (getCell model id).isBomb then | |
{model | cells = | |
(List.indexedMap | |
(revealMap (reveal model id [])) | |
model.cells | |
) | |
} | |
else | |
{model | cells = | |
(List.indexedMap | |
(revealMapOne id) | |
model.cells | |
), | |
died = model.died || (getCell model id).isBomb | |
} | |
revealMapOne: CellID -> CellID -> Cell -> Cell | |
revealMapOne revealedId id cell = | |
if revealedId == id then | |
{cell | revealed=True} | |
else | |
cell | |
revealMap: List CellID -> CellID -> Cell -> Cell | |
revealMap revealed id cell = | |
if (List.member id revealed) then | |
{cell | revealed=True} | |
else | |
cell | |
reveal: Model -> CellID -> List CellID -> List CellID | |
reveal model id revealed = | |
List.foldl | |
(revealFilter model id) | |
revealed | |
(List.indexedMap Tuple.pair model.cells) | |
revealFilter: Model -> CellID -> (CellID, Cell) -> List CellID -> List CellID | |
revealFilter model revealedId idAndCell revealed = | |
if ( | |
not (List.member (Tuple.first idAndCell) revealed) && | |
abs ((getX (Tuple.first idAndCell)) - (getX revealedId)) <= 1 && | |
abs ((getY (Tuple.first idAndCell)) - (getY revealedId)) <= 1 | |
) then ( | |
if (Tuple.second idAndCell).value==0 then | |
reveal model (Tuple.first idAndCell) ((Tuple.first idAndCell) :: revealed) | |
else | |
(Tuple.first idAndCell) :: revealed | |
) else | |
revealed | |
-- Field init | |
init: () -> (Model, Cmd Msg) | |
init _ = ( | |
{ | |
available = | |
(generateNumbers | |
(width * height - 1) | |
), | |
cells = | |
(List.repeat | |
(width * height) | |
emptyCell | |
), | |
died=False, | |
won=False | |
}, | |
getBombIndex (width * height) | |
) | |
getBombIndex: Int -> Cmd Msg | |
getBombIndex availableCount = | |
if (width * height) - availableCount < totalBombs then | |
Random.generate | |
Generate | |
(Random.int | |
0 | |
(availableCount - 1) | |
) | |
else | |
Cmd.none | |
addBomb: Model -> CellID -> Model | |
addBomb model id = { | |
model | | |
available = | |
(List.filter | |
(notEquals id) | |
model.available | |
), | |
cells = | |
(List.indexedMap | |
(addBombAt | |
model.cells | |
(getX id) | |
(getY id) | |
) | |
model.cells | |
) | |
} | |
addBombAt: List Cell -> Int -> Int -> Int -> Cell -> Cell | |
addBombAt cells x y cellIndex cell = | |
if (getX cellIndex) == x && (getY cellIndex) == y then | |
{cell | isBomb = True} | |
else if abs ((getX cellIndex) - x) <= 1 && abs ((getY cellIndex) - y) <= 1 then | |
{cell | value = cell.value + 1} | |
else | |
cell | |
-- utils | |
getX: CellID -> Int | |
getX index = modBy width index | |
getY: CellID -> Int | |
getY index = index // width | |
getCellXY: Model -> Int -> Int -> Cell | |
getCellXY model x y = getCell model (y * width + x) | |
getCell: Model -> CellID -> Cell | |
getCell model id = | |
Maybe.withDefault | |
emptyCell | |
(List.head | |
(List.drop | |
id | |
model.cells | |
) | |
) | |
notEquals: a -> a -> Bool | |
notEquals a b = a /= b | |
generateNumbers: Int -> List Int | |
generateNumbers remaining = | |
if remaining == -1 then | |
[] | |
else | |
remaining :: (generateNumbers (remaining - 1)) | |
get: Int -> List number -> number | |
get index list = | |
Maybe.withDefault | |
0 | |
(List.head | |
(List.drop | |
index | |
list | |
) | |
) | |
-- SUBSCRIPTIONS | |
subscriptions : Model -> Sub Msg | |
subscriptions model = | |
Sub.none | |
-- views | |
--- src: https://github.com/dc25/minesweeperElm/blob/master/src/RightClick.elm | |
onRightClick : msg -> Html.Attribute msg | |
onRightClick msg = | |
HEV.custom "contextmenu" | |
(Json.succeed | |
{ message = msg | |
, stopPropagation = True | |
, preventDefault = True | |
} | |
) | |
boardView: Model -> Html Msg | |
boardView model = | |
div | |
[] | |
( | |
(if model.died then | |
[ | |
div [style "display" "inline-block"] [text "game over "], | |
button [onClick Reset] [text "reset"], | |
br [] [] | |
] | |
else if model.won then | |
[ | |
div [style "display" "inline-block"] [text "you won "], | |
button [onClick Reset] [text "reset"], | |
br [] [] | |
] | |
else | |
[] | |
) ++ | |
(List.concat | |
(List.indexedMap | |
(cellView model) | |
model.cells | |
) | |
) | |
) | |
cellStyle = | |
[ | |
style "display" "inline-block", | |
style "vertical-align" "top", | |
style "width" "20px", | |
style "height" "20px" | |
] | |
cellView: Model -> CellID -> Cell -> List (Html Msg) | |
cellView model id cell = | |
newLine | |
id | |
( | |
if cell.revealed then ( | |
if cell.isBomb then | |
div ([style "background-color" "red"] ++ cellStyle) [text "B"] | |
else | |
div ([ | |
style "background-color" "green" | |
] ++ cellStyle) [ | |
text (String.fromInt cell.value) | |
] | |
) else if cell.flagged then | |
div ([ | |
style "background-color" "orange", | |
onRightClick (Flag id) | |
] ++ cellStyle) [text "F"] | |
else | |
div ([ | |
style "background-color" "grey" | |
] ++ cellStyle ++ ( | |
if not model.died then | |
[ | |
onClick (Reveal id), | |
onRightClick (Flag id) | |
] | |
else | |
[] | |
)) [] | |
) | |
newLine: CellID -> Html Msg -> List (Html Msg) | |
newLine index el= | |
if getX (index + 1) == 0 then | |
[el, br [] []] | |
else | |
[el] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment