114 lines
3.9 KiB
Haskell
114 lines
3.9 KiB
Haskell
|
-- Putting it all together: Tic Tac Toe
|
||
|
-- cabal update
|
||
|
-- cabal install gloss
|
||
|
-- ghc tictactoe.hs
|
||
|
-- tictactoe.exe
|
||
|
|
||
|
-- import is just like Java, but you can give modules (akin to Java packages) their own local name
|
||
|
import qualified Graphics.Gloss.Interface.Pure.Game as Gloss
|
||
|
import qualified Graphics.Gloss.Data.Bitmap as Bitmap
|
||
|
import qualified Data.Map as Map
|
||
|
import Data.List
|
||
|
|
||
|
-- A tic-tac-toe cell is either blank, X, or O
|
||
|
data Cell = Blank | X | O deriving (Eq, Ord)
|
||
|
|
||
|
-- To encode game state:
|
||
|
-- we have the board (a map from a pair of ints to a Cell)
|
||
|
-- and the current "player" (either X or O)
|
||
|
data Game = Game {
|
||
|
board :: Map.Map (Int, Int) Cell,
|
||
|
player :: Cell
|
||
|
}
|
||
|
|
||
|
-- Initially, the board is a 3x3 grid of blank cells
|
||
|
initialBoard = Map.fromList [((i,j), Blank) | i <- [0..2], j <- [0..2]]
|
||
|
|
||
|
-- Given a game board, and two coordinates, is the move legit?
|
||
|
validMove :: Map.Map (Int, Int) Cell -> Int -> Int -> Bool
|
||
|
validMove board i j
|
||
|
| i < 0 || i >= 3 || j < 0 || j >= 3 = False
|
||
|
| otherwise = Blank == board Map.! (i,j)
|
||
|
|
||
|
-- X starts the game
|
||
|
initialGame = Game initialBoard X
|
||
|
|
||
|
-- List the rows in the game board
|
||
|
rows :: Map.Map (Int, Int) Cell -> [[Cell]]
|
||
|
rows board = map (map snd) $ groupBy (\((i,j),_) ((k,l),_) -> i == k) $ Map.assocs board
|
||
|
|
||
|
-- List the diagonals in the game board
|
||
|
diagonals :: Map.Map (Int, Int) Cell -> [[Cell]]
|
||
|
diagonals board = [[board Map.! (0,0), board Map.! (1,1), board Map.! (2,2)],
|
||
|
[board Map.! (2,0), board Map.! (1,1), board Map.! (0,2)]]
|
||
|
|
||
|
|
||
|
-- Players switch when taking turns
|
||
|
switch :: Cell -> Cell
|
||
|
switch O = X
|
||
|
switch X = O
|
||
|
|
||
|
-- Given a game board, and a move, get the new game board state
|
||
|
takeTurn :: Game -> (Int, Int) -> Game
|
||
|
takeTurn game@Game { board=b, player=p } (i, j)
|
||
|
| validMove b i j = newGame
|
||
|
| otherwise = game
|
||
|
where
|
||
|
newGame = Game {
|
||
|
board = Map.adjust (\_ -> p) (i,j) b,
|
||
|
player = switch p
|
||
|
}
|
||
|
|
||
|
-- Did anybody win?
|
||
|
winner :: Map.Map (Int, Int) Cell -> Bool
|
||
|
winner board = any win slices
|
||
|
where
|
||
|
slices = ((rows board) ++ (diagonals board) ++ (transpose $ rows board))
|
||
|
|
||
|
-- Given a slice (row, column, diagonal), are they all the same?
|
||
|
win slice = foldl1 (\x y -> if x == y then x else Blank) slice /= Blank
|
||
|
|
||
|
-- is the game over? either somebody won, or there's no blanks left
|
||
|
gameOver :: Map.Map (Int, Int) Cell -> Bool
|
||
|
gameOver board = winner board || (not $ any (== Blank) $ Map.elems board)
|
||
|
|
||
|
-- So, who won, if anybody?
|
||
|
outcome (Game board player)
|
||
|
| winner board = switch player
|
||
|
| otherwise = Blank
|
||
|
|
||
|
-- Translate mouse clicks (Gloss.Event) into turns
|
||
|
handleMouse :: Gloss.Event -> Game -> Game
|
||
|
handleMouse (Gloss.EventKey (Gloss.MouseButton Gloss.LeftButton) Gloss.Down _ (x, y)) game@(Game board player)
|
||
|
| gameOver board = game
|
||
|
| otherwise = takeTurn game (i, j)
|
||
|
where i = round $ (y+160) / 160
|
||
|
j = round $ (x+240) / 160
|
||
|
|
||
|
-- don't care about the other events
|
||
|
handleMouse _ game = game
|
||
|
|
||
|
main = do
|
||
|
oPicture <- Bitmap.loadBMP "O.bmp"
|
||
|
xPicture <- Bitmap.loadBMP "X.bmp"
|
||
|
_Picture <- Bitmap.loadBMP "_.bmp"
|
||
|
xwin <- Bitmap.loadBMP "xwin.bmp"
|
||
|
owin <- Bitmap.loadBMP "owin.bmp"
|
||
|
tie <- Bitmap.loadBMP "tie.bmp"
|
||
|
|
||
|
let cellPicture = Map.fromList [(O,oPicture), (X, xPicture), (Blank, _Picture)]
|
||
|
let outcomePicture = Map.fromList [(O,owin), (X, xwin), (Blank, tie)]
|
||
|
let displayCell (i,j) cell = Gloss.translate ((fromIntegral j :: Float)*160-240) ((fromIntegral i :: Float)*160-160) $ cellPicture Map.! cell
|
||
|
let displayBoard (Game board _) = Gloss.Pictures $ Map.elems $ Map.mapWithKey displayCell board
|
||
|
let displayOutcome game = Gloss.translate (230) (0) (outcomePicture Map.! (outcome game))
|
||
|
let display game@(Game board player) = if gameOver board then Gloss.Pictures [displayOutcome game, displayBoard game] else displayBoard game
|
||
|
|
||
|
Gloss.play
|
||
|
(Gloss.InWindow "Rock Paper Scissors" (1920,1080) (0,0))
|
||
|
Gloss.black
|
||
|
10
|
||
|
initialGame
|
||
|
display
|
||
|
handleMouse
|
||
|
(\f g -> g)
|