packages feed

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 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