| @@ -7,3 +7,7 @@ | |||||
| *.o | *.o | ||||
| *.prof | *.prof | ||||
| *.hp | *.hp | ||||
| # ignore stack work files | |||||
| .stack-work/ | |||||
| stack.yaml.lock | |||||
| @@ -1,39 +0,0 @@ | |||||
| import Card | |||||
| import Pile | |||||
| import Utils | |||||
| import qualified Data.Map.Strict as M | |||||
| import Data.Monoid ((<>)) | |||||
| type Guess = M.Map Card [Hand] | |||||
| type Distribution = ([Card], [Card], [Card]) | |||||
| distributions :: Guess -> [Distribution] | |||||
| distributions guess = --filter equilibrated | |||||
| (helper (M.toList guess) (0, 0, 0)) | |||||
| 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) = | |||||
| let f card Hand1 = (([card], [], []), (n1+1, n2, n3)) | |||||
| f card Hand2 = (([], [card], []), (n1, n2+1, n3)) | |||||
| f card Hand3 = (([], [], [card]), (n1, n2, n3+1)) | |||||
| isOk Hand1 = n1 < cardsPerHand | |||||
| isOk Hand2 = n2 < cardsPerHand | |||||
| isOk Hand3 = n3 < cardsPerHand | |||||
| in filterMap isOk (f card) hands | |||||
| equilibrated (cs1, cs2, cs3) = | |||||
| let ls = [length cs1, length cs2, length cs3] | |||||
| in (maximum ls - minimum ls) <= 1 | |||||
| cardsPerHand = (length guess `div` 3) | |||||
| testguess :: Guess | |||||
| testguess = foldr (Hand3 `has`) m (take 10 allCards) | |||||
| where l = map (\c -> (c, [Hand1, Hand2, Hand3])) (take 30 allCards) | |||||
| m = M.fromList l | |||||
| main :: IO () | |||||
| main = print $ length $ distributions testguess | |||||
| @@ -1,5 +0,0 @@ | |||||
| import AI.Rulebased | |||||
| import Pile | |||||
| main :: IO () | |||||
| main = print $ length $ simplify Hand3 testds | |||||
| @@ -0,0 +1,3 @@ | |||||
| # Changelog for skat | |||||
| ## Unreleased changes | |||||
| @@ -0,0 +1 @@ | |||||
| # skat | |||||
| @@ -1,73 +0,0 @@ | |||||
| module Reizen where | |||||
| import Skat | |||||
| import Card | |||||
| import Utils | |||||
| import Operations | |||||
| import Render | |||||
| data Reizer = Reizer Index [Card] | |||||
| deriving Show | |||||
| getHand :: Index -> [Reizer] -> [Card] | |||||
| getHand n rs = let (Reizer _ h) = head $ filter (\(Reizer i cs) -> i == n) rs | |||||
| in h | |||||
| goWith :: [Card] -> Int -> IO Bool | |||||
| goWith cs n = query $ "Go with " ++ show n | |||||
| goUp :: [Card] -> Int -> IO Int | |||||
| goUp cs n = query $ "Go up " ++ show n | |||||
| askColour :: [Card] -> IO Colour | |||||
| askColour cs = render (sortRender cs) >> query "Trump should be:" | |||||
| askSkat :: [Card] -> IO (Card, Card) | |||||
| askSkat cs_ = do | |||||
| let cs = sortRender cs_ | |||||
| render cs | |||||
| (n1, n2) <- query "Drop two cards:" | |||||
| if n1 < length cs && n2 < length cs && n1 >= 0 && n2 >= 0 && n1 /= n2 | |||||
| then return (cs !! n1, cs !! n2) | |||||
| else askSkat cs | |||||
| reizen :: IO SkatEnv | |||||
| reizen = do | |||||
| cs <- shuffleCards | |||||
| let cards = distribute cs | |||||
| p1 = Reizer One $ findCards Hand1 cards | |||||
| p2 = Reizer Two $ findCards Hand2 cards | |||||
| p3 = Reizer Three $ findCards Hand3 cards | |||||
| skt = findCards SkatP cards | |||||
| (winner1, new) <- combat p2 p1 0 | |||||
| (Reizer idx _, _) <- combat p3 winner1 new | |||||
| let ps = Players (Player (if idx == One then Single else Team) One) | |||||
| (Player (if idx == Two then Single else Team) Two) | |||||
| (Player (if idx == Three then Single else Team) Three) | |||||
| sglHand = playerHand idx | |||||
| cards' = foldr (\c css -> moveCard c sglHand css) cards skt | |||||
| trumpCol <- askColour (findCards sglHand cards') | |||||
| (s1, s2) <- askSkat (findCards sglHand cards') | |||||
| let cards'' = moveCard s2 WonSingle (moveCard s1 WonSingle cards') | |||||
| return $ SkatEnv cards'' Nothing trumpCol ps | |||||
| combat :: Reizer -> Reizer -> Int -> IO (Reizer, Int) | |||||
| combat r2@(Reizer p2 h2) r1@(Reizer p1 h1) start = do | |||||
| -- advantage for h1 (being challenged) | |||||
| putStrLn $ "Player " ++ show p2 ++ " challenging " ++ show p1 | |||||
| putStrLn $ "Player " ++ show p2 ++ "'s turn" | |||||
| new <- goUp h2 start | |||||
| if new > start | |||||
| then do | |||||
| putStrLn $ "Player " ++ show p2 ++ " goes up to " ++ show new | |||||
| putStrLn $ "Player " ++ show p1 ++ "'s turn" | |||||
| yes <- goWith h1 new | |||||
| if yes then combat r2 r1 new | |||||
| else do | |||||
| putStrLn $ "Player " ++ show p1 ++ " gives up" | |||||
| putStrLn $ "Player " ++ show p2 ++ " wins" | |||||
| return (r2, new) | |||||
| else do | |||||
| putStrLn $ "Player " ++ show p2 ++ " gives up" | |||||
| putStrLn $ "Player " ++ show p1 ++ " wins" | |||||
| return (r1, start) | |||||
| @@ -1,16 +1,21 @@ | |||||
| module Main where | module Main where | ||||
| import Control.Monad.State | import Control.Monad.State | ||||
| import Control.Monad.Reader | |||||
| import Control.Concurrent | |||||
| import qualified Network.WebSockets as WS | |||||
| import qualified Data.ByteString.Lazy.Char8 as BS | |||||
| import Card | |||||
| import Skat | import Skat | ||||
| import Operations | |||||
| import Player | |||||
| import Pile | |||||
| import Skat.Card | |||||
| import Skat.Operations | |||||
| import Skat.Player | |||||
| import Skat.Pile | |||||
| import AI.Stupid | |||||
| import AI.Human | |||||
| import AI.Rulebased | |||||
| import Skat.AI.Stupid | |||||
| import Skat.AI.Online | |||||
| import Skat.AI.Rulebased | |||||
| main :: IO () | main :: IO () | ||||
| main = testAI 10 | main = testAI 10 | ||||
| @@ -28,7 +33,9 @@ runAI = do | |||||
| cs = handCards Hand3 ps | cs = handCards Hand3 ps | ||||
| trs = filter (isTrump Spades) cs | trs = filter (isTrump Spades) cs | ||||
| if length trs >= 5 && any ((==32) . getID) cs | if length trs >= 5 && any ((==32) . getID) cs | ||||
| then fst <$> evalStateT (turn Hand1) env | |||||
| then do | |||||
| pts <- fst <$> evalStateT (turn Hand1) env | |||||
| if pts > 60 then return 1 else return 0 | |||||
| else runAI | else runAI | ||||
| env :: SkatEnv | env :: SkatEnv | ||||
| @@ -66,3 +73,14 @@ env2 = SkatEnv piles Nothing Spades playersExamp | |||||
| h3 = map (putAt Hand3) hand3 | h3 = map (putAt Hand3) hand3 | ||||
| piles = Piles (h1 ++ h2 ++ h3) [] [] | piles = Piles (h1 ++ h2 ++ h3) [] [] | ||||
| runWebSocketServer :: IO () | |||||
| runWebSocketServer = do | |||||
| WS.runServer "localhost" 4243 application | |||||
| application :: WS.PendingConnection -> IO () | |||||
| application pending = do | |||||
| conn <- WS.acceptRequest pending | |||||
| putStrLn "someone connected" | |||||
| forever $ do | |||||
| msg <- WS.receiveData conn | |||||
| putStrLn $ BS.unpack msg | |||||
| @@ -0,0 +1,60 @@ | |||||
| name: skat | |||||
| version: 0.1.0.0 | |||||
| github: "githubuser/skat" | |||||
| license: BSD3 | |||||
| author: "Author name here" | |||||
| maintainer: "example@example.com" | |||||
| copyright: "2019 Author name here" | |||||
| extra-source-files: | |||||
| - README.md | |||||
| - ChangeLog.md | |||||
| # Metadata used when publishing your package | |||||
| # synopsis: Short description of your package | |||||
| # category: Web | |||||
| # 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> | |||||
| dependencies: | |||||
| - base >= 4.7 && < 5 | |||||
| - mtl | |||||
| - network | |||||
| - websockets | |||||
| - split | |||||
| - bytestring | |||||
| - text | |||||
| - random | |||||
| - deepseq | |||||
| - aeson | |||||
| - parallel | |||||
| - containers | |||||
| - case-insensitive | |||||
| library: | |||||
| source-dirs: src | |||||
| executables: | |||||
| skat-exe: | |||||
| main: Main.hs | |||||
| source-dirs: app | |||||
| ghc-options: | |||||
| - -threaded | |||||
| - -rtsopts | |||||
| - -with-rtsopts=-N | |||||
| dependencies: | |||||
| - skat | |||||
| tests: | |||||
| skat-test: | |||||
| main: Spec.hs | |||||
| source-dirs: test | |||||
| ghc-options: | |||||
| - -threaded | |||||
| - -rtsopts | |||||
| - -with-rtsopts=-N | |||||
| dependencies: | |||||
| - skat | |||||
| @@ -0,0 +1,111 @@ | |||||
| cabal-version: 1.12 | |||||
| -- This file has been generated from package.yaml by hpack version 0.31.2. | |||||
| -- | |||||
| -- see: https://github.com/sol/hpack | |||||
| -- | |||||
| -- hash: e2db48733c92b94d7f2d8f4991dd2f7cec26d59666cd3c618710a8a3c22616d0 | |||||
| name: skat | |||||
| version: 0.1.0.0 | |||||
| description: Please see the README on GitHub at <https://github.com/githubuser/skat#readme> | |||||
| 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 | |||||
| license: BSD3 | |||||
| license-file: LICENSE | |||||
| build-type: Simple | |||||
| extra-source-files: | |||||
| README.md | |||||
| ChangeLog.md | |||||
| source-repository head | |||||
| type: git | |||||
| location: https://github.com/githubuser/skat | |||||
| library | |||||
| exposed-modules: | |||||
| Skat | |||||
| Skat.AI.Human | |||||
| Skat.AI.Online | |||||
| Skat.AI.Rulebased | |||||
| Skat.AI.Server | |||||
| Skat.AI.Stupid | |||||
| Skat.Card | |||||
| Skat.Operations | |||||
| Skat.Pile | |||||
| Skat.Player | |||||
| Skat.Player.Utils | |||||
| Skat.Render | |||||
| Skat.Utils | |||||
| Skat.WebSocketServer | |||||
| other-modules: | |||||
| Paths_skat | |||||
| hs-source-dirs: | |||||
| src | |||||
| build-depends: | |||||
| aeson | |||||
| , base >=4.7 && <5 | |||||
| , bytestring | |||||
| , case-insensitive | |||||
| , containers | |||||
| , deepseq | |||||
| , mtl | |||||
| , network | |||||
| , parallel | |||||
| , random | |||||
| , split | |||||
| , text | |||||
| , websockets | |||||
| default-language: Haskell2010 | |||||
| executable skat-exe | |||||
| main-is: Main.hs | |||||
| other-modules: | |||||
| Paths_skat | |||||
| hs-source-dirs: | |||||
| app | |||||
| ghc-options: -threaded -rtsopts -with-rtsopts=-N | |||||
| build-depends: | |||||
| aeson | |||||
| , base >=4.7 && <5 | |||||
| , bytestring | |||||
| , case-insensitive | |||||
| , containers | |||||
| , deepseq | |||||
| , mtl | |||||
| , network | |||||
| , parallel | |||||
| , random | |||||
| , skat | |||||
| , split | |||||
| , text | |||||
| , websockets | |||||
| default-language: Haskell2010 | |||||
| test-suite skat-test | |||||
| type: exitcode-stdio-1.0 | |||||
| main-is: Spec.hs | |||||
| other-modules: | |||||
| Paths_skat | |||||
| hs-source-dirs: | |||||
| test | |||||
| ghc-options: -threaded -rtsopts -with-rtsopts=-N | |||||
| build-depends: | |||||
| aeson | |||||
| , base >=4.7 && <5 | |||||
| , bytestring | |||||
| , case-insensitive | |||||
| , containers | |||||
| , deepseq | |||||
| , mtl | |||||
| , network | |||||
| , parallel | |||||
| , random | |||||
| , skat | |||||
| , split | |||||
| , text | |||||
| , websockets | |||||
| default-language: Haskell2010 | |||||
| @@ -8,10 +8,10 @@ import Control.Monad.State | |||||
| import Control.Monad.Reader | import Control.Monad.Reader | ||||
| import Data.List | import Data.List | ||||
| import Card | |||||
| import Pile | |||||
| import Player (Players) | |||||
| import qualified Player as P | |||||
| import Skat.Card | |||||
| import Skat.Pile | |||||
| import Skat.Player (Players) | |||||
| import qualified Skat.Player as P | |||||
| data SkatEnv = SkatEnv { piles :: Piles | data SkatEnv = SkatEnv { piles :: Piles | ||||
| , turnColour :: Maybe Colour | , turnColour :: Maybe Colour | ||||
| @@ -1,12 +1,12 @@ | |||||
| module AI.Human where | |||||
| module Skat.AI.Human where | |||||
| import Control.Monad.Trans (liftIO) | import Control.Monad.Trans (liftIO) | ||||
| import Player | |||||
| import Pile | |||||
| import Card | |||||
| import Utils | |||||
| import Render | |||||
| import Skat.Player | |||||
| import Skat.Pile | |||||
| import Skat.Card | |||||
| import Skat.Utils | |||||
| import Skat.Render | |||||
| data Human = Human { getTeam :: Team | data Human = Human { getTeam :: Team | ||||
| , getHand :: Hand } | , getHand :: Hand } | ||||
| @@ -0,0 +1,87 @@ | |||||
| {-# LANGUAGE TypeSynonymInstances #-} | |||||
| {-# LANGUAGE FlexibleInstances #-} | |||||
| {-# LANGUAGE OverloadedStrings #-} | |||||
| module Skat.AI.Online where | |||||
| import Control.Monad.Reader | |||||
| import Network.WebSockets (Connection, sendTextData, receiveData) | |||||
| import Data.Aeson | |||||
| import qualified Data.ByteString.Lazy.Char8 as BS | |||||
| import Skat.Player | |||||
| import qualified Skat.Player.Utils as P | |||||
| import Skat.Pile | |||||
| import Skat.Card | |||||
| import Skat.Render | |||||
| class Monad m => MonadClient m where | |||||
| query :: String -> m () | |||||
| response :: m String | |||||
| data OnlineEnv = OnlineEnv { getTeam :: Team | |||||
| , getHand :: Hand | |||||
| , connection :: Connection } | |||||
| deriving Show | |||||
| instance Show Connection where | |||||
| show _ = "A connection" | |||||
| instance Player OnlineEnv where | |||||
| team = getTeam | |||||
| hand = getHand | |||||
| chooseCard p table _ hand = runReaderT (choose table 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 MonadIO m => MonadClient (Online m) where | |||||
| query s = do | |||||
| conn <- asks connection | |||||
| liftIO $ sendTextData conn (BS.pack s) | |||||
| response = do | |||||
| conn <- asks connection | |||||
| liftIO $ BS.unpack <$> receiveData conn | |||||
| instance MonadPlayer m => MonadPlayer (Online m) where | |||||
| trumpColour = lift $ trumpColour | |||||
| turnColour = lift $ turnColour | |||||
| showSkat = lift . showSkat | |||||
| choose :: MonadPlayer m => [CardS Played] -> [Card] -> Online m Card | |||||
| choose table hand = do | |||||
| query (BS.unpack $ encode $ ChooseQuery hand table) | |||||
| 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 | |||||
| cardPlayed :: MonadPlayer m => CardS Played -> Online 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) | |||||
| data ChooseQuery = ChooseQuery [Card] [CardS Played] | |||||
| data CardPlayedQuery = CardPlayedQuery (CardS Played) | |||||
| data GameResultsQuery = GameResultsQuery Int Int | |||||
| data ChosenResponse = ChosenResponse Card | |||||
| instance ToJSON ChooseQuery where | |||||
| toJSON (ChooseQuery hand table) = | |||||
| object ["query" .= ("choose_card" :: String), "hand" .= hand, "table" .= table] | |||||
| instance ToJSON CardPlayedQuery where | |||||
| 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] | |||||
| instance FromJSON ChosenResponse where | |||||
| parseJSON = withObject "ChosenResponse" $ \v -> ChosenResponse | |||||
| <$> v .: "card" | |||||
| @@ -3,7 +3,7 @@ | |||||
| {-# LANGUAGE FlexibleInstances #-} | {-# LANGUAGE FlexibleInstances #-} | ||||
| {-# LANGUAGE FlexibleContexts #-} | {-# LANGUAGE FlexibleContexts #-} | ||||
| module AI.Rulebased ( | |||||
| module Skat.AI.Rulebased ( | |||||
| mkAIEnv, testds, simplify | mkAIEnv, testds, simplify | ||||
| ) where | ) where | ||||
| @@ -17,13 +17,13 @@ import Control.Monad.State | |||||
| import Control.Monad.Reader | import Control.Monad.Reader | ||||
| import qualified Data.Map.Strict as M | import qualified Data.Map.Strict as M | ||||
| import Player | |||||
| import qualified Player.Utils as P | |||||
| import Pile | |||||
| import Card | |||||
| import Utils | |||||
| import Skat.Player | |||||
| import qualified Skat.Player.Utils as P | |||||
| import Skat.Pile | |||||
| import Skat.Card | |||||
| import Skat.Utils | |||||
| import Skat (Skat, modifyp, mkSkatEnv) | import Skat (Skat, modifyp, mkSkatEnv) | ||||
| import Operations | |||||
| import Skat.Operations | |||||
| data AIEnv = AIEnv { getTeam :: Team | data AIEnv = AIEnv { getTeam :: Team | ||||
| , getHand :: Hand | , getHand :: Hand | ||||
| @@ -0,0 +1,105 @@ | |||||
| module Skat.AI.Server where | |||||
| import qualified Network.Socket as Net | |||||
| import qualified System.IO as Sys | |||||
| import Control.Concurrent | |||||
| import Control.Concurrent.Chan | |||||
| import Control.Monad (forever) | |||||
| import Control.Monad.Reader | |||||
| import Data.List.Split | |||||
| data Buffering = NoBuffering | |||||
| | LengthBuffering | |||||
| | DelimiterBuffering String | |||||
| deriving (Show) | |||||
| data ServerEnv = ServerEnv | |||||
| { buffering :: Buffering -- ^ Buffermode | |||||
| , socket :: Net.Socket -- ^ the socket used to communicate | |||||
| , global :: Chan String | |||||
| , onReceive :: OnReceive | |||||
| } | |||||
| instance Show ServerEnv where | |||||
| show env = "A Server" | |||||
| type Server = ReaderT ServerEnv IO | |||||
| type OnReceive = Sys.Handle -> Net.SockAddr -> String -> Server () | |||||
| broadcast :: String -> Server () | |||||
| broadcast msg = do | |||||
| bufmode <- asks buffering | |||||
| chan <- asks global | |||||
| case bufmode of | |||||
| DelimiterBuffering delim -> liftIO $ writeChan chan $ msg ++ delim | |||||
| _ -> liftIO $ writeChan chan msg | |||||
| send :: Sys.Handle -> String -> Server () | |||||
| send connhdl msg = do | |||||
| bufmode <- asks buffering | |||||
| case bufmode of | |||||
| DelimiterBuffering delim -> liftIO $ Sys.hPutStr connhdl $ msg ++ delim | |||||
| _ -> liftIO $ Sys.hPutStr connhdl msg | |||||
| -- | Initialize a new server with the given port number and buffering mode | |||||
| 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) | |||||
| Net.listen sock 5 | |||||
| chan <- newChan | |||||
| forkIO $ forever $ do | |||||
| msg <- readChan chan -- clearing the main channel | |||||
| return () | |||||
| return (ServerEnv buffermode sock chan handler) | |||||
| close :: ServerEnv -> IO () | |||||
| close = Net.close . socket | |||||
| -- | Looping over requests and establish connection | |||||
| procRequests :: Server () | |||||
| procRequests = do | |||||
| sock <- asks socket | |||||
| (conn, clientaddr) <- liftIO $ Net.accept sock | |||||
| env <- ask | |||||
| liftIO $ forkIO $ runReaderT (procMessages conn clientaddr) env | |||||
| procRequests | |||||
| -- | Handle one client | |||||
| procMessages :: Net.Socket -> Net.SockAddr -> Server () | |||||
| procMessages conn clientaddr = do | |||||
| connhdl <- liftIO $ Net.socketToHandle conn Sys.ReadWriteMode | |||||
| liftIO $ Sys.hSetBuffering connhdl Sys.NoBuffering | |||||
| globalChan <- asks global | |||||
| commChan <- liftIO $ dupChan globalChan | |||||
| reader <- liftIO $ forkIO $ forever $ do | |||||
| msg <- readChan commChan | |||||
| Sys.hPutStrLn connhdl msg | |||||
| handler <- asks onReceive | |||||
| messages <- liftIO $ Sys.hGetContents connhdl | |||||
| buffermode <- asks buffering | |||||
| case buffermode of | |||||
| DelimiterBuffering delimiter -> | |||||
| mapM_ (handler connhdl clientaddr) (splitOn delimiter messages) | |||||
| LengthBuffering -> liftIO $ putStrLn (take 4 messages) | |||||
| _ -> return () | |||||
| -- clean up | |||||
| liftIO $ do killThread reader | |||||
| Sys.hClose connhdl | |||||
| sampleHandler :: OnReceive | |||||
| sampleHandler connhdl addr query = do | |||||
| liftIO $ putStrLn $ "new query " ++ query | |||||
| send connhdl $ "> " ++ query | |||||
| main :: IO () | |||||
| main = do | |||||
| env <- initServer 4242 LengthBuffering sampleHandler | |||||
| runReaderT procRequests env | |||||
| @@ -1,8 +1,8 @@ | |||||
| module AI.Stupid where | |||||
| module Skat.AI.Stupid where | |||||
| import Player | |||||
| import Pile | |||||
| import Card | |||||
| import Skat.Player | |||||
| import Skat.Pile | |||||
| import Skat.Card | |||||
| data Stupid = Stupid { getTeam :: Team | data Stupid = Stupid { getTeam :: Team | ||||
| , getHand :: Hand } | , getHand :: Hand } | ||||
| @@ -1,13 +1,16 @@ | |||||
| {-# LANGUAGE MultiParamTypeClasses #-} | {-# LANGUAGE MultiParamTypeClasses #-} | ||||
| {-# LANGUAGE FlexibleInstances #-} | {-# LANGUAGE FlexibleInstances #-} | ||||
| {-# LANGUAGE OverloadedStrings #-} | |||||
| module Card where | |||||
| module Skat.Card where | |||||
| import Data.List | import Data.List | ||||
| import Data.Aeson | |||||
| import System.Random (newStdGen) | import System.Random (newStdGen) | ||||
| import Utils | |||||
| import Control.DeepSeq | import Control.DeepSeq | ||||
| import Skat.Utils | |||||
| class Countable a b where | class Countable a b where | ||||
| count :: a -> b | count :: a -> b | ||||
| @@ -19,7 +22,7 @@ data Type = Seven | |||||
| | Ten | | Ten | ||||
| | Ace | | Ace | ||||
| | Jack | | Jack | ||||
| deriving (Eq, Ord, Show, Enum) | |||||
| deriving (Eq, Ord, Show, Enum, Read) | |||||
| instance Countable Type Int where | instance Countable Type Int where | ||||
| count Ace = 11 | count Ace = 11 | ||||
| @@ -38,6 +41,16 @@ data Colour = Diamonds | |||||
| data Card = Card Type Colour | data Card = Card Type Colour | ||||
| deriving (Eq, Show, Ord) | deriving (Eq, Show, Ord) | ||||
| instance ToJSON Card where | |||||
| toJSON (Card t c) = | |||||
| object ["type" .= show t, "colour" .= show c] | |||||
| instance FromJSON Card where | |||||
| parseJSON = withObject "Card" $ \v -> do | |||||
| t <- v .: "type" | |||||
| c <- v .: "colour" | |||||
| return $ Card (read t) (read c) | |||||
| getColour :: Card -> Colour | getColour :: Card -> Colour | ||||
| getColour (Card _ c) = c | getColour (Card _ c) = c | ||||
| @@ -0,0 +1,88 @@ | |||||
| module Skat.Operations where | |||||
| import Control.Monad.State | |||||
| import System.Random (newStdGen, randoms) | |||||
| import Data.List | |||||
| import Data.Ord | |||||
| import Skat | |||||
| import Skat.Card | |||||
| import Skat.Pile | |||||
| import Skat.Player (chooseCard, Players(..), Player(..), PL(..), | |||||
| updatePlayer, playersToList, player, MonadPlayer) | |||||
| import Skat.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,14 +1,16 @@ | |||||
| {-# LANGUAGE MultiParamTypeClasses #-} | {-# LANGUAGE MultiParamTypeClasses #-} | ||||
| {-# LANGUAGE FlexibleInstances #-} | {-# LANGUAGE FlexibleInstances #-} | ||||
| {-# LANGUAGE OverloadedStrings #-} | |||||
| module Pile where | |||||
| module Skat.Pile where | |||||
| import Data.List | import Data.List | ||||
| import Card | |||||
| import Utils | |||||
| import Data.Aeson | |||||
| import Control.Exception | import Control.Exception | ||||
| import Skat.Card | |||||
| import Skat.Utils | |||||
| data Team = Team | Single | data Team = Team | Single | ||||
| deriving (Show, Eq, Ord, Enum) | deriving (Show, Eq, Ord, Enum) | ||||
| @@ -19,6 +21,10 @@ data CardS p = CardS { getCard :: Card | |||||
| instance Countable (CardS p) Int where | instance Countable (CardS p) Int where | ||||
| count = count . getCard | count = count . getCard | ||||
| instance ToJSON p => ToJSON (CardS p) where | |||||
| toJSON (CardS card pile) = | |||||
| object ["card" .= card, "pile" .= pile] | |||||
| data Hand = Hand1 | Hand2 | Hand3 | data Hand = Hand1 | Hand2 | Hand3 | ||||
| deriving (Show, Eq, Ord) | deriving (Show, Eq, Ord) | ||||
| @@ -36,6 +42,12 @@ data Played = Table Hand | |||||
| | Won Hand Team | | Won Hand Team | ||||
| deriving (Show, Eq, Ord) | 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] | |||||
| data SkatP = SkatP | data SkatP = SkatP | ||||
| deriving (Show, Eq, Ord) | deriving (Show, Eq, Ord) | ||||
| @@ -1,11 +1,11 @@ | |||||
| {-# LANGUAGE ExistentialQuantification #-} | {-# LANGUAGE ExistentialQuantification #-} | ||||
| module Player where | |||||
| module Skat.Player where | |||||
| import Control.Monad.IO.Class | import Control.Monad.IO.Class | ||||
| import Card | |||||
| import Pile | |||||
| import Skat.Card | |||||
| import Skat.Pile | |||||
| class (Monad m, MonadIO m) => MonadPlayer m where | class (Monad m, MonadIO m) => MonadPlayer m where | ||||
| trumpColour :: m Colour | trumpColour :: m Colour | ||||
| @@ -38,6 +38,11 @@ class Player p where | |||||
| fallen = played piles | fallen = played piles | ||||
| myCards = handCards (hand p) piles | myCards = handCards (hand p) piles | ||||
| fmap fst $ chooseCard p table fallen myCards | fmap fst $ chooseCard p table fallen myCards | ||||
| onGameResults :: MonadIO m | |||||
| => p | |||||
| -> (Int, Int) | |||||
| -> m () | |||||
| onGameResults _ _ = return () | |||||
| data PL = forall p. (Show p, Player p) => PL p | data PL = forall p. (Show p, Player p) => PL p | ||||
| @@ -54,6 +59,7 @@ instance Player PL where | |||||
| v <- onCardPlayed p card | v <- onCardPlayed p card | ||||
| return $ PL v | return $ PL v | ||||
| chooseCardOpen (PL p) = chooseCardOpen p | chooseCardOpen (PL p) = chooseCardOpen p | ||||
| onGameResults (PL p) res = onGameResults p res | |||||
| data Players = Players PL PL PL | data Players = Players PL PL PL | ||||
| deriving Show | deriving Show | ||||
| @@ -1,10 +1,10 @@ | |||||
| module Player.Utils ( | |||||
| module Skat.Player.Utils ( | |||||
| isAllowed, isTrump | isAllowed, isTrump | ||||
| ) where | ) where | ||||
| import Player | |||||
| import qualified Card as C | |||||
| import Card (Card) | |||||
| import Skat.Player | |||||
| import qualified Skat.Card as C | |||||
| import Skat.Card (Card) | |||||
| isAllowed :: MonadPlayer m => [Card] -> Card -> m Bool | isAllowed :: MonadPlayer m => [Card] -> Card -> m Bool | ||||
| isAllowed hand card = do | isAllowed hand card = do | ||||
| @@ -1,7 +1,8 @@ | |||||
| module Render where | |||||
| module Skat.Render where | |||||
| import Card | |||||
| import Data.List | import Data.List | ||||
| import Skat.Card | |||||
| render :: [Card] -> IO () | render :: [Card] -> IO () | ||||
| render = putStrLn . intercalate "\n" . zipWith (\n c -> show n ++ ") " ++ show c) [0..] | render = putStrLn . intercalate "\n" . zipWith (\n c -> show n ++ ") " ++ show c) [0..] | ||||
| @@ -1,7 +1,9 @@ | |||||
| module Utils where | |||||
| module Skat.Utils where | |||||
| import System.Random | import System.Random | ||||
| import Text.Read | import Text.Read | ||||
| import qualified Data.ByteString.Char8 as B (ByteString, unpack, pack) | |||||
| import qualified Data.Text as T (Text, unpack, pack) | |||||
| shuffle :: StdGen -> [a] -> [a] | shuffle :: StdGen -> [a] -> [a] | ||||
| shuffle g xs = shuffle' (randoms g) xs | shuffle g xs = shuffle' (randoms g) xs | ||||
| @@ -40,3 +42,17 @@ filterMap pred f as = foldr g [] as | |||||
| grouping :: Eq a => (b -> a) -> b -> b -> Bool | grouping :: Eq a => (b -> a) -> b -> b -> Bool | ||||
| grouping f a b = f a == f b | grouping f a b = f a == f b | ||||
| -- handy little string type class that takes care of string | |||||
| -- conversion | |||||
| class Stringy a where | |||||
| toString :: a -> String | |||||
| fromString :: String -> a | |||||
| instance Stringy B.ByteString where | |||||
| toString = B.unpack | |||||
| fromString = B.pack | |||||
| instance Stringy T.Text where | |||||
| toString = T.unpack | |||||
| fromString = T.pack | |||||
| @@ -0,0 +1,123 @@ | |||||
| {-# LANGUAGE OverloadedStrings #-} | |||||
| {-# LANGUAGE FlexibleContexts #-} | |||||
| {-# LANGUAGE TypeSynonymInstances #-} | |||||
| {-# LANGUAGE FlexibleInstances #-} | |||||
| {-# LANGUAGE MultiParamTypeClasses #-} | |||||
| {-# LANGUAGE GeneralizedNewtypeDeriving #-} | |||||
| module Skat.WebSocketServer where | |||||
| import qualified Network.WebSockets as WS | |||||
| import Control.Concurrent | |||||
| import Control.Exception | |||||
| import Control.Monad | |||||
| import Control.Monad.Reader | |||||
| import Control.Monad.State | |||||
| import Control.Monad.IO.Class | |||||
| import Data.CaseInsensitive (original) | |||||
| import qualified Data.ByteString as BS | |||||
| import qualified Data.ByteString.Lazy.Char8 as BS8 | |||||
| import Data.Maybe | |||||
| import Skat.Utils (toString) | |||||
| data ServerState = ServerState { clients :: Clients | |||||
| , queue :: Clients } | |||||
| newtype Server a = Server { unServer :: ReaderT (MVar ServerState) IO a } | |||||
| deriving (Monad, MonadIO, Functor, Applicative, | |||||
| MonadReader (MVar ServerState)) | |||||
| runServer :: Server a -> MVar ServerState -> IO a | |||||
| runServer (Server action) var = runReaderT action var | |||||
| instance MonadState ServerState Server where | |||||
| get = execute get | |||||
| put = execute . put | |||||
| -- | dangerous shitty function | |||||
| -- enables to run state operations on an mvar of a reader monad | |||||
| execute :: (MonadIO m, MonadReader (MVar r) m) => StateT r m a -> m a | |||||
| execute manipulation = do | |||||
| var <- ask | |||||
| state <- liftIO $ takeMVar var | |||||
| (a, state') <- runStateT manipulation state | |||||
| liftIO $ putMVar var state' | |||||
| return a | |||||
| addClient :: String -> WS.Connection -> ServerState -> ServerState | |||||
| addClient key conn ss = ss { clients = (key, conn) : cls } | |||||
| where cls = clients ss | |||||
| removeClient :: String -> ServerState -> ServerState | |||||
| removeClient key ss = ss { clients = filter ((/=key) . fst) cls } | |||||
| where cls = clients ss | |||||
| queueClient :: String -> WS.Connection -> ServerState -> ServerState | |||||
| queueClient key conn ss = ss { queue = (key, conn) : cls } | |||||
| where cls = queue ss | |||||
| type Clients = [(String, WS.Connection)] | |||||
| instance Show WS.Connection where | |||||
| show _ = "a connection" | |||||
| send :: WS.Connection -> String -> IO () | |||||
| send conn s = WS.sendTextData conn (BS8.pack s) | |||||
| receive :: WS.Connection -> IO String | |||||
| receive conn = BS8.unpack <$> WS.receiveData conn | |||||
| currentClients :: Server Clients | |||||
| currentClients = do | |||||
| ss <- execute get | |||||
| return $ clients ss | |||||
| runDebugServer :: String -> Int -> IO (MVar ServerState) | |||||
| runDebugServer address port = do | |||||
| state <- newMVar (ServerState [] []) | |||||
| forkIO $ WS.runServer address port (application onLogin state) | |||||
| return state | |||||
| onLogin :: Server () | |||||
| onLogin = do | |||||
| liftIO $ putStrLn "a new client joined" | |||||
| cls <- currentClients | |||||
| uncurry lobby $ head cls | |||||
| lobby :: String -> WS.Connection -> Server () | |||||
| lobby key conn = do | |||||
| msg <- liftIO $ receive conn | |||||
| case msg of | |||||
| "hi" -> liftIO $ send conn "hi client" | |||||
| "queue" -> do | |||||
| qu <- gets queue | |||||
| liftIO $ send conn "ok, put you in the queue" | |||||
| liftIO $ putStrLn "client queued up" | |||||
| if length qu >= 3 | |||||
| then do | |||||
| let ps = take 3 qu | |||||
| liftIO $ putStrLn "3 players in queue, starting a game" | |||||
| --forkIO $ onlineMatch (ps !! 0) (ps !! 1) (ps !! 2) | |||||
| else return () | |||||
| modify $ queueClient key conn | |||||
| lobby key conn | |||||
| application :: Server () -> MVar ServerState -> WS.PendingConnection -> IO () | |||||
| application onlogin stateVar pending = do | |||||
| conn <- WS.acceptRequest pending | |||||
| WS.forkPingThread conn 30 | |||||
| print $ WS.pendingRequest pending | |||||
| let headers = WS.requestHeaders $ WS.pendingRequest pending | |||||
| hs = map (\(k, v) -> (toString (original k), toString v)) headers | |||||
| key = fromMaybe "" $ lookup "Sec-WebSocket-Key" hs | |||||
| putStrLn "new connection" | |||||
| let disconnect = flip runServer stateVar $ do | |||||
| modify $ removeClient key | |||||
| liftIO $ putStrLn "client disconnected" | |||||
| flip finally disconnect $ flip runServer stateVar $ do | |||||
| modify $ addClient key conn | |||||
| onlogin | |||||
| liftIO $ forever $ threadDelay 1000 | |||||
| @@ -0,0 +1,66 @@ | |||||
| # This file was automatically generated by 'stack init' | |||||
| # | |||||
| # Some commonly used options have been documented as comments in this file. | |||||
| # For advanced use and comprehensive documentation of the format, please see: | |||||
| # https://docs.haskellstack.org/en/stable/yaml_configuration/ | |||||
| # Resolver to choose a 'specific' stackage snapshot or a compiler version. | |||||
| # A snapshot resolver dictates the compiler version and the set of packages | |||||
| # to be used for project dependencies. For example: | |||||
| # | |||||
| # resolver: lts-3.5 | |||||
| # resolver: nightly-2015-09-21 | |||||
| # resolver: ghc-7.10.2 | |||||
| # | |||||
| # The location of a snapshot can be provided as a file or url. Stack assumes | |||||
| # a snapshot provided as a file might change, whereas a url resource does not. | |||||
| # | |||||
| # resolver: ./custom-snapshot.yaml | |||||
| # resolver: https://example.com/snapshots/2018-01-01.yaml | |||||
| resolver: lts-14.3 | |||||
| # User packages to be built. | |||||
| # Various formats can be used as shown in the example below. | |||||
| # | |||||
| # packages: | |||||
| # - some-directory | |||||
| # - https://example.com/foo/bar/baz-0.0.2.tar.gz | |||||
| # subdirs: | |||||
| # - auto-update | |||||
| # - wai | |||||
| packages: | |||||
| - . | |||||
| # Dependency packages to be pulled from upstream that are not in the resolver. | |||||
| # These entries can reference officially published versions as well as | |||||
| # forks / in-progress versions pinned to a git hash. For example: | |||||
| # | |||||
| # extra-deps: | |||||
| # - acme-missiles-0.3 | |||||
| # - git: https://github.com/commercialhaskell/stack.git | |||||
| # commit: e7b331f14bcffb8367cd58fbfc8b40ec7642100a | |||||
| # | |||||
| # extra-deps: [] | |||||
| # Override default flag values for local packages and extra-deps | |||||
| # flags: {} | |||||
| # Extra package databases containing global packages | |||||
| # extra-package-dbs: [] | |||||
| # Control whether we use the GHC we find on the path | |||||
| # system-ghc: true | |||||
| # | |||||
| # Require a specific version of stack, using version ranges | |||||
| # require-stack-version: -any # Default | |||||
| # require-stack-version: ">=2.1" | |||||
| # | |||||
| # Override the architecture used by stack, especially useful on Windows | |||||
| # arch: i386 | |||||
| # arch: x86_64 | |||||
| # | |||||
| # Extra directories used by stack for building | |||||
| # extra-include-dirs: [/path/to/dir] | |||||
| # extra-lib-dirs: [/path/to/dir] | |||||
| # | |||||
| # Allow a newer minor version of GHC than the snapshot specifies | |||||
| # compiler-check: newer-minor | |||||
| @@ -0,0 +1,2 @@ | |||||
| main :: IO () | |||||
| main = putStrLn "Test suite not yet implemented" | |||||