-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathgame.hs
190 lines (157 loc) · 5.47 KB
/
game.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
178
179
180
181
182
183
184
185
186
187
188
189
190
import Data.List
import System.IO
import Control.Monad.State.Lazy
import Control.Monad.IO.Class
import Data.IORef
import Graphics.UI.Gtk hiding (get, Action)
-- import Brick
-- import Brick.BChan (newBChan, writeBChan)
-- import qualified Brick.Main as M
-- import qualified Brick.Widgets.Border as B
-- import qualified Brick.Widgets.Border.Style as BS
-- import qualified Brick.Widgets.Center as C
-- import qualified Brick.Widgets.Core as CR
-- import qualified Graphics.Vty as V
data Square = X | O | Empty
deriving (Show, Eq)
type Board = [[Square]]
data Action = Quit | Play Int Int
deriving (Show)
data GameState = Won | Tied | Playing
-- generate an empty board
emptyBoard :: Board
emptyBoard = [[Empty, Empty, Empty],
[Empty, Empty, Empty],
[Empty, Empty, Empty]]
-- check if board is a tie
tied :: Board -> Bool
tied board = not (won board || any (== Empty) (concat board))
-- check if any win condition has been reached
won :: Board -> Bool
won board = any allSame (board ++ transpose board ++ diagonals board)
where diagonals [[a,b,c],
[d,e,f],
[g,h,i]] = [[a,e,i],[c,e,g]]
-- check if every value in this list has the same value, not Empty
allSame :: [Square] -> Bool
allSame [] = True
allSame (Empty:_) = False
allSame (x:xs) = all (== x) xs
-- make a move for given player
move :: Int -> Int -> Square -> State Board Board
move x y square = do
board <- get
let board' = replace x (replace y square (board !! x)) board
put board'
return board'
oneMove :: Board
oneMove = evalState (move 0 0 X) emptyBoard
-- returns a new List replacing element at index with given elem
replace :: Int -> a -> [a] -> [a]
replace index elem = map (\(index', elem') -> if index' == index then elem else elem') . zip [0..]
-- get the inverse of a player, accepts only X and O
invertPlayer :: Square -> Square
invertPlayer player
| player == X = O
| player == O = X
| otherwise = error "undefined player"
parseAction :: String -> Action
parseAction input =
if input == "q"
then Quit
else let num = read input in
let (x, y) = num `divMod` 3 in
Play x y
boardState :: Board -> GameState
boardState board =
if won board
then Won
else if tied board
then Tied
else Playing
play :: Square -> StateT Board IO ()
play player = do
lift $ putStrLn $ "Enter move, Player " ++ show player
square <- lift getLine
case parseAction square of
Quit -> lift $ putStrLn $ "Exiting"
Play x y -> do
board <- (state . runState) $ move x y player -- hoisting
lift $ print $ board
case boardState board of
Won -> lift $ putStrLn $ show player ++ " won!"
Tied -> lift $ putStrLn "game tied"
Playing -> play $ invertPlayer player
textGame :: IO Board
textGame = execStateT (play X) emptyBoard
-- get a string representation of the square at this position
getRow :: Board -> Int -> String
getRow board x = show (board !! x)
-- app :: App Board
-- app = App { appDraw = drawUI
-- , appChooseCursor =
-- }
-- renderUI :: Board -> [Widget ()]
-- renderUI b = [renderBoard b]
-- renderBoard :: Board -> Widget ()
-- renderBoard b =
-- CR.joinBorders $
-- CR.hLimit 6 $
-- CR.vLimit 7 $
-- displayRow b 0
-- <=> B.hBorder
-- <=> displayRow b 1
-- <=> B.hBorder
-- <=> displayRow b 2
-- displayRow :: Board -> Int -> Widget ()
-- displayRow board i = str (getSquare board i 0)
-- <+> B.vBorder
-- <+> str (getSquare board i 1)
-- <+> B.vBorder
-- <+> str (getSquare board i 2)
-- where display = B.border . str
-- appEvent :: Board -> BrickEvent n e -> EventM n (Next Board)
-- appEvent
-- attMap :: Brick.AttrMap
-- attMap = Brick.attrMap V.defAttr []
-- main :: IO ()
-- main = do
-- let app = M.App { M.appDraw = renderUI
-- , M.appChooseCursor = M.showFirstCursor
-- , M.appHandleEvent = appEvent
-- , M.appStartEvent = return
-- , M.appAttrMap = const attMap
-- }
-- finalState <- defaultMain app emptyBoard
-- putStr $ show finalState
-- void $ defaultMain app emptyBoard
-- simpleMain $ renderBoard emptyBoard
getSquare :: Board -> Int -> Int -> String
getSquare board x y
| square == Empty = "_"
| square == X = "X"
| square == O = "O"
where square = (board !! x) !! y
main :: IO ()
main = do
void initGUI
window <- windowNew
set window [ windowTitle := "Tic-Tac-Toe"
, windowResizable := True
, windowDefaultWidth := 600
, windowDefaultHeight := 600 ]
grid <- gridNew
-- gridSetRowHomogeneous grid True
-- let attach x y w h item = gridAttach grid item x y w h
-- mkBtn "_" >>= attach 0 0 1 1
-- mkBtn "_" >>= attach 0 1 1 1
-- mkBtn "_" >>= attach 0 2 1 1
-- mkBtn "_" >>= attach 1 0 1 1
-- mkBtn "_" >>= attach 1 1 1 1
-- mkBtn "_" >>= attach 1 2 1 1
-- mkBtn "_" >>= attach 2 0 1 1
-- mkBtn "_" >>= attach 2 1 1 1
-- mkBtn "_" >>= attach 2 2 1 1
-- containerAdd window grid
widgetShowAll window
mainGUI