tic-tac-toe (empty) → 0.1
raw patch · 12 files changed
+807/−0 lines, 12 filesdep +basedep +gladedep +gtksetup-changedbinary-added
Dependencies added: base, glade, gtk, haskell98
Files
- Cross.png binary
- DataTypes.hs +60/−0
- GetAgentMove.hs +23/−0
- GetValue.hs +74/−0
- LICENSE +29/−0
- MyTree.hs +84/−0
- Nought.png binary
- NoughtyGlade.hs +174/−0
- README +1/−0
- Setup.lhs +4/−0
- noughty.glade +341/−0
- tic-tac-toe.cabal +17/−0
+ Cross.png view
binary file changed (absent → 8439 bytes)
+ DataTypes.hs view
@@ -0,0 +1,60 @@+module DataTypes+ where+ +import Data.List++data TableMove = TableMove Board History CurrentMove deriving (Show,Ord)+data Move = X | O deriving (Eq, Show, Enum, Ord)++type History = [Integer]+type CurrentMove = Maybe Move +type Board = [[Maybe Move]]++size :: Int+size = 3++empty :: Board+empty = replicate size (replicate size Nothing)++-- make use of symmetries+-- we try to see if there is a symmetry if the move we are about to make +-- has across from it, either horizontally, vertically, or diagonally, a Nothing slot.+-- The only position that may change is the position of the move we are about to make.+-- If the symmetrical move is already included in the current ply, we may take it out.+instance Eq TableMove where+ (TableMove m1 h1 c1) == (TableMove m2 h2 c2) =+ (horizontalSym m1 == m2) || (leftDiagonalSym m1 == m2) || (verticalSym m1 == m2) || (rightDiagonalSym m1 == m2)++horizontalSym = reverse+leftDiagonalSym = transpose+verticalSym = transpose.reverse.transpose+rightDiagonalSym = verticalSym.reverse.transpose++-- these are our accessors+accessMaybeMove :: TableMove -> [[Maybe Move]]+accessMaybeMove (TableMove m _ _) = m++accessHistory :: TableMove -> [Integer]+accessHistory (TableMove _ h _) = h++accessCurrentMove :: TableMove -> CurrentMove+accessCurrentMove (TableMove _ _ c) = c++getOtherMove :: CurrentMove -> CurrentMove +getOtherMove (Just X) = Just (succ X)+getOtherMove (Just O) = Just (pred O)+getOtherMove Nothing = Nothing++toTableMove :: [[Maybe Move]] -> History -> CurrentMove -> TableMove+toTableMove m h c = TableMove m h c++chop :: Int -> [a] -> [[a]]+chop n [] = []+chop n xs = take n xs : chop n (drop n xs)++infinity :: Float+infinity = 1/0++-- gives us a list of the diagonal elements+diag :: [[a]] -> [a]+diag xss = [xss !! n !! n | n <- [0 .. length xss - 1]]
+ GetAgentMove.hs view
@@ -0,0 +1,23 @@+module GetAgentMove+ where++import MyTree+import GetValue+import DataTypes+import Data.Tree++-- This is the main driver function of the AI. It uses a composition of functions.+ans :: Board -> CurrentMove -> Integer -> TableMove+ans board currentMove diff = backtrackWith (findHistory board) $ maximise $ + myCustomTreeZip (myTreeValue $ prune diff $ gameTree (makeTableTree board currentMove)) (prune diff $ gameTree (makeTableTree board currentMove))++--test board currentMove = maximise $ myCustomTreeZip (myTreeValue $ prune 4 $ gameTree (makeTableTree board currentMove)) (prune 4 $ gameTree (makeTableTree board currentMove))++backtrackWith :: [Integer] -> (Float,TableMove) -> TableMove+backtrackWith h (m,tableMove) = remove h tableMove++remove :: [Integer] -> TableMove -> TableMove+remove h tableMove = if tail (accessHistory tableMove) == h then tableMove + else (remove h (toTableMove (chop size (xs ++ (Nothing : ys))) + (tail (accessHistory tableMove)) ((getOtherMove . accessCurrentMove) tableMove)))+ where (xs,y:ys) = splitAt (fromInteger $ head (accessHistory tableMove)) (concat (accessMaybeMove tableMove))
+ GetValue.hs view
@@ -0,0 +1,74 @@+module GetValue+ where++import Data.List+import Data.Tree+import DataTypes+import MyTree++-- since for each evaluation of a tree, we need to find the value in terms of one moves, +-- so that the moves do not alternate between each other. Kinda ugly but works.+myTreeValue :: Tree TableMove -> Tree Float+myTreeValue (Node node subTree) = myTreeMap (getValue ((getOtherMove.accessCurrentMove) node)) (Node node subTree)++maximise :: (Ord a) => Tree (a,TableMove) -> (a,TableMove)+maximise = maximum . maximise'++-- this is called alpha-beta pruning...+maximise' :: (Ord a) => Tree (a,TableMove) -> [(a,TableMove)]+maximise' (Node node []) = [node]+maximise' (Node n l) = mapmin (map minimise' l)+ where mapmin (nums:rest) = (minimum nums) : (omit1 (minimum nums) rest)++omit1 :: (Ord a) => a -> [[a]] -> [a]+omit1 pot [] = []+omit1 pot (nums:rest) = if minleq nums pot then omit1 pot rest + else (minimum nums) : (omit1 (minimum nums) rest)++minleq :: (Ord a) => [a] -> a -> Bool+minleq [] pot = False+minleq (num:rest) pot = if num <= pot then True+ else minleq rest pot+++minimise :: (Ord a) => Tree (a,TableMove) -> (a,TableMove)+minimise = minimum . minimise'++minimise' :: (Ord a) => Tree (a,TableMove) -> [(a,TableMove)]+minimise' (Node node []) = [node]+minimise' (Node n l) = mapmax (map maximise' l)+ where mapmax (nums:rest) = (maximum nums) : (omit2 (maximum nums) rest)++omit2 :: (Ord a) => a -> [[a]] -> [a]+omit2 pot [] = []+omit2 pot (nums:rest) = if maxleq nums pot then omit2 pot rest+ else (maximum nums) : (omit2 (maximum nums) rest)++maxleq :: (Ord a) => [a] -> a -> Bool+maxleq [] pot = False+maxleq (num:rest) pot = if num >= pot then True+ else maxleq rest pot+++prune :: Integer -> Tree TableMove -> Tree TableMove+prune 0 (Node node x) = Node node []+prune n (Node node x) = Node node (map (prune (n-1)) x)++-- this function evaluates a position and returns a value, based on how good it is+-- The function first takes the sum of the rows columns or diagonals it can form based on +-- the current move. It then takes the sum of the rows, columns or diagonals the opponent +-- can form, and subtracts that from its original sum, which it returns. If the function finds+-- that it has formed a full row, column, or diagonal, it returns 1/0 (infinity), or if it finds +-- the same condition for its opponent (it can't be both), it returns -1/0 (negative infinity).+-- If there is no more open spaces that it can use, the function returns 0.+getValue :: CurrentMove -> TableMove -> Float+getValue curr t = (getValueOfMove curr (accessMaybeMove t)) - (getValueOfMove (getOtherMove curr) (accessMaybeMove t))+ where getValueOfMove c l = (rowValues c l) + (rowValues c (transpose l)) + (diagValues c l) + (diagValues c (map reverse l))+ +rowValues :: Maybe Move -> [[Maybe Move]] -> Float+rowValues cMove table = if (any (==True) (findEndGame cMove table)) then 1/0 else foldr (\x y -> if x then (1+y) else (0+y)) 0 (findStatic cMove table)+ where findEndGame c m = map (all (\x -> x == c)) m+ findStatic c m = map (all (\x -> (x == c) || (x == Nothing))) m++diagValues :: Maybe Move -> [[Maybe Move]] -> Float +diagValues cMove xL = if (all (==cMove) (diag xL)) then 1/0 else if (all (\x -> (x == cMove) || (x == Nothing)) (diag xL)) then 1 else 0
+ LICENSE view
@@ -0,0 +1,29 @@+Copyright (c) 2006, Wouter Swierstra+All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are+met:++* Redistributions of source code must retain the above copyright+notice, this list of conditions and the following disclaimer.++* Redistributions in binary form must reproduce the above copyright+notice, this list of conditions and the following disclaimer in the+documentation and/or other materials provided with the distribution.++* Neither the name of the University of Nottingham nor the names of+its contributors may be used to endorse or promote products derived+from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ MyTree.hs view
@@ -0,0 +1,84 @@+module MyTree + where+ +import DataTypes+import Data.Tree+import Data.Maybe+import Data.List+import Data.Ord++-- this is an implementation without monads:+myTreeMap :: (a -> b) -> Tree a -> Tree b+myTreeMap f (Node node subTree) = Node (f node) (myTreeMap' f subTree)+ where myTreeMap' f [] = []+ myTreeMap' f (first:rest) = (myTreeMap f first) : (myTreeMap' f rest)++-- this is an implementation with monads:+--myTreeMap :: (a -> b) -> Tree a -> Tree b+--myTreeMap f tree = do+-- move <- tree+-- return (f move)++-- regular zipper, except that during execution, if a node finds that it has won or lost,+-- further generation of the tree stops. We need this so that we dont get weird results when+-- we use minimax.+myCustomTreeZip :: Tree Float -> Tree TableMove -> Tree (Float,TableMove)+myCustomTreeZip (Node n1 s1) (Node n2 s2) = if n1 == 1/0 || n1 == -1/0 then Node (n1,n2) [] else Node (n1,n2) (myTreeZip' s1 s2)+ where myTreeZip' [] _ = []+ myTreeZip' _ [] = []+ myTreeZip' (f1:r1) (f2:r2) = (myCustomTreeZip f1 f2) : (myTreeZip' r1 r2)++gameTree :: Tree TableMove -> Tree TableMove+gameTree p = reptree moves p ++-- initialize a non-empty board+makeTableTree :: Board -> CurrentMove -> Tree TableMove+makeTableTree board currentMove = Node (toTableMove board (findHistory board) currentMove) []++-- given a board, returns the history of moves already taken+findHistory :: Board -> [Integer]+findHistory board = foldr (\(p,n) xL -> if p == (Just O) || p == (Just X) then n:xL else xL) [] (zip (concat board) [0..]) ++reptree :: (TableMove -> [Tree TableMove]) -> Tree TableMove -> Tree TableMove+reptree f (Node node []) =+ Node node (map (reptree f) (f node))++-- function responsible for generating a tree and making the input the parent +-- of all the other subtrees. It does this by getting each possibility of the input+-- excluding the ones in the history that have already been processed. It then proceeds +-- to remove the symmetries from the generated possibilities, after which it it converts +-- the TableMove to a Tree of TableMove. For each input, it generates all the possible moves +-- that the node can make in a list.+moves :: TableMove -> [Tree TableMove]+moves thisNode = map (makeLeafs) (removeSymmetries (map (possibilities thisNode) ([0..8] \\ (accessHistory thisNode))))++-- makes a TableMove into a Tree of TableMove with empty leafs+makeLeafs :: TableMove -> Tree TableMove+makeLeafs (xs) = Node xs [] ++-- a container that interfaces with the possibly function, converting+-- the data into its appropriate type. The Integer is the move placeholder +-- that we want to place a X or O.+possibilities :: TableMove -> Integer -> TableMove+possibilities node n = toTableMove (possibly (accessMaybeMove node) n (accessCurrentMove node)) (n : (accessHistory node)) (getOtherMove (accessCurrentMove node))++-- breaks down the current TableMove into three rows,+-- with x being the first, y being the second, and z being the third.+-- xs is an empty list. Note: I could probably simplify this to a lot +-- less code using the chop function.+possibly :: [[Maybe Move]] -> Integer -> Maybe Move -> [[Maybe Move]]+possibly (x:y:z:xs) n currentMove + | n >= 0 && n <= 2 = possibly' x (n `mod` 3) : y : z : xs+ | n >= 3 && n <= 5 = x : possibly' y (n `mod` 3) : z : xs+ | n >= 6 && n <= 8 = x : y : possibly' z (n `mod` 3) : xs+ | otherwise = x : y : z : xs+ where trulyNothing elem = if isNothing elem then (getOtherMove currentMove) else elem+ possibly' (x:y:z:xs) m+ | m == 0 = trulyNothing x : y : z : xs+ | m == 1 = x : trulyNothing y : z : xs+ | m == 2 = x : y : trulyNothing z : xs+ | otherwise = x : y : z : xs++removeSymmetries :: [TableMove] -> [TableMove]+removeSymmetries [] = []+removeSymmetries (x:xL) = x : removeSymmetries (filter (/= x) xL)
+ Nought.png view
binary file changed (absent → 8311 bytes)
+ NoughtyGlade.hs view
@@ -0,0 +1,174 @@+-- Copyright (c) 2006, Wouter Swierstra+-- All rights reserved.+-- This code is released under the BSD license+-- included in this distribution++-- Imports++import IO+import Maybe+import Data.List+import Graphics.UI.Gtk+import Graphics.UI.Gtk.Glade+import Data.IORef+import Control.Monad++-- my own imports+import GetAgentMove+import DataTypes+import MyTree++-- With n being the square that we want to move to, p being the +-- actual move, and b being the current board, we return +-- a board with the move placed on the square+move :: Int -> CurrentMove -> Board -> Maybe Board+move n p b = case y of+ Nothing -> Just (chop size (xs ++ (p : ys)))+ _ -> Nothing+ where+ (xs,y:ys) = splitAt n (concat b)++full :: Board -> Bool+full b = all (all (/= Nothing)) b++wins :: CurrentMove -> Board -> Bool+wins p b = any (all (== p)) b+ || any (all (== p)) (transpose b)+ || all (== p) (diag b)+ || all (== p) (diag (reverse b))++won :: Board -> Bool+won b = wins (Just O) b || wins (Just X) b++-- The state and GUI++data State = State {+ board :: Board,+ turn :: CurrentMove,+ difficulty :: Integer+}++data GUI = GUI {+ disableBoard :: IO (),+ resetBoard :: IO (),+ setSquare :: Int -> CurrentMove -> IO (),+ setStatus :: String -> IO ()+}++-- reset the game+reset gui (State board turn difficulty) = do+ setStatus gui "Player Cross: make your move."+ resetBoard gui+ return (State empty (Just X) difficulty)++-- when a square is clicked on, try to make a move.+-- if the square is already occupied, nothing happens+-- otherwise, update the board, let the next player make his move,+-- and check whether someone has won or the board is full.+occupy gui square st@(State board currentMove difficulty) = do+ case move square currentMove board of+ Nothing -> return st+ Just newBoard -> do+ setSquare gui square currentMove+ handleMove gui newBoard currentMove+ return (State newBoard currentMove difficulty)++-- main function responsible for controlling the AI of the game+-- if the previous move has resulted in a win or a full board,+-- then we return the State which was passed initially.+-- Otherwise we ask the agent for a move to return, and set its controls.+getAgentAns gui st@(State board currentMove difficulty) = do+ setSquare gui (fromInteger $ head $ accessHistory newBoard) (accessCurrentMove newBoard)+ handleMove gui (accessMaybeMove newBoard) (accessCurrentMove newBoard)+ return (State (accessMaybeMove newBoard) (getOtherMove (accessCurrentMove newBoard)) difficulty)+ where newBoard = if (wins currentMove board) || (full board) then toTableMove board (findHistory board) currentMove + else ans board currentMove difficulty+ ++-- we set the difficulty into the State Monad+setDifficulty newDifficulty st@(State board currentMove difficulty) = do+ return (State board currentMove newDifficulty)++-- check whether a board is won or full+handleMove gui board currentMove+ | wins currentMove board = do+ setStatus gui ("Player " ++ thisMove currentMove ++ " wins!")+ disableBoard gui+ | full board = do+ setStatus gui "It's a draw."+ disableBoard gui+ | otherwise = do+ setStatus gui ("Player " ++ thisMove (getOtherMove currentMove) ++ ": make your move")+ where thisMove (Just X) = "Cross"+ thisMove (Just O) = "Nought"++main = do+ initGUI++ -- Extract widgets from the glade xml file+ Just xml <- xmlNew "noughty.glade"++ window <- xmlGetWidget xml castToWindow "window"+ window `onDestroy` mainQuit++ newGame <- xmlGetWidget xml castToMenuItem "newGame"+ quit <- xmlGetWidget xml castToMenuItem "quit"++ easy <- xmlGetWidget xml castToMenuItem "easy"+ medium <- xmlGetWidget xml castToMenuItem "medium"+ hard <- xmlGetWidget xml castToMenuItem "hard"++ squares <- flip mapM [1..9] $ \n -> do+ square <- xmlGetWidget xml castToButton ("button" ++ show n)+ -- we set this in the glde file but it doesn't seem to work there.+ set square [ widgetCanFocus := False ]+ return square++ images <- flip mapM [1..9] $ \n -> do+ xmlGetWidget xml castToImage ("image" ++ show n)++ statusbar <- xmlGetWidget xml castToStatusbar "statusbar"+ ctx <- statusbarGetContextId statusbar "state"+ statusbarPush statusbar ctx "Player Cross: make your move."++ -- Construct the GUI actions that abstracts from the actual widgets+ gui <- guiActions squares images statusbar ctx++ -- Initialize the state+ state <- newIORef State { board = empty, turn = (Just X), difficulty = 1 }+ let modifyState f = readIORef state >>= f >>= writeIORef state++ -- Add action handlers+ onActivateLeaf quit mainQuit+ onActivateLeaf newGame $ modifyState $ reset gui++ onActivateLeaf easy $ modifyState $ setDifficulty 2 + onActivateLeaf medium $ modifyState $ setDifficulty 4 + onActivateLeaf hard $ modifyState $ setDifficulty 6 + + zipWithM_ (\square i ->+ onPressed square $ do+ modifyState $ occupy gui i+ modifyState $ getAgentAns gui)+ squares [0..8]++ widgetShowAll window+ mainGUI++guiActions buttons images statusbar ctx = do+ noughtPic <- pixbufNewFromFile "Nought.png"+ crossPic <- pixbufNewFromFile "Cross.png"+ return GUI {+ disableBoard = mapM_ (flip widgetSetSensitivity False) buttons,+ resetBoard = do+ mapM_ (\i -> imageClear i >> widgetQueueDraw i) images+ mapM_ (flip widgetSetSensitivity True) buttons,+ setSquare = \ i player ->+ case player of+ Just X -> set (images !! i) [ imagePixbuf := crossPic ]+ Just O -> set (images !! i) [ imagePixbuf := noughtPic ],+ setStatus = \msg -> do+ statusbarPop statusbar ctx+ statusbarPush statusbar ctx msg+ return ()+ }
+ README view
@@ -0,0 +1,1 @@+This program installs the program tic-tac-toe, which uses gtk to render the graphics. It uses a minimax algorithm with alpha-beta pruning on the inside to determine best moves and play against you.
+ Setup.lhs view
@@ -0,0 +1,4 @@+#! /usr/bin/env runhaskell++>import Distribution.Simple+>main = defaultMain
+ noughty.glade view
@@ -0,0 +1,341 @@+<?xml version="1.0" encoding="UTF-8" standalone="no"?>+<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd">+<!--*- mode: xml -*-->+<glade-interface>+ <widget class="GtkWindow" id="window">+ <property name="title" translatable="yes">Noughty</property>+ <property name="resizable">False</property>+ <child>+ <widget class="GtkVBox" id="vbox1">+ <property name="visible">True</property>+ <child>+ <widget class="GtkMenuBar" id="menubar1">+ <property name="visible">True</property>+ <child>+ <widget class="GtkMenuItem" id="menuitem1">+ <property name="visible">True</property>+ <property name="label" translatable="yes">_Game</property>+ <property name="use_underline">True</property>+ <child>+ <widget class="GtkMenu" id="menuitem1_menu">+ <child>+ <widget class="GtkMenuItem" id="newGame">+ <property name="visible">True</property>+ <property name="label" translatable="yes">_New Game</property>+ <property name="use_underline">True</property>+ <accelerator key="n" modifiers="GDK_CONTROL_MASK" signal="activate"/>+ </widget>+ </child>+ <child>+ <widget class="GtkSeparatorMenuItem" id="separatormenuitem1">+ <property name="visible">True</property>+ </widget>+ </child>+ <child>+ <widget class="GtkMenuItem" id="quit">+ <property name="visible">True</property>+ <property name="label" translatable="yes">_Quit</property>+ <property name="use_underline">True</property>+ </widget>+ </child>+ </widget>+ </child>+ </widget>+ </child>+ <child>+ <widget class="GtkMenuItem" id="menuitem2">+ <property name="visible">True</property>+ <property name="label" translatable="yes">_Difficulty</property>+ <property name="use_underline">True</property>+ <child>+ <widget class="GtkMenu" id="menuitem1_menu2">+ <child>+ <widget class="GtkMenuItem" id="easy">+ <property name="visible">True</property>+ <property name="label" translatable="yes">_Easy</property>+ <property name="use_underline">True</property>+ </widget>+ </child>+ <child>+ <widget class="GtkSeparatorMenuItem" id="separatormenuitem2">+ <property name="visible">True</property>+ </widget>+ </child>+ <child>+ <widget class="GtkMenuItem" id="medium">+ <property name="visible">True</property>+ <property name="label" translatable="yes">_Medium</property>+ <property name="use_underline">True</property>+ </widget>+ </child>+ <child>+ <widget class="GtkSeparatorMenuItem" id="separatormenuitem6">+ <property name="visible">True</property>+ </widget>+ </child>+ <child>+ <widget class="GtkMenuItem" id="hard">+ <property name="visible">True</property>+ <property name="label" translatable="yes">_Hard</property>+ <property name="use_underline">True</property>+ </widget>+ </child>+ </widget>+ </child>+ </widget>+ </child>+ </widget>+ <packing>+ <property name="expand">False</property>+ <property name="fill">False</property>+ </packing>+ </child>+ <child>+ <widget class="GtkTable" id="table1">+ <property name="visible">True</property>+ <property name="border_width">10</property>+ <property name="n_rows">5</property>+ <property name="n_columns">5</property>+ <child>+ <widget class="GtkHSeparator" id="hseparator1">+ <property name="visible">True</property>+ </widget>+ <packing>+ <property name="right_attach">5</property>+ <property name="top_attach">1</property>+ <property name="bottom_attach">2</property>+ <property name="x_options">GTK_FILL</property>+ <property name="y_options">GTK_FILL</property>+ </packing>+ </child>+ <child>+ <widget class="GtkVSeparator" id="vseparator1">+ <property name="visible">True</property>+ </widget>+ <packing>+ <property name="left_attach">1</property>+ <property name="right_attach">2</property>+ <property name="bottom_attach">5</property>+ <property name="x_options">GTK_FILL</property>+ <property name="y_options">GTK_FILL</property>+ </packing>+ </child>+ <child>+ <widget class="GtkVSeparator" id="vseparator2">+ <property name="visible">True</property>+ </widget>+ <packing>+ <property name="left_attach">3</property>+ <property name="right_attach">4</property>+ <property name="bottom_attach">5</property>+ <property name="x_options">GTK_FILL</property>+ <property name="y_options">GTK_FILL</property>+ </packing>+ </child>+ <child>+ <widget class="GtkButton" id="button1">+ <property name="width_request">100</property>+ <property name="height_request">100</property>+ <property name="visible">True</property>+ <property name="relief">GTK_RELIEF_NONE</property>+ <property name="focus_on_click">False</property>+ <property name="response_id">0</property>+ <child>+ <widget class="GtkImage" id="image1">+ <property name="visible">True</property>+ </widget>+ </child>+ </widget>+ </child>+ <child>+ <widget class="GtkButton" id="button2">+ <property name="width_request">100</property>+ <property name="height_request">100</property>+ <property name="visible">True</property>+ <property name="relief">GTK_RELIEF_NONE</property>+ <property name="focus_on_click">False</property>+ <property name="response_id">0</property>+ <child>+ <widget class="GtkImage" id="image2">+ <property name="visible">True</property>+ </widget>+ </child>+ </widget>+ <packing>+ <property name="left_attach">2</property>+ <property name="right_attach">3</property>+ </packing>+ </child>+ <child>+ <widget class="GtkButton" id="button3">+ <property name="width_request">100</property>+ <property name="height_request">100</property>+ <property name="visible">True</property>+ <property name="relief">GTK_RELIEF_NONE</property>+ <property name="focus_on_click">False</property>+ <property name="response_id">0</property>+ <child>+ <widget class="GtkImage" id="image3">+ <property name="visible">True</property>+ </widget>+ </child>+ </widget>+ <packing>+ <property name="left_attach">4</property>+ <property name="right_attach">5</property>+ </packing>+ </child>+ <child>+ <widget class="GtkButton" id="button4">+ <property name="width_request">100</property>+ <property name="height_request">100</property>+ <property name="visible">True</property>+ <property name="relief">GTK_RELIEF_NONE</property>+ <property name="focus_on_click">False</property>+ <property name="response_id">0</property>+ <child>+ <widget class="GtkImage" id="image4">+ <property name="visible">True</property>+ </widget>+ </child>+ </widget>+ <packing>+ <property name="top_attach">2</property>+ <property name="bottom_attach">3</property>+ </packing>+ </child>+ <child>+ <widget class="GtkButton" id="button5">+ <property name="width_request">100</property>+ <property name="height_request">100</property>+ <property name="visible">True</property>+ <property name="relief">GTK_RELIEF_NONE</property>+ <property name="focus_on_click">False</property>+ <property name="response_id">0</property>+ <child>+ <widget class="GtkImage" id="image5">+ <property name="visible">True</property>+ </widget>+ </child>+ </widget>+ <packing>+ <property name="left_attach">2</property>+ <property name="right_attach">3</property>+ <property name="top_attach">2</property>+ <property name="bottom_attach">3</property>+ </packing>+ </child>+ <child>+ <widget class="GtkButton" id="button6">+ <property name="width_request">100</property>+ <property name="height_request">100</property>+ <property name="visible">True</property>+ <property name="relief">GTK_RELIEF_NONE</property>+ <property name="focus_on_click">False</property>+ <property name="response_id">0</property>+ <child>+ <widget class="GtkImage" id="image6">+ <property name="visible">True</property>+ </widget>+ </child>+ </widget>+ <packing>+ <property name="left_attach">4</property>+ <property name="right_attach">5</property>+ <property name="top_attach">2</property>+ <property name="bottom_attach">3</property>+ </packing>+ </child>+ <child>+ <widget class="GtkButton" id="button7">+ <property name="width_request">100</property>+ <property name="height_request">100</property>+ <property name="visible">True</property>+ <property name="relief">GTK_RELIEF_NONE</property>+ <property name="focus_on_click">False</property>+ <property name="response_id">0</property>+ <child>+ <widget class="GtkImage" id="image7">+ <property name="visible">True</property>+ </widget>+ </child>+ </widget>+ <packing>+ <property name="top_attach">4</property>+ <property name="bottom_attach">5</property>+ </packing>+ </child>+ <child>+ <widget class="GtkButton" id="button8">+ <property name="width_request">100</property>+ <property name="height_request">100</property>+ <property name="visible">True</property>+ <property name="relief">GTK_RELIEF_NONE</property>+ <property name="focus_on_click">False</property>+ <property name="response_id">0</property>+ <child>+ <widget class="GtkImage" id="image8">+ <property name="visible">True</property>+ </widget>+ </child>+ </widget>+ <packing>+ <property name="left_attach">2</property>+ <property name="right_attach">3</property>+ <property name="top_attach">4</property>+ <property name="bottom_attach">5</property>+ </packing>+ </child>+ <child>+ <widget class="GtkButton" id="button9">+ <property name="width_request">100</property>+ <property name="height_request">100</property>+ <property name="visible">True</property>+ <property name="relief">GTK_RELIEF_NONE</property>+ <property name="focus_on_click">False</property>+ <property name="response_id">0</property>+ <child>+ <widget class="GtkImage" id="image9">+ <property name="visible">True</property>+ </widget>+ </child>+ </widget>+ <packing>+ <property name="left_attach">4</property>+ <property name="right_attach">5</property>+ <property name="top_attach">4</property>+ <property name="bottom_attach">5</property>+ </packing>+ </child>+ <child>+ <widget class="GtkHSeparator" id="hseparator2">+ <property name="visible">True</property>+ </widget>+ <packing>+ <property name="right_attach">5</property>+ <property name="top_attach">3</property>+ <property name="bottom_attach">4</property>+ <property name="x_options">GTK_FILL</property>+ <property name="y_options">GTK_FILL</property>+ </packing>+ </child>+ </widget>+ <packing>+ <property name="position">1</property>+ </packing>+ </child>+ <child>+ <widget class="GtkStatusbar" id="statusbar">+ <property name="visible">True</property>+ <property name="has_resize_grip">False</property>+ </widget>+ <packing>+ <property name="expand">False</property>+ <property name="fill">False</property>+ <property name="position">2</property>+ </packing>+ </child>+ </widget>+ </child>+ </widget>+</glade-interface>
+ tic-tac-toe.cabal view
@@ -0,0 +1,17 @@+Name: tic-tac-toe+Version: 0.1+Description: An attempt to implement the minimax algorithm using tic-tac-toe+License: BSD3+License-file: LICENSE+Author: Hristo Asenov+Maintainer: asenov79@students.rowan.edu+Homepage: http://ecks.homeunix.net+Synopsis: Useful if reading "Why FP matters" by John Hughes+Category: Game+build-type: Simple+Data-files: Cross.png, Nought.png, noughty.glade+Extra-source-files: MyTree.hs, GetAgentMove.hs, GetValue.hs, DataTypes.hs, README+Build-Depends: base, gtk, glade, haskell98++Executable: tic-tac-toe+Main-is: NoughtyGlade.hs