Skip to content

Instantly share code, notes, and snippets.

@DataWraith
Last active February 22, 2017 15:55
Show Gist options
  • Save DataWraith/3b9c856cc8cf1ee732d792804b9665a9 to your computer and use it in GitHub Desktop.
Save DataWraith/3b9c856cc8cf1ee732d792804b9665a9 to your computer and use it in GitHub Desktop.
Simple two-player Go-Moku game written in Elm https://elm-gomoku.neocities.org
{-
"dependencies": {
"eeue56/elm-flat-matrix": "3.0.2 <= v < 4.0.0",
"elm-lang/core": "5.1.1 <= v < 6.0.0",
"elm-lang/html": "2.0.0 <= v < 3.0.0",
"elm-lang/svg": "2.0.0 <= v < 3.0.0"
},
"elm-version": "0.18.0 <= v < 0.19.0"
-}
import Array
import Html
import Html.Attributes
import Html.Events
import Svg exposing (g, line, circle)
import Svg.Attributes exposing (width, height, style, viewBox, x1, x2, y1, y2, cx, cy, r, visibility)
import Svg.Events exposing (onClick)
import Matrix exposing (Matrix, indexedMap, get, set, filter)
main : Program Never Model Msg
main =
Html.beginnerProgram
{ model = initialModel 15
, update = update
, view = view
}
-- MODEL
type Player
= Empty
| Black
| White
type alias Model =
{ size : Int
, board : Matrix Player
, currentPlayer : Player
, winner : Player
}
initialModel : Int -> Model
initialModel size =
{ size = size
, board = Matrix.repeat size size Empty
, winner = Empty
, currentPlayer = Black
}
-- UPDATE
type Msg
= MakeMove Int Int
| Reset
-- Returns the four (x, y) coordinates adjacent to x, y in a line pointing in
-- direction (dx, dy).
chainIndices : Int -> Int -> Int -> Int -> List ( Int, Int )
chainIndices x y dx dy =
[ ( x + 1 * dx, y + 1 * dy )
, ( x + 2 * dx, y + 2 * dy )
, ( x + 3 * dx, y + 3 * dy )
, ( x + 4 * dx, y + 4 * dy )
]
getChainLength : Player -> Matrix Player -> List ( Int, Int ) -> Int
getChainLength player board indices =
if List.isEmpty indices then
4
else
let
x =
(Tuple.first (Maybe.withDefault ( 0, 0 ) (List.head indices)))
y =
(Tuple.second (Maybe.withDefault ( 0, 0 ) (List.head indices)))
p =
get x y board
in
if p == Just player then
getChainLength player board (Maybe.withDefault [] (List.tail indices))
else
4 - (List.length indices)
checkWinner : Int -> Int -> Player -> Matrix Player -> Player
checkWinner x y p board =
let
ul =
getChainLength p board (chainIndices x y -1 -1)
u =
getChainLength p board (chainIndices x y 0 -1)
ur =
getChainLength p board (chainIndices x y 1 -1)
r =
getChainLength p board (chainIndices x y 1 0)
dr =
getChainLength p board (chainIndices x y 1 1)
d =
getChainLength p board (chainIndices x y 0 1)
dl =
getChainLength p board (chainIndices x y -1 1)
l =
getChainLength p board (chainIndices x y -1 0)
in
if ul + dr == 4 then
p
else if u + d == 4 then
p
else if ur + dl == 4 then
p
else if r + l == 4 then
p
else
Empty
update : Msg -> Model -> Model
update msg model =
case msg of
Reset ->
initialModel model.size
MakeMove x y ->
if model.currentPlayer == Empty then
model
else
let
curState =
get x y model.board
winner =
checkWinner x y model.currentPlayer model.board
boardFull =
Array.length (filter (\square -> square == Empty) model.board) == 0
nextPlayer =
if (winner /= Empty) || boardFull then
Empty
else if model.currentPlayer == White then
Black
else
White
in
if curState /= Just Empty then
model
else
{ model
| board = set x y model.currentPlayer model.board
, currentPlayer = nextPlayer
, winner = winner
}
-- VIEW
boardSquare : Int -> Int -> Player -> Svg.Svg Msg
boardSquare x y square =
let
offsetx =
101 * x
offsety =
101 * y
center =
50
dimension =
101
in
g []
[ line
[ x1 (toString (offsetx + center))
, y1 (toString (offsety))
, x2 (toString (offsetx + center))
, y2 (toString (offsety + dimension))
, style "stroke:rgb(128,128,128);stroke-width:2"
]
[]
, line
[ x1 (toString (offsetx))
, y1 (toString (offsety + center))
, x2 (toString (offsetx + dimension))
, y2 (toString (offsety + center))
, style "stroke:rgb(128,128,128);stroke-width:2"
]
[]
, if square == Black then
circle
[ cx (toString (offsetx + center))
, cy (toString (offsety + center))
, r "45"
, style "fill:rgb(0, 0, 0);"
]
[]
else if square == White then
circle
[ cx (toString (offsetx + center))
, cy (toString (offsety + center))
, r "45"
, style "fill:rgb(255,255,255);"
]
[]
else
circle
[ cx (toString (offsetx + center))
, cy (toString (offsety + center))
, r "45"
, visibility "hidden"
, Svg.Attributes.pointerEvents "all"
, onClick (MakeMove x y)
]
[]
]
playerToString : Player -> String
playerToString player =
if player == Empty then
"Neither"
else if player == Black then
"Black"
else
"White"
centerAlign : Html.Attribute Msg
centerAlign =
Html.Attributes.style
[ ( "width", "50%" )
, ( "margin-left", "auto" )
, ( "margin-right", "auto" )
]
view : Model -> Html.Html Msg
view model =
let
dimension =
model.size * 101
in
Html.div [ centerAlign ]
[ Html.h1 [] [ Html.text "Go-Moku" ]
, Html.p [] [ Html.text "Connect exactly 5 stones horizontally, vertically or diagonally" ]
, Html.p [] [ Html.text ("Winner: " ++ (playerToString model.winner)) ]
, Html.p [] [ Html.text ("To Move: " ++ (playerToString model.currentPlayer)) ]
, Svg.svg
[ width "600px"
, height "600px"
, viewBox ("0 0 " ++ (toString dimension) ++ " " ++ (toString dimension))
]
[ Svg.rect
[ width (toString dimension)
, height (toString dimension)
, style "fill:rgb(251,234,173);"
]
[]
, g [] (Array.toList (indexedMap boardSquare model.board).data)
]
, Html.button [ Html.Events.onClick Reset ] [ Html.text "Reset" ]
]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment