-
Notifications
You must be signed in to change notification settings - Fork 11
/
Copy pathConnect4.hs
96 lines (72 loc) · 3.06 KB
/
Connect4.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
module Connect4 where
import Data.Word
import Data.Int
import Data.Bits
import Data.List
import Data.Array.Unboxed
(width,height) = (7,6)
height' = height + 1
height'' = height + 2
size = width * height
bsize = width * height'
type BitBoard = Word64 -- bitmask corresponds to board as follows in 7x6 case:
-- . . . . . . . TOP
-- 5 12 19 26 33 40 47
-- 4 11 18 25 32 39 46
-- 3 10 17 24 31 38 45
-- 2 9 16 23 30 37 44
-- 1 8 15 22 29 36 43
-- 0 7 14 21 28 35 42 BOTTOM
type Heights = UArray Int Int -- index of lowest free square in each column
data Game = Game !Int !BitBoard !BitBoard Heights
bottom = (bit bsize - 1) `div` (bit height' - 1) -- mask for bottom squares
-- equals 2^0 + 2^{height'} + 2^{2*height'} + .. + 2^{(width-1)*height'}
-- which is a geometric series evaluating to the above fraction
top = bottom `shiftL` height
showbb white black =
header++'\n':unlines (map showrow [height-1,height-2..0])++wininfo where
header = ' ':unwords (map show [1..width])
showrow h = ' ':intersperse ' ' (map cell [h,h+height'..bsize-1])
cell i | testBit black i = '@'
| testBit white i = 'O'
| otherwise = '.'
wininfo | isWon black = "@ won\n"
| isWon white = "O won\n"
| otherwise = ""
instance Show Game where
show (Game n tm ntm _) = if even n then showbb tm ntm else showbb ntm tm
allmoves = [0..width-1]
isLegal bb = (bb .&. top) == 0
isLegalGame (Game _ _ ntm _) = isLegal ntm
legalmoves g = filter (isLegalGame . snd . move g) allmoves
isWon bb = any ((/= 0) . dir) [1,height,height',height''] where
dir d = let t = bb .&. (bb `shiftR` d) in t .&. (t `shiftR` (2*d))
isWonGame (Game _ _ ntm _) = isWon ntm
isLegalWon bb = isLegal bb && isWon bb
isLegalWonGame (Game _ _ ntm _) = isLegalWon ntm
move (Game n tm ntm hs) i = (h,g) where
h = hs!i
g = Game (n+1) ntm (setBit tm h) (hs//[(i,h+1)])
encode :: Game -> Word64
encode (Game n tm ntm _) =
if n >= symmrec then key else min key (revcols 0 key) where
symmrec = 10 -- symmetry normalize first SYMMREC moves
key = 2*tm + ntm + bottom
revcols 0 a = a
revcols k a = revcols (shiftR k height') (shiftL a height' .|. k.&.col1)
col1 = bit height' - 1
isWinnable (Game _ tm _ hs) = any (isLegalWon . setBit tm) (elems hs)
goodMoves g@(Game _ _ ntm hs) =
let noThreatAbove h = not $ isLegalWon $ setBit ntm $ h+1 in
case filter (isLegalWon . setBit ntm) (elems hs) of -- forced moves
[] -> [hig | i <- allmoves, let hig@(hi,(Game _ _ ntmi _)) = move g i,
isLegal ntmi && noThreatAbove hi ]
[hf] | noThreatAbove hf -> [move g (hf `div` height')]
_ -> [] -- no good moves if multiple forced
newGame = Game 0 0 0 $ listArray (0,width-1) [0,height'..]
listGame = foldl (\i->snd.(move i)) newGame
quads = [quad | i <- [0..bsize-1], dir <- [1,height,height',height''],
let max = i + 3*dir, let quad = [i,i+dir..max],
max < bsize && all (not . testBit top) quad]
cntElems b a = accumArray (+) 0 b $ a `zip` (repeat 1) :: Array Int Int
moveEval = cntElems (0,bsize-1) $ concat quads