summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGuillaume Seguin <guillaume@segu.in>2007-07-24 04:28:42 +0200
committerGuillaume Seguin <guillaume@segu.in>2007-07-24 04:28:42 +0200
commit903c2f089cc8abb1d37dffcde8870cb01048b9a1 (patch)
treede8038f93eca736d14ddd69568770938329db27a
downloadtictactoe-903c2f089cc8abb1d37dffcde8870cb01048b9a1.tar.gz
tictactoe-903c2f089cc8abb1d37dffcde8870cb01048b9a1.tar.bz2
* Fully working tictactoe, less than 1 hour and a half
-rw-r--r--tictactoe.glade154
-rwxr-xr-xtictactoe.py257
2 files changed, 411 insertions, 0 deletions
diff --git a/tictactoe.glade b/tictactoe.glade
new file mode 100644
index 0000000..8c91f23
--- /dev/null
+++ b/tictactoe.glade
@@ -0,0 +1,154 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd">
+<!--Generated with glade3 3.3.1 on Tue Jul 24 04:27:42 2007 by ixce@ed3n-m-->
+<glade-interface>
+ <widget class="GtkWindow" id="mainWindow">
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="title" translatable="yes">pyTicTacToe</property>
+ <property name="resizable">False</property>
+ <property name="window_position">GTK_WIN_POS_CENTER</property>
+ <signal name="delete_event" handler="gtk_main_quit"/>
+ <child>
+ <widget class="GtkVBox" id="mainVBox">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <child>
+ <widget class="GtkToolbar" id="toolBar">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="icon_size">GTK_ICON_SIZE_SMALL_TOOLBAR</property>
+ <child>
+ <widget class="GtkToolButton" id="newButton">
+ <property name="visible">True</property>
+ <property name="stock_id">gtk-new</property>
+ <signal name="clicked" handler="new_game"/>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkToolButton" id="quitButton">
+ <property name="visible">True</property>
+ <property name="stock_id">gtk-quit</property>
+ <signal name="clicked" handler="gtk_main_quit"/>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkToolButton" id="aboutButton">
+ <property name="visible">True</property>
+ <property name="stock_id">gtk-about</property>
+ <signal name="clicked" handler="show_about"/>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkAspectFrame" id="mainFrame">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">GTK_SHADOW_NONE</property>
+ <property name="ratio">1</property>
+ <child>
+ <placeholder/>
+ </child>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkStatusbar" id="statusBar">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="spacing">2</property>
+ <property name="has_resize_grip">False</property>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <widget class="GtkAboutDialog" id="aboutDialog">
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="border_width">5</property>
+ <property name="title" translatable="yes">À propos de Glade</property>
+ <property name="resizable">False</property>
+ <property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property>
+ <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+ <property name="has_separator">False</property>
+ <property name="program_name">pyTicTacToe</property>
+ <property name="version">0.1</property>
+ <property name="copyright" translatable="yes">Copyright (c) 2007 Guillaume Seguin &lt;guillaume@segu.in&gt;</property>
+ <signal name="close" handler="close_about"/>
+ <signal name="delete_event" handler="close_about"/>
+ <signal name="response" handler="close_about"/>
+ <child internal-child="vbox">
+ <widget class="GtkVBox" id="dialog-vbox1">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="spacing">2</property>
+ <child>
+ <placeholder/>
+ </child>
+ <child internal-child="action_area">
+ <widget class="GtkHButtonBox" id="dialog-action_area1">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="layout_style">GTK_BUTTONBOX_END</property>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <widget class="GtkMessageDialog" id="messageDialog">
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="border_width">5</property>
+ <property name="title" translatable="yes">pyTicTacToe</property>
+ <property name="resizable">False</property>
+ <property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property>
+ <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+ <property name="skip_taskbar_hint">True</property>
+ <property name="transient_for">mainWindow</property>
+ <property name="has_separator">False</property>
+ <property name="buttons">GTK_BUTTONS_OK</property>
+ <signal name="close" handler="close_message_dialog"/>
+ <signal name="delete_event" handler="close_message_dialog"/>
+ <signal name="response" handler="close_message_dialog"/>
+ <child internal-child="vbox">
+ <widget class="GtkVBox" id="dialog-vbox2">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="spacing">2</property>
+ <child internal-child="action_area">
+ <widget class="GtkHButtonBox" id="dialog-action_area2">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="layout_style">GTK_BUTTONBOX_END</property>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+</glade-interface>
diff --git a/tictactoe.py b/tictactoe.py
new file mode 100755
index 0000000..dd18183
--- /dev/null
+++ b/tictactoe.py
@@ -0,0 +1,257 @@
+#!/usr/bin/env python
+# coding=utf-8
+
+"""
+pyTicTacToe
+Author : Guillaume "iXce" Seguin
+Email : guillaume@segu.in
+
+ # Python Tic Tac Toe #
+
+Copyright (C) 2007 Guillaume Seguin
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+"""
+
+import pygtk
+pygtk.require ('2.0')
+import gtk
+import gtk.glade
+
+import random
+import cairo
+
+def autoconnect (ob):
+ '''Autoconnect every member from ob to its glade interface'''
+ handlers = {}
+ for i in dir (ob):
+ handlers[i] = getattr (ob, i)
+ ob.glade.signal_autoconnect (handlers);
+
+NO_WINNER = -1
+PLAYER_ID = 1
+COMPUTER_ID = 2
+
+class gtkTicTacToe (gtk.DrawingArea):
+
+ _parent = None
+ _surface = None
+ _map = None
+ _finished = False
+
+ def __init__ (self, side = 300, parent = None):
+ '''Prepare widget'''
+ super (gtkTicTacToe, self).__init__ ()
+ self._parent = parent
+ self.add_events (gtk.gdk.BUTTON_PRESS_MASK)
+ self.connect ("expose_event", self.expose)
+ self.connect ("button_press_event", self.button_press)
+ self.set_size_request (side, side)
+ self.new_game ()
+
+ def new_game (self):
+ '''Reset game'''
+ self._map = [[0] * 3, [0] * 3, [0] * 3]
+ self._finished = False
+ if self._surface:
+ self.redraw (queue = True)
+
+ def empty_cells (self):
+ '''Return the number of empty cells'''
+ empty = filter (lambda x: not x, self._map[0])
+ empty += filter (lambda x: not x, self._map[1])
+ empty += filter (lambda x: not x, self._map[2])
+ return len (empty)
+
+ def check (self):
+ '''Check if game is finished'''
+ for i in range (3):
+ xv = self._map[i][0]
+ yv = self._map[0][i]
+ if xv and self._map[i][1] == xv and self._map[i][2] == xv:
+ return xv
+ if yv and self._map[1][i] == yv and self._map[2][i] == yv:
+ return yv
+ diag1v = self._map[0][0]
+ diag2v = self._map[0][2]
+ if diag1v and self._map[1][1] == diag1v and self._map[2][2] == diag1v:
+ return diag1v
+ if diag2v and self._map[1][1] == diag2v and self._map[2][0] == diag2v:
+ return diag2v
+ if not self.empty_cells ():
+ return NO_WINNER
+ return False
+
+ def computer_turn (self):
+ '''Computer IA - yes, it's _very_ random'''
+ cells = self.empty_cells ()
+ cell = random.randint (1, cells)
+ for j in range (3):
+ for i in range (3):
+ if not self._map[i][j]:
+ cell -= 1
+ if cell == 0:
+ self._map[i][j] = COMPUTER_ID
+ break
+ if cell == 0:
+ break
+ self.redraw (queue = True)
+
+ def button_press (self, widget, event):
+ '''Check if the cursor clicked an interesting area'''
+ if self._finished:
+ return
+ alloc = self.get_allocation ()
+ side = min (alloc.width, alloc.height)
+ cell_side = int (float (side) / 3)
+ i = (event.x - (event.x % cell_side)) / cell_side
+ j = (event.y - (event.y % cell_side)) / cell_side
+ i, j = int (i), int (j)
+ if not self._map[i][j] and not self.check ():
+ self._map[i][j] = PLAYER_ID
+ self.redraw (queue = True)
+ if not self.check ():
+ self.computer_turn ()
+ winner = self.check ()
+ if winner:
+ self._finished = True
+ self.redraw (queue = True)
+ if not self._parent:
+ return
+ if winner == PLAYER_ID:
+ status = "You win"
+ elif winner == COMPUTER_ID:
+ status = "You fail"
+ elif winner == NO_WINNER:
+ status = "Draw!"
+ self._parent.push_status (status)
+ self._parent.open_message_dialog (status)
+
+ def redraw (self, queue = False):
+ '''Redraw internal surface'''
+ alloc = self.get_allocation ()
+ side = min (alloc.width, alloc.height)
+ cell_side = int (float (side) / 3)
+ self._surface = cairo.ImageSurface (cairo.FORMAT_ARGB32, side, side)
+ cr = cairo.Context (self._surface)
+ # Draw background
+ cr.set_source_rgb (1, 1, 1)
+ cr.paint ()
+ # Draw cells
+ for i in range (3):
+ for j in range (3):
+ if not self._map[i][j]:
+ continue
+ if self._map[i][j] == PLAYER_ID:
+ cr.set_source_rgb (0, 1, 0)
+ else:
+ cr.set_source_rgb (1, 0, 0)
+ cr.rectangle (cell_side * i, cell_side * j,
+ cell_side, cell_side)
+ cr.fill ()
+ # Draw borders
+ cr.set_source_rgb (0, 0, 0)
+ cr.move_to (cell_side, 0)
+ cr.line_to (cell_side, side)
+ cr.move_to (cell_side * 2, 0)
+ cr.line_to (cell_side * 2, side)
+ cr.move_to (0, cell_side)
+ cr.line_to (side, cell_side)
+ cr.move_to (0, cell_side * 2)
+ cr.line_to (side, cell_side * 2)
+ cr.stroke ()
+ if self._finished:
+ cr.set_source_rgba (0.8, 0.8, 0.8, 0.5)
+ cr.paint ()
+ if queue:
+ self.queue_draw ()
+
+ def expose (self, widget, event):
+ '''Expose event handler'''
+ cr = self.window.cairo_create ()
+ if not self._surface:
+ self.redraw ()
+ cr.set_source_surface (self._surface)
+ cr.rectangle (event.area.x, event.area.y,
+ event.area.width, event.area.height)
+ cr.clip ()
+ cr.paint ()
+ return False
+
+class pyTicTacToe:
+
+ glade = None
+ mainWindow = None
+ aboutDialog = None
+ mainFrame = None
+ statusBar = None
+ messageDialog = None
+ tictactoe = None
+
+ def __init__ (self):
+ '''Initialize application'''
+ self.glade = gtk.glade.XML (fname = "tictactoe.glade")
+ self.mainWindow = self.glade.get_widget ("mainWindow")
+ self.aboutDialog = self.glade.get_widget ("aboutDialog")
+ self.mainFrame = self.glade.get_widget ("mainFrame")
+ self.statusBar = self.glade.get_widget ("statusBar")
+ self.messageDialog = self.glade.get_widget ("messageDialog")
+ self.glade.get_widget ("toolBar").set_style (gtk.TOOLBAR_ICONS)
+ autoconnect (self)
+ self.tictactoe = gtkTicTacToe (parent = self)
+ self.mainFrame.add (self.tictactoe)
+
+ def new_game (self, *args):
+ '''Starts a new game'''
+ self.push_status ("")
+ self.tictactoe.new_game ()
+
+ def push_status (self, text):
+ '''Update status bar message'''
+ context = self.statusBar.get_context_id ("main")
+ self.statusBar.pop (context)
+ self.statusBar.push (context, text)
+
+ def show (self):
+ '''Show application'''
+ self.mainWindow.show_all ()
+
+ def gtk_main_quit (self, *args):
+ '''Quit gtk main loop'''
+ gtk.main_quit ()
+
+ def show_about (self, *args):
+ '''Show about dialog'''
+ self.aboutDialog.show ()
+
+ def close_about (self, *args):
+ '''Hide about dialog'''
+ self.aboutDialog.hide ()
+ return True
+
+ def open_message_dialog (self, text):
+ '''Open the message dialog and set the message'''
+ self.messageDialog.set_markup ("<b>%s</b>" % text)
+ self.messageDialog.show ()
+
+ def close_message_dialog (self, *args):
+ '''Close message dialog'''
+ self.messageDialog.hide ()
+ return False
+
+if __name__ == "__main__":
+ game = pyTicTacToe ()
+ game.show ()
+ gtk.main ()