Skip to content

Instantly share code, notes, and snippets.

@ralfw
Last active August 22, 2016 06:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ralfw/52a24689db63b09b4b1edd3ef2b056c9 to your computer and use it in GitHub Desktop.
Save ralfw/52a24689db63b09b4b1edd3ef2b056c9 to your computer and use it in GitHub Desktop.
Tic Tac Toe in Elm
module Main exposing(main)
import Html exposing (Html, div, span, br, text, table, tr, td, button)
import Html.Attributes exposing (style)
import Html.App exposing (beginnerProgram)
import Html.Events exposing (onClick)
import Array exposing (Array)
import List
import Game
main =
beginnerProgram {
model = Game.newGame,
view = view,
update = update
}
view model =
let
n = 3
fieldIndexOf r c = n*r+c
fieldValueAt r c =
model.board |> Array.get (fieldIndexOf r c) |> Maybe.withDefault " "
renderBoard =
table [] (
[0..n-1] |> List.map (\r ->
tr [] (
[0..n-1] |> List.map (\c ->
td [] [(button [fieldButtonStyle, onClick (MoveTo (fieldIndexOf r c))]
[text (fieldValueAt r c)])]
)
)
)
)
in
div [] [
renderBoard
,span [] [text model.msg], br [] []
,button [onClick NewGame] [text "New Game"]
]
fieldButtonStyle =
style
[ ("font-size", "300%")
,("width", "60px")
,("height", "60px")
]
type Msg =
NewGame
| MoveTo Int
update msg model =
case msg of
NewGame ->
Game.newGame
MoveTo fieldIndex ->
Game.executeMove fieldIndex model
module Game exposing(..)
import Array exposing(Array)
import Referee
type alias Model = {
board : Array String
,player : Int
,msg : String
,gameOver : Bool
}
newGame = {board = Array.fromList [
" ", " ", " ",
" ", " ", " ",
" ", " ", " "]
,player = 0
,msg = "It's your turn player X"
,gameOver = False}
executeMove fieldIndex model =
let
checkValidityOfMove onIsValid onIsInvalid model =
if model.gameOver then
{model | msg = "No more moves allowed! Game over."}
else
if (model.board |> Array.get fieldIndex) == Just " " then
onIsValid model
else
{model | msg = "Invalid move; field already taken!"} |> onIsInvalid
currentPlayerSymbol player = if player == 0 then "X" else "O"
placeToken model =
{model | board = model.board |> Array.set fieldIndex (currentPlayerSymbol model.player)}
togglePlayer model =
{model | player = if model.player == 0 then 1 else 0}
promptPlayer model =
{model | msg = "It's your turn, player " ++ (currentPlayerSymbol model.player)}
in
model |> checkValidityOfMove
(placeToken
>> Referee.checkGameOver
(\m -> m)
(togglePlayer >> promptPlayer))
(\m -> m)
module Referee exposing (checkGameOver)
import Array
checkGameOver onGameOver onGameOn model =
model |> checkWinner "X"
onGameOver
(checkWinner "O"
onGameOver
(checkTie
onGameOver
onGameOn)
)
checkWinner candidateWinnerToken onGameOver onGameOn model =
let
winningPatterns = [
[0,1,2], [3,4,5], [6,7,8],
[0,3,6], [1,4,7], [2,5,8],
[0,4,8], [2,4,6]
]
isOnBoard pattern =
pattern |> List.all (\i -> (model.board |> Array.get i) == Just candidateWinnerToken)
in
if (winningPatterns |> List.any isOnBoard) then
onGameOver {model | msg = "Game over! " ++ candidateWinnerToken ++ " wins.", gameOver = True}
else
onGameOn model
checkTie onGameOver onGameOn model =
let
numberOfAvailableFields = model.board |> Array.filter (\f -> f == " ") |> Array.length
in
if numberOfAvailableFields == 0 then
onGameOver {model | msg = "Game over! It's a tie.", gameOver = True}
else
onGameOn model
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment