Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
08e94e4386 | ||
|
|
da217b5196 |
@@ -6,3 +6,8 @@
|
||||
*.hi
|
||||
*.o
|
||||
*.prof
|
||||
*.hp
|
||||
|
||||
# ignore stack work files
|
||||
.stack-work/
|
||||
stack.yaml.lock
|
||||
|
||||
-39
@@ -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
|
||||
@@ -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)
|
||||
+42
-13
@@ -1,19 +1,42 @@
|
||||
module Main where
|
||||
|
||||
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 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 = putStrLn "Hello World"
|
||||
main = testAI 10
|
||||
|
||||
testAI :: Int -> IO ()
|
||||
testAI n = do
|
||||
let acs = repeat runAI
|
||||
vals <- sequence (take n acs)
|
||||
putStrLn $ "average won points " ++ show (fromIntegral (sum vals) / fromIntegral n)
|
||||
|
||||
runAI :: IO Int
|
||||
runAI = do
|
||||
env <- shuffledEnv
|
||||
let ps = piles env
|
||||
cs = handCards Hand3 ps
|
||||
trs = filter (isTrump 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
|
||||
else runAI
|
||||
|
||||
env :: SkatEnv
|
||||
env = SkatEnv piles Nothing Spades playersExamp
|
||||
@@ -50,8 +73,14 @@ env2 = SkatEnv piles Nothing Spades playersExamp
|
||||
h3 = map (putAt Hand3) hand3
|
||||
piles = Piles (h1 ++ h2 ++ h3) [] []
|
||||
|
||||
testAI :: Int -> IO ()
|
||||
testAI n = do
|
||||
let acs = repeat (shuffledEnv >>= evalStateT (turnGeneric playOpen 10 Hand1) )
|
||||
vals <- sequence (take n acs)
|
||||
putStrLn $ "average won points " ++ show (fromIntegral (sum (map fst vals)) / fromIntegral n)
|
||||
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
|
||||
+111
@@ -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 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
|
||||
, turnColour :: Maybe Colour
|
||||
@@ -1,12 +1,12 @@
|
||||
module AI.Human where
|
||||
module Skat.AI.Human where
|
||||
|
||||
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
|
||||
, 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,10 +3,12 @@
|
||||
{-# LANGUAGE FlexibleInstances #-}
|
||||
{-# LANGUAGE FlexibleContexts #-}
|
||||
|
||||
module AI.Rulebased (
|
||||
module Skat.AI.Rulebased (
|
||||
mkAIEnv, testds, simplify
|
||||
) where
|
||||
|
||||
import Control.Parallel.Strategies
|
||||
|
||||
import Data.Ord
|
||||
import Data.Monoid ((<>))
|
||||
import Data.List
|
||||
@@ -15,13 +17,13 @@ import Control.Monad.State
|
||||
import Control.Monad.Reader
|
||||
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 Operations
|
||||
import Skat.Operations
|
||||
|
||||
data AIEnv = AIEnv { getTeam :: Team
|
||||
, getHand :: Hand
|
||||
@@ -163,6 +165,7 @@ compareGuess (c1, ops1) (c2, ops2)
|
||||
distributions :: Guess -> (Int, Int, Int, Int) -> [Distribution]
|
||||
distributions 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 =
|
||||
@@ -246,9 +249,9 @@ chooseStatistic = do
|
||||
2 -> 2
|
||||
3 -> 3
|
||||
-- simulate only partially
|
||||
4 -> 2
|
||||
5 -> 1
|
||||
6 -> 1
|
||||
4 -> 3
|
||||
5 -> 2
|
||||
6 -> 2
|
||||
7 -> 1
|
||||
8 -> 1
|
||||
9 -> 1
|
||||
@@ -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
|
||||
, getHand :: Hand }
|
||||
@@ -1,11 +1,15 @@
|
||||
{-# LANGUAGE MultiParamTypeClasses #-}
|
||||
{-# LANGUAGE FlexibleInstances #-}
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
|
||||
module Card where
|
||||
module Skat.Card where
|
||||
|
||||
import Data.List
|
||||
import Data.Aeson
|
||||
import System.Random (newStdGen)
|
||||
import Utils
|
||||
import Control.DeepSeq
|
||||
|
||||
import Skat.Utils
|
||||
|
||||
class Countable a b where
|
||||
count :: a -> b
|
||||
@@ -18,7 +22,7 @@ data Type = Seven
|
||||
| Ten
|
||||
| Ace
|
||||
| Jack
|
||||
deriving (Eq, Ord, Show, Enum)
|
||||
deriving (Eq, Ord, Show, Enum, Read)
|
||||
|
||||
instance Countable Type Int where
|
||||
count Ace = 11
|
||||
@@ -37,6 +41,16 @@ data Colour = Diamonds
|
||||
data Card = Card Type Colour
|
||||
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 _ c) = c
|
||||
|
||||
@@ -57,6 +71,9 @@ instance Countable Card Int where
|
||||
instance Countable [Card] Int where
|
||||
count = sum . map count
|
||||
|
||||
instance NFData Card where
|
||||
rnf (Card t c) = t `seq` c `seq` ()
|
||||
|
||||
equals :: Colour -> Maybe Colour -> Bool
|
||||
equals col (Just x) = col == x
|
||||
equals col Nothing = True
|
||||
@@ -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 FlexibleInstances #-}
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
|
||||
module Pile where
|
||||
module Skat.Pile where
|
||||
|
||||
import Data.List
|
||||
|
||||
import Card
|
||||
import Utils
|
||||
import Data.Aeson
|
||||
import Control.Exception
|
||||
|
||||
import Skat.Card
|
||||
import Skat.Utils
|
||||
|
||||
data Team = Team | Single
|
||||
deriving (Show, Eq, Ord, Enum)
|
||||
|
||||
@@ -19,6 +21,10 @@ data CardS p = CardS { getCard :: Card
|
||||
instance Countable (CardS p) Int where
|
||||
count = count . getCard
|
||||
|
||||
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)
|
||||
|
||||
@@ -36,6 +42,12 @@ data Played = Table Hand
|
||||
| Won Hand Team
|
||||
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
|
||||
deriving (Show, Eq, Ord)
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
{-# LANGUAGE ExistentialQuantification #-}
|
||||
|
||||
module Player where
|
||||
module Skat.Player where
|
||||
|
||||
import Control.Monad.IO.Class
|
||||
|
||||
import Card
|
||||
import Pile
|
||||
import Skat.Card
|
||||
import Skat.Pile
|
||||
|
||||
class (Monad m, MonadIO m) => MonadPlayer m where
|
||||
trumpColour :: m Colour
|
||||
@@ -38,6 +38,11 @@ class Player p where
|
||||
fallen = played piles
|
||||
myCards = handCards (hand p) piles
|
||||
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
|
||||
|
||||
@@ -54,6 +59,7 @@ instance Player PL where
|
||||
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
|
||||
@@ -1,10 +1,10 @@
|
||||
module Player.Utils (
|
||||
module Skat.Player.Utils (
|
||||
isAllowed, isTrump
|
||||
) 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 hand card = do
|
||||
@@ -1,7 +1,8 @@
|
||||
module Render where
|
||||
module Skat.Render where
|
||||
|
||||
import Card
|
||||
import Data.List
|
||||
|
||||
import Skat.Card
|
||||
|
||||
render :: [Card] -> IO ()
|
||||
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 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 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 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
|
||||
+66
@@ -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"
|
||||
Reference in New Issue
Block a user