Added strategy
This commit is contained in:
parent
553809fc56
commit
8ebe233bec
|
@ -21,5 +21,6 @@
|
||||||
| [purpleflow](./purpleflow) | Unfinished dependently-typed programming language |
|
| [purpleflow](./purpleflow) | Unfinished dependently-typed programming language |
|
||||||
| [slice](./slice) | Basic benchmarks and a `Slice` type |
|
| [slice](./slice) | Basic benchmarks and a `Slice` type |
|
||||||
| [sprint](./sprint) | Failled effect-system based on typelevel lists |
|
| [sprint](./sprint) | Failled effect-system based on typelevel lists |
|
||||||
|
| [strategy](./strategy) | Unfinished attempt at implementing a mixed strategy nash equilibrium solver |
|
||||||
| [streams](./streams) | Playing with `purescript-pipes` |
|
| [streams](./streams) | Playing with `purescript-pipes` |
|
||||||
| [typelevel](./typelevel) | Typelevel naturals, vectors, sum-types, orderings and lambda-calculus evaluation and a value-level bounded-type GADT |
|
| [typelevel](./typelevel) | Typelevel naturals, vectors, sum-types, orderings and lambda-calculus evaluation and a value-level bounded-type GADT |
|
||||||
|
|
10
purescript/strategy/.gitignore
vendored
Normal file
10
purescript/strategy/.gitignore
vendored
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
/bower_components/
|
||||||
|
/node_modules/
|
||||||
|
/.pulp-cache/
|
||||||
|
/output/
|
||||||
|
/generated-docs/
|
||||||
|
/.psc-package/
|
||||||
|
/.psc*
|
||||||
|
/.purs*
|
||||||
|
/.psa*
|
||||||
|
/.spago
|
105
purescript/strategy/packages.dhall
Normal file
105
purescript/strategy/packages.dhall
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
{-
|
||||||
|
Welcome to your new Dhall package-set!
|
||||||
|
|
||||||
|
Below are instructions for how to edit this file for most use
|
||||||
|
cases, so that you don't need to know Dhall to use it.
|
||||||
|
|
||||||
|
## Use Cases
|
||||||
|
|
||||||
|
Most will want to do one or both of these options:
|
||||||
|
1. Override/Patch a package's dependency
|
||||||
|
2. Add a package not already in the default package set
|
||||||
|
|
||||||
|
This file will continue to work whether you use one or both options.
|
||||||
|
Instructions for each option are explained below.
|
||||||
|
|
||||||
|
### Overriding/Patching a package
|
||||||
|
|
||||||
|
Purpose:
|
||||||
|
- Change a package's dependency to a newer/older release than the
|
||||||
|
default package set's release
|
||||||
|
- Use your own modified version of some dependency that may
|
||||||
|
include new API, changed API, removed API by
|
||||||
|
using your custom git repo of the library rather than
|
||||||
|
the package set's repo
|
||||||
|
|
||||||
|
Syntax:
|
||||||
|
where `entityName` is one of the following:
|
||||||
|
- dependencies
|
||||||
|
- repo
|
||||||
|
- version
|
||||||
|
-------------------------------
|
||||||
|
let upstream = --
|
||||||
|
in upstream
|
||||||
|
with packageName.entityName = "new value"
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
Example:
|
||||||
|
-------------------------------
|
||||||
|
let upstream = --
|
||||||
|
in upstream
|
||||||
|
with halogen.version = "master"
|
||||||
|
with halogen.repo = "https://example.com/path/to/git/repo.git"
|
||||||
|
|
||||||
|
with halogen-vdom.version = "v4.0.0"
|
||||||
|
with halogen-vdom.dependencies = [ "extra-dependency" ] # halogen-vdom.dependencies
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
### Additions
|
||||||
|
|
||||||
|
Purpose:
|
||||||
|
- Add packages that aren't already included in the default package set
|
||||||
|
|
||||||
|
Syntax:
|
||||||
|
where `<version>` is:
|
||||||
|
- a tag (i.e. "v4.0.0")
|
||||||
|
- a branch (i.e. "master")
|
||||||
|
- commit hash (i.e. "701f3e44aafb1a6459281714858fadf2c4c2a977")
|
||||||
|
-------------------------------
|
||||||
|
let upstream = --
|
||||||
|
in upstream
|
||||||
|
with new-package-name =
|
||||||
|
{ dependencies =
|
||||||
|
[ "dependency1"
|
||||||
|
, "dependency2"
|
||||||
|
]
|
||||||
|
, repo =
|
||||||
|
"https://example.com/path/to/git/repo.git"
|
||||||
|
, version =
|
||||||
|
"<version>"
|
||||||
|
}
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
Example:
|
||||||
|
-------------------------------
|
||||||
|
let upstream = --
|
||||||
|
in upstream
|
||||||
|
with benchotron =
|
||||||
|
{ dependencies =
|
||||||
|
[ "arrays"
|
||||||
|
, "exists"
|
||||||
|
, "profunctor"
|
||||||
|
, "strings"
|
||||||
|
, "quickcheck"
|
||||||
|
, "lcg"
|
||||||
|
, "transformers"
|
||||||
|
, "foldable-traversable"
|
||||||
|
, "exceptions"
|
||||||
|
, "node-fs"
|
||||||
|
, "node-buffer"
|
||||||
|
, "node-readline"
|
||||||
|
, "datetime"
|
||||||
|
, "now"
|
||||||
|
]
|
||||||
|
, repo =
|
||||||
|
"https://github.com/hdgarrood/purescript-benchotron.git"
|
||||||
|
, version =
|
||||||
|
"v7.0.0"
|
||||||
|
}
|
||||||
|
-------------------------------
|
||||||
|
-}
|
||||||
|
let upstream =
|
||||||
|
https://github.com/purescript/package-sets/releases/download/psc-0.15.4-20220921/packages.dhall
|
||||||
|
sha256:169bd823a71ae033eaf4f77776e184f12c656163feae14e7f649a48932ca6ac0
|
||||||
|
|
||||||
|
in upstream
|
30
purescript/strategy/spago.dhall
Normal file
30
purescript/strategy/spago.dhall
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
{-
|
||||||
|
Welcome to a Spago project!
|
||||||
|
You can edit this file as you like.
|
||||||
|
|
||||||
|
Need help? See the following resources:
|
||||||
|
- Spago documentation: https://github.com/purescript/spago
|
||||||
|
- Dhall language tour: https://docs.dhall-lang.org/tutorials/Language-Tour.html
|
||||||
|
|
||||||
|
When creating a new Spago project, you can use
|
||||||
|
`spago init --no-comments` or `spago init -C`
|
||||||
|
to generate this file without the comments in this block.
|
||||||
|
-}
|
||||||
|
{ name = "my-project"
|
||||||
|
, dependencies =
|
||||||
|
[ "arrays"
|
||||||
|
, "console"
|
||||||
|
, "dodo-printer"
|
||||||
|
, "effect"
|
||||||
|
, "either"
|
||||||
|
, "fast-vect"
|
||||||
|
, "foldable-traversable"
|
||||||
|
, "maybe"
|
||||||
|
, "prelude"
|
||||||
|
, "random"
|
||||||
|
, "safe-coerce"
|
||||||
|
, "tuples"
|
||||||
|
]
|
||||||
|
, packages = ./packages.dhall
|
||||||
|
, sources = [ "src/**/*.purs", "test/**/*.purs" ]
|
||||||
|
}
|
203
purescript/strategy/src/Data/Game.purs
Normal file
203
purescript/strategy/src/Data/Game.purs
Normal file
|
@ -0,0 +1,203 @@
|
||||||
|
module EG.Data.Game where
|
||||||
|
|
||||||
|
import Prelude
|
||||||
|
|
||||||
|
import Data.Number as Number
|
||||||
|
import Data.Array as Array
|
||||||
|
import Data.Foldable (maximum, maximumBy, sum)
|
||||||
|
import Data.Function (on)
|
||||||
|
import Data.Maybe (Maybe(..), fromMaybe, maybe)
|
||||||
|
import Data.Number (infinity)
|
||||||
|
import Data.Profunctor.Strong (second, (&&&))
|
||||||
|
import Data.Tuple (fst, snd)
|
||||||
|
import Data.Tuple.Nested (type (/\), (/\))
|
||||||
|
import Dodo (Doc)
|
||||||
|
import Dodo as DD
|
||||||
|
import EG.Data.Payoff (Payoff(..))
|
||||||
|
import EG.Data.Tuple (TupleIndex)
|
||||||
|
import EG.Data.Tuple as ETuple
|
||||||
|
import Effect (Effect)
|
||||||
|
import Effect.Random (randomInt)
|
||||||
|
import Safe.Coerce (coerce)
|
||||||
|
|
||||||
|
type Player = TupleIndex
|
||||||
|
|
||||||
|
newtype Game state choice = Game
|
||||||
|
{ state :: state
|
||||||
|
, choices :: Array (choice /\ (Game state choice))
|
||||||
|
}
|
||||||
|
|
||||||
|
newtype GameReplay state choice = GameReplay
|
||||||
|
{ state :: state
|
||||||
|
, next :: Maybe (choice /\ GameReplay state choice)
|
||||||
|
}
|
||||||
|
|
||||||
|
newtype SolvedState state choice = SolvedState
|
||||||
|
{ state :: state
|
||||||
|
, choice :: Maybe (choice /\ Game (SolvedState state choice) choice)
|
||||||
|
, payoffs :: Maybe (Payoff /\ Payoff)
|
||||||
|
}
|
||||||
|
|
||||||
|
type PayoffFunction s = Int -> s -> Maybe (Payoff /\ Payoff)
|
||||||
|
|
||||||
|
---------- Helpers
|
||||||
|
solvedStatePayoffs :: forall s c. SolvedState s c -> Maybe (Payoff /\ Payoff)
|
||||||
|
solvedStatePayoffs (SolvedState { payoffs }) = payoffs
|
||||||
|
|
||||||
|
gameNodeCount :: forall s c. Game s c -> Int
|
||||||
|
gameNodeCount (Game { choices }) = 1 + sum (map (gameNodeCount <<< snd) choices)
|
||||||
|
|
||||||
|
gameLeafCount :: forall s c. Game s c -> Int
|
||||||
|
gameLeafCount (Game { choices: [] }) = 1
|
||||||
|
gameLeafCount (Game { choices }) = sum (map (gameLeafCount <<< snd) choices)
|
||||||
|
|
||||||
|
gameDepth :: forall s c. Game s c -> Int
|
||||||
|
gameDepth (Game { choices }) = maybe 0 ((+) 1)
|
||||||
|
$ maximum
|
||||||
|
$ map (snd >>> gameDepth) choices
|
||||||
|
|
||||||
|
randomGame :: forall s c. Game s c -> Effect (GameReplay s c)
|
||||||
|
randomGame (Game { choices, state }) = do
|
||||||
|
index <- randomInt 0 (Array.length choices - 1)
|
||||||
|
next <- case Array.index choices index of
|
||||||
|
Nothing -> pure Nothing
|
||||||
|
Just (choice /\ next) -> do
|
||||||
|
next <- randomGame next
|
||||||
|
pure (Just (choice /\ next))
|
||||||
|
pure $ GameReplay { state, next }
|
||||||
|
|
||||||
|
displayReplay :: forall s c a. (s -> Doc a) -> (c -> Doc a) -> GameReplay s c -> Doc a
|
||||||
|
displayReplay printState printChoice (GameReplay { state, next }) =
|
||||||
|
DD.lines
|
||||||
|
[ DD.indent (printState state)
|
||||||
|
, case next of
|
||||||
|
Nothing -> mempty
|
||||||
|
Just (choice /\ next) ->
|
||||||
|
DD.lines
|
||||||
|
[ Array.intercalate DD.space
|
||||||
|
[ DD.text "----"
|
||||||
|
, printChoice choice
|
||||||
|
, DD.text "---->"
|
||||||
|
]
|
||||||
|
, displayReplay printState printChoice next
|
||||||
|
]
|
||||||
|
|
||||||
|
]
|
||||||
|
|
||||||
|
gameState :: forall s c. Game s c -> s
|
||||||
|
gameState (Game { state }) = state
|
||||||
|
|
||||||
|
computePayoffs
|
||||||
|
:: forall s c
|
||||||
|
. Player
|
||||||
|
-> PayoffFunction s
|
||||||
|
-> Game s c
|
||||||
|
-> Game (SolvedState s c) c
|
||||||
|
computePayoffs player calculatePayoffs game = go player 0 game
|
||||||
|
where
|
||||||
|
go player depth (Game { state, choices }) = case choices of
|
||||||
|
[] -> Game
|
||||||
|
{ choices: []
|
||||||
|
, state: SolvedState
|
||||||
|
{ state
|
||||||
|
, payoffs: calculatePayoffs depth state
|
||||||
|
, choice: Nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
choices -> Game
|
||||||
|
{ choices: nested
|
||||||
|
, state: SolvedState
|
||||||
|
{ state
|
||||||
|
, payoffs: map _.payoff optimalChoice
|
||||||
|
, choice: map (_.choice &&& _.game) optimalChoice
|
||||||
|
}
|
||||||
|
}
|
||||||
|
where
|
||||||
|
nested = choices
|
||||||
|
# map (second (go (ETuple.other player) (depth + 1)))
|
||||||
|
|
||||||
|
optimalChoice = nested
|
||||||
|
# map
|
||||||
|
( \(choice /\ game) ->
|
||||||
|
{ choice
|
||||||
|
, game
|
||||||
|
, payoff: game
|
||||||
|
# gameState
|
||||||
|
# solvedStatePayoffs
|
||||||
|
# fromMaybe (hell /\ hell)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
# maximumBy (on compare (_.payoff >>> ETuple.lookupPair player))
|
||||||
|
|
||||||
|
hell = Payoff (-infinity)
|
||||||
|
|
||||||
|
-- minmax :: forall s c. PayoffFunction s -> Player -> Game s c -> Maybe (c /\ Game s c)
|
||||||
|
-- minmax calculatePayoffs player (Game { choices, state }) =
|
||||||
|
-- choices
|
||||||
|
-- # Array.mapMaybe
|
||||||
|
-- ( \(choice /\ game) -> case calculatePayoffs state of
|
||||||
|
-- Just payoffs -> Just { choice, game, payoffs }
|
||||||
|
-- Nothing -> Nothing
|
||||||
|
-- )
|
||||||
|
-- # maximumBy (on compare (_.payoffs >>> ETuple.lookup player))
|
||||||
|
-- # map \{ choice, game } -> choice /\ game
|
||||||
|
|
||||||
|
---------- Display stuff
|
||||||
|
displaySolvedState :: forall a s c. (s -> Doc a) -> (c -> Doc a) -> SolvedState s c -> Doc a
|
||||||
|
displaySolvedState displayState displayChoice (SolvedState { payoffs, choice, state }) = DD.lines
|
||||||
|
[ DD.indent (displayState state)
|
||||||
|
, Array.intercalate DD.space
|
||||||
|
[ DD.text "Payoffs:"
|
||||||
|
, displayUnknown displayPayoffs payoffs
|
||||||
|
]
|
||||||
|
, Array.intercalate DD.space
|
||||||
|
[ DD.text "Optimal choice:"
|
||||||
|
, displayUnknown displayChoice (map fst choice)
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
where
|
||||||
|
displayUnknown :: forall e. (e -> Doc a) -> Maybe e -> Doc a
|
||||||
|
displayUnknown f (Just a) = f a
|
||||||
|
displayUnknown f Nothing = DD.text "???"
|
||||||
|
|
||||||
|
displayPayoffs (l /\ r) = Array.fold
|
||||||
|
[ DD.text "("
|
||||||
|
, displayNum (coerce l)
|
||||||
|
, DD.text ","
|
||||||
|
, DD.space
|
||||||
|
, displayNum (coerce r)
|
||||||
|
, DD.text ")"
|
||||||
|
]
|
||||||
|
where
|
||||||
|
displayNum num = DD.text (show $ Number.floor (num * precision) / precision)
|
||||||
|
precision = 1000.0
|
||||||
|
|
||||||
|
-- newtype Payoff = Payoff Int
|
||||||
|
--
|
||||||
|
-- newtype PayoffTreeNode label state choice = PayoffTreeNode
|
||||||
|
-- { expectedPayoff :: Payoff /\ Payoff
|
||||||
|
-- , children :: PayoffTree label state choicehoi--
|
||||||
|
--
|
||||||
|
-- newtype PayoffTree label state choice = PayoffTree
|
||||||
|
-- { label :: label
|
||||||
|
-- , state :: state
|
||||||
|
-- , choices :: Array (PayoffTreeNode label state choice)
|
||||||
|
-- }
|
||||||
|
|
||||||
|
-- minmax
|
||||||
|
-- :: forall label choice
|
||||||
|
-- . Foldable choice
|
||||||
|
-- => Player
|
||||||
|
-- -> PayoffTree label choice
|
||||||
|
-- -> PayoffTreeNode label choice
|
||||||
|
-- minmax player (PayoffTree { label, choices }) = choice
|
||||||
|
-- where
|
||||||
|
-- choice = choices # maximumBy
|
||||||
|
-- ( on compare \(PayoffTreeNode { expectedPayoff, children }) ->
|
||||||
|
-- ETuple.lookup player expectedPayoff
|
||||||
|
-- )
|
||||||
|
|
||||||
|
---------- Typeclass instances
|
||||||
|
-- derive instance Eq Payoff
|
||||||
|
-- derive instance Ord Payoff
|
13
purescript/strategy/src/Data/Payoff.purs
Normal file
13
purescript/strategy/src/Data/Payoff.purs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
module EG.Data.Payoff where
|
||||||
|
|
||||||
|
import Prelude
|
||||||
|
import Data.FastVect.FastVect (Vect)
|
||||||
|
|
||||||
|
newtype Payoff = Payoff Number
|
||||||
|
|
||||||
|
newtype PayoffMatrix p1 p2 = PayoffMatrix (Vect p1 (Vect p2 Payoff))
|
||||||
|
|
||||||
|
---------- Typeclass instances
|
||||||
|
derive instance Eq Payoff
|
||||||
|
derive instance Ord Payoff
|
||||||
|
derive newtype instance Show Payoff
|
32
purescript/strategy/src/Data/Tuple.purs
Normal file
32
purescript/strategy/src/Data/Tuple.purs
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
module EG.Data.Tuple (TupleIndex, left, right, lookupPair, lookup, other) where
|
||||||
|
|
||||||
|
import Data.Either (Either(..))
|
||||||
|
import Data.Either.Nested (type (\/))
|
||||||
|
import Data.HeytingAlgebra (not)
|
||||||
|
import Data.Tuple.Nested (type (/\), (/\))
|
||||||
|
import Safe.Coerce (coerce)
|
||||||
|
|
||||||
|
-- | Position in a tuple.
|
||||||
|
-- | Logically equivalent to a boolean
|
||||||
|
-- | Constructable using `left` and `right`
|
||||||
|
newtype TupleIndex = TupleIndex Boolean
|
||||||
|
|
||||||
|
other :: TupleIndex -> TupleIndex
|
||||||
|
other = coerce (not :: Boolean -> Boolean)
|
||||||
|
|
||||||
|
left :: TupleIndex
|
||||||
|
left = TupleIndex false
|
||||||
|
|
||||||
|
right :: TupleIndex
|
||||||
|
right = TupleIndex true
|
||||||
|
|
||||||
|
lookupPair :: forall a. TupleIndex -> a /\ a -> a
|
||||||
|
lookupPair (TupleIndex inner) (a /\ b) =
|
||||||
|
if inner then b
|
||||||
|
else a
|
||||||
|
|
||||||
|
lookup :: forall a b. TupleIndex -> a /\ b -> a \/ b
|
||||||
|
lookup (TupleIndex inner) (a /\ b) =
|
||||||
|
if inner then Right b
|
||||||
|
else Left a
|
||||||
|
|
188
purescript/strategy/src/Games/Tictactoe.purs
Normal file
188
purescript/strategy/src/Games/Tictactoe.purs
Normal file
|
@ -0,0 +1,188 @@
|
||||||
|
module EG.Games.Tictactoe where
|
||||||
|
|
||||||
|
import Prelude
|
||||||
|
|
||||||
|
import Data.Array (findMap, foldl, intercalate)
|
||||||
|
import Data.Array as Array
|
||||||
|
import Data.Int as Int
|
||||||
|
import Data.Maybe (Maybe(..), isJust)
|
||||||
|
import Data.Tuple.Nested (type (/\), (/\))
|
||||||
|
import Dodo (Doc)
|
||||||
|
import Dodo as DD
|
||||||
|
import EG.Data.Game (Game(..))
|
||||||
|
import EG.Data.Payoff (Payoff(..))
|
||||||
|
import EG.Data.Tuple as ETuple
|
||||||
|
|
||||||
|
---------- Types
|
||||||
|
data Piece = X | O
|
||||||
|
|
||||||
|
type Board = Array (Maybe Piece)
|
||||||
|
type Player = ETuple.TupleIndex
|
||||||
|
|
||||||
|
data TurnState = NextMove Player | WonBy Player | Draw
|
||||||
|
|
||||||
|
newtype State = State
|
||||||
|
{ board :: Board
|
||||||
|
, turnState :: TurnState
|
||||||
|
}
|
||||||
|
|
||||||
|
type GameGenerationState = { board :: Board, player :: Player }
|
||||||
|
|
||||||
|
type Choice = Int -- mod 9
|
||||||
|
|
||||||
|
type TictactoeGame = Game State Choice
|
||||||
|
|
||||||
|
---------- Helpers
|
||||||
|
initialState :: GameGenerationState
|
||||||
|
initialState =
|
||||||
|
{ board: Array.replicate 9 Nothing
|
||||||
|
, player: ETuple.left
|
||||||
|
}
|
||||||
|
|
||||||
|
pieceToPlayer :: Piece -> Player
|
||||||
|
pieceToPlayer X = ETuple.left
|
||||||
|
pieceToPlayer O = ETuple.right
|
||||||
|
|
||||||
|
playerToPiece :: Player -> Piece
|
||||||
|
playerToPiece player = ETuple.lookupPair player (X /\ O)
|
||||||
|
|
||||||
|
winner :: Board -> Maybe Player
|
||||||
|
winner = case _ of
|
||||||
|
[ a11
|
||||||
|
, a12
|
||||||
|
, a13
|
||||||
|
, a21
|
||||||
|
, a22
|
||||||
|
, a23
|
||||||
|
, a31
|
||||||
|
, a32
|
||||||
|
, a33
|
||||||
|
-- For 10 element version
|
||||||
|
-- , a41
|
||||||
|
] ->
|
||||||
|
patterns # findMap (allEqual >=> map pieceToPlayer)
|
||||||
|
where
|
||||||
|
patterns =
|
||||||
|
[ [ a11, a12, a13 ]
|
||||||
|
, [ a21, a22, a23 ]
|
||||||
|
, [ a31, a32, a33 ]
|
||||||
|
, [ a11, a21, a31 ]
|
||||||
|
, [ a12, a22, a32 ]
|
||||||
|
, [ a13, a23, a33 ]
|
||||||
|
, [ a11, a22, a33 ]
|
||||||
|
, [ a13, a22, a31 ]
|
||||||
|
-- for weird version
|
||||||
|
-- , [ a11, a23, a32 ]
|
||||||
|
-- , [ a33, a12, a21 ]
|
||||||
|
-- even more weird version
|
||||||
|
-- , [ a13, a32, a21 ]
|
||||||
|
-- , [ a31, a23, a12 ]
|
||||||
|
|
||||||
|
-- for 10 element version
|
||||||
|
-- , [ a21, a31, a41 ]
|
||||||
|
-- , [ a23, a32, a41 ]
|
||||||
|
|
||||||
|
-- for 12 element version
|
||||||
|
-- , [ a41, a42, a43 ]
|
||||||
|
-- , [ a21, a31, a41 ]
|
||||||
|
-- , [ a22, a32, a42 ]
|
||||||
|
-- , [ a23, a33, a43 ]
|
||||||
|
-- , [ a21, a32, a43 ]
|
||||||
|
-- , [ a23, a32, a41 ]
|
||||||
|
]
|
||||||
|
_ -> Nothing
|
||||||
|
where
|
||||||
|
allEqual :: forall a. Eq a => Array a -> Maybe a
|
||||||
|
allEqual = foldl go (Just Nothing) >>> join
|
||||||
|
where
|
||||||
|
go (Just Nothing) e = Just (Just e)
|
||||||
|
go whole@(Just (Just p)) e
|
||||||
|
| e == p = whole
|
||||||
|
go _ _ = Nothing
|
||||||
|
|
||||||
|
---------- Game related functions
|
||||||
|
game :: TictactoeGame
|
||||||
|
game = go initialState
|
||||||
|
where
|
||||||
|
go { board, player }
|
||||||
|
| Just player <- winner board = Game
|
||||||
|
{ state: State { board, turnState: WonBy player }
|
||||||
|
, choices: []
|
||||||
|
}
|
||||||
|
| Array.all isJust board = Game
|
||||||
|
{ state: State { board, turnState: Draw }
|
||||||
|
, choices: []
|
||||||
|
}
|
||||||
|
| otherwise = Game
|
||||||
|
{ state: State { board, turnState: NextMove player }
|
||||||
|
, choices: do
|
||||||
|
emptySpot <- board
|
||||||
|
# Array.mapWithIndex (/\)
|
||||||
|
# Array.mapMaybe \(index /\ piece) -> case piece of
|
||||||
|
Nothing -> Just index
|
||||||
|
Just _ -> Nothing
|
||||||
|
|
||||||
|
let
|
||||||
|
state =
|
||||||
|
{ player: ETuple.other player
|
||||||
|
, board: board #
|
||||||
|
Array.modifyAtIndices
|
||||||
|
[ emptySpot ]
|
||||||
|
(const $ Just $ playerToPiece player)
|
||||||
|
}
|
||||||
|
|
||||||
|
pure (emptySpot /\ (go state))
|
||||||
|
}
|
||||||
|
|
||||||
|
payoff :: Int -> State -> Maybe (Payoff /\ Payoff)
|
||||||
|
payoff depth (State { turnState }) = case turnState of
|
||||||
|
WonBy player -> Just $ ETuple.lookupPair player ((pos /\ neg) /\ (neg /\ pos))
|
||||||
|
Draw -> Just (mid /\ mid)
|
||||||
|
_ -> Nothing
|
||||||
|
where
|
||||||
|
depth' = Int.toNumber depth
|
||||||
|
pos = Payoff (1.0 / depth')
|
||||||
|
mid = Payoff 0.0
|
||||||
|
neg = Payoff (-1.0 / depth')
|
||||||
|
|
||||||
|
---------- Display stuff
|
||||||
|
displayChoice :: forall a. Choice -> Doc a
|
||||||
|
displayChoice num = Array.intercalate DD.space
|
||||||
|
[ DD.text "Place a piece at"
|
||||||
|
, DD.text "("
|
||||||
|
<> DD.text (show (num `mod` 3))
|
||||||
|
<> DD.text ","
|
||||||
|
<> DD.space
|
||||||
|
<> DD.text (show (num / 3))
|
||||||
|
<> DD.text ")"
|
||||||
|
]
|
||||||
|
|
||||||
|
displayState :: forall a. State -> Doc a
|
||||||
|
displayState (State { board, turnState }) = DD.lines
|
||||||
|
[ intercalate DD.space (map printPiece $ Array.slice 0 3 board)
|
||||||
|
, intercalate DD.space (map printPiece $ Array.slice 3 6 board)
|
||||||
|
, intercalate DD.space (map printPiece $ Array.slice 6 9 board)
|
||||||
|
|
||||||
|
-- for 10 element version
|
||||||
|
-- , intercalate DD.space (map printPiece $ Array.slice 9 10 board)
|
||||||
|
, DD.text "State: " <> printTurnState turnState
|
||||||
|
]
|
||||||
|
where
|
||||||
|
printPiece (Just X) = DD.text "X"
|
||||||
|
printPiece (Just O) = DD.text "O"
|
||||||
|
printPiece Nothing = DD.text "-"
|
||||||
|
|
||||||
|
printTurnState Draw = DD.text "Draw!"
|
||||||
|
printTurnState (NextMove player) = Array.intercalate DD.space
|
||||||
|
[ DD.text "player "
|
||||||
|
, DD.text (ETuple.lookupPair player ("1" /\ "2"))
|
||||||
|
, DD.text "should make a move"
|
||||||
|
]
|
||||||
|
printTurnState (WonBy player) = Array.intercalate DD.space
|
||||||
|
[ DD.text "won by - "
|
||||||
|
, DD.text (ETuple.lookupPair player ("X player" /\ "O player"))
|
||||||
|
]
|
||||||
|
|
||||||
|
---------- Typeclass instances
|
||||||
|
derive instance Eq Piece
|
||||||
|
|
10
purescript/strategy/src/Main.purs
Normal file
10
purescript/strategy/src/Main.purs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
module Main where
|
||||||
|
|
||||||
|
import Prelude
|
||||||
|
|
||||||
|
import Effect (Effect)
|
||||||
|
import Effect.Console (log)
|
||||||
|
|
||||||
|
main :: Effect Unit
|
||||||
|
main = do
|
||||||
|
log "🍝"
|
11
purescript/strategy/test/Main.purs
Normal file
11
purescript/strategy/test/Main.purs
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
module Test.Main where
|
||||||
|
|
||||||
|
import Prelude
|
||||||
|
|
||||||
|
import Effect (Effect)
|
||||||
|
import Effect.Class.Console (log)
|
||||||
|
|
||||||
|
main :: Effect Unit
|
||||||
|
main = do
|
||||||
|
log "🍝"
|
||||||
|
log "You should add some tests."
|
Loading…
Reference in a new issue