Compare commits
41
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
df561c2b85
|
||
|
|
de36cf244c
|
||
|
|
3aa6a62391
|
||
|
|
40908ddcf3
|
||
|
|
0984a188db | ||
|
|
444e50cdb1 | ||
|
|
3306e349d3 | ||
|
|
c6f43b2c96 | ||
|
|
195fd7ec34 | ||
|
|
4a89eddc24 | ||
|
|
dd629db320 | ||
|
|
fac461b759 | ||
|
|
1c3f85b9a6 | ||
|
|
a7824bebea | ||
|
|
be52a008df | ||
|
|
2567bf4cd9 | ||
|
|
5241033cb3 | ||
|
|
a1e45a0db4 | ||
|
|
18aa516905 | ||
|
|
cc9223245b | ||
|
|
8f692d36ac | ||
|
|
9eb443638a | ||
|
|
696c76887e | ||
|
|
030b3defd0 | ||
|
|
1ccce66d4a | ||
|
|
b6b92c2cf9 | ||
|
|
e8ce4d60f8 | ||
|
|
c2389a95c0 | ||
|
|
fc28c2918b | ||
|
|
672746e302 | ||
|
|
5846a22d8a | ||
|
|
173bd0df2e | ||
|
|
cbdf357121 | ||
|
|
c9eb1b5bc9 | ||
|
|
b94584aee4 | ||
|
|
255971b2f5 | ||
|
|
7138f74e8e | ||
|
|
409ef29da1 | ||
|
|
045b3fc00a | ||
|
|
30406df4d7 | ||
|
|
98e875eeea |
@@ -2,6 +2,7 @@
|
||||
|
||||
!*.*
|
||||
!*/
|
||||
!LICENSE
|
||||
|
||||
*.hi
|
||||
*.o
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
Copyright Author name here (c) 2019
|
||||
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following
|
||||
disclaimer in the documentation and/or other materials provided
|
||||
with the distribution.
|
||||
|
||||
* Neither the name of Author name here nor the names of other
|
||||
contributors may be used to endorse or promote products derived
|
||||
from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
@@ -1,88 +0,0 @@
|
||||
module Operations where
|
||||
|
||||
import Control.Monad.State
|
||||
import System.Random (newStdGen, randoms)
|
||||
import Data.List
|
||||
import Data.Ord
|
||||
|
||||
import Card
|
||||
import Skat
|
||||
import Pile
|
||||
import Player (chooseCard, Players(..), Player(..), PL(..),
|
||||
updatePlayer, playersToList, player)
|
||||
import Utils (shuffle)
|
||||
|
||||
compareRender :: Card -> Card -> Ordering
|
||||
compareRender (Card t1 c1) (Card t2 c2) = case compare c1 c2 of
|
||||
EQ -> compare t1 t2
|
||||
v -> v
|
||||
|
||||
sortRender :: [Card] -> [Card]
|
||||
sortRender = sortBy compareRender
|
||||
|
||||
turnGeneric :: (PL -> Skat Card)
|
||||
-> Int
|
||||
-> Hand
|
||||
-> Skat (Int, Int)
|
||||
turnGeneric playFunc depth n = do
|
||||
table <- getp tableCards
|
||||
ps <- gets players
|
||||
let p = player ps n
|
||||
hand <- getp $ handCards n
|
||||
trCol <- gets trumpColour
|
||||
case length table of
|
||||
0 -> playFunc p >> turnGeneric playFunc depth (next n)
|
||||
1 -> do
|
||||
modify $ setTurnColour
|
||||
(Just $ effectiveColour trCol $ head table)
|
||||
playFunc p
|
||||
turnGeneric playFunc depth (next n)
|
||||
2 -> playFunc p >> turnGeneric playFunc depth (next n)
|
||||
3 -> do
|
||||
w <- evaluateTable
|
||||
if depth <= 1 || length hand == 0
|
||||
then countGame
|
||||
else turnGeneric playFunc (depth - 1) w
|
||||
|
||||
turn :: Hand -> Skat (Int, Int)
|
||||
turn n = turnGeneric play 10 n
|
||||
|
||||
evaluateTable :: Skat Hand
|
||||
evaluateTable = do
|
||||
trumpCol <- gets trumpColour
|
||||
turnCol <- gets turnColour
|
||||
table <- getp tableCards
|
||||
ps <- gets players
|
||||
let winningCard = highestCard trumpCol turnCol table
|
||||
Just winnerHand <- getp $ originOfCard winningCard
|
||||
let winner = player ps winnerHand
|
||||
modifyp $ cleanTable (team winner)
|
||||
modify $ setTurnColour Nothing
|
||||
return $ hand winner
|
||||
|
||||
countGame :: Skat (Int, Int)
|
||||
countGame = getp count
|
||||
|
||||
play :: (Show p, Player p) => p -> Skat Card
|
||||
play p = do
|
||||
liftIO $ putStrLn "playing"
|
||||
table <- getp tableCardsS
|
||||
turnCol <- gets turnColour
|
||||
trump <- gets trumpColour
|
||||
hand <- getp $ handCards (hand p)
|
||||
fallen <- getp played
|
||||
(card, p') <- chooseCard p table fallen hand
|
||||
modifyPlayers $ updatePlayer p'
|
||||
modifyp $ playCard card
|
||||
ps <- fmap playersToList $ gets players
|
||||
table' <- getp tableCardsS
|
||||
ps' <- mapM (\p -> onCardPlayed p (head table')) ps
|
||||
mapM_ (modifyPlayers . updatePlayer) ps'
|
||||
return card
|
||||
|
||||
playOpen :: (Show p, Player p) => p -> Skat Card
|
||||
playOpen p = do
|
||||
--liftIO $ putStrLn $ show (hand p) ++ " playing open"
|
||||
card <- chooseCardOpen p
|
||||
modifyp $ playCard card
|
||||
return card
|
||||
@@ -1 +1,4 @@
|
||||
# skat
|
||||
# Skat
|
||||
|
||||
This is a Haskell implementation of the famous german card game Skat. It provides
|
||||
a library implementing all the game mechanics and a simple AI.
|
||||
|
||||
+47
-15
@@ -12,13 +12,24 @@ import Skat.Card
|
||||
import Skat.Operations
|
||||
import Skat.Player
|
||||
import Skat.Pile
|
||||
import Skat.Bidding
|
||||
|
||||
import Skat.AI.Stupid
|
||||
import Skat.AI.Online
|
||||
import Skat.AI.Rulebased
|
||||
import Skat.AI.Minmax (playCLI)
|
||||
import Skat.AI.Games.Skat.Guess
|
||||
import Skat.AI.Skat (playSkat)
|
||||
|
||||
main :: IO ()
|
||||
main = testAI 10
|
||||
main = playSkat 42
|
||||
|
||||
{-
|
||||
testMinmax :: Int -> IO ()
|
||||
testMinmax n = do
|
||||
let acs = repeat playSkat
|
||||
sequence_ (take n acs)
|
||||
-}
|
||||
|
||||
testAI :: Int -> IO ()
|
||||
testAI n = do
|
||||
@@ -31,19 +42,20 @@ runAI = do
|
||||
env <- shuffledEnv
|
||||
let ps = piles env
|
||||
cs = handCards Hand3 ps
|
||||
trs = filter (isTrump Spades) cs
|
||||
trs = filter (isTrump $ TrumpColour Spades) cs
|
||||
if length trs >= 5 && any ((==32) . getID) cs
|
||||
then do
|
||||
pts <- fst <$> evalStateT (turn Hand1) env
|
||||
if pts > 60 then return 1 else return 0
|
||||
pts <- fst <$> evalSkat turn env
|
||||
-- if pts > 60 then return 1 else return 0
|
||||
return pts
|
||||
else runAI
|
||||
|
||||
env :: SkatEnv
|
||||
env = SkatEnv piles Nothing Spades playersExamp
|
||||
env = SkatEnv piles Nothing (Colour Spades Einfach) playersExamp Hand1 Hand3
|
||||
where piles = distribute allCards
|
||||
|
||||
envStupid :: SkatEnv
|
||||
envStupid = SkatEnv piles Nothing Spades pls2
|
||||
envStupid = SkatEnv piles Nothing (Colour Spades Einfach) pls2 Hand1 Hand3
|
||||
where piles = distribute allCards
|
||||
|
||||
playersExamp :: Players
|
||||
@@ -56,22 +68,37 @@ pls2 :: Players
|
||||
pls2 = Players
|
||||
(PL $ Stupid Team Hand1)
|
||||
(PL $ Stupid Team Hand2)
|
||||
(PL $ Stupid Team Hand3)
|
||||
(PL $ Stupid Single Hand3)
|
||||
|
||||
shuffledEnv :: IO SkatEnv
|
||||
shuffledEnv = do
|
||||
cards <- shuffleCards
|
||||
return $ SkatEnv (distribute cards) Nothing Spades playersExamp
|
||||
return $ SkatEnv (distribute cards) Nothing (Colour Spades Einfach) playersExamp Hand1 Hand3
|
||||
|
||||
shuffledEnv2 :: IO SkatEnv
|
||||
shuffledEnv2 = do
|
||||
cards <- shuffleCards
|
||||
return $ SkatEnv (distribute cards) Nothing (Colour Spades Einfach) pls2 Hand1 Hand3
|
||||
|
||||
env2 :: SkatEnv
|
||||
env2 = SkatEnv piles Nothing Spades playersExamp
|
||||
where hand1 = [Card Seven Clubs, Card King Clubs, Card Ace Clubs, Card Queen Diamonds]
|
||||
hand2 = [Card Seven Hearts, Card King Hearts, Card Ace Hearts, Card Queen Spades]
|
||||
env2 = SkatEnv piles Nothing (Colour Hearts Einfach) playersExamp Hand2 Hand3
|
||||
where hand1 = [Card Eight Hearts, Card Queen Hearts, Card Ace Clubs, Card Queen Diamonds]
|
||||
hand2 = [Card Seven Hearts, Card King Hearts, Card Ten Hearts, Card Queen Spades]
|
||||
hand3 = [Card Seven Spades, Card King Spades, Card Ace Spades, Card Queen Clubs]
|
||||
h1 = map (putAt Hand1) hand1
|
||||
h2 = map (putAt Hand2) hand2
|
||||
h3 = map (putAt Hand3) hand3
|
||||
piles = Piles (h1 ++ h2 ++ h3) [] []
|
||||
piles = emptyPiles hand1 hand2 hand3 []
|
||||
|
||||
env3 :: SkatEnv
|
||||
env3 = SkatEnv piles Nothing (Colour Diamonds Einfach) pls2 Hand3 Hand3
|
||||
where hand1 = [ Card Jack Diamonds, Card Jack Clubs, Card Nine Spades, Card King Spades
|
||||
, Card Seven Diamonds, Card Nine Diamonds, Card Seven Clubs, Card Eight Clubs
|
||||
, Card Ten Clubs, Card Eight Hearts ]
|
||||
hand2 = [ Card Seven Spades, Card Eight Spades, Card Seven Hearts, Card Nine Hearts
|
||||
, Card Ace Hearts, Card King Diamonds, Card Ace Diamonds, Card Nine Clubs
|
||||
, Card King Clubs, Card Ace Clubs ]
|
||||
hand3 = [ Card Jack Hearts, Card Jack Spades, Card Ten Spades, Card Ace Spades, Card Eight Diamonds
|
||||
, Card Queen Diamonds, Card Ten Diamonds, Card Ten Hearts, Card Queen Hearts, Card King Hearts ]
|
||||
skat = [ Card Queen Clubs, Card Queen Spades]
|
||||
piles = emptyPiles hand1 hand2 hand3 skat
|
||||
|
||||
runWebSocketServer :: IO ()
|
||||
runWebSocketServer = do
|
||||
@@ -84,3 +111,8 @@ application pending = do
|
||||
forever $ do
|
||||
msg <- WS.receiveData conn
|
||||
putStrLn $ BS.unpack msg
|
||||
|
||||
{-
|
||||
playSkat :: IO ()
|
||||
playSkat = void $ (flip runSkat) env3 playCLI
|
||||
-}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
module TestEnvs where
|
||||
|
||||
import Skat
|
||||
import Skat.Card
|
||||
import Skat.Pile
|
||||
import Skat.Player
|
||||
import Skat.AI.Stupid
|
||||
import Skat.Bidding
|
||||
|
||||
pls2 :: Players
|
||||
pls2 = Players
|
||||
(PL $ Stupid Team Hand1)
|
||||
(PL $ Stupid Team Hand2)
|
||||
(PL $ Stupid Single Hand3)
|
||||
|
||||
env3 :: SkatEnv
|
||||
env3 = SkatEnv piles Nothing (Colour Diamonds Einfach) pls2 Hand3 Hand3
|
||||
where hand1 = [ Card Jack Diamonds, Card Jack Clubs, Card Nine Spades, Card King Spades
|
||||
, Card Seven Diamonds, Card Nine Diamonds, Card Seven Clubs, Card Eight Clubs
|
||||
, Card Ten Clubs, Card Eight Hearts ]
|
||||
hand2 = [ Card Seven Spades, Card Eight Spades, Card Seven Hearts, Card Nine Hearts
|
||||
, Card Ace Hearts, Card King Diamonds, Card Ace Diamonds, Card Nine Clubs
|
||||
, Card King Clubs, Card Ace Clubs ]
|
||||
hand3 = [ Card Jack Hearts, Card Jack Spades, Card Ten Spades, Card Ace Spades, Card Eight Diamonds
|
||||
, Card Queen Diamonds, Card Ten Diamonds, Card Ten Hearts, Card Queen Hearts, Card King Hearts ]
|
||||
skat = [ Card Queen Clubs, Card Queen Spades]
|
||||
piles = emptyPiles hand1 hand2 hand3 skat
|
||||
|
||||
shuffledEnv2 :: IO SkatEnv
|
||||
shuffledEnv2 = do
|
||||
cards <- shuffleCards
|
||||
return $ SkatEnv (distribute cards) Nothing (Colour Spades Einfach) pls2 Hand1 Hand3
|
||||
+10
-5
@@ -1,10 +1,10 @@
|
||||
name: skat
|
||||
version: 0.1.0.0
|
||||
version: 0.1.0.8
|
||||
github: "githubuser/skat"
|
||||
license: BSD3
|
||||
author: "Author name here"
|
||||
maintainer: "example@example.com"
|
||||
copyright: "2019 Author name here"
|
||||
author: "flavis"
|
||||
maintainer: "christian@flavigny.de"
|
||||
copyright: "2019"
|
||||
|
||||
extra-source-files:
|
||||
- README.md
|
||||
@@ -17,7 +17,7 @@ extra-source-files:
|
||||
# To avoid duplicated efforts in documentation and dealing with the
|
||||
# complications of embedding Haddock markup inside cabal files, it is
|
||||
# common to point users to the README.md file.
|
||||
description: Please see the README on GitHub at <https://github.com/githubuser/skat#readme>
|
||||
description: Please see the README on Gitea at <https://git.flavigny.de/christian/skat>
|
||||
|
||||
dependencies:
|
||||
- base >= 4.7 && < 5
|
||||
@@ -33,6 +33,9 @@ dependencies:
|
||||
- parallel
|
||||
- containers
|
||||
- case-insensitive
|
||||
- vector
|
||||
- transformers
|
||||
- exceptions
|
||||
|
||||
library:
|
||||
source-dirs: src
|
||||
@@ -45,6 +48,7 @@ executables:
|
||||
- -threaded
|
||||
- -rtsopts
|
||||
- -with-rtsopts=-N
|
||||
- -O2
|
||||
dependencies:
|
||||
- skat
|
||||
|
||||
@@ -56,5 +60,6 @@ tests:
|
||||
- -threaded
|
||||
- -rtsopts
|
||||
- -with-rtsopts=-N
|
||||
- -O2
|
||||
dependencies:
|
||||
- skat
|
||||
|
||||
+29
-9
@@ -1,19 +1,19 @@
|
||||
cabal-version: 1.12
|
||||
|
||||
-- This file has been generated from package.yaml by hpack version 0.31.2.
|
||||
-- This file has been generated from package.yaml by hpack version 0.35.0.
|
||||
--
|
||||
-- see: https://github.com/sol/hpack
|
||||
--
|
||||
-- hash: e2db48733c92b94d7f2d8f4991dd2f7cec26d59666cd3c618710a8a3c22616d0
|
||||
-- hash: 8a975ca39edf7adfa4bbf95bd068d1b2f4f3fa9e954eb61fa3cf553f03b7dd56
|
||||
|
||||
name: skat
|
||||
version: 0.1.0.0
|
||||
description: Please see the README on GitHub at <https://github.com/githubuser/skat#readme>
|
||||
version: 0.1.0.8
|
||||
description: Please see the README on Gitea at <https://git.flavigny.de/christian/skat>
|
||||
homepage: https://github.com/githubuser/skat#readme
|
||||
bug-reports: https://github.com/githubuser/skat/issues
|
||||
author: Author name here
|
||||
maintainer: example@example.com
|
||||
copyright: 2019 Author name here
|
||||
author: flavis
|
||||
maintainer: christian@flavigny.de
|
||||
copyright: 2019
|
||||
license: BSD3
|
||||
license-file: LICENSE
|
||||
build-type: Simple
|
||||
@@ -28,16 +28,26 @@ source-repository head
|
||||
library
|
||||
exposed-modules:
|
||||
Skat
|
||||
Skat.AI.Base
|
||||
Skat.AI.Games.Skat.Guess
|
||||
Skat.AI.Human
|
||||
Skat.AI.Markov
|
||||
Skat.AI.Minmax
|
||||
Skat.AI.MonteCarlo
|
||||
Skat.AI.Online
|
||||
Skat.AI.Rulebased
|
||||
Skat.AI.Server
|
||||
Skat.AI.Skat
|
||||
Skat.AI.Stupid
|
||||
Skat.AI.TicTacToe
|
||||
Skat.Bidding
|
||||
Skat.Card
|
||||
Skat.Matches
|
||||
Skat.Operations
|
||||
Skat.Pile
|
||||
Skat.Player
|
||||
Skat.Player.Utils
|
||||
Skat.Preperation
|
||||
Skat.Render
|
||||
Skat.Utils
|
||||
Skat.WebSocketServer
|
||||
@@ -52,22 +62,26 @@ library
|
||||
, case-insensitive
|
||||
, containers
|
||||
, deepseq
|
||||
, exceptions
|
||||
, mtl
|
||||
, network
|
||||
, parallel
|
||||
, random
|
||||
, split
|
||||
, text
|
||||
, transformers
|
||||
, vector
|
||||
, websockets
|
||||
default-language: Haskell2010
|
||||
|
||||
executable skat-exe
|
||||
main-is: Main.hs
|
||||
other-modules:
|
||||
TestEnvs
|
||||
Paths_skat
|
||||
hs-source-dirs:
|
||||
app
|
||||
ghc-options: -threaded -rtsopts -with-rtsopts=-N
|
||||
ghc-options: -threaded -rtsopts -with-rtsopts=-N -O2
|
||||
build-depends:
|
||||
aeson
|
||||
, base >=4.7 && <5
|
||||
@@ -75,6 +89,7 @@ executable skat-exe
|
||||
, case-insensitive
|
||||
, containers
|
||||
, deepseq
|
||||
, exceptions
|
||||
, mtl
|
||||
, network
|
||||
, parallel
|
||||
@@ -82,6 +97,8 @@ executable skat-exe
|
||||
, skat
|
||||
, split
|
||||
, text
|
||||
, transformers
|
||||
, vector
|
||||
, websockets
|
||||
default-language: Haskell2010
|
||||
|
||||
@@ -92,7 +109,7 @@ test-suite skat-test
|
||||
Paths_skat
|
||||
hs-source-dirs:
|
||||
test
|
||||
ghc-options: -threaded -rtsopts -with-rtsopts=-N
|
||||
ghc-options: -threaded -rtsopts -with-rtsopts=-N -O2
|
||||
build-depends:
|
||||
aeson
|
||||
, base >=4.7 && <5
|
||||
@@ -100,6 +117,7 @@ test-suite skat-test
|
||||
, case-insensitive
|
||||
, containers
|
||||
, deepseq
|
||||
, exceptions
|
||||
, mtl
|
||||
, network
|
||||
, parallel
|
||||
@@ -107,5 +125,7 @@ test-suite skat-test
|
||||
, skat
|
||||
, split
|
||||
, text
|
||||
, transformers
|
||||
, vector
|
||||
, websockets
|
||||
default-language: Haskell2010
|
||||
|
||||
+40
-10
@@ -1,49 +1,79 @@
|
||||
{-# LANGUAGE NamedFieldPuns #-}
|
||||
{-# LANGUAGE TypeSynonymInstances #-}
|
||||
{-# LANGUAGE FlexibleInstances #-}
|
||||
{-# LANGUAGE FlexibleContexts #-}
|
||||
|
||||
module Skat where
|
||||
|
||||
import Control.Monad.State
|
||||
import Control.Monad.Writer
|
||||
import Control.Monad.Reader
|
||||
import Data.List
|
||||
import Data.Vector (Vector)
|
||||
|
||||
import Skat.Card
|
||||
import Skat.Bidding
|
||||
import Skat.Pile
|
||||
import Skat.Player (Players)
|
||||
import qualified Skat.Player as P
|
||||
|
||||
data SkatEnv = SkatEnv { piles :: Piles
|
||||
, turnColour :: Maybe Colour
|
||||
, trumpColour :: Colour
|
||||
, players :: Players }
|
||||
, turnColour :: Maybe TurnColour
|
||||
, skatGame :: Game
|
||||
, players :: Players
|
||||
, currentHand :: Hand
|
||||
, skatSinglePlayer :: Hand }
|
||||
deriving Show
|
||||
|
||||
type Skat = StateT SkatEnv IO
|
||||
type Skat = StateT SkatEnv (WriterT [Trick] IO)
|
||||
|
||||
runSkat :: Skat a -> SkatEnv -> IO (a, SkatEnv, [Trick])
|
||||
runSkat action env = do
|
||||
((val, env'), tricks) <- runWriterT $ runStateT action env
|
||||
return (val, env', tricks)
|
||||
|
||||
evalSkat :: Skat a -> SkatEnv -> IO a
|
||||
evalSkat action = (fmap fst) . runWriterT . evalStateT action
|
||||
|
||||
execSkat :: Skat a -> SkatEnv -> IO SkatEnv
|
||||
execSkat action = (fmap fst) . runWriterT . execStateT action
|
||||
|
||||
instance P.MonadPlayer Skat where
|
||||
trumpColour = gets trumpColour
|
||||
trump = getTrump <$> P.game
|
||||
turnColour = gets turnColour
|
||||
showSkat p = case P.team p of
|
||||
Single -> fmap (Just . skatCards) $ gets piles
|
||||
Team -> return Nothing
|
||||
singlePlayer = gets skatSinglePlayer
|
||||
game = gets skatGame
|
||||
|
||||
instance P.MonadPlayerOpen Skat where
|
||||
showPiles = gets piles
|
||||
|
||||
modifyp :: (Piles -> Piles) -> Skat ()
|
||||
modifyp :: MonadState SkatEnv m => (Piles -> Piles) -> m ()
|
||||
modifyp f = modify g
|
||||
where g env@(SkatEnv {piles}) = env { piles = f piles}
|
||||
|
||||
getp :: (Piles -> a) -> Skat a
|
||||
getp :: MonadState SkatEnv m => (Piles -> a) -> m a
|
||||
getp f = gets piles >>= return . f
|
||||
|
||||
modifyPlayers :: (Players -> Players) -> Skat ()
|
||||
modifyPlayers :: MonadState SkatEnv m => (Players -> Players) -> m ()
|
||||
modifyPlayers f = modify g
|
||||
where g env@(SkatEnv {players}) = env { players = f players }
|
||||
|
||||
setTurnColour :: Maybe Colour -> SkatEnv -> SkatEnv
|
||||
setTurnColour :: Maybe TurnColour -> SkatEnv -> SkatEnv
|
||||
setTurnColour col sk = sk { turnColour = col }
|
||||
|
||||
mkSkatEnv :: Piles -> Maybe Colour -> Colour -> Players -> SkatEnv
|
||||
setCurrentHand :: Hand -> SkatEnv -> SkatEnv
|
||||
setCurrentHand hand sk = sk { currentHand = hand }
|
||||
|
||||
mkSkatEnv :: Piles -> Maybe TurnColour -> Game -> Players -> Hand -> Hand -> SkatEnv
|
||||
mkSkatEnv = SkatEnv
|
||||
|
||||
allowedCards :: (P.MonadPlayer m, MonadState SkatEnv m) => m [CardS Owner]
|
||||
allowedCards = do
|
||||
curHand <- gets currentHand
|
||||
pls <- gets players
|
||||
turnCol <- P.turnColour
|
||||
trumpCol <- P.trump
|
||||
getp $ allowed curHand trumpCol turnCol
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
{-# LANGUAGE MultiParamTypeClasses #-}
|
||||
{-# LANGUAGE TypeSynonymInstances #-}
|
||||
{-# LANGUAGE FlexibleInstances #-}
|
||||
{-# LANGUAGE FlexibleContexts #-}
|
||||
{-# LANGUAGE FunctionalDependencies #-}
|
||||
{-# LANGUAGE TupleSections #-}
|
||||
|
||||
module Skat.AI.Base where
|
||||
|
||||
import Data.Set (Set)
|
||||
import qualified Data.Set as S
|
||||
import System.Random (Random)
|
||||
import qualified System.Random as Rand
|
||||
import Control.Monad.State
|
||||
import Control.Exception (assert)
|
||||
import Control.Monad.Fail
|
||||
import Data.Ord
|
||||
import Text.Read (readMaybe)
|
||||
import Data.List (maximumBy, sortBy)
|
||||
import Debug.Trace
|
||||
|
||||
class (Ord v, Eq v) => Value v where
|
||||
invert :: v -> v
|
||||
win :: v
|
||||
loss :: v
|
||||
tie :: v
|
||||
tonum :: v -> Float
|
||||
tonum v
|
||||
| v == win = 1.0
|
||||
| v == loss = 0.0
|
||||
| v == tie = 0.5
|
||||
|
||||
class Player p where
|
||||
maxing :: p -> Bool
|
||||
|
||||
class (Traversable l, Monad m, Value v, Player p, Eq t) => MonadGame t l v p m | m -> t, m -> p, m -> v, m -> l where
|
||||
currentPlayer :: m p
|
||||
turns :: m (l t)
|
||||
play :: t -> m ()
|
||||
simulate :: t -> m a -> m a
|
||||
evaluate :: m v
|
||||
over :: m Bool
|
||||
|
||||
class (MonadIO m, Show t, Show v, Show p, MonadGame t l v p m) => PlayableGame t l v p m | m -> t, m -> p, m -> v where
|
||||
showTurns :: m ()
|
||||
showBoard :: m ()
|
||||
askTurn :: m (Maybe t)
|
||||
showTurn :: t -> m ()
|
||||
winner :: m (Maybe p)
|
||||
|
||||
class Choose t m | m -> t where
|
||||
choose :: m t
|
||||
|
||||
class Monad m => MonadRandom m where
|
||||
random :: Random a => m a
|
||||
randomR :: Random a => (a, a) -> m a
|
||||
chooser :: [a] -> m a
|
||||
chooser [] = error "chooser: empty list"
|
||||
chooser os = (os!!) <$> randomR (0, length os - 1)
|
||||
chooserS :: Set a -> m a
|
||||
chooserS set
|
||||
| S.null set = error "chooserS: empty set"
|
||||
| otherwise = (`S.elemAt` set) <$> randomR (0, S.size set - 1)
|
||||
|
||||
instance MonadRandom IO where
|
||||
random = Rand.randomIO
|
||||
randomR = Rand.randomRIO
|
||||
|
||||
instance MonadRandom (State Rand.StdGen) where
|
||||
random = do
|
||||
gen <- get
|
||||
let (a, gen') = Rand.random gen
|
||||
put gen'
|
||||
return a
|
||||
randomR bds = do
|
||||
gen <- get
|
||||
let (a, gen') = Rand.randomR bds gen
|
||||
put gen'
|
||||
return a
|
||||
@@ -0,0 +1,338 @@
|
||||
{-# LANGUAGE DeriveGeneric #-}
|
||||
{-# LANGUAGE DeriveAnyClass #-}
|
||||
{-# LANGUAGE BangPatterns #-}
|
||||
|
||||
module Skat.AI.Games.Skat.Guess where
|
||||
|
||||
import GHC.Generics (Generic, Generic1)
|
||||
import Data.Ord
|
||||
import Data.Aeson
|
||||
import Data.Monoid ((<>))
|
||||
import Data.List
|
||||
import Data.Set (Set)
|
||||
import qualified Data.Set as S
|
||||
import Control.Monad.State
|
||||
import Control.Monad.Reader
|
||||
import Data.Map.Strict (Map)
|
||||
import qualified Data.Map.Strict as M
|
||||
import Data.List (delete)
|
||||
import Data.Bits
|
||||
import Debug.Trace
|
||||
|
||||
import Skat
|
||||
import Skat.AI.Base
|
||||
import Skat.Utils
|
||||
import Skat.Card
|
||||
import Skat.Pile
|
||||
import Skat.Player
|
||||
import Skat.Player
|
||||
import Control.Parallel.Strategies
|
||||
import Control.DeepSeq
|
||||
|
||||
data Option = H Hand
|
||||
| Skt
|
||||
deriving (Show, Eq, Ord, Generic, NFData, ToJSON)
|
||||
|
||||
type Guess = Map Card (Set Option)
|
||||
|
||||
newGuess :: Guess
|
||||
newGuess = newGuessWith allCards
|
||||
|
||||
newGuessWith :: [Card] -> Guess
|
||||
newGuessWith cards = M.fromList l
|
||||
where l = map (\c -> (c, S.fromList [H Hand1, H Hand2, H Hand3, Skt])) cards
|
||||
|
||||
hasBeenPlayed :: Card -> Guess -> Guess
|
||||
hasBeenPlayed card = M.delete card
|
||||
|
||||
has :: Hand -> [Card] -> Guess -> Guess
|
||||
has hand cs = M.mapWithKey f
|
||||
where f card hands
|
||||
| card `elem` cs = S.singleton (H hand)
|
||||
| otherwise = hands
|
||||
|
||||
hasOnly :: Hand -> [Card] -> Guess -> Guess
|
||||
hasOnly hand cs = M.mapWithKey f
|
||||
where f card hands
|
||||
| card `elem` cs = S.singleton (H hand)
|
||||
| otherwise = S.delete (H hand) hands
|
||||
|
||||
hasOnly_ :: Option -> [Card] -> Guess -> Guess
|
||||
hasOnly_ option cs = M.mapWithKey f
|
||||
where f card hands
|
||||
| card `elem` cs = S.singleton option
|
||||
| otherwise = S.delete option hands
|
||||
|
||||
hasNoLonger :: Trump -> Hand -> TurnColour -> Guess -> Guess
|
||||
hasNoLonger trump hand effCol = M.mapWithKey f
|
||||
where f card hands
|
||||
| effectiveColour trump card == effCol && (H hand) `S.member` hands =
|
||||
S.filter (/=H hand) hands
|
||||
| otherwise = hands
|
||||
|
||||
observe :: Trump -> Maybe TurnColour -> [CardS Played] -> Guess -> Guess
|
||||
observe _ Nothing _ guess = guess
|
||||
observe trpCol (Just turnCol) tbl oldGuess = foldr f oldGuess tbl
|
||||
where f :: CardS Played -> Guess -> Guess
|
||||
f c g = let col = effectiveColour trpCol (toCard c)
|
||||
in if col /= turnCol
|
||||
then hasNoLonger trpCol (uorigin $ getPile c) turnCol g
|
||||
else g
|
||||
|
||||
observeS :: Guess -> Skat Guess
|
||||
observeS guess = do
|
||||
trpCol <- trump
|
||||
turnCol <- gets Skat.turnColour
|
||||
tbl <- getp tableCards
|
||||
pure $ observe trpCol turnCol tbl guess
|
||||
|
||||
isSkat :: [Card] -> Guess -> Guess
|
||||
isSkat cs = M.mapWithKey f
|
||||
where f card hands
|
||||
| card `elem` cs = S.singleton Skt
|
||||
| otherwise = if length cs == 2 then S.delete Skt hands else hands
|
||||
|
||||
choosen1 :: Int -> [a] -> [[a]]
|
||||
choosen1 !n !cs = map f (filter ((==n) . popCount) [0..(m-1)])
|
||||
where m = 2^(length cs) :: Int
|
||||
f !i = collect $! filter (< length cs) $! getSetBits i
|
||||
collect !idx = map (cs!!) $! idx
|
||||
|
||||
getSetBits :: Int -> [Int]
|
||||
getSetBits !a = filter (\i -> 2^i .&. a /= 0) [0..a]
|
||||
{-# INLINE getSetBits #-}
|
||||
|
||||
choosen2 :: Int -> [a] -> [[a]]
|
||||
choosen2 !n !cs = map f (filter ((==n) . popCount) [0..(m-1)])
|
||||
where m = 2^(length cs) :: Int
|
||||
f !i = filterMap (g i) fst $! zip cs [0..]
|
||||
g !i (c, k) = 2^k .&. i /= 0
|
||||
|
||||
choosen = choosen2
|
||||
|
||||
smplguess :: Guess
|
||||
smplguess = Hand1 `hasOnly` [(Card Seven Diamonds)..(Card Eight Hearts)] $! newGuess
|
||||
|
||||
smplguess2 :: Guess
|
||||
smplguess2 = M.fromList
|
||||
[ ( Card Seven Diamonds, S.fromList [H Hand2, H Hand3] )
|
||||
, ( Card Eight Hearts, S.fromList [H Hand2, H Hand1] )
|
||||
, ( Card Nine Spades, S.fromList [H Hand1, H Hand2] )
|
||||
, ( Card Nine Diamonds, S.fromList [Skt] )
|
||||
, ( Card Eight Diamonds, S.fromList [Skt] )
|
||||
]
|
||||
|
||||
smplguess3 :: Guess
|
||||
smplguess3 = M.fromList
|
||||
[ (Card Nine Clubs, S.fromList [Skt])
|
||||
, (Card Queen Clubs, S.fromList [Skt])
|
||||
, (Card Ten Hearts, S.fromList [H Hand2,H Hand3])
|
||||
, (Card Ace Diamonds, S.fromList [H Hand2])
|
||||
]
|
||||
|
||||
smplguess4 :: Guess
|
||||
smplguess4 = M.fromList
|
||||
[ (Card Seven Spades, S.fromList [H Hand1])
|
||||
, (Card Nine Spades, S.fromList [H Hand1])
|
||||
, (Card Eight Spades, S.fromList [H Hand2])
|
||||
, (Card Queen Diamonds, S.fromList [H Hand3])
|
||||
, (Card Ace Diamonds, S.fromList [H Hand3])
|
||||
, (Card King Clubs, S.fromList [H Hand2,H Hand3])
|
||||
, (Card Ace Clubs, S.fromList [H Hand2,H Hand3])
|
||||
, (Card Nine Clubs, S.fromList [Skt])
|
||||
, (Card Queen Clubs, S.fromList [Skt])
|
||||
]
|
||||
|
||||
distributions2 :: Guess -> (Int, Int, Int, Int) -> [Distribution]
|
||||
distributions2 !guess1 !(n1, n2, n3, nskt) = do
|
||||
let h1cards = M.keys $!! M.filter (H Hand1 `elem`) guess1
|
||||
hand1 <- choosen 10 h1cards
|
||||
let guess2 = Hand1 `hasOnly` hand1 $! guess1
|
||||
h2cards = M.keys $!! M.filter (H Hand2 `elem`) guess2
|
||||
hand2 <- choosen 10 h2cards
|
||||
let guess3 = Hand2 `hasOnly` hand2 $! guess2
|
||||
h3cards = M.keys $!! M.filter (H Hand3 `elem`) guess3
|
||||
x = choosen 10 $!! h3cards
|
||||
hand3 <- x
|
||||
--let guess4 = Hand3 `hasOnly` hand3 $! guess3
|
||||
-- sktcards = M.keys $!! M.filter (Skt `elem`) guess4
|
||||
--skt <- choosen (2 + nskt) sktcards
|
||||
return (hand1, hand2, hand3, [])--, skt)
|
||||
|
||||
carddist :: Option -> Int -> Guess -> [[Card]]
|
||||
carddist option n guess = choosen n options
|
||||
where options = M.keys $ M.filter (option `S.member`) guess
|
||||
|
||||
carddistS :: Option -> Int -> StateT Guess [] [Card]
|
||||
carddistS option n = do
|
||||
guess <- get
|
||||
sels <- lift $ carddist option n guess
|
||||
put $ option `hasOnly_` sels $ guess
|
||||
return sels
|
||||
|
||||
distributions3 :: Guess -> (Int, Int, Int, Int) -> [Distribution]
|
||||
distributions3 guess (n1, n2, n3, n4) = (flip evalStateT) guess $ do
|
||||
hand1 <- carddistS (H Hand1) (cardsPerHand + n1)
|
||||
hand2 <- carddistS (H Hand2) (cardsPerHand + n2)
|
||||
hand3 <- carddistS (H Hand3) (cardsPerHand + n3)
|
||||
skt <- carddistS Skt (2 + n4)
|
||||
return (hand1, hand2, hand3, skt)
|
||||
where cardsPerHand = (length guess-2-n1-n2-n3) `div` 3
|
||||
|
||||
randomChoice :: (MonadRandom m, Monad m) => Set Option -> StateT (Int, Int, Int, Int) m Option
|
||||
randomChoice options = do
|
||||
--when (null options) $ error "randomChoice: options are empty"
|
||||
(n1, n2, n3, n4) <- get
|
||||
let g (H Hand1) = n1 > 0
|
||||
g (H Hand2) = n2 > 0
|
||||
g (H Hand3) = n3 > 0
|
||||
g Skt = n4 > 0
|
||||
opts = S.toList $ S.filter g options
|
||||
--when (null opts) $ error "randomChoice: after filtering options are empty"
|
||||
option <- if null opts then (error ("randomChoice: opts empty, " ++ show options ++ " " ++ show (n1,n2,n3,n4))) else lift (chooser opts)
|
||||
let (n1', n2', n3', n4') = case option of
|
||||
H Hand1 -> (n1-1, n2, n3, n4)
|
||||
H Hand2 -> (n1, n2-1, n3, n4)
|
||||
H Hand3 -> (n1, n2, n3-1, n4)
|
||||
Skt -> (n1, n2, n3, n4-1)
|
||||
put (n1', n2', n3', n4')
|
||||
return option
|
||||
|
||||
randomGuess :: (MonadRandom m, Monad m) => Guess -> (Int, Int, Int, Int) -> m Guess
|
||||
randomGuess guess (n1, n2, n3, n4) = (flip evalStateT) ( cardsPerHand + n1
|
||||
, cardsPerHand + n2
|
||||
, cardsPerHand + n3
|
||||
, 2 + n4
|
||||
) $ do
|
||||
foldM helper guess (M.keys guess)
|
||||
where cardsPerHand = (length guess-2-n1-n2-n3) `div` 3
|
||||
helper g card = do
|
||||
let opts = M.findWithDefault (error "findWithDefault") card g
|
||||
o <- randomChoice opts
|
||||
pure $ M.insert card (S.singleton o) g
|
||||
|
||||
choosern :: (Eq a, Monad m, MonadRandom m) => Int -> [a] -> m [a]
|
||||
choosern 0 _ = pure []
|
||||
choosern _ [] = error "chooseRn: list is empty and n /= 0"
|
||||
choosern !n !os = do
|
||||
o <- chooser os
|
||||
let !os' = delete o os
|
||||
rest <- choosern (n-1) os'
|
||||
pure $ o : rest
|
||||
|
||||
choosernS :: (Ord a, Monad m, MonadRandom m) => Int -> Set a -> m (Set a)
|
||||
choosernS 0 _ = pure S.empty
|
||||
choosernS !n !os
|
||||
| S.size os == 0 = error "chooseRn: list is empty and n /= 0"
|
||||
| otherwise = do
|
||||
o <- chooserS os
|
||||
let !os' = S.delete o os
|
||||
rest <- choosernS (n-1) os'
|
||||
pure $ S.insert o rest
|
||||
|
||||
|
||||
snd3 :: (a,b,c) -> b
|
||||
snd3 (a,b,c) = b
|
||||
|
||||
randomDistr2 :: (MonadRandom m, Monad m) => Guess -> (Int, Int, Int, Int) -> m Distribution
|
||||
randomDistr2 guess1 (n1, n2, n3, _) = do
|
||||
let h1cs = M.keysSet $!! M.filter (H Hand1 `S.member`) guess1
|
||||
h2cs = M.keysSet $!! M.filter (H Hand2 `S.member`) guess1
|
||||
h3cs = M.keysSet $!! M.filter (H Hand3 `S.member`) guess1
|
||||
skcs = M.keysSet $!! M.filter (Skt `S.member`) guess1
|
||||
priority = M.filter ((==1) . S.size) guess1
|
||||
predist = M.foldrWithKey
|
||||
(\card opts dist -> M.insertWith (++) (S.elemAt 0 opts) [card] dist)
|
||||
M.empty
|
||||
priority
|
||||
banned = M.keysSet priority
|
||||
pots = sortBy (comparing $ \(_, cs, n) -> length cs - n)
|
||||
$ [ (H Hand1, h1cs, nh1 - length (M.findWithDefault [] (H Hand1) predist))
|
||||
, (H Hand2, h2cs, nh2 - length (M.findWithDefault [] (H Hand2) predist))
|
||||
, (H Hand3, h3cs, nh3 - length (M.findWithDefault [] (H Hand3) predist))
|
||||
, (Skt , skcs, nh4 - length (M.findWithDefault [] Skt predist))
|
||||
]
|
||||
(dist, _) <- foldM f (predist, banned) pots
|
||||
|
||||
return ( M.findWithDefault (error "randomDistr: missing option Hand1") (H Hand1) dist
|
||||
, M.findWithDefault (error "randomDistr: missing option Hand2") (H Hand2) dist
|
||||
, M.findWithDefault (error "randomDistr: missing option Hand3") (H Hand3) dist
|
||||
, M.findWithDefault (error "randomDistr: missing option Skt") Skt dist
|
||||
)
|
||||
where cardsPerHand = (length guess1-2-n1-n2-n3) `div` 3
|
||||
nh1 = cardsPerHand + n1
|
||||
nh2 = cardsPerHand + n2
|
||||
nh3 = cardsPerHand + n3
|
||||
nh4 = 2
|
||||
f (dist, banned) (option, cards, n) = do
|
||||
let available = S.filter (not . (`S.member` banned)) cards
|
||||
cs <- if S.size available < n
|
||||
then error ("Not enough options available: wanted " ++ show n ++ " for " ++ show option ++ " and got " ++ show (length available) ++ ", " ++ show guess1 ++ " with " ++ show (n1, n2, n3))
|
||||
else choosernS n available
|
||||
let dist' = M.insertWith (++) option (S.toList cs) dist
|
||||
pure (dist', S.union banned cs)
|
||||
|
||||
randomDistr :: (MonadRandom m, Monad m) => Guess -> (Int, Int, Int, Int) -> m Distribution
|
||||
randomDistr = randomDistr2
|
||||
|
||||
randomDistr1 :: (MonadRandom m, Monad m) => Guess -> (Int, Int, Int, Int) -> m Distribution
|
||||
randomDistr1 guess (n1, n2, n3, n4) = (flip evalStateT) ( cardsPerHand + n1
|
||||
, cardsPerHand + n2
|
||||
, cardsPerHand + n3
|
||||
, 2 + n4
|
||||
) $ do
|
||||
randomGuess <- foldM helper guess (M.keys guess)
|
||||
let [d] = distributions randomGuess (n1, n2, n3, n4)
|
||||
pure d
|
||||
where cardsPerHand = (length guess-2-n1-n2-n3) `div` 3
|
||||
helper g card = do
|
||||
let opts = M.findWithDefault (error "findWithDefault") card g
|
||||
o <- randomChoice opts
|
||||
pure $ M.insert card (S.singleton o) g
|
||||
{-
|
||||
distributions1 :: Guess -> (Int, Int, Int, Int) -> [Distribution]
|
||||
distributions1 guess nos =
|
||||
helper (sortBy compareGuess $ M.toList guess) nos
|
||||
`using` parList rdeepseq
|
||||
where helper [] _ = []
|
||||
helper ((c, hs):[]) ns = map fst (distr c hs ns)
|
||||
helper ((c, hs):gs) ns =
|
||||
let dsWithNs = distr c hs ns
|
||||
go (d, ns') = map (d <>) (helper gs ns')
|
||||
in concatMap go dsWithNs
|
||||
distr card hands (n1, n2, n3, n4) =
|
||||
let f card (H Hand1) =
|
||||
(([card], [], [], []), (n1+1, n2, n3, n4))
|
||||
f card (H Hand2) =
|
||||
(([], [card], [], []), (n1, n2+1, n3, n4))
|
||||
f card (H Hand3) =
|
||||
(([], [], [card], []), (n1, n2, n3+1, n4))
|
||||
f card Skt =
|
||||
(([], [], [], [card]), (n1, n2, n3, n4+1))
|
||||
isOk (H Hand1) = n1 < cardsPerHand
|
||||
isOk (H Hand2) = n2 < cardsPerHand
|
||||
isOk (H Hand3) = n3 < cardsPerHand
|
||||
isOk Skt = n4 < 2
|
||||
in filterMap isOk (f card) hands
|
||||
cardsPerHand = (length guess - 2) `div` 3
|
||||
-}
|
||||
|
||||
distributions = distributions3
|
||||
|
||||
type Distribution = ([Card], [Card], [Card], [Card])
|
||||
|
||||
compareGuess :: (Card, [Option]) -> (Card, [Option]) -> Ordering
|
||||
compareGuess (c1, ops1) (c2, ops2)
|
||||
| length ops1 == 1 = LT
|
||||
| length ops2 == 1 = GT
|
||||
| c1 > c2 = LT
|
||||
| c1 < c2 = GT
|
||||
|
||||
toPiles :: [CardS Played] -> Distribution -> Piles
|
||||
toPiles table (h1, h2, h3, skt) = makePiles h1 h2 h3 table skt
|
||||
|
||||
updatePiles :: Distribution -> Piles -> Piles
|
||||
updatePiles (h1, h2, h3, skt) piles = piles { _hand1 = fmap (putAt $ P Hand1) h1
|
||||
, _hand2 = fmap (putAt $ P Hand2) h2
|
||||
, _hand3 = fmap (putAt $ P Hand3) h3
|
||||
, _skat = fmap (putAt S) skt }
|
||||
@@ -15,11 +15,11 @@ data Human = Human { getTeam :: Team
|
||||
instance Player Human where
|
||||
team = getTeam
|
||||
hand = getHand
|
||||
chooseCard p table _ hand = do
|
||||
trumpCol <- trumpColour
|
||||
chooseCard p table _ _ hand = do
|
||||
trumpCol <- trump
|
||||
turnCol <- turnColour
|
||||
let possible = filter (isAllowed trumpCol turnCol hand) hand
|
||||
c <- liftIO $ askIO (map getCard table) possible hand
|
||||
c <- liftIO $ askIO (map getCard table) (map toCard possible) (map toCard hand)
|
||||
return $ (c, p)
|
||||
|
||||
askIO :: [Card] -> [Card] -> [Card] -> IO Card
|
||||
|
||||
@@ -0,0 +1,510 @@
|
||||
{-# LANGUAGE MultiParamTypeClasses #-}
|
||||
{-# LANGUAGE BlockArguments #-}
|
||||
{-# LANGUAGE TypeSynonymInstances #-}
|
||||
{-# LANGUAGE FlexibleInstances #-}
|
||||
{-# LANGUAGE FlexibleContexts #-}
|
||||
{-# LANGUAGE FunctionalDependencies #-}
|
||||
{-# LANGUAGE TupleSections #-}
|
||||
{-# LANGUAGE InstanceSigs #-}
|
||||
{-# LANGUAGE StandaloneDeriving #-}
|
||||
{-# LANGUAGE ImportQualifiedPost #-}
|
||||
|
||||
module Skat.AI.Markov (
|
||||
) where
|
||||
|
||||
import Control.Monad.State
|
||||
import Control.Exception (assert)
|
||||
import Control.Monad.Fail
|
||||
import Data.Ord
|
||||
import Text.Read (readMaybe)
|
||||
import Data.List (maximumBy, sortBy, delete)
|
||||
import Debug.Trace
|
||||
import Data.Ratio
|
||||
import Data.Set (Set)
|
||||
import qualified Data.Set as Set
|
||||
import Data.Map (Map)
|
||||
import qualified Data.Map as Map
|
||||
import Data.Bits
|
||||
import Data.Vector (Vector)
|
||||
import qualified Data.Vector as Vector
|
||||
|
||||
import qualified Skat as S
|
||||
import qualified Skat.Card as S
|
||||
import qualified Skat.Operations as S
|
||||
import qualified Skat.Pile as S
|
||||
import qualified Skat.Player as S hiding (trumpColour, turnColour)
|
||||
import qualified Skat.Render as S
|
||||
--import TestEnvs (env3, shuffledEnv2)
|
||||
|
||||
data Possibility d a = Possibility { value :: a
|
||||
, probability :: d
|
||||
}
|
||||
|
||||
newtype Distribution d a = Distribution { runDistribution :: [Possibility d a] }
|
||||
|
||||
instance Num d => Monad (Distribution d) where
|
||||
return :: a -> Distribution d a
|
||||
return x = Distribution [Possibility x 1]
|
||||
(>>=) :: Distribution d a -> (a -> Distribution d b) -> Distribution d b
|
||||
(Distribution ps) >>= f = Distribution $ do
|
||||
(Possibility x1 p1) <- ps
|
||||
let (Distribution ds) = f x1
|
||||
(Possibility x2 p2) <- ds
|
||||
return $ Possibility x2 (p1*p2)
|
||||
|
||||
instance Num d => Applicative (Distribution d) where
|
||||
pure = return
|
||||
(<*>) = ap
|
||||
|
||||
instance Num d => Functor (Distribution d) where
|
||||
fmap = liftM
|
||||
|
||||
sumDist :: (Num d, Ord a) => Distribution d a -> Distribution d a
|
||||
sumDist = distFromMap . distToMap
|
||||
where distToMap (Distribution ps) = Map.fromListWith (+) $ do
|
||||
(Possibility x p) <- ps
|
||||
return (x, p)
|
||||
distFromMap m = Distribution $ do
|
||||
(x, p) <- Map.toList m
|
||||
return $ Possibility x p
|
||||
|
||||
deriving instance (Show d, Show a) => Show (Possibility d a)
|
||||
deriving instance (Show d, Show a) => Show (Distribution d a)
|
||||
deriving instance (Eq d, Eq a) => Eq (Possibility d a)
|
||||
deriving instance (Eq d, Eq a) => Eq (Distribution d a)
|
||||
deriving instance (Ord d, Ord a) => Ord (Possibility d a)
|
||||
deriving instance (Ord d, Ord a) => Ord (Distribution d a)
|
||||
|
||||
drawSome :: Int -> StateT (Set S.Card) (Distribution Rational) (Set S.Card)
|
||||
drawSome n = do
|
||||
s <- get
|
||||
let ds = sumDist $ runStateT (Set.fromList <$> replicateM n draw) s
|
||||
(d, s') <- lift ds
|
||||
put s'
|
||||
return d
|
||||
--put s'
|
||||
|
||||
draw2 = sumDist $ (flip evalStateT) (Set.fromList $ take 22 S.allCards) do
|
||||
ss <- replicateM 5 (drawSome 2)
|
||||
return $ Set.unions ss
|
||||
|
||||
basen = 22
|
||||
taken = 10
|
||||
|
||||
allCards = Vector.fromList $ take basen S.allCards
|
||||
|
||||
example = stupid taken (Set.fromList $ take basen S.allCards)
|
||||
example2 = stupid2 taken (take basen S.allCards)
|
||||
example3 = stupid3 taken (take basen S.allCards)
|
||||
example4 = smart taken (take basen S.allCards)
|
||||
example5 = stupid4 taken allCards
|
||||
example6 = stupid5 taken (take basen S.allCards)
|
||||
|
||||
stupid :: Int -> Set S.Card -> Set (Set S.Card)
|
||||
stupid 0 _ = Set.singleton Set.empty
|
||||
stupid n cs
|
||||
| length cs == 0 = Set.empty
|
||||
| otherwise = xs
|
||||
where f :: S.Card -> Set (Set S.Card)
|
||||
f c = let cs' = Set.delete c cs
|
||||
distrs = stupid (n-1) cs'
|
||||
distrs' = Set.map (Set.insert c) distrs
|
||||
in distrs'
|
||||
--xs :: Set (Set (Set S.Card))
|
||||
xs = Set.foldr (\c s -> Set.union s $ f c) Set.empty cs
|
||||
|
||||
--stupid2 :: (Monoid f, Foldable f, Functor f) => Int -> f S.Card -> f (f S.Card)
|
||||
stupid2 0 _ = [mempty]
|
||||
stupid2 n cs
|
||||
| length cs == 0 = mempty
|
||||
| otherwise = xs
|
||||
where f c = let cs' = delete c cs
|
||||
distrs = stupid2 (n-1) cs'
|
||||
distrs' = fmap (c:) distrs
|
||||
in distrs'
|
||||
--xs :: Set (Set (Set S.Card))
|
||||
xs = foldr (\c s -> s <> f c) mempty cs
|
||||
|
||||
stupid3 :: Int -> [S.Card] -> [[S.Card]]
|
||||
stupid3 n cs = map (f cs) (filter ((==n) . popCount) [1..m])
|
||||
where m = 2^(length cs) :: Int
|
||||
f cs i = collect cs $ filter (< length cs) $ getSetBits i
|
||||
collect l idx = map (l!!) idx
|
||||
|
||||
getSetBits :: Int -> [Int]
|
||||
getSetBits a = filter (\i -> 2^i .&. a /= 0) [0..a]
|
||||
|
||||
-- very bad suddenly
|
||||
stupid5 :: Int -> [S.Card] -> Set (Set S.Card)
|
||||
stupid5 n cs = Set.map (Set.fromList . f cs) (Set.filter ((==n) . popCount) $ Set.fromList [1..m])
|
||||
where m = 2^(length cs) :: Int
|
||||
f cs i = collect cs $ filter (< length cs) $ getSetBits i
|
||||
collect l idx = map (\i -> l!!i) idx
|
||||
|
||||
stupid4 :: Int -> Vector S.Card -> Vector [S.Card]
|
||||
stupid4 n cs = fmap f (Vector.filter ((==n) . popCount) bs)
|
||||
where bs = Vector.fromList [1..m]
|
||||
m = 2^(length cs) :: Int
|
||||
f i = collect $ filter (< length cs) $ getSetBits i
|
||||
collect idx = map (\i -> cs Vector.! (i-1)) idx
|
||||
getSetBits a = filter ((/=0) . (.&.a)) [1..a]
|
||||
{-
|
||||
getSetBits a
|
||||
| popCount a == n = filter (\k -> (k.&.a) /= 0) [1..a]
|
||||
| otherwise = []
|
||||
-}
|
||||
|
||||
|
||||
smart n = map Set.fromList . stupid3 n
|
||||
|
||||
carddist :: Int -> Set S.Card -> Distribution Rational (Set S.Card)
|
||||
carddist n cs = Distribution $ fmap (\x -> Possibility x (1%l)) raw
|
||||
where raw = smart n cards
|
||||
l = fromIntegral $ length raw
|
||||
cards = Set.toList cs
|
||||
|
||||
carddistS :: Int -> StateT (Set S.Card) (Distribution Rational) (Set S.Card)
|
||||
carddistS n = do
|
||||
cards <- get
|
||||
sels <- lift $ carddist n cards
|
||||
put $ cards `Set.difference` sels
|
||||
return sels
|
||||
|
||||
draw :: StateT (Set S.Card) (Distribution Rational) S.Card
|
||||
draw = do
|
||||
cards <- get
|
||||
card <- lift $ Distribution $ Set.toList $ Set.map (flip Possibility $ (1 % (fromIntegral $ length cards))) cards
|
||||
let cards' = Set.delete (card) cards
|
||||
put cards'
|
||||
return card
|
||||
|
||||
{-
|
||||
draw2 :: StateT (Set S.Card) Identity (Distribution Rational S.Card)
|
||||
draw2 = do
|
||||
cards <- get
|
||||
cards <- get
|
||||
card <- lift $ Distribution $ Set.toList $ Set.map (flip Possibility $ (1 % (fromIntegral $ length cards))) cards
|
||||
let cards' = Set.delete (card) cards
|
||||
put cards'
|
||||
return card
|
||||
-}
|
||||
|
||||
{-
|
||||
coprod :: (Ord a, Num d) => Possibility d a -> Possibility d a -> Possibility d a
|
||||
coprod (Possibility x p) (Possibility y q) = Or (Set.fromList [x, y]) $ p + q
|
||||
-}
|
||||
|
||||
skat :: Distribution Rational (Set S.Card, Set S.Card, Set S.Card)
|
||||
skat = (flip evalStateT) (Set.fromList $ take 22 S.allCards) $ do
|
||||
sndHand <- carddistS 10
|
||||
trdHand <- carddistS 10
|
||||
skt <- carddistS 2
|
||||
return ( sndHand
|
||||
, trdHand
|
||||
, skt
|
||||
)
|
||||
|
||||
coin :: Distribution Rational Bool
|
||||
coin = Distribution [ Possibility True (1%2), Possibility False (1%2)]
|
||||
|
||||
tosstwice :: Distribution Rational (Bool, Bool)
|
||||
tosstwice = do
|
||||
c1 <- coin
|
||||
c2 <- coin
|
||||
return (c1, c2)
|
||||
|
||||
debug :: Bool
|
||||
debug = False
|
||||
|
||||
class (Ord v, Eq v) => Value v where
|
||||
invert :: v -> v
|
||||
win :: v
|
||||
loss :: v
|
||||
|
||||
class Player p where
|
||||
maxing :: p -> Bool
|
||||
|
||||
class (Traversable l, Monad m, Value v, Player p, Eq t) => MonadGame t l v p m | m -> t, m -> p, m -> v, m -> l where
|
||||
currentPlayer :: m p
|
||||
turns :: m (l t)
|
||||
play :: t -> m ()
|
||||
simulate :: t -> m a -> m a
|
||||
evaluate :: m v
|
||||
over :: m Bool
|
||||
|
||||
class (MonadIO m, Show t, Show v, Show p, MonadGame t l v p m) => PlayableGame t l v p m | m -> t, m -> p, m -> v where
|
||||
showTurns :: m ()
|
||||
showBoard :: m ()
|
||||
askTurn :: m (Maybe t)
|
||||
showTurn :: t -> m ()
|
||||
winner :: m (Maybe p)
|
||||
|
||||
-- Skat implementation
|
||||
|
||||
instance Player S.PL where
|
||||
maxing p = S.team p == S.Team
|
||||
|
||||
instance Value Int where
|
||||
invert = negate
|
||||
win = 120
|
||||
loss = -120
|
||||
|
||||
instance MonadGame (S.CardS S.Owner) [] Int S.PL S.Skat where
|
||||
currentPlayer = do
|
||||
hand <- gets S.currentHand
|
||||
pls <- gets S.players
|
||||
return $! S.player pls hand
|
||||
turns = S.allowedCards
|
||||
--player <- currentPlayer
|
||||
--trCol <- gets S.trumpColour
|
||||
--return $! if maxing player
|
||||
-- then sortBy (optimalTeam trCol) cards
|
||||
-- else sortBy (optimalSingle trCol) cards
|
||||
play = S.play_
|
||||
simulate card action = do
|
||||
--oldCurrent <- gets S.currentHand
|
||||
--oldTurnCol <- gets S.turnColour
|
||||
backup <- get
|
||||
play card
|
||||
--oldWinner <- currentPlayer
|
||||
res <- action
|
||||
--S.undo_ card oldCurrent oldTurnCol (S.team oldWinner)
|
||||
put backup
|
||||
return $! res
|
||||
over = ((==0) . length) <$!> S.allowedCards
|
||||
evaluate = do
|
||||
player <- currentPlayer
|
||||
piles <- gets S.piles
|
||||
let (sgl, tm) = S.count piles
|
||||
return $! (if maxing player then tm - sgl else sgl - tm)
|
||||
|
||||
potentialByType :: S.Type -> Int
|
||||
potentialByType S.Ace = 11
|
||||
potentialByType S.Jack = 10
|
||||
potentialByType S.Ten = 4
|
||||
potentialByType S.Seven = 7
|
||||
potentialByType S.Eight = 7
|
||||
potentialByType S.Nine = 7
|
||||
potentialByType S.Queen = 5
|
||||
potentialByType S.King = 5
|
||||
|
||||
optimalSingle :: S.Colour -> S.Card -> S.Card -> Ordering
|
||||
optimalSingle trCol (S.Card t1 _) (S.Card t2 _) = (comparing potentialByType) t2 t1
|
||||
|
||||
optimalTeam :: S.Colour -> S.Card -> S.Card -> Ordering
|
||||
optimalTeam trCol (S.Card t1 _) (S.Card t2 _) = (comparing potentialByType) t2 t1
|
||||
|
||||
-- TIC TAC TOE implementation
|
||||
|
||||
data TicTacToe = Tic | Tac | Toe
|
||||
deriving (Eq, Ord)
|
||||
|
||||
instance Show TicTacToe where
|
||||
show Tic = "O"
|
||||
show Tac = "X"
|
||||
show Toe = "_"
|
||||
|
||||
data WinLossTie = Loss | Tie | Win
|
||||
deriving (Eq, Show, Ord)
|
||||
|
||||
instance Value WinLossTie where
|
||||
invert Win = Loss
|
||||
invert Loss = Win
|
||||
invert Tie = Tie
|
||||
win = Win
|
||||
loss = Loss
|
||||
|
||||
data GameState = GameState { getBoard :: [TicTacToe]
|
||||
, getCurrent :: Bool }
|
||||
deriving Show
|
||||
|
||||
instance Player Bool where
|
||||
maxing = id
|
||||
|
||||
instance Monad m => MonadGame Int [] WinLossTie Bool (StateT GameState m) where
|
||||
currentPlayer = gets getCurrent
|
||||
turns = do
|
||||
board <- gets getBoard
|
||||
let fields = zip [0..] board
|
||||
return $ map fst $ filter ((==Toe) . snd) fields
|
||||
play turn = do
|
||||
env <- get
|
||||
let value = if getCurrent env then Tic else Tac
|
||||
board' = updateAt turn (getBoard env) value
|
||||
current' = not $ getCurrent env
|
||||
put $ GameState board' current'
|
||||
simulate turn action = do
|
||||
backup <- get
|
||||
play turn
|
||||
res <- action
|
||||
put backup
|
||||
return $! res
|
||||
evaluate = do
|
||||
board <- gets getBoard
|
||||
current <- currentPlayer
|
||||
let mayWinner = ticWinner board
|
||||
case mayWinner of
|
||||
Just Tic -> return $ if current then Win else Loss
|
||||
Just Tac -> return $ if current then Loss else Win
|
||||
Just Toe -> return Tie
|
||||
Nothing -> return Tie
|
||||
over = do
|
||||
board <- gets getBoard
|
||||
case ticWinner board of
|
||||
Just _ -> return True
|
||||
_ -> return False
|
||||
|
||||
ticWinner :: [TicTacToe] -> Maybe TicTacToe
|
||||
ticWinner board
|
||||
| ticWon = Just Tic
|
||||
| tacWon = Just Tac
|
||||
| over = Just Toe
|
||||
| otherwise = Nothing
|
||||
where ticWon = hasWon $ map (==Tic) board
|
||||
tacWon = hasWon $ map (==Tac) board
|
||||
hasWon (True:_:_:True:_:_:True:_:_:[]) = True
|
||||
hasWon (True:_:_:_:True:_:_:_:True:[]) = True
|
||||
hasWon (_:True:_:_:True:_:_:True:_:[]) = True
|
||||
hasWon (_:_:True:_:_:True:_:_:True:[]) = True
|
||||
hasWon (_:_:True:_:True:_:True:_:_:[]) = True
|
||||
hasWon (True:True:True:_:_:_:_:_:_:[]) = True
|
||||
hasWon (_:_:_:True:True:True:_:_:_:[]) = True
|
||||
hasWon (_:_:_:_:_:_:True:True:True:[]) = True
|
||||
hasWon _ = False
|
||||
over = (length $ filter (==Toe) board) == 0
|
||||
|
||||
updateAt :: Int -> [a] -> a -> [a]
|
||||
updateAt n xs y = map f $ zip [0..] xs
|
||||
where f (i, x) = if i == n then y else x
|
||||
|
||||
toss :: Distribution Rational Coin
|
||||
toss = Distribution [Possibility Head (1%2), Possibility Tail (1%2)]
|
||||
|
||||
data Coin = Head
|
||||
| Tail
|
||||
deriving (Show, Eq, Ord)
|
||||
|
||||
data CoinGameState = CGS { tosses :: [Coin]
|
||||
, turn :: Int }
|
||||
deriving (Show, Eq)
|
||||
|
||||
initCGS :: CoinGameState
|
||||
initCGS = CGS { tosses = []
|
||||
, turn = 0
|
||||
}
|
||||
|
||||
markov :: StateT CoinGameState (Distribution Rational) Int
|
||||
markov = do
|
||||
coin <- lift toss
|
||||
cgs <- get
|
||||
let newtosses = coin:(tosses cgs)
|
||||
newturn = turn cgs + 1
|
||||
put $ cgs { tosses = newtosses
|
||||
, turn = newturn }
|
||||
if length (filter (==Head) newtosses) >= 3 || (newturn >= 10)
|
||||
then return newturn
|
||||
else markov
|
||||
|
||||
{-
|
||||
choose :: (MonadIO m, Show v, Show t, Show p, Value v, Eq t, Player p, MonadGame t l v p m)
|
||||
=> Int
|
||||
-> m t
|
||||
choose depth = fst <$> minmax depth (error "choose") loss win
|
||||
|
||||
emptyBoard :: [TicTacToe]
|
||||
emptyBoard = [Toe, Toe, Toe, Toe, Toe, Toe, Toe, Toe, Toe]
|
||||
|
||||
otherBoard :: [TicTacToe]
|
||||
otherBoard = [Tic, Tac, Tac, Tic, Tac, Tic, Toe, Tic, Toe]
|
||||
|
||||
print9x9 :: (Int -> IO ()) -> IO ()
|
||||
print9x9 pr = pr 0 >> pr 1 >> pr 2 >> putStrLn ""
|
||||
>> pr 3 >> pr 4 >> pr 5 >> putStrLn ""
|
||||
>> pr 6 >> pr 7 >> pr 8 >> putStrLn ""
|
||||
|
||||
printBoard :: [TicTacToe] -> IO ()
|
||||
printBoard board = print9x9 pr >> putStrLn ""
|
||||
where pr n = putStr (show $ board !! n) >> putStr " "
|
||||
|
||||
printOptions :: [Int] -> IO ()
|
||||
printOptions opts = print9x9 pr
|
||||
where pr n
|
||||
| n `elem` opts = putStr (show n) >> putStr " "
|
||||
| otherwise = putStr " "
|
||||
|
||||
instance MonadIO m => PlayableGame Int [] WinLossTie Bool (StateT GameState m) where
|
||||
showBoard = do
|
||||
board <- gets getBoard
|
||||
liftIO $ printBoard board
|
||||
showTurns = turns >>= liftIO . printOptions
|
||||
winner = do
|
||||
board <- gets getBoard
|
||||
let win = ticWinner board
|
||||
case win of
|
||||
Just Toe -> return Nothing
|
||||
Just Tic -> return $ Just True
|
||||
Just Tac -> return $ Just False
|
||||
Nothing -> return Nothing
|
||||
askTurn = readMaybe <$> liftIO getLine
|
||||
showTurn _ = return ()
|
||||
|
||||
instance PlayableGame (S.CardS S.Owner) [] Int S.PL S.Skat where
|
||||
showBoard = do
|
||||
liftIO $ putStrLn ""
|
||||
table <- S.getp S.tableCards
|
||||
liftIO $ putStr "Table: "
|
||||
liftIO $ print table
|
||||
showTurns = do
|
||||
cards <- turns
|
||||
player <- currentPlayer
|
||||
liftIO $ print player
|
||||
liftIO $ S.render cards
|
||||
winner = do
|
||||
piles <- gets S.piles
|
||||
pls <- gets S.players
|
||||
let res = S.count piles :: (Int, Int)
|
||||
winnerTeam = trace (show res) $ if fst res > snd res then S.Single else S.Team
|
||||
winners = filter ((==winnerTeam) . S.team) (S.playersToList pls)
|
||||
return $ Just $ head winners
|
||||
askTurn = do
|
||||
cards <- turns
|
||||
let sorted = cards
|
||||
input <- liftIO getLine
|
||||
case readMaybe input of
|
||||
Just n -> if n >= 0 && n < length sorted then return $ Just (sorted !! n)
|
||||
else return Nothing
|
||||
Nothing -> return Nothing
|
||||
showTurn card = do
|
||||
player <- currentPlayer
|
||||
liftIO $ putStrLn $ show player ++ " plays " ++ show card
|
||||
|
||||
playCLI :: (MonadFail m, Read t, PlayableGame t l v p m) => m ()
|
||||
playCLI = do
|
||||
gameOver <- over
|
||||
if gameOver
|
||||
then announceWinner
|
||||
else do
|
||||
when debug showBoard
|
||||
current <- currentPlayer
|
||||
turn <- choose 10
|
||||
when debug $ showTurn turn
|
||||
play turn
|
||||
playCLI
|
||||
where
|
||||
readTurn :: (MonadFail m, Read t, PlayableGame t l v p m) => m t
|
||||
readTurn = do
|
||||
options <- turns
|
||||
showTurns
|
||||
liftIO $ putStr "> "
|
||||
mayTurn <- askTurn
|
||||
case mayTurn of
|
||||
Just val -> if val `elem` options then return val else readTurn
|
||||
Nothing -> readTurn
|
||||
announceWinner = do
|
||||
showBoard
|
||||
win <- winner
|
||||
liftIO $ putStrLn $ show win ++ " wins the game!"
|
||||
|
||||
playTicTacToe :: IO ()
|
||||
playTicTacToe = void $ (flip runStateT) (GameState emptyBoard True) playCLI
|
||||
-}
|
||||
@@ -0,0 +1,111 @@
|
||||
{-# LANGUAGE MultiParamTypeClasses #-}
|
||||
{-# LANGUAGE TypeSynonymInstances #-}
|
||||
{-# LANGUAGE FlexibleInstances #-}
|
||||
{-# LANGUAGE FlexibleContexts #-}
|
||||
{-# LANGUAGE FunctionalDependencies #-}
|
||||
{-# LANGUAGE TupleSections #-}
|
||||
|
||||
module Skat.AI.Minmax (
|
||||
choose, playCLI
|
||||
) where
|
||||
|
||||
import Control.Monad.State
|
||||
import Control.Exception (assert)
|
||||
import Control.Monad.Fail
|
||||
import Data.Ord
|
||||
import Text.Read (readMaybe)
|
||||
import Data.List (maximumBy, sortBy)
|
||||
import Debug.Trace
|
||||
|
||||
import qualified Skat as S
|
||||
import qualified Skat.Card as S
|
||||
import qualified Skat.Operations as S
|
||||
import qualified Skat.Pile as S
|
||||
import qualified Skat.Player as S hiding (trumpColour, turnColour)
|
||||
import qualified Skat.Render as S
|
||||
import Skat.AI.Base hiding (playCLI, Choose(..))
|
||||
import Skat.AI.TicTacToe hiding (playCLI)
|
||||
import Skat.AI.Skat hiding (playCLI)
|
||||
--import TestEnvs (env3, shuffledEnv2)
|
||||
|
||||
debug :: Bool
|
||||
debug = False
|
||||
-- Skat implementation
|
||||
|
||||
potentialByType :: S.Type -> Int
|
||||
potentialByType S.Ace = 11
|
||||
potentialByType S.Jack = 10
|
||||
potentialByType S.Ten = 4
|
||||
potentialByType S.Seven = 7
|
||||
potentialByType S.Eight = 7
|
||||
potentialByType S.Nine = 7
|
||||
potentialByType S.Queen = 5
|
||||
potentialByType S.King = 5
|
||||
|
||||
optimalSingle :: S.Colour -> S.Card -> S.Card -> Ordering
|
||||
optimalSingle trCol (S.Card t1 _) (S.Card t2 _) = (comparing potentialByType) t2 t1
|
||||
|
||||
optimalTeam :: S.Colour -> S.Card -> S.Card -> Ordering
|
||||
optimalTeam trCol (S.Card t1 _) (S.Card t2 _) = (comparing potentialByType) t2 t1
|
||||
|
||||
minmax :: (MonadIO m, Show v, Show t, Show p, Value v, Eq t, Player p, MonadGame t l v p m)
|
||||
=> Int
|
||||
-> t
|
||||
-> v
|
||||
-> v
|
||||
-> m (t, v)
|
||||
minmax depth turn_ alpha beta = (flip evalStateT) (alpha, beta) $ do
|
||||
gameOver <- lift over
|
||||
-- if last step or game is over then evaluate situation
|
||||
if depth == 0 || gameOver then (turn_,) <$> lift evaluate
|
||||
else do
|
||||
-- generate a list of possible turns
|
||||
currentlyMaxing <- maxing <$> lift currentPlayer
|
||||
availableTurns <- lift turns
|
||||
(alpha, beta) <- get
|
||||
-- try every turn, StateT wraps current best turn and current max value
|
||||
(flip execStateT) (turn_, alpha) $ forM_ availableTurns $ \turn -> do
|
||||
currentMax <- gets snd
|
||||
-- beta cutoff
|
||||
unless (currentMax >= beta) $ do
|
||||
value <- lift $! lift $! simulate turn $! do
|
||||
nextMaxing <- maxing <$!> currentPlayer
|
||||
if nextMaxing /= currentlyMaxing
|
||||
then (invert . snd) <$!> minmax (depth-1) turn (invert beta) (invert currentMax)
|
||||
else snd <$!> minmax (depth-1) turn currentMax beta
|
||||
when (value > currentMax) (put (turn, value))
|
||||
|
||||
choose :: (MonadIO m, Show v, Show t, Show p, Value v, Eq t, Player p, MonadGame t l v p m)
|
||||
=> Int
|
||||
-> m t
|
||||
choose depth = fst <$> minmax depth (error "choose") loss win
|
||||
|
||||
playCLI :: (MonadFail m, Read t, PlayableGame t l v p m) => m ()
|
||||
playCLI = do
|
||||
gameOver <- over
|
||||
if gameOver
|
||||
then announceWinner
|
||||
else do
|
||||
when debug showBoard
|
||||
current <- currentPlayer
|
||||
turn <- choose 10
|
||||
when debug $ showTurn turn
|
||||
play turn
|
||||
playCLI
|
||||
where
|
||||
readTurn :: (MonadFail m, Read t, PlayableGame t l v p m) => m t
|
||||
readTurn = do
|
||||
options <- turns
|
||||
showTurns
|
||||
liftIO $ putStr "> "
|
||||
mayTurn <- askTurn
|
||||
case mayTurn of
|
||||
Just val -> if val `elem` options then return val else readTurn
|
||||
Nothing -> readTurn
|
||||
announceWinner = do
|
||||
showBoard
|
||||
win <- winner
|
||||
liftIO $ putStrLn $ show win ++ " wins the game!"
|
||||
|
||||
playTicTacToe :: IO ()
|
||||
playTicTacToe = void $ (flip runStateT) (GameState emptyBoard True) playCLI
|
||||
@@ -0,0 +1,251 @@
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
{-# LANGUAGE MultiParamTypeClasses #-}
|
||||
{-# LANGUAGE BlockArguments #-}
|
||||
{-# LANGUAGE TypeSynonymInstances #-}
|
||||
{-# LANGUAGE FlexibleInstances #-}
|
||||
{-# LANGUAGE FlexibleContexts #-}
|
||||
{-# LANGUAGE FunctionalDependencies #-}
|
||||
{-# LANGUAGE TupleSections #-}
|
||||
{-# LANGUAGE InstanceSigs #-}
|
||||
{-# LANGUAGE StandaloneDeriving #-}
|
||||
{-# LANGUAGE ImportQualifiedPost #-}
|
||||
{-# LANGUAGE UndecidableInstances #-}
|
||||
{-# LANGUAGE DeriveGeneric #-}
|
||||
|
||||
module Skat.AI.MonteCarlo where
|
||||
|
||||
import GHC.Generics
|
||||
import Control.Monad.State
|
||||
import Control.Exception (assert)
|
||||
import Control.Monad.Fail
|
||||
import Data.Ord
|
||||
import Text.Read (readMaybe)
|
||||
import Data.List (maximumBy, minimumBy, sortBy, delete, intercalate)
|
||||
import Debug.Trace
|
||||
import Data.Ratio
|
||||
import Data.Set (Set)
|
||||
import qualified Data.Set as Set
|
||||
import Data.Map (Map)
|
||||
import qualified Data.Map as Map
|
||||
import Data.Bits
|
||||
import Data.Vector (Vector)
|
||||
import qualified Data.Vector as Vector
|
||||
import System.Random (Random)
|
||||
import qualified System.Random as Rand
|
||||
import Text.Printf
|
||||
import Data.List.Split
|
||||
import Data.Aeson hiding (Value)
|
||||
|
||||
import Skat.AI.Base hiding (simulate)
|
||||
import qualified Skat as S
|
||||
import qualified Skat.Card as S
|
||||
import qualified Skat.Operations as S
|
||||
import qualified Skat.Pile as S
|
||||
import qualified Skat.Player as S hiding (trumpColour, turnColour)
|
||||
import qualified Skat.Render as S
|
||||
import Skat.Utils
|
||||
--import TestEnvs (env3, shuffledEnv2)
|
||||
|
||||
type WinCount = Float
|
||||
type SimCount = Int
|
||||
|
||||
data Tree t s = Leaf s Bool (WinCount, SimCount)
|
||||
| Node s Bool (WinCount, SimCount) [Tree t s]
|
||||
| Pending s t
|
||||
deriving (Generic)
|
||||
|
||||
instance (ToJSON t, ToJSON s) => ToJSON (Tree t s) where
|
||||
toJSON x@Leaf{} = object [ "state" .= toJSON (treestate x)
|
||||
, "valuation" .= toJSON (valuation x)
|
||||
]
|
||||
toJSON x@(Node _ _ _ children) = object [ "state" .= toJSON (treestate x)
|
||||
, "valuation" .= toJSON (valuation x)
|
||||
, "children" .= toJSON children
|
||||
]
|
||||
toJSON x@(Pending _ t) = object [ "valuation" .= ("pending" :: String)
|
||||
, "turn" .= toJSON t
|
||||
]
|
||||
|
||||
simruns :: Tree t s -> SimCount
|
||||
simruns (Leaf _ _ d) = snd d
|
||||
simruns (Node _ _ d _) = snd d
|
||||
simruns Pending{} = 0
|
||||
|
||||
wins :: Tree t s -> WinCount
|
||||
wins (Leaf _ _ d) = fst d
|
||||
wins (Node _ _ d _) = fst d
|
||||
wins Pending{} = 0
|
||||
|
||||
childrenwins :: Tree t s -> WinCount
|
||||
childrenwins (Node _ _ _ cs) = sum $ fmap wins cs
|
||||
childrenwins _ = 0
|
||||
|
||||
treestate :: Tree t s -> s
|
||||
treestate (Leaf s _ _) = s
|
||||
treestate (Node s _ _ _) = s
|
||||
treestate (Pending s _) = s
|
||||
|
||||
isterminal :: Tree t s -> Bool
|
||||
isterminal (Leaf _ b _) = b
|
||||
isterminal (Node _ b _ _) = b
|
||||
isterminal Pending{} = False
|
||||
|
||||
class Draw s where
|
||||
draw :: s -> String
|
||||
|
||||
instance Draw Int where
|
||||
draw = show
|
||||
|
||||
indent :: Int -> String -> String
|
||||
indent n s = intercalate ("\n" ++ replicate n ' ') $ splitOn "\n" s
|
||||
|
||||
visualise :: (HasGameState t p d s, Draw s, Draw t) => Tree t s -> String
|
||||
visualise (Node s _ d children) = printf "[%f/%d]: %s %s:\n%s" (fst d) (snd d) (show . maxing . current $ s) (indent 14 $ draw s) (intercalate "\n" $ fmap f children)
|
||||
where f c = printf "---%s" (indent 3 $ visualise c)
|
||||
visualise (Leaf s _ d) = printf "[%f/%d]: %s" (fst d) (snd d) (indent 9 $ draw s)
|
||||
visualise (Pending s t) = printf "[pend]: %s %s" (indent 9 $ draw s) (indent 9 $ draw t)
|
||||
|
||||
emptytree :: s -> Tree t s
|
||||
emptytree s = Leaf s False (0, 0)
|
||||
|
||||
valuation :: Tree t s -> (WinCount, SimCount)
|
||||
valuation (Leaf _ _ d) = d
|
||||
valuation (Node _ _ d _) = d
|
||||
valuation Pending{} = (0,0)
|
||||
|
||||
deriving instance (Show s, Show t) => Show (Tree t s)
|
||||
|
||||
{-
|
||||
valuetonum :: (Fractional a, Value v) => v -> a
|
||||
valuetonum v
|
||||
| v == win = 1
|
||||
| v == loss = 0
|
||||
| v == tie = 0.5
|
||||
-}
|
||||
|
||||
{-
|
||||
restoint :: (Player p, Value v) => p -> v -> Float
|
||||
restoint p v = tonum $ if maxing p then v else invert v
|
||||
-}
|
||||
|
||||
{-
|
||||
updateval :: (Player p, Value d) => p -> [d] -> (WinCount, SimCount) -> (WinCount, SimCount)
|
||||
updateval team xs d =
|
||||
let newSimCount = snd d + fromIntegral (length xs)
|
||||
newWinCount = fst d + sum (fmap (tonum . cvt) xs)
|
||||
cvt = if maxing team then id else invert
|
||||
in (newWinCount, newSimCount)
|
||||
-}
|
||||
|
||||
class (Player p, Value d) => HasGameState t p d s | s -> d, s -> p, s -> t where
|
||||
moves :: s -> [t]
|
||||
execute :: t -> s -> s
|
||||
monteevaluate :: s -> d
|
||||
current :: s -> p
|
||||
simulate :: (Monad m, MonadRandom m) => s -> m d
|
||||
simulate = montesimulate
|
||||
|
||||
montecarlo :: (Show s, Show t, Eq p, Show d, Monad m, HasGameState t p d s, MonadRandom m)
|
||||
=> Tree t s
|
||||
-> m (Tree t s)
|
||||
montecarlo (Pending state turn) = do
|
||||
let currentTeam = current state
|
||||
state' = execute turn state
|
||||
-- objectively get a final score of random playout (independent of perspective)
|
||||
values <- replicateM 1000 (simulate state')
|
||||
let --tr = if maxing (current state) then id else invert
|
||||
tr = id
|
||||
vs = fmap (tonum . tr) values
|
||||
n = sum vs
|
||||
--let v = if maxing (current state') then value else invert value
|
||||
let val = (n, 1000)
|
||||
pure $ Leaf state' False val
|
||||
montecarlo (Leaf state terminal d)
|
||||
| terminal || length ms == 0 = pure $ Leaf state True d
|
||||
| otherwise = let children = map (Pending state) ms in pure $ Node state False d children
|
||||
where ms = moves state
|
||||
montecarlo (Node state _ d []) = pure $ Leaf state True d
|
||||
montecarlo n@(Node state True d children) = pure n
|
||||
montecarlo n@(Node state _ d children)
|
||||
| all isterminal children =
|
||||
let d' = reevaluateminmax n
|
||||
in pure $ Node state True d' children
|
||||
| otherwise = do
|
||||
let myruns = snd d
|
||||
cmp c
|
||||
| isterminal c = -1
|
||||
| otherwise = selectcoeff (maxing $ current state) myruns $ valuation c
|
||||
(idx, bestChild) =
|
||||
maximumBy (comparing $ cmp . snd) $ zipWith (,) [0..] children
|
||||
updated <- montecarlo bestChild
|
||||
let cs = updateAt idx children updated
|
||||
newSimRuns = simruns updated - simruns bestChild + snd d
|
||||
diff = wins updated - wins bestChild
|
||||
--diff2 =
|
||||
-- if newSimRuns == snd d then 0
|
||||
-- else
|
||||
-- if current state == current (treestate updated)
|
||||
-- then diff
|
||||
-- else fromIntegral (simruns updated) - diff
|
||||
newWins = diff + fst d
|
||||
--return $ trace ("updating node " ++ show diff2 ++ "\n" ++ show updated ++ "\n" ++ show bestChild) (Node state False (newWins, newSimRuns) cs)
|
||||
return $ Node state False (newWins, newSimRuns) cs
|
||||
|
||||
montesimulate :: (Monad m, MonadRandom m, HasGameState t p d s)
|
||||
=> s
|
||||
-> m d
|
||||
montesimulate state = case moves state of
|
||||
[] -> pure $ monteevaluate state
|
||||
allowed -> do
|
||||
turn <- chooser allowed
|
||||
montesimulate $ execute turn state
|
||||
|
||||
runmonte :: Int -> State Rand.StdGen (Tree t s) -> Tree t s
|
||||
runmonte n action = evalState action (Rand.mkStdGen n)
|
||||
|
||||
{-
|
||||
bestmove :: Tree s -> s
|
||||
bestmove (Leaf s _ _) = s
|
||||
bestmove (Node s _ _ cs) = treestate $ selection (comparing $ rate . valuation) cs
|
||||
where rate (w, s) = w / fromIntegral s
|
||||
mxing = maxing . current $ s
|
||||
selection = if mxing then maximumBy else minimumBy
|
||||
-}
|
||||
bestmove :: (HasGameState t p d s, Player p) => Tree t s -> s
|
||||
bestmove (Leaf s _ _) = s
|
||||
bestmove (Node s _ _ cs) = treestate $ choice (comparing $ rate . valuation) cs
|
||||
where rate (w, s) = w / fromIntegral s
|
||||
choice = if maxing (current s) then maximumBy else minimumBy
|
||||
|
||||
selectcoeff :: Bool -> SimCount -> (WinCount, SimCount) -> Float
|
||||
selectcoeff _ _ (_, 0) = 10000000
|
||||
selectcoeff m t (w, s) = w' / fromIntegral s + explorationParam * sqrt (log (fromIntegral t) / fromIntegral s)
|
||||
where explorationParam = sqrt 2
|
||||
w' = if m then w else fromIntegral s - w
|
||||
|
||||
reevaluate :: Tree t s -> (WinCount, SimCount)
|
||||
reevaluate tree
|
||||
| isterminal tree = valuation tree
|
||||
| otherwise = case tree of
|
||||
(Pending{}) -> valuation tree
|
||||
(Leaf{}) -> valuation tree
|
||||
(Node _ _ _ children) -> let total = sum $ fmap simruns children
|
||||
wns = fromIntegral total - sum (fmap wins children)
|
||||
in (wns, total)
|
||||
|
||||
reevaluateminmax :: HasGameState t p d s => Tree t s -> (WinCount, SimCount)
|
||||
reevaluateminmax tree
|
||||
| isterminal tree = valuation tree
|
||||
| otherwise = case tree of
|
||||
(Pending{}) -> valuation tree
|
||||
(Leaf{}) -> valuation tree
|
||||
(Node state _ _ children) ->
|
||||
let vals = fmap ((\(w, s) -> w / fromIntegral s) . valuation) children
|
||||
-- m = maxing . current $ state
|
||||
--childrenMaxing = all (maxing . current . treestate) children
|
||||
selfMaxing = maxing . current $ state
|
||||
choice = if selfMaxing then maximum else minimum
|
||||
newval = choice vals
|
||||
in (newval, 1)
|
||||
|
||||
--playCLI :: (MonadFail m, Read t, Choose t m, PlayableGame t l v p m) => m ()
|
||||
+164
-36
@@ -5,8 +5,9 @@
|
||||
module Skat.AI.Online where
|
||||
|
||||
import Control.Monad.Reader
|
||||
import Network.WebSockets (Connection, sendTextData, receiveData)
|
||||
import Data.Aeson
|
||||
import Control.Concurrent.Chan
|
||||
import Data.Aeson hiding (Result)
|
||||
import Data.Maybe
|
||||
import qualified Data.ByteString.Lazy.Char8 as BS
|
||||
|
||||
import Skat.Player
|
||||
@@ -14,74 +15,201 @@ import qualified Skat.Player.Utils as P
|
||||
import Skat.Pile
|
||||
import Skat.Card
|
||||
import Skat.Render
|
||||
import Skat.Preperation
|
||||
import Skat.Bidding
|
||||
|
||||
class Communicator a where
|
||||
send :: a -> String -> IO ()
|
||||
receive :: a -> IO String
|
||||
|
||||
instance Communicator (Chan String) where
|
||||
send = writeChan
|
||||
receive = readChan
|
||||
|
||||
class Monad m => MonadClient m where
|
||||
query :: String -> m ()
|
||||
response :: m String
|
||||
|
||||
data OnlineEnv = OnlineEnv { getTeam :: Team
|
||||
data OnlineEnv c = OnlineEnv { getTeam :: Team
|
||||
, getHand :: Hand
|
||||
, connection :: Connection }
|
||||
deriving Show
|
||||
, connection :: c }
|
||||
|
||||
instance Show Connection where
|
||||
show _ = "A connection"
|
||||
data PrepOnline c = PrepOnline { prepHand :: Hand
|
||||
, prepConnection :: c
|
||||
, prepCards :: [Card] }
|
||||
|
||||
instance Player OnlineEnv where
|
||||
instance Show (OnlineEnv c) where
|
||||
show _ = "An online env"
|
||||
|
||||
instance Show (PrepOnline c) where
|
||||
show _ = "An online prep env"
|
||||
|
||||
instance Communicator c => Player (OnlineEnv c) where
|
||||
team = getTeam
|
||||
hand = getHand
|
||||
chooseCard p table _ hand = runReaderT (choose table hand) p >>= \c -> return (c, p)
|
||||
chooseCard p table _ mayOuvert hand = runReaderT (choose table mayOuvert hand) p >>= \c -> return (c, p)
|
||||
onCardPlayed p c = runReaderT (cardPlayed c) p >> return p
|
||||
onGameResults p res = runReaderT (onResults res) p
|
||||
|
||||
type Online m = ReaderT OnlineEnv m
|
||||
instance Communicator c => Bidder (PrepOnline c) where
|
||||
hand = prepHand
|
||||
askBid p against bid = do
|
||||
liftIO $ send (prepConnection p) (BS.unpack $ encode $ BidQuery against bid)
|
||||
r <- liftIO $ receive (prepConnection p)
|
||||
case decode (BS.pack r) of
|
||||
Just (BidResponse newBid) -> do
|
||||
if newBid > bid then return $ Just newBid else return Nothing
|
||||
Nothing -> askBid p against bid
|
||||
askResponse p bidder bid = do
|
||||
liftIO $ send (prepConnection p) (BS.unpack $ encode $ BidResponseQuery bidder bid)
|
||||
r <- liftIO $ receive (prepConnection p)
|
||||
case decode (BS.pack r) of
|
||||
Just (YesNo value) -> return value
|
||||
Nothing -> askResponse p bidder bid
|
||||
askGame p bid = do
|
||||
liftIO $ send (prepConnection p) (BS.unpack $ encode $ AskGameQuery bid)
|
||||
r <- liftIO $ receive (prepConnection p)
|
||||
case decode (BS.pack r) of
|
||||
Just (GameResponse game) -> return game
|
||||
Nothing -> askGame p bid
|
||||
askHand p bid = do
|
||||
liftIO $ send (prepConnection p) (BS.unpack $ encode $ AskHandQuery)
|
||||
r <- liftIO $ receive (prepConnection p)
|
||||
case decode (BS.pack r) of
|
||||
Just (YesNo value) -> return value
|
||||
Nothing -> askHand p bid
|
||||
askSkat p bid cards = do
|
||||
liftIO $ send (prepConnection p) (BS.unpack $ encode $ AskSkatQuery cards bid)
|
||||
r <- liftIO $ receive (prepConnection p)
|
||||
case decode (BS.pack r) of
|
||||
Just (ChosenCards cards) -> return cards
|
||||
Nothing -> askSkat p bid cards
|
||||
toPlayer p tm = PL $ OnlineEnv tm (prepHand p) (prepConnection p)
|
||||
onBid p mayBid reizer gereizter =
|
||||
liftIO $ send (prepConnection p) (BS.unpack $ encode $ BidEvent mayBid reizer gereizter)
|
||||
onResponse p response reizer gereizter =
|
||||
liftIO $ send (prepConnection p) (BS.unpack $ encode $ ResponseEvent response reizer gereizter)
|
||||
onStart p = do
|
||||
let cards = sortRender Jacks $ prepCards p
|
||||
liftIO $ send (prepConnection p) (BS.unpack $ encode $ CardsQuery cards)
|
||||
onResult p res =
|
||||
liftIO $ send (prepConnection p) (BS.unpack $ encode $ GameResultsQuery res)
|
||||
onGame p game sglPlayer = do
|
||||
liftIO $ send (prepConnection p) (BS.unpack $ encode $ GameStartQuery game sglPlayer)
|
||||
onNoGame p = do
|
||||
liftIO $ send (prepConnection p) (BS.unpack $ encode $ NoGameQuery)
|
||||
|
||||
instance MonadIO m => MonadClient (Online m) where
|
||||
type Online a m = ReaderT (OnlineEnv a) m
|
||||
|
||||
instance (Communicator c, MonadIO m) => MonadClient (Online c m) where
|
||||
query s = do
|
||||
conn <- asks connection
|
||||
liftIO $ sendTextData conn (BS.pack s)
|
||||
liftIO $ send conn s
|
||||
response = do
|
||||
conn <- asks connection
|
||||
liftIO $ BS.unpack <$> receiveData conn
|
||||
liftIO $ receive conn
|
||||
|
||||
instance MonadPlayer m => MonadPlayer (Online m) where
|
||||
trumpColour = lift $ trumpColour
|
||||
instance MonadPlayer m => MonadPlayer (Online a m) where
|
||||
trump = lift $ trump
|
||||
turnColour = lift $ turnColour
|
||||
showSkat = lift . showSkat
|
||||
singlePlayer = lift singlePlayer
|
||||
game = lift game
|
||||
|
||||
choose :: MonadPlayer m => [CardS Played] -> [Card] -> Online m Card
|
||||
choose table hand = do
|
||||
query (BS.unpack $ encode $ ChooseQuery hand table)
|
||||
choose :: (MonadIO m, HasCard b, HasCard a) => (Communicator c, MonadPlayer m) => [CardS Played] -> Maybe [b] -> [a] -> Online c m Card
|
||||
choose table mayOuvert hand' = do
|
||||
gm <- game
|
||||
let hand = sortRender (getTrump gm) $ map toCard hand'
|
||||
ouvertCards = fmap (sortRender (getTrump gm) . map toCard) mayOuvert
|
||||
query (BS.unpack $ encode $ ChooseQuery hand table ouvertCards)
|
||||
r <- response
|
||||
case decode (BS.pack r) of
|
||||
Just (ChosenResponse card) -> do
|
||||
allowed <- P.isAllowed hand card
|
||||
if card `elem` hand && allowed then return card else choose table hand
|
||||
Nothing -> choose table hand
|
||||
if card `elem` hand && allowed then return card else choose table mayOuvert hand'
|
||||
Nothing -> choose table mayOuvert hand'
|
||||
|
||||
cardPlayed :: MonadPlayer m => CardS Played -> Online m ()
|
||||
cardPlayed :: (MonadIO m, Communicator c, MonadPlayer m) => CardS Played -> Online c m ()
|
||||
cardPlayed card = query (BS.unpack $ encode $ CardPlayedQuery card)
|
||||
|
||||
onResults :: MonadIO m => (Int, Int) -> Online m ()
|
||||
onResults (sgl, tm) = query (BS.unpack $ encode $ GameResultsQuery sgl tm)
|
||||
-- | QUERIES AND RESPONSES
|
||||
|
||||
data ChooseQuery = ChooseQuery [Card] [CardS Played]
|
||||
data CardPlayedQuery = CardPlayedQuery (CardS Played)
|
||||
data GameResultsQuery = GameResultsQuery Int Int
|
||||
data ChosenResponse = ChosenResponse Card
|
||||
data Query = ChooseQuery [Card] [CardS Played] (Maybe [Card])
|
||||
| CardPlayedQuery (CardS Played)
|
||||
| GameResultsQuery Result
|
||||
| GameStartQuery HideGame Hand
|
||||
| BidQuery Hand Bid
|
||||
| BidResponseQuery Hand Bid
|
||||
| AskGameQuery Bid
|
||||
| AskHandQuery
|
||||
| AskSkatQuery [Card] Bid
|
||||
| CardsQuery [Card]
|
||||
| BidEvent (Maybe Bid) Hand Hand
|
||||
| ResponseEvent Bool Hand Hand
|
||||
| NoGameQuery
|
||||
|
||||
instance ToJSON ChooseQuery where
|
||||
toJSON (ChooseQuery hand table) =
|
||||
object ["query" .= ("choose_card" :: String), "hand" .= hand, "table" .= table]
|
||||
newtype ChosenResponse = ChosenResponse Card
|
||||
newtype BidResponse = BidResponse Int
|
||||
newtype YesNo = YesNo Bool
|
||||
newtype GameResponse = GameResponse Game
|
||||
deriving Show
|
||||
newtype ChosenCards = ChosenCards [Card]
|
||||
|
||||
instance ToJSON CardPlayedQuery where
|
||||
instance ToJSON Query where
|
||||
toJSON (ChooseQuery hand table mayOuvert) =
|
||||
object [ "query" .= ("choose_card" :: String), "hand" .= hand, "table" .= table
|
||||
, "single_hand" .= mayOuvert]
|
||||
toJSON (CardPlayedQuery card) =
|
||||
object ["query" .= ("card_played" :: String), "card" .= card]
|
||||
|
||||
instance ToJSON GameResultsQuery where
|
||||
toJSON (GameResultsQuery sgl tm) =
|
||||
object ["query" .= ("results" :: String), "single" .= sgl, "team" .= tm]
|
||||
toJSON (GameResultsQuery result) =
|
||||
object ["query" .= ("results" :: String), "result" .= result]
|
||||
toJSON (GameStartQuery game sglPlayer) =
|
||||
object [ "query" .= ("start_game" :: String)
|
||||
, "game" .= game
|
||||
, "single" .= show sglPlayer ]
|
||||
toJSON (BidQuery hand bid) =
|
||||
object ["query" .= ("bid" :: String), "whom" .= show hand, "current" .= bid]
|
||||
toJSON (BidResponseQuery hand bid) =
|
||||
object ["query" .= ("bid_response" :: String), "from" .= show hand, "bid" .= bid ]
|
||||
toJSON (AskHandQuery) =
|
||||
object ["query" .= ("play_hand" :: String)]
|
||||
toJSON (AskSkatQuery cards bid) =
|
||||
object ["query" .= ("skat" :: String), "cards" .= cards, "bid" .= bid ]
|
||||
toJSON (CardsQuery cards) =
|
||||
object ["query" .= ("cards" :: String), "cards" .= cards ]
|
||||
toJSON (AskGameQuery bid) =
|
||||
object ["query" .= ("ask_game" :: String), "bid" .= bid]
|
||||
toJSON (BidEvent (Just bid) reizer gereizter) =
|
||||
object ["query" .= ("bid_event" :: String), "bid" .= bid, "reizer" .= show reizer,
|
||||
"gereizter" .= show gereizter ]
|
||||
toJSON (BidEvent Nothing reizer gereizter) =
|
||||
object [ "query" .= ("bid_event" :: String)
|
||||
, "bid" .= ("weg" :: String)
|
||||
, "reizer" .= show reizer
|
||||
, "gereizter" .= show gereizter ]
|
||||
toJSON (ResponseEvent response reizer gereizter) =
|
||||
object [ "query" .= ("response_event" :: String)
|
||||
, "response" .= response
|
||||
, "reizer" .= show reizer
|
||||
, "gereizter" .= show gereizter ]
|
||||
toJSON NoGameQuery =
|
||||
object [ "query" .= ("no_game" :: String) ]
|
||||
|
||||
instance FromJSON ChosenResponse where
|
||||
parseJSON = withObject "ChosenResponse" $ \v -> ChosenResponse
|
||||
<$> v .: "card"
|
||||
|
||||
instance FromJSON BidResponse where
|
||||
parseJSON = withObject "BidResponse" $ \v -> BidResponse
|
||||
<$> v .: "bid"
|
||||
|
||||
instance FromJSON YesNo where
|
||||
parseJSON = withObject "BidYesNo" $ \v -> YesNo
|
||||
<$> v .: "yesno"
|
||||
|
||||
instance FromJSON GameResponse where
|
||||
parseJSON = withObject "GameResponse" $ \v -> GameResponse
|
||||
<$> v .: "game"
|
||||
|
||||
instance FromJSON ChosenCards where
|
||||
parseJSON = withObject "ChosenCards" $ \v -> ChosenCards
|
||||
<$> v .: "cards"
|
||||
|
||||
+76
-82
@@ -19,11 +19,14 @@ import qualified Data.Map.Strict as M
|
||||
|
||||
import Skat.Player
|
||||
import qualified Skat.Player.Utils as P
|
||||
import Skat.Pile
|
||||
import Skat.Pile hiding (isSkat)
|
||||
import Skat.Card
|
||||
import Skat.Utils
|
||||
import Skat (Skat, modifyp, mkSkatEnv)
|
||||
import Skat (Skat, modifyp, mkSkatEnv, evalSkat)
|
||||
import Skat.Operations
|
||||
import qualified Skat.AI.Minmax as Minmax
|
||||
import qualified Skat.AI.Stupid as Stupid (Stupid(..))
|
||||
import Skat.Bidding
|
||||
|
||||
data AIEnv = AIEnv { getTeam :: Team
|
||||
, getHand :: Hand
|
||||
@@ -53,8 +56,8 @@ modifyg f = modify g
|
||||
type AI m = StateT AIEnv m
|
||||
|
||||
instance MonadPlayer m => MonadPlayer (AI m) where
|
||||
trumpColour = lift $ trumpColour
|
||||
turnColour = lift $ turnColour
|
||||
trump = lift trump
|
||||
turnColour = lift turnColour
|
||||
showSkat = lift . showSkat
|
||||
|
||||
instance MonadPlayerOpen m => MonadPlayerOpen (AI m) where
|
||||
@@ -63,11 +66,11 @@ instance MonadPlayerOpen m => MonadPlayerOpen (AI m) where
|
||||
type Simulator m = ReaderT Piles (AI m)
|
||||
|
||||
instance MonadPlayer m => MonadPlayer (Simulator m) where
|
||||
trumpColour = lift $ trumpColour
|
||||
trump = lift trump
|
||||
turnColour = lift $ turnColour
|
||||
showSkat = lift . showSkat
|
||||
|
||||
instance MonadPlayer m => MonadPlayerOpen (Simulator m) where
|
||||
instance (MonadIO m, MonadPlayer m) => MonadPlayerOpen (Simulator m) where
|
||||
showPiles = ask
|
||||
|
||||
runWithPiles :: MonadPlayer m
|
||||
@@ -77,9 +80,9 @@ runWithPiles ps sim = runReaderT sim ps
|
||||
instance Player AIEnv where
|
||||
team = getTeam
|
||||
hand = getHand
|
||||
chooseCard p table fallen hand = runStateT (do
|
||||
chooseCard p table fallen _ hand = runStateT (do
|
||||
modify $ setTable table
|
||||
modify $ setHand hand
|
||||
modify $ setHand (map toCard hand)
|
||||
modify $ setFallen fallen
|
||||
choose) p
|
||||
onCardPlayed p card = execStateT (do
|
||||
@@ -110,15 +113,15 @@ has hand cs = M.mapWithKey f
|
||||
| card `elem` cs = [H hand]
|
||||
| otherwise = hands
|
||||
|
||||
hasNoLonger :: MonadPlayer m => Hand -> Colour -> AI m ()
|
||||
hasNoLonger :: MonadPlayer m => Hand -> TurnColour -> AI m ()
|
||||
hasNoLonger hand colour = do
|
||||
trCol <- trumpColour
|
||||
trCol <- trump
|
||||
modifyg $ hasNoLonger_ trCol hand colour
|
||||
|
||||
hasNoLonger_ :: Colour -> Hand -> Colour -> Guess -> Guess
|
||||
hasNoLonger_ trColour hand effCol = M.mapWithKey f
|
||||
hasNoLonger_ :: Trump -> Hand -> TurnColour -> Guess -> Guess
|
||||
hasNoLonger_ trump hand effCol = M.mapWithKey f
|
||||
where f card hands
|
||||
| effectiveColour trColour card == effCol && (H hand) `elem` hands = filter (/=H hand) hands
|
||||
| effectiveColour trump card == effCol && (H hand) `elem` hands = filter (/=H hand) hands
|
||||
| otherwise = hands
|
||||
|
||||
isSkat :: [Card] -> Guess -> Guess
|
||||
@@ -134,26 +137,22 @@ analyzeTurn (c1, c2, c3) = do
|
||||
modifyg (getCard c1 `hasBeenPlayed`)
|
||||
modifyg (getCard c2 `hasBeenPlayed`)
|
||||
modifyg (getCard c3 `hasBeenPlayed`)
|
||||
trCol <- trumpColour
|
||||
trCol <- trump
|
||||
let turnCol = getColour $ getCard c1
|
||||
demanded = effectiveColour trCol (getCard c1)
|
||||
col2 = effectiveColour trCol (getCard c2)
|
||||
col3 = effectiveColour trCol (getCard c3)
|
||||
if col2 /= demanded
|
||||
then origin c2 `hasNoLonger` demanded
|
||||
then uorigin (getPile c2) `hasNoLonger` demanded
|
||||
else return ()
|
||||
if col3 /= demanded
|
||||
then origin c3 `hasNoLonger` demanded
|
||||
then uorigin (getPile c3) `hasNoLonger` demanded
|
||||
else return ()
|
||||
|
||||
type Distribution = ([Card], [Card], [Card], [Card])
|
||||
|
||||
toPiles :: [CardS Played] -> Distribution -> Piles
|
||||
toPiles table (h1, h2, h3, skt) = Piles (cs1 ++ cs2 ++ cs3) table ss
|
||||
where cs1 = map (putAt Hand1) h1
|
||||
cs2 = map (putAt Hand2) h2
|
||||
cs3 = map (putAt Hand3) h3
|
||||
ss = map (putAt SkatP) skt
|
||||
toPiles table (h1, h2, h3, skt) = makePiles h1 h2 h3 table skt
|
||||
|
||||
compareGuess :: (Card, [Option]) -> (Card, [Option]) -> Ordering
|
||||
compareGuess (c1, ops1) (c2, ops2)
|
||||
@@ -216,46 +215,37 @@ simplify :: Hand -> [Distribution] -> [(Distribution, Int)]
|
||||
simplify hand ds = M.elems cleaned
|
||||
where cleaned = remove789s hand ds
|
||||
|
||||
onPlayed :: MonadPlayer m => CardS Played -> AI m ()
|
||||
onPlayed :: (MonadIO m, MonadPlayer m) => CardS Played -> AI m ()
|
||||
onPlayed c = do
|
||||
liftIO $ print c
|
||||
modifyg (getCard c `hasBeenPlayed`)
|
||||
trCol <- trumpColour
|
||||
trCol <- trump
|
||||
turnCol <- turnColour
|
||||
let col = effectiveColour trCol (getCard c)
|
||||
case turnCol of
|
||||
Just demanded -> if col /= demanded
|
||||
then origin c `hasNoLonger` demanded else return ()
|
||||
then uorigin (getPile c) `hasNoLonger` demanded else return ()
|
||||
Nothing -> return ()
|
||||
|
||||
choose :: MonadPlayer m => AI m Card
|
||||
choose = do
|
||||
handCards <- gets myHand
|
||||
table <- gets table
|
||||
case length table of
|
||||
0 -> if length handCards >= 7
|
||||
then chooseLead
|
||||
else chooseStatistic
|
||||
n -> chooseStatistic
|
||||
choose :: (MonadIO m, MonadPlayer m) => AI m Card
|
||||
choose = chooseStatistic
|
||||
|
||||
chooseStatistic :: MonadPlayer m => AI m Card
|
||||
chooseStatistic :: (MonadIO m, MonadPlayer m) => AI m Card
|
||||
chooseStatistic = do
|
||||
h <- gets getHand
|
||||
handCards <- gets myHand
|
||||
let depth = case length handCards of
|
||||
0 -> 0
|
||||
1 -> 1
|
||||
-- simulate whole game
|
||||
2 -> 2
|
||||
3 -> 3
|
||||
-- simulate only partially
|
||||
4 -> 3
|
||||
5 -> 2
|
||||
6 -> 2
|
||||
7 -> 1
|
||||
8 -> 1
|
||||
9 -> 1
|
||||
10 -> 1
|
||||
table <- gets table
|
||||
let tableNo = length table
|
||||
left = 3 - tableNo
|
||||
depth = case length handCards of
|
||||
10 -> 3 + tableNo
|
||||
9 -> 3 + tableNo
|
||||
8 -> 3 + tableNo
|
||||
7 -> 6 + tableNo
|
||||
6 -> 9 + tableNo
|
||||
5 -> 12 + tableNo
|
||||
4 -> 15 + tableNo
|
||||
_ -> 100
|
||||
modify $ setDepth depth
|
||||
guess__ <- gets guess
|
||||
self <- get
|
||||
@@ -264,8 +254,7 @@ chooseStatistic = do
|
||||
guess = case maySkat of
|
||||
Just cs -> (cs `isSkat`) guess_
|
||||
Nothing -> guess_
|
||||
table <- gets table
|
||||
let ns = case length table of
|
||||
let ns = case tableNo of
|
||||
0 -> (0, 0, 0, 0)
|
||||
1 -> (-1, 0, -1, 0)
|
||||
2 -> (0, 0, -1, 0)
|
||||
@@ -274,9 +263,8 @@ chooseStatistic = do
|
||||
reducedDis = simplify Hand3 realDis
|
||||
reducedDisNo = length reducedDis
|
||||
piless = map (\(d, n) -> (toPiles table d, n)) reducedDis
|
||||
limit = if depth == 1 && length table == 2
|
||||
then 1
|
||||
else min 10000 $ realDisNo `div` 2
|
||||
limit = min 10000 $ realDisNo `div` 2
|
||||
liftIO $ putStrLn $ "players hand" ++ show handCards
|
||||
liftIO $ putStrLn $ "possible distrs without simp " ++ show realDisNo
|
||||
liftIO $ putStrLn $ "possible distrs " ++ show reducedDisNo
|
||||
vals <- M.toList <$> foldWithLimit limit runOnPiles M.empty piless
|
||||
@@ -296,40 +284,40 @@ foldWithLimit limit f start (x:xs) = do
|
||||
foldWithLimit limit f m xs
|
||||
_ -> return start
|
||||
|
||||
runOnPiles :: MonadPlayer m
|
||||
runOnPiles :: (MonadIO m, MonadPlayer m)
|
||||
=> M.Map Card Int -> (Piles, Int) -> AI m (M.Map Card Int)
|
||||
runOnPiles m (ps, n) = do
|
||||
c <- runWithPiles ps chooseOpen
|
||||
return $ M.insertWith (+) c n m
|
||||
|
||||
chooseOpen :: (MonadState AIEnv m, MonadPlayerOpen m) => m Card
|
||||
chooseOpen :: (MonadIO m, MonadState AIEnv m, MonadPlayerOpen m) => m Card
|
||||
chooseOpen = do
|
||||
piles <- showPiles
|
||||
hand <- gets getHand
|
||||
let myCards = handCards hand piles
|
||||
possible <- filterM (P.isAllowed myCards) myCards
|
||||
case length myCards of
|
||||
case length possible of
|
||||
0 -> do
|
||||
liftIO $ print hand
|
||||
liftIO $ print piles
|
||||
error "no cards left to choose from"
|
||||
1 -> return $ head myCards
|
||||
1 -> return $ toCard $ head possible
|
||||
_ -> chooseSimulating
|
||||
|
||||
chooseSimulating :: (MonadState AIEnv m, MonadPlayerOpen m)
|
||||
=> m Card
|
||||
chooseSimulating = do
|
||||
piles <- showPiles
|
||||
hand <- gets getHand
|
||||
let myCards = handCards hand piles
|
||||
possible <- filterM (P.isAllowed myCards) myCards
|
||||
case possible of
|
||||
[card] -> return card
|
||||
cs -> do
|
||||
results <- mapM simulate cs
|
||||
let both = zip results cs
|
||||
best = maximumBy (comparing fst) both
|
||||
return $ snd best
|
||||
turnCol <- turnColour
|
||||
trumpCol <- trump
|
||||
myHand <- gets getHand
|
||||
depth <- gets simulationDepth
|
||||
let ps = Players (PL $ Stupid.Stupid Team Hand1)
|
||||
(PL $ Stupid.Stupid Team Hand2)
|
||||
(PL $ Stupid.Stupid Single Hand3)
|
||||
-- TODO: fix
|
||||
env = mkSkatEnv piles turnCol undefined ps myHand undefined
|
||||
liftIO $ evalSkat (toCard <$> (Minmax.choose depth :: Skat (CardS Owner))) env
|
||||
|
||||
simulate :: (MonadState AIEnv m, MonadPlayerOpen m)
|
||||
=> Card -> m Int
|
||||
@@ -337,21 +325,23 @@ simulate card = do
|
||||
-- retrieve all relevant info
|
||||
piles <- showPiles
|
||||
turnCol <- turnColour
|
||||
trumpCol <- trumpColour
|
||||
trumpCol <- trump
|
||||
myTeam <- gets getTeam
|
||||
myHand <- gets getHand
|
||||
depth <- gets simulationDepth
|
||||
liftIO $ putStrLn $ "simulate: " ++ show myHand ++ " plays " ++ show card
|
||||
let newDepth = depth - 1
|
||||
-- create a virtual env with 3 ai players
|
||||
ps = Players
|
||||
(PL $ mkAIEnv Team Hand1 newDepth)
|
||||
(PL $ mkAIEnv Team Hand2 newDepth)
|
||||
(PL $ mkAIEnv Single Hand3 newDepth)
|
||||
env = mkSkatEnv piles turnCol trumpCol ps
|
||||
-- TODO: fix
|
||||
env = mkSkatEnv piles turnCol undefined ps (next myHand) undefined
|
||||
-- simulate the game after playing the given card
|
||||
(sgl, tm) <- liftIO $ evalStateT (do
|
||||
modifyp $ playCard card
|
||||
turnGeneric playOpen depth (next myHand)) env
|
||||
(sgl, tm) <- liftIO $ evalSkat (do
|
||||
modifyp $ playCard myHand card
|
||||
turnGeneric playOpen depth) env
|
||||
let v = if myTeam == Single then (sgl, tm) else (tm, sgl)
|
||||
-- put the value into context for when not the whole game is
|
||||
-- simulated
|
||||
@@ -364,26 +354,27 @@ predictValue (own, others) = do
|
||||
piles <- showPiles
|
||||
let cs = handCards hand piles
|
||||
pot <- potential cs
|
||||
return $ own + pot
|
||||
--return $ own + pot
|
||||
return (own-others)
|
||||
|
||||
potential :: (MonadState AIEnv m, MonadPlayerOpen m)
|
||||
=> [Card] -> m Int
|
||||
potential :: (MonadState AIEnv m, MonadPlayerOpen m, HasCard c)
|
||||
=> [c] -> m Int
|
||||
potential cs = do
|
||||
tr <- trumpColour
|
||||
tr <- trump
|
||||
let trs = filter (isTrump tr) cs
|
||||
value = count cs
|
||||
positions <- filter (==0) <$> mapM position cs
|
||||
value = count . map toCard $ cs
|
||||
positions <- filter (==0) <$> mapM (position . toCard) cs
|
||||
return $ length trs * 10 + value + length positions * 5
|
||||
|
||||
position :: (MonadState AIEnv m, MonadPlayer m)
|
||||
=> Card -> m Int
|
||||
position card = do
|
||||
tr <- trumpColour
|
||||
tr <- trump
|
||||
guess <- gets guess
|
||||
let effCol = effectiveColour tr card
|
||||
l = M.toList guess
|
||||
cs = filterMap ((==effCol) . effectiveColour tr . fst) fst l
|
||||
csInd = zip [0..] cs
|
||||
csInd = zip [0..] (reverse cs)
|
||||
Just (pos, _) = find ((== card) . snd) csInd
|
||||
return pos
|
||||
|
||||
@@ -397,12 +388,15 @@ leadPotential card = do
|
||||
0 -> return value
|
||||
_ -> return $ -value
|
||||
|
||||
chooseLead :: (MonadState AIEnv m, MonadPlayer m) => m Card
|
||||
chooseLead :: (MonadIO m, MonadState AIEnv m, MonadPlayer m) => m Card
|
||||
chooseLead = do
|
||||
cards <- gets myHand
|
||||
possible <- filterM (P.isAllowed cards) cards
|
||||
liftIO $ putStrLn $ "choosing lead from " ++ show possible
|
||||
pots <- mapM leadPotential possible
|
||||
return $ snd $ maximumBy (comparing fst) (zip pots possible)
|
||||
let ps = zip pots possible
|
||||
liftIO $ putStrLn $ "lead potential of cards " ++ show ps
|
||||
return $ snd $ maximumBy (comparing fst) ps
|
||||
|
||||
mkAIEnv :: Team -> Hand -> Int -> AIEnv
|
||||
mkAIEnv tm h depth = AIEnv tm h [] [] [] newGuess depth
|
||||
|
||||
@@ -48,13 +48,18 @@ initServer :: Net.PortNumber -> Buffering -> OnReceive -> IO ServerEnv
|
||||
initServer port buffermode handler = do
|
||||
sock <- Net.socket Net.AF_INET Net.Stream 0
|
||||
Net.setSocketOption sock Net.ReuseAddr 1
|
||||
Net.bind sock (Net.SockAddrInet port Net.iNADDR_ANY)
|
||||
addr <- Net.addrAddress <$> resolve
|
||||
Net.bind sock addr
|
||||
Net.listen sock 5
|
||||
chan <- newChan
|
||||
forkIO $ forever $ do
|
||||
msg <- readChan chan -- clearing the main channel
|
||||
return ()
|
||||
return (ServerEnv buffermode sock chan handler)
|
||||
where resolve = do
|
||||
let hints = Net.defaultHints { Net.addrSocketType = Net.Stream }
|
||||
addrs <- Net.getAddrInfo (Just hints) (Just "127.0.0.1") (Just $ show port)
|
||||
return $ head addrs
|
||||
|
||||
close :: ServerEnv -> IO ()
|
||||
close = Net.close . socket
|
||||
|
||||
@@ -0,0 +1,318 @@
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
{-# LANGUAGE MultiParamTypeClasses #-}
|
||||
{-# LANGUAGE TypeSynonymInstances #-}
|
||||
{-# LANGUAGE FlexibleInstances #-}
|
||||
{-# LANGUAGE FlexibleContexts #-}
|
||||
{-# LANGUAGE FunctionalDependencies #-}
|
||||
{-# LANGUAGE TupleSections #-}
|
||||
{-# LANGUAGE ScopedTypeVariables #-}
|
||||
{-# LANGUAGE DeriveGeneric #-}
|
||||
|
||||
module Skat.AI.Skat where
|
||||
|
||||
import Data.String
|
||||
import System.IO
|
||||
import GHC.Generics
|
||||
import Control.Monad.State
|
||||
import Control.Exception (assert)
|
||||
import Control.Monad.Fail
|
||||
import Control.Monad.Writer
|
||||
import Data.Ord
|
||||
import Data.Aeson hiding (Value)
|
||||
import Text.Read (readMaybe)
|
||||
import Data.List (maximumBy, sortBy)
|
||||
import Debug.Trace
|
||||
import Data.Map.Strict (Map)
|
||||
import qualified Data.Map.Strict as Map
|
||||
import qualified System.Random as Rand
|
||||
import qualified Data.ByteString.Lazy.Char8 as BS8
|
||||
|
||||
import System.IO.Unsafe
|
||||
|
||||
import qualified Skat as S
|
||||
import qualified Skat.Card as S
|
||||
import qualified Skat.Utils as S
|
||||
import qualified Skat.AI.Stupid as S
|
||||
import qualified Skat.Operations as S
|
||||
import qualified Skat.Pile as S
|
||||
import qualified Skat.Player as P hiding (trumpColour)
|
||||
import qualified Skat.Render as S
|
||||
import qualified Skat.Bidding as S
|
||||
import Skat.AI.Base hiding (playCLI, Choose(..))
|
||||
import Skat.AI.MonteCarlo
|
||||
import Skat.AI.Games.Skat.Guess
|
||||
|
||||
instance Player P.PL where
|
||||
maxing p = P.team p == S.Team
|
||||
|
||||
instance Player Bool where
|
||||
maxing = id
|
||||
|
||||
instance Value Float where
|
||||
invert = (1-)
|
||||
win = undefined
|
||||
loss = undefined
|
||||
tie = undefined
|
||||
tonum = id
|
||||
|
||||
instance P.MonadPlayer (StateT S.SkatEnv (Writer [S.Trick])) where
|
||||
trump = S.getTrump <$> P.game
|
||||
turnColour = gets S.turnColour
|
||||
showSkat p = case P.team p of
|
||||
S.Single -> fmap (Just . S.skatCards) $ gets S.piles
|
||||
S.Team -> return Nothing
|
||||
singlePlayer = gets S.skatSinglePlayer
|
||||
game = gets S.skatGame
|
||||
|
||||
data SkatState = SkatState { skatEnv :: S.SkatEnv
|
||||
, self :: S.Hand
|
||||
, guess :: Guess
|
||||
}
|
||||
deriving (Show, Generic)
|
||||
|
||||
instance ToJSON SkatState where
|
||||
toJSON state = object [ "guess" .= toJSON (guess state)
|
||||
, "table" .= toJSON (S.tableCards $ S.piles $ skatEnv state)
|
||||
, "won_single" .= toJSON (S.wonCards S.Single $ S.piles $ skatEnv state)
|
||||
, "won_team" .= toJSON (S.wonCards S.Team $ S.piles $ skatEnv state)
|
||||
]
|
||||
|
||||
instance Draw SkatState where
|
||||
draw = show . S.tableCards . S.piles . skatEnv
|
||||
|
||||
instance PlayableGame (S.CardS S.Owner) [] Float P.PL S.Skat where
|
||||
showBoard = do
|
||||
liftIO $ putStrLn ""
|
||||
table <- S.getp S.tableCards
|
||||
liftIO $ putStr "Table: "
|
||||
liftIO $ print table
|
||||
showTurns = do
|
||||
cards <- turns
|
||||
player <- currentPlayer
|
||||
liftIO $ print player
|
||||
liftIO $ S.render cards
|
||||
winner = do
|
||||
piles <- gets S.piles
|
||||
pls <- gets S.players
|
||||
let res = S.count piles :: (Int, Int)
|
||||
winnerTeam = trace (show res) $ if fst res > snd res then S.Single else S.Team
|
||||
winners = filter ((==winnerTeam) . P.team) (P.playersToList pls)
|
||||
return $ Just $ head winners
|
||||
askTurn = do
|
||||
cards <- turns
|
||||
let sorted = cards
|
||||
input <- liftIO getLine
|
||||
case readMaybe input of
|
||||
Just n -> if n >= 0 && n < length sorted then return $ Just (sorted !! n)
|
||||
else return Nothing
|
||||
Nothing -> return Nothing
|
||||
showTurn card = do
|
||||
player <- currentPlayer
|
||||
liftIO $ putStrLn $ show player ++ " plays " ++ show card
|
||||
|
||||
instance MonadGame (S.CardS S.Owner) [] Float P.PL S.Skat where
|
||||
currentPlayer = do
|
||||
hand <- gets S.currentHand
|
||||
pls <- gets S.players
|
||||
return $! P.player pls hand
|
||||
turns = S.allowedCards
|
||||
--player <- currentPlayer
|
||||
--trCol <- gets S.trumpColour
|
||||
--return $! if maxing player
|
||||
-- then sortBy (optimalTeam trCol) cards
|
||||
-- else sortBy (optimalSingle trCol) cards
|
||||
play = S.play_
|
||||
simulate card action = do
|
||||
--oldCurrent <- gets S.currentHand
|
||||
--oldTurnCol <- gets S.turnColour
|
||||
backup <- get
|
||||
play card
|
||||
--oldWinner <- currentPlayer
|
||||
res <- action
|
||||
--S.undo_ card oldCurrent oldTurnCol (P.team oldWinner)
|
||||
put backup
|
||||
return $! res
|
||||
over = ((==0) . length) <$!> S.allowedCards
|
||||
evaluate = do
|
||||
player <- currentPlayer
|
||||
piles <- gets S.piles
|
||||
let (sgl, tm) = S.count piles :: (Int, Int)
|
||||
return $! fromIntegral (if maxing player then tm - sgl else sgl - tm)
|
||||
|
||||
data Turn = Turn { turnStartingEnv :: S.SkatEnv
|
||||
, turnCard :: S.Card }
|
||||
deriving Show
|
||||
|
||||
instance ToJSON Turn where
|
||||
toJSON turn = object [ "turn_card" .= turnCard turn ]
|
||||
|
||||
instance Draw Turn where
|
||||
draw = show . turnCard
|
||||
|
||||
instance HasGameState Turn Bool Float SkatState where
|
||||
current s =
|
||||
let curhand = S.currentHand $ skatEnv s
|
||||
sglhand = S.skatSinglePlayer $ skatEnv s
|
||||
in sglhand == curhand
|
||||
monteevaluate s = let (sgl, tm) = ev S.countGame (skatEnv s)
|
||||
in if sgl > tm then 1.0 else 0.0 --fromIntegral sgl / (fromIntegral $ sgl + tm)
|
||||
execute turn state =
|
||||
let tbl = ev (S.getp S.tableCards) env
|
||||
curhand = S.currentHand env
|
||||
trpCol :: S.Trump
|
||||
trpCol = ev (S.getTrump <$> gets S.skatGame) env
|
||||
turnCol = ev (gets S.turnColour) env
|
||||
observed = observe trpCol turnCol (card':tbl) (guess state)
|
||||
guess' = card `hasBeenPlayed` observed
|
||||
card' = S.CardS card (S.P curhand)
|
||||
newEnv = ex (S.play_ card) env
|
||||
in state { skatEnv = newEnv
|
||||
, guess = guess'
|
||||
}
|
||||
where env = turnStartingEnv turn
|
||||
card = turnCard turn
|
||||
moves s
|
||||
| S.currentHand env == self s =
|
||||
let options = fmap S.toCard $ ev S.allowedCards env
|
||||
in fmap (Turn env) options
|
||||
| otherwise =
|
||||
let currentPiles = ev (gets S.piles) env
|
||||
table = S.tableCards currentPiles
|
||||
n1 = length $ filter ((S.P S.Hand1==) . S.getPile) table
|
||||
n2 = length $ filter ((S.P S.Hand2==) . S.getPile) table
|
||||
n3 = length $ filter ((S.P S.Hand3==) . S.getPile) table
|
||||
ns = (-n1, -n2, -n3, 0)
|
||||
possibleDistrs = distributions (guess s) ns
|
||||
piless = fmap ((flip updatePiles) currentPiles) possibleDistrs
|
||||
in do
|
||||
piles <- piless
|
||||
let newEnv = env { S.piles = piles }
|
||||
card <- ev S.allowedCards newEnv
|
||||
pure $ Turn newEnv (S.toCard card)
|
||||
where env = skatEnv s
|
||||
simulate s
|
||||
| Map.size (guess s) <= 2 = pure $ monteevaluate s
|
||||
| otherwise = do
|
||||
let currentPiles = ev (gets S.piles) env
|
||||
table = S.tableCards currentPiles
|
||||
n1 = length $ filter ((S.P S.Hand1==) . S.getPile) table
|
||||
n2 = length $ filter ((S.P S.Hand2==) . S.getPile) table
|
||||
n3 = length $ filter ((S.P S.Hand3==) . S.getPile) table
|
||||
ns = (-n1, -n2, -n3, 0)
|
||||
d <- randomDistr (guess s) ns
|
||||
let newEnv = env { S.piles = updatePiles d (S.piles env) }
|
||||
cards = ev S.allowedCards newEnv
|
||||
card <- chooser cards
|
||||
let newState = execute (Turn newEnv (S.toCard card)) s
|
||||
Skat.AI.MonteCarlo.simulate newState
|
||||
where env = skatEnv s
|
||||
|
||||
ev :: StateT S.SkatEnv (Writer [S.Trick]) a -> S.SkatEnv -> a
|
||||
ev action = fst . runWriter . evalStateT action
|
||||
ev2 = flip ev
|
||||
ex :: StateT S.SkatEnv (Writer [S.Trick]) a -> S.SkatEnv -> S.SkatEnv
|
||||
ex action = fst . runWriter . execStateT action
|
||||
ex2 = flip ex
|
||||
|
||||
playCLI :: Int -> StateT SkatState S.Skat ()
|
||||
playCLI n = do
|
||||
gameOver <- lift over
|
||||
if gameOver
|
||||
then lift announceWinner
|
||||
else do
|
||||
current <- (lift currentPlayer) :: StateT SkatState S.Skat (P.PL)
|
||||
self <- gets self
|
||||
--let current = False
|
||||
if P.hand current == self then do
|
||||
liftIO $ putStrLn "iterating"
|
||||
s <- get
|
||||
let tree = Leaf s False (0, 0)
|
||||
l = length $ guess s
|
||||
depth
|
||||
| l >= 26 = 15
|
||||
| l >= 20 = 100
|
||||
| l >= 14 = 2000
|
||||
| otherwise = 5000
|
||||
t = runmonte n (foldM (\tree _ -> montecarlo tree) tree [1..depth])
|
||||
newstate = bestmove t
|
||||
json :: String
|
||||
json = BS8.unpack $ encode t
|
||||
liftIO $ print newstate
|
||||
--liftIO $ withFile "tree.json" WriteMode $ \handle ->
|
||||
-- hPutStrLn handle json
|
||||
--liftIO $ putStrLn $ visualise t
|
||||
put newstate
|
||||
lift (put $ skatEnv newstate)
|
||||
else do
|
||||
liftIO $ putStrLn "new turn"
|
||||
lift $ showBoard
|
||||
t <- lift readTurn
|
||||
lift $ play t
|
||||
s <- get
|
||||
env <- lift get
|
||||
let guess' = (S.toCard t) `hasBeenPlayed` (guess s)
|
||||
observed <- lift $ observeS guess'
|
||||
let s' = s { skatEnv = env
|
||||
, guess = observed
|
||||
}
|
||||
put s'
|
||||
{-
|
||||
showBoard
|
||||
liftIO $ getLine
|
||||
-}
|
||||
--playCLI n
|
||||
where
|
||||
--readTurn :: (MonadFail m, Read t, PlayableGame t l v p m) => m t
|
||||
readTurn :: S.Skat (S.CardS S.Owner)
|
||||
readTurn = do
|
||||
v <- evaluate :: S.Skat Float
|
||||
options <- (turns :: S.Skat [S.CardS S.Owner])
|
||||
showTurns
|
||||
liftIO $ putStr "> "
|
||||
mayTurn <- askTurn
|
||||
case mayTurn of
|
||||
Just val -> if val `elem` options then return val else readTurn
|
||||
Nothing -> readTurn
|
||||
announceWinner :: S.Skat ()
|
||||
announceWinner = do
|
||||
showBoard
|
||||
win <- (winner :: S.Skat (Maybe P.PL))
|
||||
liftIO $ putStrLn $ show win ++ " wins the game!"
|
||||
|
||||
initSkatEnv :: Int -> S.SkatEnv
|
||||
initSkatEnv n =
|
||||
let gen = Rand.mkStdGen n
|
||||
--cards = S.shuffle gen S.allCards
|
||||
--piles = S.distribute cards
|
||||
piles = S.cardDistr9
|
||||
players = P.Players
|
||||
(P.PL $ S.Stupid S.Single S.Hand1)
|
||||
(P.PL $ S.Stupid S.Team S.Hand2)
|
||||
(P.PL $ S.Stupid S.Team S.Hand3)
|
||||
in S.SkatEnv { S.piles = piles
|
||||
, S.turnColour = Just (S.TurnColour S.Hearts)
|
||||
, S.skatGame = S.Colour S.Spades S.Einfach
|
||||
, S.players = players
|
||||
, S.currentHand = S.Hand1
|
||||
, S.skatSinglePlayer = S.Hand1
|
||||
}
|
||||
|
||||
initSkatState :: SkatState
|
||||
initSkatState =
|
||||
let env = initSkatEnv 42
|
||||
ownCards = S.handCards S.Hand1 $ S.piles env
|
||||
sktCards = S.skatCards $ S.piles env
|
||||
tblCards = fmap S.toCard $ S.tableCards $ S.piles env
|
||||
totalcards = fmap S.toCard $ S.fromPiles $ S.piles env
|
||||
guess = (\g -> foldr hasBeenPlayed g tblCards) . isSkat sktCards . (S.Hand1 `hasOnly` (fmap S.toCard ownCards)) $ newGuessWith totalcards
|
||||
in SkatState { skatEnv = env
|
||||
, self = S.Hand1
|
||||
, guess = guess
|
||||
}
|
||||
|
||||
playSkat :: Int -> IO ()
|
||||
playSkat n = let env = skatEnv initSkatState
|
||||
in void $ S.evalSkat ( (flip runStateT) initSkatState (playCLI n) ) env
|
||||
|
||||
skattree :: Tree Turn SkatState
|
||||
skattree = Leaf initSkatState False (0,0)
|
||||
+23
-3
@@ -1,8 +1,13 @@
|
||||
module Skat.AI.Stupid where
|
||||
|
||||
import Control.Concurrent
|
||||
import Control.Monad.State
|
||||
|
||||
import Skat.Player
|
||||
import Skat.Pile
|
||||
import Skat.Card
|
||||
import Skat.Preperation
|
||||
import Skat.Bidding
|
||||
|
||||
data Stupid = Stupid { getTeam :: Team
|
||||
, getHand :: Hand }
|
||||
@@ -11,8 +16,23 @@ data Stupid = Stupid { getTeam :: Team
|
||||
instance Player Stupid where
|
||||
team = getTeam
|
||||
hand = getHand
|
||||
chooseCard p _ _ hand = do
|
||||
trumpCol <- trumpColour
|
||||
chooseCard p _ _ _ hand = do
|
||||
trumpCol <- trump
|
||||
turnCol <- turnColour
|
||||
--liftIO $ threadDelay 1000000
|
||||
let possible = filter (isAllowed trumpCol turnCol hand) hand
|
||||
return (head possible, p)
|
||||
return (toCard $ head possible, p)
|
||||
|
||||
newtype NoBidder = NoBidder Hand
|
||||
deriving Show
|
||||
|
||||
-- | no bidding from that player
|
||||
instance Bidder NoBidder where
|
||||
hand (NoBidder h) = h
|
||||
askBid _ _ bid = return Nothing
|
||||
askResponse _ _ bid = if bid < 24 then return True else return False
|
||||
askGame _ _ = return $ Grand Hand
|
||||
askHand _ _ = return True
|
||||
askSkat _ _ _ = undefined -- never called
|
||||
toPlayer (NoBidder h) team = PL $ Stupid team h
|
||||
onStart _ = return ()
|
||||
|
||||
@@ -0,0 +1,242 @@
|
||||
{-# LANGUAGE MultiParamTypeClasses #-}
|
||||
{-# LANGUAGE BlockArguments #-}
|
||||
{-# LANGUAGE TypeSynonymInstances #-}
|
||||
{-# LANGUAGE FlexibleInstances #-}
|
||||
{-# LANGUAGE FlexibleContexts #-}
|
||||
{-# LANGUAGE FunctionalDependencies #-}
|
||||
{-# LANGUAGE TupleSections #-}
|
||||
{-# LANGUAGE InstanceSigs #-}
|
||||
{-# LANGUAGE StandaloneDeriving #-}
|
||||
{-# LANGUAGE ImportQualifiedPost #-}
|
||||
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
|
||||
|
||||
module Skat.AI.TicTacToe where
|
||||
|
||||
import Control.Monad.State
|
||||
import Control.Exception (assert)
|
||||
import Control.Monad.Fail
|
||||
import Data.Ord
|
||||
import Text.Read (readMaybe)
|
||||
import Data.List (maximumBy, sortBy)
|
||||
import Debug.Trace
|
||||
import Text.Printf
|
||||
import Data.Maybe
|
||||
import qualified System.Random as Rand
|
||||
|
||||
import Skat.AI.Base
|
||||
import Skat.AI.MonteCarlo
|
||||
import Skat.Utils
|
||||
-- TIC TAC TOE implementation
|
||||
|
||||
data TicTacToe = Tic | Tac | Toe
|
||||
deriving (Eq, Ord)
|
||||
|
||||
instance Show TicTacToe where
|
||||
show Tic = "O"
|
||||
show Tac = "X"
|
||||
show Toe = "_"
|
||||
|
||||
data WinLossTie = Loss | Tie | Win
|
||||
deriving (Eq, Show, Ord)
|
||||
|
||||
instance Value WinLossTie where
|
||||
invert Win = Loss
|
||||
invert Loss = Win
|
||||
invert Tie = Tie
|
||||
win = Win
|
||||
loss = Loss
|
||||
tie = Tie
|
||||
|
||||
data GameState = GameState { getBoard :: [TicTacToe]
|
||||
, getCurrent :: Bool }
|
||||
deriving Show
|
||||
|
||||
instance HasGameState Int Bool WinLossTie GameState where
|
||||
execute turn state = execState (play turn) state
|
||||
moves state = evalState turns state
|
||||
monteevaluate s = let b = getBoard s
|
||||
w = fromMaybe Toe $ ticWinner b
|
||||
in case w of
|
||||
Tac -> Win
|
||||
Tic -> Loss
|
||||
Toe -> Tie
|
||||
current s = evalState currentPlayer s
|
||||
|
||||
instance Player Bool where
|
||||
maxing = id
|
||||
|
||||
instance Monad m => MonadGame Int [] WinLossTie Bool (StateT GameState m) where
|
||||
currentPlayer = gets getCurrent
|
||||
turns = do
|
||||
o <- over
|
||||
if o then return [] else do
|
||||
board <- gets getBoard
|
||||
let fields = zip [0..] board
|
||||
return $ map fst $ filter ((==Toe) . snd) fields
|
||||
play turn = do
|
||||
env <- get
|
||||
let value = if getCurrent env then Tic else Tac
|
||||
board' = updateAt turn (getBoard env) value
|
||||
current' = not $ getCurrent env
|
||||
put $ GameState board' current'
|
||||
simulate turn action = do
|
||||
backup <- get
|
||||
play turn
|
||||
res <- action
|
||||
put backup
|
||||
return $! res
|
||||
evaluate = do
|
||||
board <- gets getBoard
|
||||
current <- currentPlayer
|
||||
let mayWinner = ticWinner board
|
||||
case mayWinner of
|
||||
Just Tic -> return $ if current then Win else Loss
|
||||
Just Tac -> return $ if current then Loss else Win
|
||||
Just Toe -> return Tie
|
||||
Nothing -> return Tie
|
||||
over = do
|
||||
board <- gets getBoard
|
||||
case ticWinner board of
|
||||
Just _ -> return True
|
||||
_ -> return False
|
||||
|
||||
ticWinner :: [TicTacToe] -> Maybe TicTacToe
|
||||
ticWinner board
|
||||
| ticWon = Just Tic
|
||||
| tacWon = Just Tac
|
||||
| over = Just Toe
|
||||
| otherwise = Nothing
|
||||
where ticWon = hasWon $ map (==Tic) board
|
||||
tacWon = hasWon $ map (==Tac) board
|
||||
hasWon (True:_:_:True:_:_:True:_:_:[]) = True
|
||||
hasWon (True:_:_:_:True:_:_:_:True:[]) = True
|
||||
hasWon (_:True:_:_:True:_:_:True:_:[]) = True
|
||||
hasWon (_:_:True:_:_:True:_:_:True:[]) = True
|
||||
hasWon (_:_:True:_:True:_:True:_:_:[]) = True
|
||||
hasWon (True:True:True:_:_:_:_:_:_:[]) = True
|
||||
hasWon (_:_:_:True:True:True:_:_:_:[]) = True
|
||||
hasWon (_:_:_:_:_:_:True:True:True:[]) = True
|
||||
hasWon _ = False
|
||||
over = (length $ filter (==Toe) board) == 0
|
||||
|
||||
-- some consts
|
||||
|
||||
emptyBoard :: [TicTacToe]
|
||||
emptyBoard = [Toe, Toe, Toe, Toe, Toe, Toe, Toe, Toe, Toe]
|
||||
|
||||
otherBoard2 :: [TicTacToe]
|
||||
otherBoard2 = [Tic, Tac, Toe, Tac, Tac, Tic, Tic, Toe, Toe]
|
||||
|
||||
otherBoard3 :: [TicTacToe]
|
||||
otherBoard3 = [Tic, Toe, Toe, Tac, Tic, Toe, Toe, Tac, Toe]
|
||||
|
||||
tree2 = emptytree (initGameState { getBoard = otherBoard2
|
||||
, getCurrent = True })
|
||||
|
||||
tree3 = emptytree (initGameState { getBoard = otherBoard3
|
||||
, getCurrent = False })
|
||||
|
||||
initGameState :: GameState
|
||||
initGameState = GameState { getBoard = emptyBoard
|
||||
, getCurrent = False }
|
||||
|
||||
tictree :: Tree Int GameState
|
||||
tictree = emptytree initGameState
|
||||
|
||||
instance Draw GameState where
|
||||
draw s = let b = getBoard s
|
||||
in printf "%s %s %s\n%s %s %s\n%s %s %s"
|
||||
(show $ b !! 0)
|
||||
(show $ b !! 1)
|
||||
(show $ b !! 2)
|
||||
(show $ b !! 3)
|
||||
(show $ b !! 4)
|
||||
(show $ b !! 5)
|
||||
(show $ b !! 6)
|
||||
(show $ b !! 7)
|
||||
(show $ b !! 8)
|
||||
|
||||
otherBoard :: [TicTacToe]
|
||||
otherBoard = [Tic, Tac, Tac, Tic, Tac, Tic, Toe, Tic, Toe]
|
||||
|
||||
print9x9 :: (Int -> IO ()) -> IO ()
|
||||
print9x9 pr = pr 0 >> pr 1 >> pr 2 >> putStrLn ""
|
||||
>> pr 3 >> pr 4 >> pr 5 >> putStrLn ""
|
||||
>> pr 6 >> pr 7 >> pr 8 >> putStrLn ""
|
||||
|
||||
printBoard :: [TicTacToe] -> IO ()
|
||||
printBoard board = print9x9 pr >> putStrLn ""
|
||||
where pr n = putStr (show $ board !! n) >> putStr " "
|
||||
|
||||
printOptions :: [Int] -> IO ()
|
||||
printOptions opts = print9x9 pr
|
||||
where pr n
|
||||
| n `elem` opts = putStr (show n) >> putStr " "
|
||||
| otherwise = putStr " "
|
||||
|
||||
instance MonadIO m => PlayableGame Int [] WinLossTie Bool (StateT GameState m) where
|
||||
showBoard = do
|
||||
board <- gets getBoard
|
||||
liftIO $ printBoard board
|
||||
showTurns = turns >>= liftIO . printOptions
|
||||
winner = do
|
||||
board <- gets getBoard
|
||||
let win = ticWinner board
|
||||
case win of
|
||||
Just Toe -> return Nothing
|
||||
Just Tic -> return $ Just True
|
||||
Just Tac -> return $ Just False
|
||||
Nothing -> return Nothing
|
||||
askTurn = readMaybe <$> liftIO getLine
|
||||
showTurn _ = return ()
|
||||
|
||||
playTicTacToe :: Int -> IO ()
|
||||
playTicTacToe n = void $ (flip runStateT) (GameState emptyBoard False) (playCLI n)
|
||||
|
||||
playoften :: Int -> IO ()
|
||||
playoften n = mapM_ playTicTacToe [1..n]
|
||||
|
||||
{-
|
||||
newtype TicMCTS a = TicMCTS (StateT GameState (State Rand.StdGen) a)
|
||||
deriving (Functor, Applicative, Monad, MonadState GameState)
|
||||
|
||||
instance Choose Int TicMCTS where
|
||||
choose = do
|
||||
s <- get
|
||||
-}
|
||||
playCLI :: Int -> StateT GameState IO ()
|
||||
playCLI n = do
|
||||
gameOver <- over
|
||||
if gameOver
|
||||
then announceWinner
|
||||
else do
|
||||
--current <- currentPlayer
|
||||
let current = False
|
||||
if not current then do
|
||||
s <- get
|
||||
let tree = Leaf s False (0, 0)
|
||||
t = bestmove $ runmonte n (foldM (\tree _ -> montecarlo tree) tree [1..1000])
|
||||
put t
|
||||
else do
|
||||
showBoard
|
||||
t <- readTurn
|
||||
play t
|
||||
showBoard
|
||||
{-
|
||||
liftIO $ getLine
|
||||
-}
|
||||
playCLI n
|
||||
where
|
||||
readTurn :: (MonadFail m, Read t, PlayableGame t l v p m) => m t
|
||||
readTurn = do
|
||||
options <- turns
|
||||
showTurns
|
||||
liftIO $ putStr "> "
|
||||
mayTurn <- askTurn
|
||||
case mayTurn of
|
||||
Just val -> if val `elem` options then return val else readTurn
|
||||
Nothing -> readTurn
|
||||
announceWinner = do
|
||||
showBoard
|
||||
win <- winner
|
||||
liftIO $ putStrLn $ show win ++ " wins the game!"
|
||||
@@ -0,0 +1,283 @@
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
|
||||
module Skat.Bidding (
|
||||
biddingScore, Game(..), Modifier(..), isHand, getTrump, Result(..),
|
||||
getResults, isOuvert, isSchwarz, Bid, checkGame, HideGame(..)
|
||||
) where
|
||||
|
||||
import Data.Aeson hiding (Null, Result)
|
||||
|
||||
import Skat.Card
|
||||
import Data.List (sortOn)
|
||||
import Data.Ord (Down(..))
|
||||
import Control.Monad
|
||||
import Skat.Pile
|
||||
|
||||
type Bid = Int
|
||||
|
||||
-- | different game types
|
||||
data Game = Colour Colour Modifier
|
||||
| Grand Modifier
|
||||
| Null
|
||||
| NullHand
|
||||
| NullOuvert
|
||||
| NullOuvertHand
|
||||
deriving (Show, Eq)
|
||||
|
||||
newtype HideGame = HideGame Game
|
||||
deriving (Show, Eq)
|
||||
|
||||
instance ToJSON Game where
|
||||
toJSON (Grand mod) =
|
||||
object ["game" .= ("grand" :: String), "modifier" .= show mod]
|
||||
toJSON (Colour col mod) =
|
||||
object ["game" .= ("colour" :: String), "modifier" .= show mod, "colour" .= show col]
|
||||
toJSON Null = object ["game" .= ("null" :: String)]
|
||||
toJSON NullHand = object ["game" .= ("nullhand" :: String)]
|
||||
toJSON NullOuvert = object ["game" .= ("nullouvert" :: String)]
|
||||
toJSON NullOuvertHand = object ["game" .= ("nullouverthand" :: String)]
|
||||
|
||||
instance ToJSON HideGame where
|
||||
toJSON (HideGame (Grand mod)) =
|
||||
object ["game" .= ("grand" :: String), "modifier" .= prettyShow mod]
|
||||
toJSON (HideGame (Colour col mod)) =
|
||||
object ["game" .= ("colour" :: String), "modifier" .= prettyShow mod, "colour" .= show col]
|
||||
toJSON (HideGame game) = toJSON game
|
||||
|
||||
instance FromJSON Game where
|
||||
parseJSON = withObject "Game" $ \v -> do
|
||||
gamekind <- v .: "game"
|
||||
case (gamekind :: String) of
|
||||
"colour" -> do
|
||||
col <- v .: "colour"
|
||||
mod <- v .: "modifier"
|
||||
return $ Colour (read col) mod
|
||||
"grand" -> do
|
||||
mod <- v .: "modifier"
|
||||
return $ Grand mod
|
||||
"null" -> return Null
|
||||
"nullhand" -> return NullHand
|
||||
"nullouvert" -> return NullOuvert
|
||||
"nullouverthand" -> return NullOuvertHand
|
||||
_ -> mzero
|
||||
|
||||
-- | modifiers for grand and colour games
|
||||
data Modifier = Einfach
|
||||
| Schneider
|
||||
| Schwarz
|
||||
| Hand
|
||||
| HandSchneider
|
||||
| HandSchneiderAngesagt
|
||||
| HandSchwarz
|
||||
| HandSchneiderAngesagtSchwarz
|
||||
| HandSchwarzAngesagt
|
||||
| Ouvert
|
||||
deriving (Show, Eq)
|
||||
|
||||
instance FromJSON Modifier where
|
||||
parseJSON = withObject "Modifier" $ \v -> do
|
||||
hnd <- v .: "hand"
|
||||
if hnd then do
|
||||
schneider <- v .:? "schneider" .!= False
|
||||
schwarz <- v .:? "schwarz" .!= False
|
||||
ouvert <- v .:? "ouvert" .!= False
|
||||
case (schneider, schwarz, ouvert) of
|
||||
(_, _, True) -> return Ouvert
|
||||
(True, False, _) -> return HandSchneiderAngesagt
|
||||
(_, True, _) -> return HandSchwarzAngesagt
|
||||
_ -> return Hand
|
||||
else return Einfach
|
||||
|
||||
prettyShow :: Modifier -> String
|
||||
prettyShow Schneider = show Einfach
|
||||
prettyShow Schwarz = show Einfach
|
||||
prettyShow HandSchneider = show Hand
|
||||
prettyShow HandSchwarz = show Hand
|
||||
prettyShow HandSchneiderAngesagtSchwarz = show HandSchneiderAngesagt
|
||||
prettyShow mod = show mod
|
||||
|
||||
isHand :: Game -> Bool
|
||||
isHand NullHand = True
|
||||
isHand NullOuvertHand = True
|
||||
isHand (Colour _ mod) = modIsHand mod
|
||||
isHand (Grand mod) = modIsHand mod
|
||||
isHand _ = False
|
||||
|
||||
modIsHand :: Modifier -> Bool
|
||||
modIsHand Einfach = False
|
||||
modIsHand Schneider = False
|
||||
modIsHand Schwarz = False
|
||||
modIsHand _ = True
|
||||
|
||||
isOuvert :: Game -> Bool
|
||||
isOuvert NullOuvert = True
|
||||
isOuvert NullOuvertHand = True
|
||||
isOuvert (Grand Ouvert) = True
|
||||
isOuvert (Colour _ Ouvert) = True
|
||||
isOuvert _ = False
|
||||
|
||||
baseFactor :: Game -> Int
|
||||
baseFactor (Grand _) = 24
|
||||
baseFactor (Colour Clubs _) = 12
|
||||
baseFactor (Colour Spades _) = 11
|
||||
baseFactor (Colour Hearts _) = 10
|
||||
baseFactor (Colour Diamonds _) = 9
|
||||
baseFactor Null = 23
|
||||
baseFactor NullHand = 35
|
||||
baseFactor NullOuvert = 46
|
||||
baseFactor NullOuvertHand = 59
|
||||
|
||||
-- | calculate the value of a game with given cards
|
||||
biddingScore :: HasCard c => Game -> [c] -> Int
|
||||
biddingScore game@(Grand mod) cards = (spitzen game cards + modifierFactor mod) * 24
|
||||
biddingScore game@(Colour Clubs mod) cards = (spitzen game cards + modifierFactor mod) * 12
|
||||
biddingScore game@(Colour Spades mod) cards = (spitzen game cards + modifierFactor mod) * 11
|
||||
biddingScore game@(Colour Hearts mod) cards = (spitzen game cards + modifierFactor mod) * 10
|
||||
biddingScore game@(Colour Diamonds mod) cards = (spitzen game cards + modifierFactor mod) * 9
|
||||
biddingScore game _ = baseFactor game
|
||||
|
||||
-- | calculate the modifier based on the game kind
|
||||
modifierFactor :: Modifier -> Int
|
||||
modifierFactor Einfach = 1
|
||||
modifierFactor Schneider = 2
|
||||
modifierFactor Schwarz = 3
|
||||
modifierFactor Hand = 2
|
||||
modifierFactor HandSchneider = 3
|
||||
modifierFactor HandSchneiderAngesagt = 4
|
||||
modifierFactor HandSchwarz = 4
|
||||
modifierFactor HandSchneiderAngesagtSchwarz = 5
|
||||
modifierFactor HandSchwarzAngesagt = 6
|
||||
modifierFactor Ouvert = 7
|
||||
|
||||
-- | get all available trumps for a given game
|
||||
allTrumps :: Game -> [Card]
|
||||
allTrumps (Grand _) = jacks
|
||||
allTrumps (Colour col _) = jacks ++ [Card t col | t <- [Ace,Ten .. Seven] ]
|
||||
allTrumps _ = []
|
||||
|
||||
jacks :: [Card]
|
||||
jacks = [ Card Jack Clubs, Card Jack Spades, Card Jack Hearts, Card Jack Diamonds ]
|
||||
|
||||
-- | calculate the spitzen count
|
||||
spitzen :: HasCard c => Game -> [c] -> Int
|
||||
spitzen game cards
|
||||
| null trumps = length $ allTrumps game
|
||||
| mit = foldl (\val (a, o) -> if a == o then val + 1 else val) 0 zipped
|
||||
| otherwise = findOhne (allTrumps game) 0
|
||||
where trumps = getTrumps game cards
|
||||
zipped = zip (allTrumps game) trumps
|
||||
mit = Card Jack Clubs == head trumps
|
||||
findOhne [] acc = acc
|
||||
findOhne (c:cs) acc = if c /= highest then findOhne cs (acc+1) else acc
|
||||
highest = head trumps
|
||||
|
||||
-- | get all trumps for a given game out of a hand of cards
|
||||
getTrumps :: HasCard c => Game -> [c] -> [Card]
|
||||
getTrumps (Grand _) cards = sortOn Down $ filter (isTrump Jacks) $ map toCard cards
|
||||
getTrumps (Colour col _) cards = sortOn Down $ filter (isTrump $ TrumpColour col) $ map toCard cards
|
||||
getTrumps _ _ = []
|
||||
|
||||
-- | get trump for a given game
|
||||
getTrump :: Game -> Trump
|
||||
getTrump (Colour col _) = TrumpColour col
|
||||
getTrump (Grand _) = Jacks
|
||||
getTrump _ = None
|
||||
|
||||
data Result = Result { resultGame :: Game
|
||||
, resultScore :: Int
|
||||
, resultSinglePoints :: Int
|
||||
, resultTeamPoints :: Int }
|
||||
deriving (Show, Eq)
|
||||
|
||||
instance ToJSON Result where
|
||||
toJSON (Result game points sgl tm) =
|
||||
object ["game" .= game, "points" .= points, "single" .= sgl, "team" .= tm]
|
||||
|
||||
isSchwarz :: Team -> Piles -> Bool
|
||||
isSchwarz tm = null . wonCards tm
|
||||
|
||||
hasWon :: Game -> Piles -> (Bool, Game)
|
||||
hasWon Null ps = (Single `isSchwarz` ps, Null)
|
||||
hasWon NullHand ps = (Single `isSchwarz` ps, NullHand)
|
||||
hasWon NullOuvert ps = (Single `isSchwarz` ps, NullOuvert)
|
||||
hasWon NullOuvertHand ps = (Single `isSchwarz` ps, NullOuvertHand)
|
||||
hasWon (Colour col mod) ps = let (b, mod') = meetsCall mod ps
|
||||
in (b, Colour col mod')
|
||||
hasWon (Grand mod) ps = let (b, mod') = meetsCall mod ps
|
||||
in (b, Grand mod')
|
||||
|
||||
meetsCall :: Modifier -> Piles -> (Bool, Modifier)
|
||||
meetsCall Hand ps = case wonByPoints ps of
|
||||
(b, Schneider) -> (b, HandSchneider)
|
||||
(b, Schwarz) -> (b, HandSchwarz)
|
||||
(b, Einfach) -> (b, Hand)
|
||||
meetsCall Schneider ps = case wonByPoints ps of
|
||||
(b, Schneider) -> (b, Schneider)
|
||||
(b, Schwarz) -> (b, Schwarz)
|
||||
(b, Einfach) -> (False, Schneider)
|
||||
meetsCall Schwarz ps = case wonByPoints ps of
|
||||
(b, Schneider) -> (False, Schwarz)
|
||||
(b, Schwarz) -> (b, Schwarz)
|
||||
(b, Einfach) -> (False, Schwarz)
|
||||
meetsCall HandSchneider ps = case wonByPoints ps of
|
||||
(b, Schneider) -> (b, HandSchneider)
|
||||
(b, Schwarz) -> (b, HandSchwarz)
|
||||
(b, Einfach) -> (False, HandSchneider)
|
||||
meetsCall HandSchneiderAngesagt ps = case wonByPoints ps of
|
||||
(b, Schneider) -> (b, HandSchneiderAngesagt)
|
||||
(b, Schwarz) -> (b, HandSchneiderAngesagtSchwarz)
|
||||
(b, Einfach) -> (False, HandSchneiderAngesagt)
|
||||
meetsCall HandSchwarz ps = case wonByPoints ps of
|
||||
(b, Schneider) -> (False, HandSchwarz)
|
||||
(b, Schwarz) -> (b, HandSchwarz)
|
||||
(b, Einfach) -> (False, HandSchwarz)
|
||||
meetsCall HandSchwarzAngesagt ps = case wonByPoints ps of
|
||||
(b, Schneider) -> (False, HandSchwarzAngesagt)
|
||||
(b, Schwarz) -> (b, HandSchwarzAngesagt)
|
||||
(b, Einfach) -> (False, HandSchwarzAngesagt)
|
||||
meetsCall Ouvert ps = case wonByPoints ps of
|
||||
(b, Schneider) -> (False, Ouvert)
|
||||
(b, Schwarz) -> (b, Ouvert)
|
||||
(b, Einfach) -> (False, Ouvert)
|
||||
meetsCall _ ps = wonByPoints ps
|
||||
|
||||
wonByPoints :: Piles -> (Bool, Modifier)
|
||||
wonByPoints ps
|
||||
| Team `isSchwarz` ps = (True, Schwarz)
|
||||
| sgl >= 90 = (True, Schneider)
|
||||
| Single `isSchwarz` ps = (False, Schwarz)
|
||||
| sgl <= 30 = (False, Schneider)
|
||||
| otherwise = (sgl > 60, Einfach)
|
||||
where (sgl, _) = count ps :: (Int, Int)
|
||||
|
||||
-- | get result of game
|
||||
getResults :: Game -> Bid -> Hand -> Piles -> Piles -> Result
|
||||
getResults game bid sglPlayer before after = case checkGame bid hand game of
|
||||
Just game' -> let (won, afterGame) = hasWon game' after
|
||||
gameScore = biddingScore afterGame hand
|
||||
score = if won then gameScore else (-2) * gameScore
|
||||
in Result afterGame score sglPoints teamPoints
|
||||
Nothing -> let gameScore = baseFactor game * ceiling (fromIntegral bid / fromIntegral (baseFactor game))
|
||||
score = (-2) * gameScore
|
||||
in Result game score sglPoints teamPoints
|
||||
where hand = skatCards before ++ (map toCard $ handCards sglPlayer before)
|
||||
(sglPoints, teamPoints) = count after
|
||||
|
||||
checkGame :: HasCard c => Bid -> [c] -> Game -> Maybe Game
|
||||
checkGame bid cards game@(Colour col mod)
|
||||
| biddingScore game cards >= bid = Just game
|
||||
| otherwise = upgrade mod >>= \mod' -> checkGame bid cards (Colour col mod')
|
||||
checkGame bid cards game@(Grand mod)
|
||||
| biddingScore game cards >= bid = Just game
|
||||
| otherwise = upgrade mod >>= \mod' -> checkGame bid cards (Grand mod')
|
||||
checkGame bid cards game
|
||||
| biddingScore game cards >= bid = Just game
|
||||
| otherwise = Nothing
|
||||
|
||||
upgrade :: Modifier -> Maybe Modifier
|
||||
upgrade Einfach = Just Schneider
|
||||
upgrade Schneider = Just Schwarz
|
||||
upgrade Hand = Just HandSchneider
|
||||
upgrade HandSchneider = Just HandSchwarz
|
||||
upgrade HandSchneiderAngesagt = Just HandSchneiderAngesagtSchwarz
|
||||
upgrade _ = Nothing
|
||||
+139
-37
@@ -1,16 +1,27 @@
|
||||
{-# LANGUAGE MultiParamTypeClasses #-}
|
||||
{-# LANGUAGE FlexibleInstances #-}
|
||||
{-# LANGUAGE FlexibleContexts #-}
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
{-# LANGUAGE DeriveGeneric #-}
|
||||
{-# LANGUAGE DeriveAnyClass #-}
|
||||
{-# LANGUAGE BangPatterns #-}
|
||||
|
||||
module Skat.Card where
|
||||
|
||||
import GHC.Generics (Generic, Generic1)
|
||||
import Data.List
|
||||
import Data.Foldable (Foldable)
|
||||
import qualified Data.Foldable as F
|
||||
import qualified Data.Set as S
|
||||
import Data.Aeson
|
||||
import System.Random (newStdGen)
|
||||
import System.Random (newStdGen, StdGen)
|
||||
import Control.DeepSeq
|
||||
|
||||
import Skat.Utils
|
||||
|
||||
class HasCard c where
|
||||
toCard :: c -> Card
|
||||
|
||||
class Countable a b where
|
||||
count :: a -> b
|
||||
|
||||
@@ -22,7 +33,17 @@ data Type = Seven
|
||||
| Ten
|
||||
| Ace
|
||||
| Jack
|
||||
deriving (Eq, Ord, Show, Enum, Read)
|
||||
deriving (Eq, Ord, Show, Enum, Read, Bounded, Generic, NFData, ToJSON)
|
||||
|
||||
data NullType = NSeven
|
||||
| NEight
|
||||
| NNine
|
||||
| NTen
|
||||
| NJack
|
||||
| NQueen
|
||||
| NKing
|
||||
| NAce
|
||||
deriving (Eq, Ord, Show, Enum, Read, Bounded)
|
||||
|
||||
instance Countable Type Int where
|
||||
count Ace = 11
|
||||
@@ -36,10 +57,28 @@ data Colour = Diamonds
|
||||
| Hearts
|
||||
| Spades
|
||||
| Clubs
|
||||
deriving (Eq, Ord, Show, Enum, Read)
|
||||
deriving (Eq, Ord, Show, Enum, Read, Bounded, Generic, NFData, ToJSON)
|
||||
|
||||
data Card = Card Type Colour
|
||||
deriving (Eq, Show, Ord)
|
||||
data Trump = TrumpColour Colour
|
||||
| Jacks
|
||||
| None
|
||||
deriving (Show, Eq)
|
||||
|
||||
data TurnColour = TurnColour Colour
|
||||
| Trump
|
||||
deriving (Show, Eq)
|
||||
|
||||
data Card = Card !Type !Colour
|
||||
deriving (Eq, Show, Ord, Read, Bounded, Generic, ToJSONKey)
|
||||
|
||||
getType :: Card -> Type
|
||||
getType (Card t _) = t
|
||||
|
||||
getColour :: Card -> Colour
|
||||
getColour (Card _ c) = c
|
||||
|
||||
instance HasCard Card where
|
||||
toCard = id
|
||||
|
||||
instance ToJSON Card where
|
||||
toJSON (Card t c) =
|
||||
@@ -51,11 +90,8 @@ instance FromJSON Card where
|
||||
c <- v .: "colour"
|
||||
return $ Card (read t) (read c)
|
||||
|
||||
getColour :: Card -> Colour
|
||||
getColour (Card _ c) = c
|
||||
|
||||
getID :: Card -> Int
|
||||
getID (Card t _) = case t of
|
||||
getID :: HasCard c => c -> Int
|
||||
getID card = let t = getType $ toCard card in case t of
|
||||
Seven -> 0
|
||||
Eight -> 0
|
||||
Nine -> 0
|
||||
@@ -65,64 +101,130 @@ getID (Card t _) = case t of
|
||||
Ace -> 16
|
||||
Jack -> 32
|
||||
|
||||
instance Enum Card where
|
||||
fromEnum (Card tp col) = fromEnum col * 8 + fromEnum tp
|
||||
toEnum n = Card tp col
|
||||
where col = toEnum (n `div` 8)
|
||||
tp = toEnum (n `mod` 8)
|
||||
|
||||
instance Countable Card Int where
|
||||
count (Card t _) = count t
|
||||
|
||||
instance Countable [Card] Int where
|
||||
count = sum . map count
|
||||
instance Foldable t => Countable (t Card) Int where
|
||||
count = foldl' f 0
|
||||
where f acc c = count c + acc
|
||||
|
||||
instance Countable (S.Set Card) Int where
|
||||
count = S.foldl' f 0
|
||||
where f acc card = count card + acc
|
||||
|
||||
instance NFData Card where
|
||||
rnf (Card t c) = t `seq` c `seq` ()
|
||||
|
||||
equals :: Colour -> Maybe Colour -> Bool
|
||||
base64table :: [Char]
|
||||
base64table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
|
||||
|
||||
class Serialize c a where
|
||||
serialize :: a -> c
|
||||
deserialize :: c -> Maybe a
|
||||
|
||||
instance Serialize Char Card where
|
||||
serialize card = base64table !! fromEnum card
|
||||
deserialize char = base64table `indexOf` char >>= safeToEnum
|
||||
|
||||
equals :: TurnColour -> Maybe TurnColour -> Bool
|
||||
equals col (Just x) = col == x
|
||||
equals col Nothing = True
|
||||
|
||||
isTrump :: Colour -> Card -> Bool
|
||||
isTrump trumpCol (Card tp col)
|
||||
| tp == Jack = True
|
||||
| otherwise = col == trumpCol
|
||||
isTrump :: HasCard c => Trump -> c -> Bool
|
||||
isTrump None crd = False
|
||||
isTrump Jacks crd = getType (toCard crd) == Jack
|
||||
isTrump (TrumpColour trumpCol) crd
|
||||
| getType (toCard crd) == Jack = True
|
||||
| otherwise = getColour (toCard crd) == trumpCol
|
||||
|
||||
effectiveColour :: Colour -> Card -> Colour
|
||||
effectiveColour trumpCol card@(Card _ col) =
|
||||
if trump then trumpCol else col
|
||||
where trump = isTrump trumpCol card
|
||||
effectiveColour :: HasCard c => Trump -> c -> TurnColour
|
||||
effectiveColour trump card
|
||||
| isTrump trump card = Trump
|
||||
| otherwise = TurnColour $ getColour (toCard card)
|
||||
|
||||
isAllowed :: Colour -> Maybe Colour -> [Card] -> Card -> Bool
|
||||
isAllowed trumpCol turnCol cs card =
|
||||
isAllowed :: (Foldable t, HasCard c1, HasCard c2) => Trump -> Maybe TurnColour -> t c1 -> c2 -> Bool
|
||||
isAllowed trump turnCol cs crd =
|
||||
if col `equals` turnCol
|
||||
then True
|
||||
else not $ any (\ca -> effectiveColour trumpCol ca `equals` turnCol && ca /= card) cs
|
||||
where col = effectiveColour trumpCol card
|
||||
else not $ F.any (\ca -> effectiveColour trump ca `equals` turnCol && toCard ca /= toCard crd) cs
|
||||
where col = effectiveColour trump (toCard crd)
|
||||
|
||||
compareCards :: Colour
|
||||
-> Maybe Colour
|
||||
compareCards :: Trump
|
||||
-> Maybe TurnColour
|
||||
-> Card
|
||||
-> Card
|
||||
-> Ordering
|
||||
compareCards _ _ (Card Jack col1) (Card Jack col2) = compare col1 col2
|
||||
compareCards trumpCol turnCol c1@(Card tp1 col1) c2@(Card tp2 col2) =
|
||||
compareCards trump turnCol c1@(Card tp1 col1) c2@(Card tp2 col2) =
|
||||
case (trp1, trp2) of
|
||||
(True, True) -> compare tp1 tp2
|
||||
(False, False) -> case compare (col1 `equals` turnCol)
|
||||
(col2 `equals` turnCol) of
|
||||
EQ -> compare tp1 tp2
|
||||
(False, False) -> case ( effectiveColour trump c1 `equals` turnCol
|
||||
, effectiveColour trump c2 `equals` turnCol ) of
|
||||
(True, True) -> compareTypes trump tp1 tp2
|
||||
(True, False) -> GT
|
||||
(False, True) -> LT
|
||||
_ -> EQ
|
||||
_ -> compare trp1 trp2
|
||||
where trp1 = isTrump trump c1
|
||||
trp2 = isTrump trump c2
|
||||
|
||||
compareRender :: Trump -> Card -> Card -> Ordering
|
||||
compareRender trump c1@(Card tp1 col1) c2@(Card tp2 col2) =
|
||||
case (trp1, trp2) of
|
||||
(True, True) -> case compare tp1 tp2 of
|
||||
EQ -> compare col1 col2
|
||||
v -> v
|
||||
(False, False) -> case compare col1 col2 of
|
||||
EQ -> compareTypes trump tp1 tp2
|
||||
v -> v
|
||||
_ -> compare trp1 trp2
|
||||
where trp1 = isTrump trumpCol c1
|
||||
trp2 = isTrump trumpCol c2
|
||||
where trp1 = isTrump trump c1
|
||||
trp2 = isTrump trump c2
|
||||
|
||||
sortCards :: Colour -> Maybe Colour -> [Card] -> [Card]
|
||||
sortCards trumpCol turnCol cs = sortBy (compareCards trumpCol turnCol) cs
|
||||
compareTypes :: Trump
|
||||
-> Type
|
||||
-> Type
|
||||
-> Ordering
|
||||
compareTypes None tp1 tp2 = compare (toNullType tp1) (toNullType tp2)
|
||||
where toNullType Seven = NSeven
|
||||
toNullType Eight = NEight
|
||||
toNullType Nine = NNine
|
||||
toNullType Ten = NTen
|
||||
toNullType Jack = NJack
|
||||
toNullType Queen = NQueen
|
||||
toNullType King = NKing
|
||||
toNullType Ace = NAce
|
||||
compareTypes _ tp1 tp2 = compare tp1 tp2
|
||||
|
||||
highestCard :: Colour -> Maybe Colour -> [Card] -> Card
|
||||
highestCard trumpCol turnCol cs = maximumBy (compareCards trumpCol turnCol) cs
|
||||
-- | ascending sort of cards, depending on turn colour
|
||||
sortCards :: HasCard c => Trump -> Maybe TurnColour -> [c] -> [c]
|
||||
sortCards trump turnCol cs = sortBy f cs
|
||||
where f c1 c2 = compareCards trump turnCol (toCard c1) (toCard c2)
|
||||
|
||||
-- | descending sort of cards, independent of turn colour
|
||||
sortRender :: HasCard c => Trump -> [c] -> [c]
|
||||
sortRender trump cs = sortBy f cs
|
||||
-- note: reversed order of c1 and c2 to get a descending sort
|
||||
where f c1 c2 = compareRender trump (toCard c2) (toCard c1)
|
||||
|
||||
highestCard :: HasCard c => Trump -> Maybe TurnColour -> [c] -> c
|
||||
highestCard trump turnCol cs = maximumBy f cs
|
||||
where f c1 c2 = compareCards trump turnCol (toCard c1) (toCard c2)
|
||||
|
||||
shuffleCards :: IO [Card]
|
||||
shuffleCards = do
|
||||
gen <- newStdGen
|
||||
return $ shuffle gen allCards
|
||||
|
||||
shuffleCardsWithGen :: StdGen -> [Card]
|
||||
shuffleCardsWithGen gen = shuffle gen allCards
|
||||
|
||||
-- TESTING VARS
|
||||
|
||||
c1 :: Card
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
module Skat.Matches (
|
||||
singleVsBots, pvp, singleWithBidding, Match(..), Unfinished(..), continue,
|
||||
Table(..), twoWithBidding, H(..), randomPositions
|
||||
) where
|
||||
|
||||
import Control.Monad.State
|
||||
import Control.Monad.Reader
|
||||
import System.Random (mkStdGen, newStdGen)
|
||||
|
||||
import Skat
|
||||
import Skat.Operations
|
||||
import Skat.Player as P
|
||||
import Skat.Pile
|
||||
import Skat.Card
|
||||
import Skat.Preperation
|
||||
import Skat.Bidding
|
||||
import Skat.Utils (shuffle)
|
||||
|
||||
import Skat.AI.Rulebased
|
||||
import Skat.AI.Online
|
||||
import Skat.AI.Stupid
|
||||
|
||||
data Table = Unfinished Unfinished
|
||||
| Finished Match
|
||||
| Pass { tablePiles :: Piles }
|
||||
deriving Show
|
||||
|
||||
data Match = Match { matchPiles :: Piles
|
||||
, matchResult :: Result
|
||||
, matchTricks :: [Trick]
|
||||
, matchSingle :: Hand }
|
||||
deriving Show
|
||||
|
||||
data Unfinished = UnfinishedGame { unfinishedGame :: SkatEnv
|
||||
, unfinishedPrep :: PrepEnv
|
||||
, unfinishedTricks :: [Trick] }
|
||||
| UnfinishedPrep { unfinishedPrep :: PrepEnv }
|
||||
deriving Show
|
||||
|
||||
continue :: Communicator c => Unfinished -> c -> c -> c -> IO Table
|
||||
continue (UnfinishedGame skatEnv prepEnv tricks) comm1 comm2 comm3 = do
|
||||
let ps = players skatEnv
|
||||
ps' = Players
|
||||
(PL $ OnlineEnv (P.team $ player ps Hand1) (P.hand $ player ps Hand1) comm1)
|
||||
(PL $ OnlineEnv (P.team $ player ps Hand2) (P.hand $ player ps Hand2) comm2)
|
||||
(PL $ OnlineEnv (P.team $ player ps Hand3) (P.hand $ player ps Hand3) comm3)
|
||||
bs = bidders prepEnv
|
||||
bs' = Bidders
|
||||
(BD $ PrepOnline (Skat.Preperation.hand $ bidder bs Hand1) comm1 [])
|
||||
(BD $ PrepOnline (Skat.Preperation.hand $ bidder bs Hand2) comm2 [])
|
||||
(BD $ PrepOnline (Skat.Preperation.hand $ bidder bs Hand3) comm3 [])
|
||||
skatEnv' = skatEnv { players = ps' }
|
||||
prepEnv' = prepEnv { bidders = bs' }
|
||||
runGame prepEnv' skatEnv'
|
||||
|
||||
match :: PrepEnv -> IO Table
|
||||
match prepEnv = do
|
||||
(maySkatEnv, prepEnv') <- runStateT runPreperation prepEnv
|
||||
case maySkatEnv of
|
||||
Just skatEnv -> runGame prepEnv' skatEnv
|
||||
Nothing -> do
|
||||
putStrLn "no one wanted to play"
|
||||
return $ Pass $ Skat.Preperation.piles prepEnv'
|
||||
|
||||
runGame :: PrepEnv -> SkatEnv -> IO Table
|
||||
runGame prepEnv skatEnv = do
|
||||
(isFinished, finalEnv, tricks) <- (flip runSkat) skatEnv $ do
|
||||
-- send current table cards to clients
|
||||
-- only relevant if this is a continued game
|
||||
-- otherwise table is empty
|
||||
table <- getp tableCards
|
||||
ps <- playersToList <$> gets players
|
||||
mapM_ (\card -> mapM_ (\p -> onCardPlayed p card) ps) (reverse table)
|
||||
-- run game
|
||||
turn
|
||||
-- return if game has finished
|
||||
gameOver
|
||||
if isFinished then do
|
||||
let res = getResults
|
||||
(skatGame skatEnv)
|
||||
(Skat.Preperation.current prepEnv)
|
||||
(skatSinglePlayer skatEnv)
|
||||
(Skat.Preperation.piles prepEnv)
|
||||
(Skat.piles finalEnv)
|
||||
publishGameResults res (bidders prepEnv)
|
||||
return $ Finished $ Match (Skat.Preperation.piles prepEnv) res tricks (skatSinglePlayer skatEnv)
|
||||
else do -- if not finished an error has occured, thus returning unfinished game state
|
||||
return $ Unfinished $ UnfinishedGame finalEnv prepEnv tricks
|
||||
|
||||
-- | predefined card distribution for testing purposes
|
||||
cardDistr :: Piles
|
||||
cardDistr = emptyPiles hand1 hand2 hand3 skt
|
||||
where hand3 = [Card Ace Spades, Card Jack Diamonds, Card Jack Clubs, Card King Spades,
|
||||
Card Nine Spades, Card Ace Diamonds, Card Queen Diamonds, Card Ten Clubs,
|
||||
Card Eight Clubs, Card King Clubs]
|
||||
hand1 = [Card Jack Spades, Card Jack Hearts, Card Ten Spades, Card Ace Hearts, Card Ten Hearts,
|
||||
Card Nine Hearts, Card Seven Clubs, Card Ace Clubs, Card King Diamonds,
|
||||
Card Ten Diamonds]
|
||||
hand2 = [Card Eight Spades, Card Queen Spades, Card Seven Spades, Card Seven Diamonds,
|
||||
Card Seven Hearts, Card Eight Hearts, Card Queen Hearts, Card King Hearts,
|
||||
Card Nine Diamonds, Card Eight Diamonds]
|
||||
skt = [Card Nine Clubs, Card Queen Clubs]
|
||||
|
||||
singleVsBots :: Communicator c => c -> IO ()
|
||||
singleVsBots comm = do
|
||||
cards <- shuffleCards
|
||||
let ps = Players
|
||||
(PL $ OnlineEnv Team Hand1 comm)
|
||||
(PL $ Stupid Team Hand2)
|
||||
(PL $ mkAIEnv Single Hand3 10)
|
||||
env = SkatEnv (distribute cards) Nothing (Colour Spades Einfach) ps Hand1 Hand3
|
||||
void $ evalSkat turn env
|
||||
|
||||
singleWithBidding :: Communicator c => c -> IO ()
|
||||
singleWithBidding comm = do
|
||||
cards <- shuffleCards
|
||||
let ps = distribute cards
|
||||
h1 = map toCard $ handCards Hand1 ps
|
||||
bs = Bidders
|
||||
(BD $ PrepOnline Hand1 comm h1)
|
||||
(BD $ NoBidder Hand2)
|
||||
(BD $ NoBidder Hand3)
|
||||
env = makePrep ps bs
|
||||
void $ match env
|
||||
|
||||
--- helper object for twoWithBidding
|
||||
data H = P1 | P2 | AI
|
||||
|
||||
randomPositions :: IO [H]
|
||||
randomPositions = do
|
||||
gen <- newStdGen
|
||||
return $ shuffle gen [P1, P2, AI]
|
||||
|
||||
twoWithBidding :: Communicator c => [H] -> c -> c -> IO ()
|
||||
twoWithBidding positions comm1 comm2 = do
|
||||
cards <- shuffleCards
|
||||
let bds = zipWith mkBidder [Hand1, Hand2, Hand3] positions
|
||||
ps = distribute cards
|
||||
mkBidder hand P1 = BD $ PrepOnline hand comm1 (map toCard $ handCards hand ps)
|
||||
mkBidder hand P2 = BD $ PrepOnline hand comm2 (map toCard $ handCards hand ps)
|
||||
mkBidder hand AI = BD $ NoBidder hand
|
||||
bs = Bidders (bds !! 0) (bds !! 1) (bds !! 2)
|
||||
env = makePrep ps bs
|
||||
void $ match env
|
||||
|
||||
pvp :: Communicator c => c -> c -> c -> IO Table
|
||||
pvp comm1 comm2 comm3 = do
|
||||
cards <- shuffleCards
|
||||
let ps = distribute cards
|
||||
h1 = map toCard $ handCards Hand1 ps
|
||||
h2 = map toCard $ handCards Hand2 ps
|
||||
h3 = map toCard $ handCards Hand3 ps
|
||||
bs = Bidders
|
||||
(BD $ PrepOnline Hand1 comm1 $ h1)
|
||||
(BD $ PrepOnline Hand2 comm2 $ h2)
|
||||
(BD $ PrepOnline Hand3 comm3 $ h3)
|
||||
env = makePrep ps bs
|
||||
match env
|
||||
+84
-34
@@ -1,88 +1,138 @@
|
||||
module Skat.Operations where
|
||||
{-# LANGUAGE FlexibleContexts #-}
|
||||
|
||||
module Skat.Operations (
|
||||
turn, turnGeneric, play, playOpen,
|
||||
play_, sortRender, undo_, gameOver,
|
||||
countGame
|
||||
) where
|
||||
|
||||
import Control.Monad.State
|
||||
import Control.Monad.Catch
|
||||
import Control.Exception hiding (catch, bracketOnError)
|
||||
import Control.Monad.Writer
|
||||
import System.Random (newStdGen, randoms)
|
||||
import Data.List
|
||||
import Data.Ord
|
||||
import qualified Data.Set as S
|
||||
|
||||
import Skat
|
||||
import Skat.Card
|
||||
import Skat.Pile
|
||||
import Skat.Player (chooseCard, Players(..), Player(..), PL(..),
|
||||
updatePlayer, playersToList, player, MonadPlayer)
|
||||
updatePlayer, playersToList, player, MonadPlayer, getSinglePlayer, trump, game,
|
||||
singlePlayer)
|
||||
import Skat.Utils (shuffle)
|
||||
import Skat.Bidding
|
||||
|
||||
compareRender :: Card -> Card -> Ordering
|
||||
compareRender (Card t1 c1) (Card t2 c2) = case compare c1 c2 of
|
||||
EQ -> compare t1 t2
|
||||
v -> v
|
||||
play_ :: (MonadWriter [Trick] m, MonadPlayer m, MonadState SkatEnv m, HasCard c) => c -> m ()
|
||||
play_ card = do
|
||||
hand <- gets currentHand
|
||||
trCol <- trump
|
||||
modifyp $ playCard hand card
|
||||
table <- getp tableCards
|
||||
case length table of
|
||||
1 -> do modify (setCurrentHand $ next hand)
|
||||
modify $ setTurnColour (Just $ effectiveColour trCol $ head table)
|
||||
3 -> evaluateTable >>= modify . setCurrentHand
|
||||
_ -> modify (setCurrentHand $ next hand)
|
||||
|
||||
sortRender :: [Card] -> [Card]
|
||||
sortRender = sortBy compareRender
|
||||
undo_ :: HasCard c => c -> Hand -> Maybe TurnColour -> Team -> Skat ()
|
||||
undo_ card oldCurrent oldTurnCol oldWinner = do
|
||||
modify $ setCurrentHand oldCurrent
|
||||
modify $ setTurnColour oldTurnCol
|
||||
modifyp $ unplayCard oldCurrent (toCard card) oldWinner
|
||||
|
||||
turnGeneric :: (PL -> Skat Card)
|
||||
-> Int
|
||||
-> Hand
|
||||
-> Skat (Int, Int)
|
||||
turnGeneric playFunc depth n = do
|
||||
turnGeneric playFunc depth = do
|
||||
n <- gets currentHand
|
||||
table <- getp tableCards
|
||||
ps <- gets players
|
||||
let p = player ps n
|
||||
hand <- getp $ handCards n
|
||||
trCol <- gets trumpColour
|
||||
trCol <- trump
|
||||
case length table of
|
||||
0 -> playFunc p >> turnGeneric playFunc depth (next n)
|
||||
0 -> do
|
||||
catchAll
|
||||
(do
|
||||
playFunc p
|
||||
modify (setCurrentHand $ next n)
|
||||
turnGeneric playFunc depth)
|
||||
(\_ -> countGame)
|
||||
1 -> do
|
||||
modify $ setTurnColour
|
||||
(Just $ effectiveColour trCol $ head table)
|
||||
catchAll
|
||||
(do
|
||||
playFunc p
|
||||
turnGeneric playFunc depth (next n)
|
||||
2 -> playFunc p >> turnGeneric playFunc depth (next n)
|
||||
modify (setCurrentHand $ next n)
|
||||
turnGeneric playFunc depth)
|
||||
(\_ -> countGame)
|
||||
2 -> do
|
||||
catchAll
|
||||
(do
|
||||
playFunc p
|
||||
modify (setCurrentHand $ next n)
|
||||
turnGeneric playFunc depth)
|
||||
(\_ -> countGame)
|
||||
3 -> do
|
||||
w <- evaluateTable
|
||||
if depth <= 1 || length hand == 0
|
||||
over <- gameOver
|
||||
if depth <= 1 || over
|
||||
then countGame
|
||||
else turnGeneric playFunc (depth - 1) w
|
||||
else modify (setCurrentHand w) >> turnGeneric playFunc (depth - 1)
|
||||
|
||||
turn :: Hand -> Skat (Int, Int)
|
||||
turn n = turnGeneric play 10 n
|
||||
turn :: Skat (Int, Int)
|
||||
turn = turnGeneric play 10
|
||||
|
||||
evaluateTable :: Skat Hand
|
||||
evaluateTable :: (MonadPlayer m, MonadState SkatEnv m, MonadWriter [Trick] m) => m Hand
|
||||
evaluateTable = do
|
||||
trumpCol <- gets trumpColour
|
||||
trumpCol <- trump
|
||||
turnCol <- gets turnColour
|
||||
table <- getp tableCards
|
||||
ps <- gets players
|
||||
let winningCard = highestCard trumpCol turnCol table
|
||||
Just winnerHand <- getp $ originOfCard winningCard
|
||||
let winner = player ps winnerHand
|
||||
let winnerHand = uorigin $ getPile $ highestCard trumpCol turnCol table
|
||||
winner = player ps winnerHand
|
||||
modifyp $ cleanTable (team winner)
|
||||
modify $ setTurnColour Nothing
|
||||
tell [(table !! 2, table !! 1, table !! 0)]
|
||||
return $ hand winner
|
||||
|
||||
countGame :: Skat (Int, Int)
|
||||
countGame :: (MonadState SkatEnv m) => m (Int, Int)
|
||||
countGame = getp count
|
||||
|
||||
play :: (Show p, Player p) => p -> Skat Card
|
||||
play p = do
|
||||
liftIO $ putStrLn "playing"
|
||||
table <- getp tableCardsS
|
||||
table <- getp tableCards
|
||||
turnCol <- gets turnColour
|
||||
trump <- gets trumpColour
|
||||
hand <- getp $ handCards (hand p)
|
||||
trump <- trump
|
||||
cards <- getp $ handCards (hand p)
|
||||
fallen <- getp played
|
||||
(card, p') <- chooseCard p table fallen hand
|
||||
ouvert <- isOuvert <$> game
|
||||
mayOuvert <- if ouvert then Just <$> (singlePlayer >>= getp . handCards)
|
||||
else return Nothing
|
||||
(card, p') <- chooseCard p table fallen mayOuvert cards
|
||||
modifyPlayers $ updatePlayer p'
|
||||
modifyp $ playCard card
|
||||
modifyp $ playCard (hand p) card
|
||||
ps <- fmap playersToList $ gets players
|
||||
table' <- getp tableCardsS
|
||||
table' <- getp tableCards
|
||||
ps' <- mapM (\p -> onCardPlayed p (head table')) ps
|
||||
mapM_ (modifyPlayers . updatePlayer) ps'
|
||||
return card
|
||||
return (toCard card)
|
||||
|
||||
playOpen :: (Show p, Player p) => p -> Skat Card
|
||||
playOpen p = do
|
||||
--liftIO $ putStrLn $ show (hand p) ++ " playing open"
|
||||
card <- chooseCardOpen p
|
||||
modifyp $ playCard card
|
||||
modifyp $ playCard (hand p) card
|
||||
return card
|
||||
|
||||
gameOver :: (MonadPlayer m, MonadState SkatEnv m) => m Bool
|
||||
gameOver = do
|
||||
tr <- trump
|
||||
case tr of
|
||||
None -> do
|
||||
singleLost <- gets piles >>= return . not . (Single `isSchwarz`)
|
||||
if singleLost then return True
|
||||
else gets currentHand >>= getp . handCards >>= return . null
|
||||
_ -> gets currentHand >>= getp . handCards >>= return . null
|
||||
|
||||
+264
-52
@@ -1,32 +1,64 @@
|
||||
{-# LANGUAGE MultiParamTypeClasses #-}
|
||||
{-# LANGUAGE FlexibleInstances #-}
|
||||
{-# LANGUAGE FlexibleContexts #-}
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
{-# LANGUAGE TupleSections #-}
|
||||
{-# LANGUAGE DeriveGeneric #-}
|
||||
{-# LANGUAGE DeriveAnyClass #-}
|
||||
|
||||
module Skat.Pile where
|
||||
|
||||
import Data.List
|
||||
import Control.Monad.State
|
||||
import Control.Monad.Trans.Maybe
|
||||
|
||||
import GHC.Generics
|
||||
import Control.DeepSeq
|
||||
import Prelude hiding (lookup)
|
||||
import qualified Data.Map.Strict as M
|
||||
import qualified Data.Vector as V
|
||||
import Data.Vector (Vector)
|
||||
import Data.Foldable (toList, foldl', Foldable)
|
||||
import Data.Maybe
|
||||
import Data.Aeson
|
||||
import Control.Exception
|
||||
import Data.List (delete)
|
||||
import Text.Read (readMaybe)
|
||||
import Debug.Trace
|
||||
|
||||
import Skat.Card
|
||||
import Skat.Utils
|
||||
|
||||
data Team = Team | Single
|
||||
deriving (Show, Eq, Ord, Enum)
|
||||
deriving (Show, Eq, Ord, Enum, Read)
|
||||
|
||||
data CardS p = CardS { getCard :: Card
|
||||
, getPile :: p }
|
||||
deriving (Show, Eq, Ord)
|
||||
deriving (Eq, Ord, Read)
|
||||
|
||||
instance (Show p) => Show (CardS p) where
|
||||
show (CardS card pile) = show card ++ " from " ++ show pile
|
||||
|
||||
instance HasCard (CardS p) where
|
||||
toCard = getCard
|
||||
|
||||
instance Countable (CardS p) Int where
|
||||
count = count . getCard
|
||||
|
||||
instance Foldable t => Countable (t (CardS p)) Int where
|
||||
count = foldl' f 0
|
||||
where f acc c = count c + acc
|
||||
|
||||
instance ToJSON p => ToJSON (CardS p) where
|
||||
toJSON (CardS card pile) =
|
||||
object ["card" .= card, "pile" .= pile]
|
||||
|
||||
data Hand = Hand1 | Hand2 | Hand3
|
||||
deriving (Show, Eq, Ord)
|
||||
deriving (Show, Eq, Ord, Read, Enum, Bounded, Generic, NFData, ToJSON)
|
||||
|
||||
toInt :: Hand -> Int
|
||||
toInt Hand1 = 1
|
||||
toInt Hand2 = 2
|
||||
toInt Hand3 = 3
|
||||
|
||||
next :: Hand -> Hand
|
||||
next Hand1 = Hand2
|
||||
@@ -38,76 +70,150 @@ prev Hand1 = Hand3
|
||||
prev Hand2 = Hand1
|
||||
prev Hand3 = Hand2
|
||||
|
||||
data Played = Table Hand
|
||||
| Won Hand Team
|
||||
data Owner = P Hand | S
|
||||
deriving (Show, Eq, Ord, Read)
|
||||
|
||||
instance Enum Owner where
|
||||
fromEnum (P hand) = fromEnum hand
|
||||
fromEnum S = 3
|
||||
toEnum 0 = P Hand1
|
||||
toEnum 1 = P Hand2
|
||||
toEnum 2 = P Hand3
|
||||
toEnum 3 = S
|
||||
|
||||
instance Bounded Owner where
|
||||
maxBound = S
|
||||
minBound = P Hand1
|
||||
|
||||
instance ToJSON Owner where
|
||||
toJSON (P hand) = object ["owner" .= show hand]
|
||||
toJSON S = object ["owner" .= ("skat" :: String) ]
|
||||
|
||||
instance Serialize String (CardS Owner) where
|
||||
serialize (CardS card owner) = show (fromEnum owner) ++ [serialize card]
|
||||
deserialize str = (flip evalState) str $ runMaybeT $ do
|
||||
owner <- pop >>= MaybeT . return . (>>= safeToEnum) . readMaybe . (:[])
|
||||
card <- pop >>= MaybeT . return . deserialize
|
||||
return $ CardS card owner
|
||||
|
||||
type Played = Owner -- TODO: remove
|
||||
|
||||
type Trick = (CardS Owner, CardS Owner, CardS Owner)
|
||||
|
||||
data Piles = Piles { _hand1 :: [CardS Owner]
|
||||
, _hand2 :: [CardS Owner]
|
||||
, _hand3 :: [CardS Owner]
|
||||
, _table :: [CardS Owner]
|
||||
, _wonSingle :: [CardS Owner]
|
||||
, _wonTeam :: [CardS Owner]
|
||||
, _skat :: [CardS Owner] }
|
||||
deriving (Show, Eq, Ord)
|
||||
|
||||
instance ToJSON Played where
|
||||
toJSON (Table hand) =
|
||||
object ["state" .= ("table" :: String), "played_by" .= show hand]
|
||||
toJSON (Won hand team) =
|
||||
object ["state" .= ("won" :: String), "played_by" .= show hand, "won_by" .= show team]
|
||||
fromPiles :: Piles -> [CardS Owner]
|
||||
fromPiles ps = _hand1 ps ++ _hand2 ps ++ _hand3 ps ++ _table ps ++ _wonSingle ps ++ _wonTeam ps ++ _skat ps
|
||||
|
||||
data SkatP = SkatP
|
||||
deriving (Show, Eq, Ord)
|
||||
|
||||
data Piles = Piles { hands :: [CardS Hand]
|
||||
, played :: [CardS Played]
|
||||
, skat :: [CardS SkatP] }
|
||||
deriving (Show, Eq, Ord)
|
||||
toTable :: Hand -> Card -> Piles -> Piles
|
||||
toTable hand card ps = ps { _table = (CardS card (P hand)) : _table ps }
|
||||
|
||||
instance Countable Piles (Int, Int) where
|
||||
count ps = (sgl, tm)
|
||||
where sgl = count (skatCards ps) + count (wonCards Single ps)
|
||||
tm = count (wonCards Team ps)
|
||||
|
||||
origin :: CardS Played -> Hand
|
||||
origin (CardS _ (Table hand)) = hand
|
||||
origin (CardS _ (Won hand _)) = hand
|
||||
played :: Piles -> [CardS Owner]
|
||||
played ps = _wonSingle ps ++ _wonTeam ps ++ _table ps
|
||||
|
||||
originOfCard :: Card -> Piles -> Maybe Hand
|
||||
originOfCard card (Piles _ pld _) = origin <$> find ((==card) . getCard) pld
|
||||
origin :: Owner -> Maybe Hand
|
||||
origin (P hand) = Just hand
|
||||
origin S = Nothing
|
||||
|
||||
playCard :: Card -> Piles -> Piles
|
||||
playCard card (Piles hs pld skt) = Piles hs' (ca : pld) skt
|
||||
where (CardS _ hand, hs') = remove ((==card) . getCard) hs
|
||||
ca = CardS card (Table hand)
|
||||
uorigin :: Owner -> Hand
|
||||
uorigin owner = case origin owner of
|
||||
Just hand -> hand
|
||||
Nothing -> error "has no origin"
|
||||
|
||||
winCard :: Team -> CardS Played -> CardS Played
|
||||
winCard team (CardS card (Table hand)) = CardS card (Won hand team)
|
||||
winCard team c = c
|
||||
removeFromHand :: Hand -> Card -> Piles -> Piles
|
||||
removeFromHand Hand1 card ps = ps { _hand1 = delete (CardS card (P Hand1)) (_hand1 ps) }
|
||||
removeFromHand Hand2 card ps = ps { _hand2 = delete (CardS card (P Hand2)) (_hand2 ps) }
|
||||
removeFromHand Hand3 card ps = ps { _hand3 = delete (CardS card (P Hand3)) (_hand3 ps) }
|
||||
|
||||
wonCards :: Team -> Piles -> [Card]
|
||||
wonCards team (Piles _ pld _) = filterMap (f . getPile) getCard pld
|
||||
where f (Won _ tm) = tm == team
|
||||
f _ = False
|
||||
addToHand :: Hand -> Card -> Piles -> Piles
|
||||
addToHand Hand1 card ps = ps { _hand1 = (CardS card (P Hand1)) : (_hand1 ps) }
|
||||
addToHand Hand2 card ps = ps { _hand2 = (CardS card (P Hand2)) : (_hand2 ps) }
|
||||
addToHand Hand3 card ps = ps { _hand3 = (CardS card (P Hand3)) : (_hand3 ps) }
|
||||
|
||||
playCard :: HasCard c => Hand -> c -> Piles -> Piles
|
||||
playCard hand card' ps = (removeFromHand hand card ps) { _table = (CardS card (P hand)) : _table ps }
|
||||
where card = toCard card'
|
||||
|
||||
moveToSkat :: HasCard c => Hand -> [c] -> Piles -> Maybe Piles
|
||||
moveToSkat hand cards' piles
|
||||
| length cards' == 2 && all (`elem` possible) cards =
|
||||
Just $ updated { _skat = newSkat }
|
||||
| otherwise = Nothing
|
||||
where cards = map toCard cards'
|
||||
oldSkat = skatCards piles
|
||||
noLongerSkat = filter (not . (`elem` cards)) oldSkat
|
||||
possible = map toCard (handCards hand piles) ++ oldSkat
|
||||
newSkat = map (putAt S) cards
|
||||
removed = foldr (\card ps -> removeFromHand hand card ps) piles cards
|
||||
updated = foldr (\card ps -> addToHand hand card ps) removed noLongerSkat
|
||||
|
||||
unplayCard :: Hand -> Card -> Team -> Piles -> Piles
|
||||
unplayCard hand card winner ps
|
||||
| null table = case winner of
|
||||
Team -> ps' { _table = tail $ take 3 (_wonTeam ps), _wonTeam = drop 3 (_wonTeam ps) }
|
||||
Single -> ps' { _table = tail $ take 3 (_wonSingle ps), _wonSingle = drop 3 (_wonSingle ps) }
|
||||
| otherwise = ps' { _table = tail (_table ps) }
|
||||
where ps' = addToHand hand card ps
|
||||
table = tableCards ps
|
||||
|
||||
wonCards :: Team -> Piles -> [CardS Owner]
|
||||
wonCards Team = _wonTeam
|
||||
wonCards Single = _wonSingle
|
||||
|
||||
cleanTable :: Team -> Piles -> Piles
|
||||
cleanTable winner ps@(Piles hs pld skt) = Piles hs pld' skt
|
||||
where table = tableCards ps
|
||||
pld' = map (winCard winner) pld
|
||||
cleanTable Team ps = ps { _table = [], _wonTeam = _table ps ++ _wonTeam ps }
|
||||
cleanTable Single ps = ps { _table = [], _wonSingle = _table ps ++ _wonSingle ps }
|
||||
|
||||
tableCards :: Piles -> [Card]
|
||||
tableCards (Piles _ pld _) = filterMap (f . getPile) getCard pld
|
||||
where f (Table _) = True
|
||||
f _ = False
|
||||
tableCards :: Piles -> [CardS Owner]
|
||||
tableCards = _table
|
||||
|
||||
tableCardsS :: Piles -> [CardS Played]
|
||||
tableCardsS (Piles _ pld _) = filter (f . getPile) pld
|
||||
where f (Table _) = True
|
||||
f _ = False
|
||||
handEmpty :: Hand -> Piles -> Bool
|
||||
handEmpty Hand1 = null . _hand1
|
||||
handEmpty Hand2 = null . _hand2
|
||||
handEmpty Hand3 = null . _hand3
|
||||
|
||||
handCards :: Hand -> Piles -> [Card]
|
||||
handCards hand (Piles hs _ _) = filterMap ((==hand) . getPile) getCard hs
|
||||
handCards :: Hand -> Piles -> [CardS Owner]
|
||||
handCards Hand1 = _hand1
|
||||
handCards Hand2 = _hand2
|
||||
handCards Hand3 = _hand3
|
||||
|
||||
allowed :: Hand -> Trump -> Maybe TurnColour -> Piles -> [CardS Owner]
|
||||
allowed hand trump turnCol ps
|
||||
| null sameColour = cards
|
||||
| otherwise = sameColour
|
||||
where cards = handCards hand ps
|
||||
sameColour = filter (\ca -> effectiveColour trump ca `equals` turnCol) cards
|
||||
|
||||
skatCards :: Piles -> [Card]
|
||||
skatCards (Piles _ _ skat) = map getCard skat
|
||||
skatCards = map getCard . _skat
|
||||
|
||||
emptyPiles :: [Card] -> [Card] -> [Card] -> [Card] -> Piles
|
||||
emptyPiles h1 h2 h3 skt = makePiles h1 h2 h3 [] skt
|
||||
|
||||
putAt :: p -> Card -> CardS p
|
||||
putAt = flip CardS
|
||||
|
||||
makePiles :: [Card] -> [Card] -> [Card] -> [CardS Owner] -> [Card] -> Piles
|
||||
makePiles h1 h2 h3 table skt = Piles h1' h2' h3' table [] [] skt'
|
||||
where h1' = map (putAt $ P Hand1) h1
|
||||
h2' = map (putAt $ P Hand2) h2
|
||||
h3' = map (putAt $ P Hand3) h3
|
||||
skt' = map (putAt S) skt
|
||||
|
||||
distribute :: [Card] -> Piles
|
||||
distribute cards = Piles hands [] (map (putAt SkatP) skt)
|
||||
distribute cards = emptyPiles hand1 hand2 hand3 skt
|
||||
where round1 = chunksOf 3 (take 9 cards)
|
||||
skt = take 2 $ drop 9 cards
|
||||
round2 = chunksOf 4 (take 12 $ drop 11 cards)
|
||||
@@ -115,6 +221,112 @@ distribute cards = Piles hands [] (map (putAt SkatP) skt)
|
||||
hand1 = concatMap (!! 0) [round1, round2, round3]
|
||||
hand2 = concatMap (!! 1) [round1, round2, round3]
|
||||
hand3 = concatMap (!! 2) [round1, round2, round3]
|
||||
hands = map (putAt Hand1) hand1
|
||||
++ map (putAt Hand2) hand2
|
||||
++ map (putAt Hand3) hand3
|
||||
|
||||
instance Serialize String Piles where
|
||||
serialize piles = sers (_hand1 piles) ++ sers (_hand2 piles) ++ sers (_hand3 piles)
|
||||
++ sers (_skat piles)
|
||||
where sers cards = map (serialize . toCard) cards
|
||||
deserialize str = (flip evalState) str $ runMaybeT $ do
|
||||
hand1 <- takeG 10 >>= mapM deser
|
||||
hand2 <- takeG 10 >>= mapM deser
|
||||
hand3 <- takeG 10 >>= mapM deser
|
||||
skat <- takeG 2 >>= mapM deser
|
||||
return $ emptyPiles hand1 hand2 hand3 skat
|
||||
where deser char = MaybeT $ return $ deserialize char
|
||||
|
||||
instance Serialize String [Trick] where
|
||||
serialize [] = ""
|
||||
serialize ((c1, c2, c3):tricks) = serialize c1 ++ serialize c2 ++ serialize c3
|
||||
++ serialize tricks
|
||||
deserialize str = (flip evalState) str $ runMaybeT $ reverse <$> go []
|
||||
where go acc = do
|
||||
empty <- isEmpty
|
||||
if empty then return acc else do
|
||||
card1 <- takeG 2 >>= MaybeT . return . deserialize
|
||||
card2 <- takeG 2 >>= MaybeT . return . deserialize
|
||||
card3 <- takeG 2 >>= MaybeT . return . deserialize
|
||||
go ((card1, card2, card3):acc)
|
||||
|
||||
cardDistr :: Piles
|
||||
cardDistr = emptyPiles hand1 hand2 hand3 skt
|
||||
where hand1 = [Card Ace Spades, Card Jack Diamonds, Card Jack Clubs, Card King Spades,
|
||||
Card Nine Spades, Card Ace Diamonds, Card Queen Diamonds, Card Ten Clubs,
|
||||
Card Eight Clubs, Card King Clubs]
|
||||
hand3 = [Card Jack Spades, Card Jack Hearts, Card Ten Spades, Card Ace Hearts, Card Ten Hearts,
|
||||
Card Nine Hearts, Card Seven Clubs, Card Ace Clubs, Card King Diamonds,
|
||||
Card Ten Diamonds]
|
||||
hand2 = [Card Eight Spades, Card Queen Spades, Card Seven Spades, Card Seven Diamonds,
|
||||
Card Seven Hearts, Card Eight Hearts, Card Queen Hearts, Card King Hearts,
|
||||
Card Nine Diamonds, Card Eight Diamonds]
|
||||
skt = [Card Nine Clubs, Card Queen Clubs]
|
||||
|
||||
cardDistr2 :: Piles
|
||||
cardDistr2 = emptyPiles hand1 hand2 hand3 skt
|
||||
where hand3 = [Card Ace Spades, Card Eight Spades, Card Queen Diamonds, Card Ace Clubs]
|
||||
hand1 = [Card Jack Spades, Card Seven Spades, Card Ten Diamonds, Card Nine Spades]
|
||||
hand2 = [Card Ten Hearts, Card Eight Hearts, Card Ace Diamonds, Card King Clubs]
|
||||
skt = [Card Nine Clubs, Card Queen Clubs]
|
||||
|
||||
cardDistr3 :: Piles
|
||||
cardDistr3 = emptyPiles hand1 hand2 hand3 skt
|
||||
where hand3 = [Card Ace Spades, Card Eight Spades, Card Ace Clubs]
|
||||
hand1 = [Card Jack Spades, Card Seven Spades, Card Nine Spades]
|
||||
hand2 = [Card Ten Hearts, Card Ace Hearts, Card Ten Clubs]
|
||||
skt = [Card Nine Clubs, Card Seven Clubs]
|
||||
|
||||
cardDistr4 :: Piles
|
||||
cardDistr4 = makePiles hand1 hand2 hand3 tbl skt
|
||||
where hand3 = [Card Ace Spades]
|
||||
hand1 = [Card Jack Spades, Card Nine Spades]
|
||||
hand2 = [Card Eight Spades]
|
||||
skt = [Card Nine Clubs, Card Eight Clubs]
|
||||
tbl = [CardS (Card Ace Clubs) (P Hand3), CardS (Card King Clubs) (P Hand2)]
|
||||
|
||||
cardDistr5 :: Piles
|
||||
cardDistr5 = makePiles hand1 hand2 hand3 tbl skt
|
||||
where hand3 = [Card Ace Spades]
|
||||
hand1 = []
|
||||
hand2 = []
|
||||
skt = [Card Nine Clubs, Card Queen Clubs]
|
||||
tbl = [CardS (Card Jack Spades) (P Hand1), CardS (Card Eight Spades) (P Hand2)]
|
||||
|
||||
cardDistr6 :: Piles
|
||||
cardDistr6 = emptyPiles hand1 hand2 hand3 skt
|
||||
where hand1 = [Card Jack Diamonds, Card Jack Clubs, Card King Spades,
|
||||
Card Nine Spades, Card Ace Diamonds, Card Queen Diamonds
|
||||
]
|
||||
hand3 = [Card Jack Spades, Card Ten Spades, Card Ace Hearts,
|
||||
Card Ten Hearts, Card Nine Hearts, Card Seven Clubs
|
||||
]
|
||||
hand2 = [Card Queen Spades, Card Seven Spades, Card Seven Diamonds,
|
||||
Card Seven Hearts, Card Eight Hearts, Card Queen Hearts
|
||||
]
|
||||
skt = [Card Nine Clubs, Card Queen Clubs]
|
||||
|
||||
cardDistr7 :: Piles
|
||||
cardDistr7 = emptyPiles hand1 hand2 hand3 skt
|
||||
where hand3 = [Card Eight Spades, Card Ace Clubs]
|
||||
hand1 = [Card Seven Spades, Card Nine Spades]
|
||||
hand2 = [Card Ace Hearts, Card Ten Clubs]
|
||||
skt = [Card Nine Clubs, Card Seven Clubs]
|
||||
|
||||
cardDistr8 :: Piles
|
||||
cardDistr8 = emptyPiles hand1 hand2 hand3 skt
|
||||
where hand3 = [Card Ace Spades, Card Ace Clubs]
|
||||
hand1 = [Card Jack Spades, Card Seven Spades]
|
||||
hand2 = [Card Eight Hearts, Card King Clubs]
|
||||
skt = [Card Nine Clubs, Card Seven Clubs]
|
||||
|
||||
cardDistr9 :: Piles
|
||||
cardDistr9 = makePiles hand1 hand2 hand3 tbl skt
|
||||
where hand1 = [Card Ace Spades, Card Jack Diamonds, Card Jack Clubs, Card King Spades,
|
||||
Card Nine Spades, Card Ace Diamonds, Card Queen Diamonds, Card Ten Clubs,
|
||||
Card Eight Clubs]
|
||||
hand3 = [Card Jack Spades, Card Jack Hearts, Card Ten Spades, Card Ten Hearts,
|
||||
Card Nine Hearts, Card Seven Clubs, Card King Diamonds,
|
||||
Card Ten Diamonds]
|
||||
hand2 = [Card Eight Spades, Card Seven Spades, Card Seven Diamonds,
|
||||
Card Seven Hearts, Card Eight Hearts, Card Queen Hearts,
|
||||
Card Nine Diamonds, Card Eight Diamonds]
|
||||
skt = [Card Nine Clubs, Card Queen Clubs]
|
||||
tbl = [CardS (Card Ace Hearts) (P Hand3), CardS (Card King Hearts) (P Hand2)]
|
||||
|
||||
+24
-17
@@ -6,11 +6,14 @@ import Control.Monad.IO.Class
|
||||
|
||||
import Skat.Card
|
||||
import Skat.Pile
|
||||
import Skat.Bidding
|
||||
|
||||
class (Monad m, MonadIO m) => MonadPlayer m where
|
||||
trumpColour :: m Colour
|
||||
turnColour :: m (Maybe Colour)
|
||||
class Monad m => MonadPlayer m where
|
||||
trump :: m Trump
|
||||
turnColour :: m (Maybe TurnColour)
|
||||
showSkat :: Player p => p -> m (Maybe [Card])
|
||||
singlePlayer :: m Hand
|
||||
game :: m Game
|
||||
|
||||
class (Monad m, MonadIO m, MonadPlayer m) => MonadPlayerOpen m where
|
||||
showPiles :: m (Piles)
|
||||
@@ -18,31 +21,30 @@ class (Monad m, MonadIO m, MonadPlayer m) => MonadPlayerOpen m where
|
||||
class Player p where
|
||||
team :: p -> Team
|
||||
hand :: p -> Hand
|
||||
chooseCard :: MonadPlayer m
|
||||
chooseCard :: (MonadIO m, HasCard d, HasCard c, MonadPlayer m)
|
||||
=> p
|
||||
-> [CardS Played]
|
||||
-> [CardS Played]
|
||||
-> [Card]
|
||||
-> Maybe [d]
|
||||
-> [c]
|
||||
-> m (Card, p)
|
||||
onCardPlayed :: MonadPlayer m
|
||||
onCardPlayed :: (MonadPlayer m, MonadIO m)
|
||||
=> p
|
||||
-> CardS Played
|
||||
-> m p
|
||||
onCardPlayed p _ = return p
|
||||
chooseCardOpen :: MonadPlayerOpen m
|
||||
chooseCardOpen :: (MonadIO m, MonadPlayerOpen m)
|
||||
=> p
|
||||
-> m Card
|
||||
chooseCardOpen p = do
|
||||
piles <- showPiles
|
||||
let table = tableCardsS piles
|
||||
let table = tableCards piles
|
||||
fallen = played piles
|
||||
myCards = handCards (hand p) piles
|
||||
fmap fst $ chooseCard p table fallen myCards
|
||||
onGameResults :: MonadIO m
|
||||
=> p
|
||||
-> (Int, Int)
|
||||
-> m ()
|
||||
onGameResults _ _ = return ()
|
||||
ouvert <- isOuvert <$> game
|
||||
mayOuvert <- if ouvert then Just <$> (singlePlayer >>= \hnd -> return $ handCards hnd piles)
|
||||
else return Nothing
|
||||
fst <$> chooseCard p table fallen mayOuvert myCards
|
||||
|
||||
data PL = forall p. (Show p, Player p) => PL p
|
||||
|
||||
@@ -52,14 +54,13 @@ instance Show PL where
|
||||
instance Player PL where
|
||||
team (PL p) = team p
|
||||
hand (PL p) = hand p
|
||||
chooseCard (PL p) table fallen hand = do
|
||||
(v, a) <- chooseCard p table fallen hand
|
||||
chooseCard (PL p) table fallen mayOuvert hand = do
|
||||
(v, a) <- chooseCard p table fallen mayOuvert hand
|
||||
return $ (v, PL a)
|
||||
onCardPlayed (PL p) card = do
|
||||
v <- onCardPlayed p card
|
||||
return $ PL v
|
||||
chooseCardOpen (PL p) = chooseCardOpen p
|
||||
onGameResults (PL p) res = onGameResults p res
|
||||
|
||||
data Players = Players PL PL PL
|
||||
deriving Show
|
||||
@@ -77,3 +78,9 @@ updatePlayer p (Players p1 p2 p3) = case hand p of
|
||||
|
||||
playersToList :: Players -> [PL]
|
||||
playersToList (Players p1 p2 p3) = [p1, p2, p3]
|
||||
|
||||
getSinglePlayer :: Players -> Hand
|
||||
getSinglePlayer (Players p1 p2 p3) = case (team p1, team p2, team p3) of
|
||||
(Single, _, _) -> Hand1
|
||||
(_, Single, _) -> Hand2
|
||||
_ -> Hand3
|
||||
|
||||
@@ -4,15 +4,15 @@ module Skat.Player.Utils (
|
||||
|
||||
import Skat.Player
|
||||
import qualified Skat.Card as C
|
||||
import Skat.Card (Card)
|
||||
import Skat.Card (Card, HasCard(..))
|
||||
|
||||
isAllowed :: MonadPlayer m => [Card] -> Card -> m Bool
|
||||
isAllowed :: (HasCard c, MonadPlayer m) => [c] -> c -> m Bool
|
||||
isAllowed hand card = do
|
||||
trCol <- trumpColour
|
||||
tr <- trump
|
||||
turnCol <- turnColour
|
||||
return $ C.isAllowed trCol turnCol hand card
|
||||
return $ C.isAllowed tr turnCol hand card
|
||||
|
||||
isTrump :: MonadPlayer m => Card -> m Bool
|
||||
isTrump card = do
|
||||
trCol <- trumpColour
|
||||
return $ C.isTrump trCol card
|
||||
tr <- trump
|
||||
return $ C.isTrump tr card
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
{-# LANGUAGE ExistentialQuantification #-}
|
||||
{-# LANGUAGE TupleSections #-}
|
||||
|
||||
module Skat.Preperation (
|
||||
Bidder(..), Bid, BD(..), Bidders(..), PrepEnv(..), runPreperation,
|
||||
publishGameResults, bidder, makePrep
|
||||
) where
|
||||
|
||||
import Control.Monad.IO.Class
|
||||
import Control.Monad.State
|
||||
|
||||
import Skat.Pile
|
||||
import Skat.Card
|
||||
import Skat.Player (PL, Players(..))
|
||||
import Skat.Bidding
|
||||
import Skat (SkatEnv, mkSkatEnv)
|
||||
|
||||
data PrepEnv = PrepEnv { piles :: Piles
|
||||
, bidders :: Bidders
|
||||
, current :: Bid }
|
||||
deriving Show
|
||||
|
||||
makePrep :: Piles -> Bidders -> PrepEnv
|
||||
makePrep ps bd = PrepEnv ps bd 0
|
||||
|
||||
type Preperation = StateT PrepEnv IO
|
||||
|
||||
class Bidder a where
|
||||
hand :: a -> Hand
|
||||
onStart :: MonadIO m => a -> m ()
|
||||
askBid :: MonadIO m => a -> Hand -> Bid -> m (Maybe Bid)
|
||||
askResponse :: MonadIO m => a -> Hand -> Bid -> m Bool
|
||||
askGame :: MonadIO m => a -> Bid -> m Game
|
||||
askHand :: MonadIO m => a -> Bid -> m Bool
|
||||
askSkat :: MonadIO m => a -> Bid -> [Card] -> m [Card]
|
||||
toPlayer :: a -> Team -> PL
|
||||
onBid :: MonadIO m => a -> Maybe Bid -> Hand -> Hand -> m ()
|
||||
onBid _ _ _ _ = return ()
|
||||
onResponse :: MonadIO m => a -> Bool -> Hand -> Hand -> m ()
|
||||
onResponse _ _ _ _ = return ()
|
||||
onGame :: MonadIO m => a -> HideGame -> Hand -> m ()
|
||||
onGame _ _ _ = return ()
|
||||
onResult :: MonadIO m => a -> Result -> m ()
|
||||
onResult _ _ = return ()
|
||||
onNoGame :: MonadIO m => a -> m ()
|
||||
onNoGame _ = return ()
|
||||
|
||||
-- | trick to allow heterogenous bidder list
|
||||
data BD = forall b. (Show b, Bidder b) => BD b
|
||||
|
||||
instance Show BD where
|
||||
show (BD b) = show b
|
||||
|
||||
instance Bidder BD where
|
||||
hand (BD b) = hand b
|
||||
askBid (BD b) = askBid b
|
||||
askGame (BD b) = askGame b
|
||||
askHand (BD b) = askHand b
|
||||
askSkat (BD b) = askSkat b
|
||||
askResponse (BD b) = askResponse b
|
||||
toPlayer (BD b) = toPlayer b
|
||||
onStart (BD b) = onStart b
|
||||
onGame (BD b) = onGame b
|
||||
onResult (BD b) = onResult b
|
||||
onBid (BD b) = onBid b
|
||||
onResponse (BD b) = onResponse b
|
||||
onNoGame (BD b) = onNoGame b
|
||||
|
||||
data Bidders = Bidders BD BD BD
|
||||
deriving Show
|
||||
|
||||
bidder :: Bidders -> Hand -> BD
|
||||
bidder (Bidders b _ _) Hand1 = b
|
||||
bidder (Bidders _ b _) Hand2 = b
|
||||
bidder (Bidders _ _ b) Hand3 = b
|
||||
|
||||
toPlayers :: Hand -> Bidders -> Players
|
||||
toPlayers single (Bidders b1 b2 b3) =
|
||||
Players (toPlayer b1 $ if single == Hand1 then Single else Team)
|
||||
(toPlayer b2 $ if single == Hand2 then Single else Team)
|
||||
(toPlayer b3 $ if single == Hand3 then Single else Team)
|
||||
|
||||
runPreperation :: Preperation (Maybe SkatEnv)
|
||||
runPreperation = do
|
||||
bds <- gets bidders
|
||||
onStart (bidder bds Hand1)
|
||||
onStart (bidder bds Hand2)
|
||||
onStart (bidder bds Hand3)
|
||||
(winner, bid) <- runBidding 0 (bidder bds Hand2) (bidder bds Hand1)
|
||||
(finalWinner, finalBid) <- runBidding bid (bidder bds Hand3) (bidder bds winner)
|
||||
if finalBid == 0 then do
|
||||
bid <- askBid (bidder bds finalWinner) finalWinner 0
|
||||
publishBid bid finalWinner finalWinner
|
||||
case bid of
|
||||
Just val -> Just <$> initGame finalWinner val
|
||||
Nothing -> publishNoGame >> return Nothing
|
||||
else Just <$> initGame finalWinner finalBid
|
||||
|
||||
runBidding :: Bid -> BD -> BD -> Preperation (Hand, Bid)
|
||||
runBidding startingBid reizer gereizter = do
|
||||
first <- askBid reizer (hand gereizter) startingBid
|
||||
case first of
|
||||
Just val
|
||||
| val > startingBid -> do
|
||||
publishBid first (hand reizer) (hand gereizter)
|
||||
modify $ \env -> env { current = val }
|
||||
response <- askResponse gereizter (hand reizer) val
|
||||
publishResponse response (hand reizer) (hand gereizter)
|
||||
if response then runBidding val reizer gereizter
|
||||
else return (hand reizer, val)
|
||||
| otherwise -> do
|
||||
publishBid Nothing (hand reizer) (hand gereizter)
|
||||
return (hand gereizter, startingBid)
|
||||
Nothing -> do
|
||||
publishBid Nothing (hand reizer) (hand gereizter)
|
||||
return (hand gereizter, startingBid)
|
||||
|
||||
initGame :: Hand -> Bid -> Preperation SkatEnv
|
||||
initGame single bid = do
|
||||
ps <- gets piles
|
||||
bds <- gets bidders
|
||||
-- ask if player wants to play hand
|
||||
noSkat <- askHand (bidder bds single) bid
|
||||
-- either return piles or ask for skat cards and modify piles
|
||||
ps' <- if noSkat then return ps else handleSkat (bidder bds single) bid ps
|
||||
-- ask for game kind
|
||||
game <- handleGame (bidder bds single) bid noSkat
|
||||
-- publish game start
|
||||
publishGameStart game single
|
||||
-- construct skat env
|
||||
return $ mkSkatEnv ps' Nothing game (toPlayers single bds) Hand1 single
|
||||
|
||||
handleGame :: BD -> Bid -> Bool -> Preperation Game
|
||||
handleGame bd bid noSkat = do
|
||||
cards <- (\ps -> map toCard (handCards (hand bd) ps) ++ skatCards ps) <$> gets piles
|
||||
-- ask bidder for game
|
||||
proposal <- askGame bd bid
|
||||
-- check if proposal is allowed
|
||||
if isHand proposal == noSkat then return proposal else handleGame bd bid noSkat
|
||||
|
||||
handleSkat :: BD -> Bid -> Piles -> Preperation Piles
|
||||
handleSkat bd bid ps = do
|
||||
let skat = skatCards ps
|
||||
skat' <- askSkat bd bid skat
|
||||
liftIO $ putStrLn $ "received skat " ++ show skat'
|
||||
case moveToSkat (hand bd) skat' ps of
|
||||
Just correct -> return correct
|
||||
Nothing -> handleSkat bd bid ps
|
||||
|
||||
publishGameResults :: MonadIO m => Result -> Bidders -> m ()
|
||||
publishGameResults res bidders = do
|
||||
onResult (bidder bidders Hand1) res
|
||||
onResult (bidder bidders Hand2) res
|
||||
onResult (bidder bidders Hand3) res
|
||||
|
||||
publishGameStart :: Game -> Hand -> Preperation ()
|
||||
publishGameStart game sglPlayer = mapBidders (\b -> onGame b (HideGame game) sglPlayer)
|
||||
|
||||
publishBid :: Maybe Bid -> Hand -> Hand -> Preperation ()
|
||||
publishBid bid reizer gereizter = mapBidders (\b -> onBid b bid reizer gereizter)
|
||||
|
||||
publishResponse :: Bool -> Hand -> Hand -> Preperation ()
|
||||
publishResponse response reizer gereizter = mapBidders (\b -> onResponse b response reizer gereizter)
|
||||
|
||||
publishNoGame :: Preperation ()
|
||||
publishNoGame = mapBidders onNoGame
|
||||
|
||||
mapBidders :: (BD -> Preperation ()) -> Preperation ()
|
||||
mapBidders f = do
|
||||
bds <- gets bidders
|
||||
f (bidder bds Hand1)
|
||||
f (bidder bds Hand2)
|
||||
f (bidder bds Hand3)
|
||||
+6
-2
@@ -1,8 +1,12 @@
|
||||
module Skat.Render where
|
||||
|
||||
import Data.List
|
||||
import Data.Vector (Vector, toList)
|
||||
|
||||
import Skat.Card
|
||||
|
||||
render :: [Card] -> IO ()
|
||||
render = putStrLn . intercalate "\n" . zipWith (\n c -> show n ++ ") " ++ show c) [0..]
|
||||
render :: HasCard c => [c] -> IO ()
|
||||
render = putStrLn . intercalate "\n" . zipWith (\n c -> show n ++ ") " ++ show c) [0..] . map toCard
|
||||
|
||||
renderVector :: Vector Card -> IO ()
|
||||
renderVector = render . toList
|
||||
|
||||
+45
-2
@@ -1,9 +1,15 @@
|
||||
{-# LANGUAGE ScopedTypeVariables #-}
|
||||
|
||||
module Skat.Utils where
|
||||
|
||||
import Control.Monad.State
|
||||
import Control.Monad.Trans.Maybe
|
||||
|
||||
import System.Random
|
||||
import Text.Read
|
||||
import Text.Read hiding (get, lift)
|
||||
import qualified Data.ByteString.Char8 as B (ByteString, unpack, pack)
|
||||
import qualified Data.Text as T (Text, unpack, pack)
|
||||
import Data.List (foldl')
|
||||
|
||||
shuffle :: StdGen -> [a] -> [a]
|
||||
shuffle g xs = shuffle' (randoms g) xs
|
||||
@@ -31,7 +37,7 @@ remove pred xs = foldr f (undefined, []) xs
|
||||
|
||||
filterMap :: (a -> Bool) -> (a -> b) -> [a] -> [b]
|
||||
filterMap pred f as = foldr g [] as
|
||||
where g a bs = if pred a then f a : bs else bs
|
||||
where g a bs = if pred a then (f $! a) : bs else bs
|
||||
|
||||
--filterM :: Monad m => (a -> m Bool) -> [a] -> m [a]
|
||||
--filterM _ [] = return []
|
||||
@@ -56,3 +62,40 @@ instance Stringy B.ByteString where
|
||||
instance Stringy T.Text where
|
||||
toString = T.unpack
|
||||
fromString = T.pack
|
||||
|
||||
indexOf :: Eq a => [a] -> a -> Maybe Int
|
||||
indexOf [] _ = Nothing
|
||||
indexOf (x:xs) item
|
||||
| x == item = Just 0
|
||||
| otherwise = (1+) <$> xs `indexOf` item
|
||||
|
||||
type Generator c = MaybeT (State [c])
|
||||
|
||||
pop :: Generator c c
|
||||
pop = do
|
||||
cs <- get
|
||||
if null cs then mzero else put (tail cs) >> return (head cs)
|
||||
|
||||
isEmpty :: Generator c Bool
|
||||
isEmpty = get >>= return . null
|
||||
|
||||
takeG :: Int -> Generator c [c]
|
||||
takeG n = do
|
||||
cs <- lift get
|
||||
if length cs >= n
|
||||
then do
|
||||
put (drop n cs)
|
||||
return (take n cs)
|
||||
else mzero
|
||||
|
||||
-- forall is needed to allow scoped type variables
|
||||
safeToEnum :: forall a. (Enum a, Bounded a) => Int -> Maybe a
|
||||
safeToEnum n
|
||||
| maxN < n || minN > n = Nothing
|
||||
| otherwise = Just $ toEnum n
|
||||
where maxN = fromEnum (maxBound :: a)
|
||||
minN = fromEnum (minBound :: a)
|
||||
|
||||
updateAt :: Int -> [a] -> a -> [a]
|
||||
updateAt n xs y = map f $ zip [0..] xs
|
||||
where f (i, x) = if i == n then y else x
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@
|
||||
#
|
||||
# resolver: ./custom-snapshot.yaml
|
||||
# resolver: https://example.com/snapshots/2018-01-01.yaml
|
||||
resolver: lts-14.3
|
||||
resolver: lts-18.18
|
||||
|
||||
# User packages to be built.
|
||||
# Various formats can be used as shown in the example below.
|
||||
|
||||
Reference in New Issue
Block a user