1 {-# LANGUAGE GeneralizedNewtypeDeriving #-}
3 import Data
.Array.IArray
8 import Control
.Monad
.State
10 --import Database.HDBC
11 --import Database.HDBC.PostgreSQL
12 import Database
.HaskellDB
.HDBC
.PostgreSQL
13 import Database
.HaskellDB
hiding ((!))
15 import System
.Posix
.Process
16 import System
.Posix
.Files
18 import System
.Directory
19 import Control
.Concurrent
21 import Data
.Digest
.SHA512
23 data Color
= Black | White
deriving Eq
24 type Board
= Array (Int, Int) (Maybe Color
)
25 type Form
= [(Int, Int)]
26 data Move
= Move
Int Int Color | Pass
deriving Eq
29 data Status
= Connected | LoggedOn | Playing | Counting
deriving (Eq
, Ord
)
31 data ServerState
= ServerState
{
32 username
:: Maybe String,
37 newtype Server a
= S
{
38 runS
:: StateT ServerState
IO a
39 } deriving (Monad
, MonadState ServerState
, MonadIO
)
41 evalServer
:: Server a
-> IO a
42 evalServer x
= evalStateT
(runS x
) $ ServerState Nothing Connected
48 say
:: String -> Server
()
49 say
= liftIO
. hPutStrLn stderr
52 readMove
:: Color
-> String -> Maybe Move
53 readMove c a
= case words a
of
54 [x
, y
] -> Just
$ Move
(read x
) (read y
) c
58 instance Show Color
where
69 - unlines $ (zipWith (\x y -> "f " ++ show x ++ " = Just '" ++ [y] ++ "'") [0..18] "ABCDEFGHJKLMNOPQRST") ++ ["f _ = Nothing"]
71 showX :: Int -> String
74 instance Show Move
where
75 show (Move x y c
) = unwords [show c
, show x
, show y
]
82 dbconn <- postgresqlConnect [
83 ("host", "localhost"),
84 ("dbname", "hategod"),
85 ("sslmode", "disable")] return
88 hPutStrLn stderr "hello"
89 evalServer
$ server
$ stdin
95 server
:: String -> Server
()
97 mapM_ interpreter
$ lines x
98 liftIO
. maybe (return ()) (removeFile . ("hategod-player." ++)) . username
=<< get
100 interpreter
:: String -> Server
()
104 ("username":username
:[]) -> if not (isGoodUsername username
) then
105 say
"error: bad username" else do
107 put
$ s
{ username
= Just username
, status
= LoggedOn
}
108 say
$ "user " ++ username
110 ("version":[]) -> say protocolVersion
111 ("who":[]) -> liftIO who
112 ("play":username
:x
:y
:[]) -> if status s
/= LoggedOn
then
113 say
"error: wrong status" else if not $ isGoodUsername username
then
114 say
"error: bad username" else do
115 put
$ s
{ status
= Playing
}
117 game username
(emptyBoard
(read x
) (read y
))
118 ("listen":[]) -> if status s
/= LoggedOn
then
119 say
"error: wrong status" else do
120 let fn
= "hategod-player." ++ (fromJust $ username s
)
121 return $ createNamedPipe fn socketMode
-- that sucks?
122 rival
<- liftIO
$ openFile fn ReadWriteMode
124 _
-> say
"error: parse failed"
126 isGoodUsername
= and . map isAlphaNum
129 who
= sequence_ . map (hPutStrLn stderr) . catMaybes . map (stripPrefix
"hategod-player.") =<< getDirectoryContents =<< getCurrentDirectory
131 game
:: String -> Board
-> Server
()
134 rival
<- liftIO
$ openFile ("hategod-player." ++ user
) ReadWriteMode
140 play
:: Handle -> Server Game
141 play rival
= return []
145 pushToDB :: Game -> Server ()
146 pushToDB = return () -- TODO
149 myMove
:: Color
-> Server Move
150 myMove c
= liftIO
$ (return . fromJust . readMove c
) =<< getLine
152 -- theirs move is always a good one
153 theirsMove
:: Handle -> Server Move
154 theirsMove rival
= do
155 c
<- liftIO
$ (return . readColor
) =<< hGetChar rival
156 liftIO
$ (return . fromJust . readMove c
) =<< getLine
158 gameToBoard
:: Game
-> Board
-> Maybe Board
159 gameToBoard g b
= foldM doMove b g
161 doMove
:: Board
-> Move
-> Maybe Board
162 doMove b Pass
= Just b
163 doMove b m
@(Move x y c
) | isGood b m
= Just
$ flip doKill c
$ doMove_ b m
164 |
otherwise = Nothing
166 doMove_
:: Board
-> Move
-> Board
167 doMove_ b
(Move x y c
) = b
// [((x
, y
), Just c
)]
169 isGood
:: Board
-> Move
-> Bool
170 isGood b m
= and $ (\a b c
-> zipWith uncurry a
(repeat (b
, c
))) goodSigns b m
173 goodSigns
:: [Board
-> Move
-> Bool]
174 goodSigns
= [(\z
(Move x y _
) -> not $ isOccupied z
(x
, y
)),
175 curry $ not . uncurry isSuicide
]
177 isOccupied
:: Board
-> (Int, Int) -> Bool
178 isOccupied b i
= maybe False (const True) $ b
! i
180 isSuicide b m
@(Move x y c
) = if isKilling b m
then False else countDameF b
(fromJust $ findForm
(doMove_ b m
) (x
, y
)) == 0
183 isKilling b m
@(Move x y c
) = b
/= (flip doKill
(other c
) $ doMove_ b m
)
185 doKill
:: Board
-> Color
-> Board
186 doKill board color
= remove board
$ concat $ filter (\f -> countDameF board f
== 0) $ nub $ mapMaybe (\i
-> findForm board i
) $ indices board
188 countDameF
:: Board
-> Form
-> Int -- STUPID!
189 countDameF b f
= sum $ map (countDame b
) f
191 countDame
:: Board
-> (Int, Int) -> Int
192 countDame b i
= sum $ map (b2i
. not . isOccupied b
) $ findNeighbours b i
194 remove
:: Board
-> Form
-> Board
195 remove b f
= b
// (map (\i
-> (i
, Nothing
))) f
200 findForm
:: Board
-> (Int, Int) -> Maybe Form
201 findForm b i
= liftM (findForm_ b
[i
]) (b
! i
)
203 findForm_
:: Board
-> Form
-> Color
-> Form
204 findForm_ b i c
= let f
= nub $ sort $ concatMap (flip (friendlyNeighbours b
) c
) i
in
205 if i
== f
then f
else findForm_ b f c
207 friendlyNeighbours
:: Board
-> (Int, Int) -> Color
-> [(Int, Int)]
208 friendlyNeighbours b i c
= map fst $ filter (\x
-> Just c
== snd x
) $ map (\i
-> (i
, b
! i
)) $ findNeighbours b i
210 findNeighbours
:: Board
-> (Int, Int) -> [(Int, Int)]
211 findNeighbours b
(x
, y
) = filter (inRange $ bounds b
) [(x
+ 1, y
+ 1), (x
- 1, y
+ 1), (x
+ 1, y
- 1), (x
- 1, y
- 1)]
213 emptyBoard
:: Int -> Int -> Board
214 emptyBoard x y
= array ((1, 1), (x
, y
)) [((a
, b
), Nothing
) | a
<- [1..x
], b
<- [1..y
]]