-
Notifications
You must be signed in to change notification settings - Fork 1
/
chess.hs
177 lines (142 loc) · 4.55 KB
/
chess.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
import Data.Char
import Data.Array
import Data.Maybe
import Data.List
import Data.Ord
import Contorl.Monad
import Control.Monad.Loops
type Pos = (Int, Int)
data PieceType = P | N | B | R | Q | K deriving (Show, Read, Eq, Ord)
data Side = Black | White deriving (Eq, Show)
data Piece = Piece {side :: Side, ptype :: PieceType, pos :: Pos} deriving Show
type Board = Array Pos (Maybe Piece)
type Move = (Piece, Pos)
main = do
putStrLn "Let's play a game"
iterateM_ (humanMove White >=> aiMove Black) startBoard
humanMove s b = do
printBoard b
putStrLn $ show s ++ " to move."
let moves = getMoves b s
zipWithM_ (\n move -> show n ++ ". " ++ agnMove move) [0..] moves
moveNumber <- getLine
return $ uncurry (move b) (moves !! read moveNumber)
aiMove s b = do
putStrLn $ "\n Computer plays " ++ agnMove bestMove ++ ".\n"
return $ uncurry (move b) bestMove
where
bestMove = bestMoveBy evaluateBoard s b
setup :: [Piece]
setup =
[Piece Black P (x,2)|x<-[1..8]] ++
[Piece White P (x,7)|x<-[1..8]] ++
zipWith (Piece Black) (map (\l->read[l]) "RNBKQBNR") [(i,1)|i<-[1..8]] ++
zipWith (Piece White) (map (\l->read[l]) "RNBQKBNR") [(i,8)|i<-[1..8]]
startBoard = boardUpdate blankBoard setup
readPos [file,rank] = (ord file - 96, digitToInt rank)
showPos ((x,y)) = [chr (x+96), intToDigit y]
agnMove (Piece _ t _, pos) = (if t==P then "" else show t) ++ showPos pos
-- need to add special moves
legal :: Board -> Piece -> Pos -> Bool
legal board piece@(Piece s t (oldX,oldY)) newPos@(newX,newY)
=
inBounds newX && inBounds newY -- don't go out of bounds
&&
dX+dY/=0 -- no non-moves!
&&
case t of -- gotta move according to the rules
P -> dX==0 && oldY`pm`1==newY
N -> (dX, dY) `elem`[(1,2),(2,1)]
B -> dX==dY
R -> dX==0 || dY==0
Q -> any (\t'-> legal board (swap piece t') newPos) [B,R]
K -> not (dX>1 || dY>1)
&&
(null inbetweens -- can't pass through other pieces
|| (((pos firstHit == newPos) --unless killing
|| t == N) && -- or a knight
hitEnemy)) --and you have to kill the other team
where
pm = case s of Black->(+); White->(-)
inBounds x = x>0 && x<9
dX = abs $ oldX-newX
dY = abs $ oldY-newY
inbetweens = catMaybes $ tail [board!(x,y)
| x <- between oldX newX, y <- between oldY newY]
between a b = case compare a b of
LT -> [a..b]
GT -> reverse [b..a]
EQ -> [a]
firstHit = head inbetweens
hitEnemy = case board!newPos of
Nothing -> otherwise
Just victim -> side victim /= s
move :: Board -> Piece -> Pos -> Board
move board piece to =
if legal board piece to then
board//[
(pos piece,Nothing),
(to, Just $ piece {pos=to})]
else error "Illegal Move Bro!"
getPieces :: Board -> [Piece]
getPieces = catMaybes.elems
swap :: Piece->PieceType->Piece
swap (Piece s t p) t' = Piece s t' p
wholeBoard = [(x,y) | x<-[1..8], y<-[1..8]]
possibleMoves board piece = filter (legal board piece) wholeBoard
blankBoard :: Board
blankBoard = listArray ((1,1),(8,8)) $ repeat Nothing
boardUpdate :: Board -> [Piece] -> Board
boardUpdate board update = board//[(pos piece, Just piece) | piece<-update]
-- instance Show Board where
showBoard :: Board -> [String]
showBoard ps =
[[case ps!(y,x) of
Just p -> icon p
Nothing -> '.'
| y<-[1..8]]| x<-[1..8]]
printBoard b = do
putStrLn $ ' ':['a'..'h']
mapM_ putStrLn $ zipWith (:) ['8','7'..] $showBoard b
icon (Piece s t _) =
case s of
Black -> fst
White -> snd
$
case t of
P -> ('♙','♟')
N -> ('♘','♞')
B -> ('♗','♝')
R -> ('♖','♜')
Q -> ('♕','♛')
K -> ('♔','♚')
justMove b =
map (\p ->move b p (head $ possibleMoves b p))
$ getPieces b
getMoves b s =
concatMap (\x-> zip (repeat x) $ possibleMoves b x) $ getSide b s
getSide b s = filter (\x-> side x == s) $ getPieces b
nextBoards b s = map (uncurry $ move b) $ getMoves b s
-- the hard part
type Evaluator = Side -> Board -> Int
bestMoveBy :: Evaluator -> Side -> Board -> Move
bestMoveBy eval s b = maximumBy (comparing $ eval s . uncurry (move b)) $ getMoves b s
evaluateBoard :: Evaluator
evaluateBoard = iterate evalPredictive evalMaterial !! 2 -- any more than three takes forever
evalSimple s b = fromIntegral . length $ getSide b s
evalMaterial s b =
sum .
map (\(Piece s1 t _)->sign s s1 * value t) $
getPieces b
where
sign me it = if it==me then 1 else (-1)
evalPredictive e s b = e s $ uncurry (move b) $ bestMoveBy e s b
-- Misc
value :: PieceType -> Int
value t = case t of
P -> 1
N -> 3
B -> 3
R -> 5
Q -> 9
K -> 1000