1
Fork 0

Added strategy

This commit is contained in:
Matei Adriel 2023-10-29 02:53:29 +01:00
parent 553809fc56
commit 8ebe233bec
10 changed files with 603 additions and 0 deletions

View file

@ -21,5 +21,6 @@
| [purpleflow](./purpleflow) | Unfinished dependently-typed programming language |
| [slice](./slice) | Basic benchmarks and a `Slice` type |
| [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` |
| [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
View file

@ -0,0 +1,10 @@
/bower_components/
/node_modules/
/.pulp-cache/
/output/
/generated-docs/
/.psc-package/
/.psc*
/.purs*
/.psa*
/.spago

View 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

View 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" ]
}

View 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

View 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

View 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

View 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

View file

@ -0,0 +1,10 @@
module Main where
import Prelude
import Effect (Effect)
import Effect.Console (log)
main :: Effect Unit
main = do
log "🍝"

View 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."