diff options
-rw-r--r-- | PathsActivity.py | 139 | ||||
-rw-r--r-- | activity/activity-paths.svg | 37 | ||||
-rw-r--r-- | activity/activity.info | 8 | ||||
-rw-r--r-- | card.py | 69 | ||||
-rw-r--r-- | deck.py | 140 | ||||
-rw-r--r-- | game.py | 256 | ||||
-rwxr-xr-x | genpieces.py | 208 | ||||
-rw-r--r-- | grid.py | 147 | ||||
-rwxr-xr-x | path.py | 68 | ||||
-rw-r--r-- | sprites.py | 453 |
10 files changed, 1525 insertions, 0 deletions
diff --git a/PathsActivity.py b/PathsActivity.py new file mode 100644 index 0000000..3e97940 --- /dev/null +++ b/PathsActivity.py @@ -0,0 +1,139 @@ +#Copyright (c) 2011 Walter Bender + +# 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 3 of the License, or +# (at your option) any later version. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the +# Free Software Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. + +import gtk +import gobject + +import sugar +from sugar.activity import activity +try: + from sugar.graphics.toolbarbox import ToolbarBox + _have_toolbox = True +except ImportError: + _have_toolbox = False + +if _have_toolbox: + from sugar.bundle.activitybundle import ActivityBundle + from sugar.activity.widgets import ActivityToolbarButton + from sugar.activity.widgets import StopButton + from sugar.graphics.toolbarbox import ToolbarButton + +from sugar.graphics.toolbutton import ToolButton +from sugar.graphics.menuitem import MenuItem +from sugar.graphics.icon import Icon +from sugar.datastore import datastore + +from gettext import gettext as _ +import locale +import os.path + +from game import Game + +SERVICE = 'org.sugarlabs.PathsActivity' +IFACE = SERVICE +PATH = '/org/augarlabs/PathsActivity' + + +def _button_factory(icon_name, tooltip, callback, toolbar, cb_arg=None, + accelerator=None): + """Factory for making toolbar buttons""" + my_button = ToolButton(icon_name) + my_button.set_tooltip(tooltip) + my_button.props.sensitive = True + if accelerator is not None: + my_button.props.accelerator = accelerator + if cb_arg is not None: + my_button.connect('clicked', callback, cb_arg) + else: + my_button.connect('clicked', callback) + if hasattr(toolbar, 'insert'): # the main toolbar + toolbar.insert(my_button, -1) + else: # or a secondary toolbar + toolbar.props.page.insert(my_button, -1) + my_button.show() + return my_button + + +def _label_factory(label, toolbar): + """ Factory for adding a label to a toolbar """ + my_label = gtk.Label(label) + my_label.set_line_wrap(True) + my_label.show() + _toolitem = gtk.ToolItem() + _toolitem.add(my_label) + toolbar.insert(_toolitem, -1) + _toolitem.show() + return my_label + + +def _separator_factory(toolbar, visible=True, expand=False): + """ Factory for adding a separator to a toolbar """ + _separator = gtk.SeparatorToolItem() + _separator.props.draw = visible + _separator.set_expand(expand) + toolbar.insert(_separator, -1) + _separator.show() + + +class PathsActivity(activity.Activity): + """ Path puzzle game """ + + def __init__(self, handle): + """ Initialize the toolbars and the game board """ + super(PathsActivity,self).__init__(handle) + + self._setup_toolbars(_have_toolbox) + + # Create a canvas + canvas = gtk.DrawingArea() + canvas.set_size_request(gtk.gdk.screen_width(), \ + gtk.gdk.screen_height()) + self.set_canvas(canvas) + canvas.show() + self.show_all() + + self.game = Game(canvas, self) + self.game.new_game() + + def _setup_toolbars(self, have_toolbox): + """ Setup the toolbars.. """ + + if have_toolbox: + toolbox = ToolbarBox() + + # Activity toolbar + activity_button = ActivityToolbarButton(self) + + toolbox.toolbar.insert(activity_button, 0) + activity_button.show() + + self.set_toolbar_box(toolbox) + toolbox.show() + toolbar = toolbox.toolbar + + else: + # Use pre-0.86 toolbar design + games_toolbar = gtk.Toolbar() + toolbox = activity.ActivityToolbox(self) + self.set_toolbox(toolbox) + toolbox.add_toolbar(_('Game'), games_toolbar) + toolbox.show() + toolbox.set_current_toolbar(1) + toolbar = games_toolbar + + if _have_toolbox: + _separator_factory(toolbox.toolbar, False, True) + + stop_button = StopButton(self) + stop_button.props.accelerator = '<Ctrl>q' + toolbox.toolbar.insert(stop_button, -1) + stop_button.show() diff --git a/activity/activity-paths.svg b/activity/activity-paths.svg new file mode 100644 index 0000000..0ce63ea --- /dev/null +++ b/activity/activity-paths.svg @@ -0,0 +1,37 @@ +<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd' [ + <!ENTITY stroke_color "#000"> + <!ENTITY fill_color "#eee"> +]><svg height="55px" viewBox="0 0 55 55" width="55px" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" stroke-linecap="round" stroke-linejoin="round" stroke-width="3.5" stroke="&stroke_color;" fill="&fill_color;"> + <g + transform="translate(-133.34375,-475.4375)"> + <path + d="m 138.21806,502.9375 45.25138,0" + fill="none;" stroke="&stroke_color;" stroke-width="10" stroke-linecap="square" /> + <path + d="m 160.84375,525.57355 0,-45.2721" + fill="none;" stroke="&stroke_color;" stroke-width="10" stroke-linecap="square" /> + <path + d="m 138.21806,503.00075 45.25138,0" + fill="none;" stroke="&stroke_color;" stroke-width="10" stroke-linecap="square" /> + <g + transform="matrix(1.0,0,0,1.0,-0.62570935,0.77971155)" + stroke="&stroke_color;"> + <path + d="m 138.90476,502.66172 21.98482,0" + fill="none;" stroke="&stroke_color;" stroke-width="10" stroke-linecap="square" /> + <path + d="m 160.88958,524.66172 0,-22" + fill="none;" stroke="&stroke_color;" stroke-width="10" stroke-linecap="square" /> + </g> + <path + d="m 138.34375,502.93751 18.46999,0" + fill="none;" stroke="&stroke_color;" stroke-width="10" stroke-linecap="square" /> + <path + d="M 0,27.5 27.5,0" + transform="translate(133.34375,475.4375)" + fill="none;" stroke="&fill_color;" stroke-width="10" stroke-linecap="square" /> + <path + d="m 160.84375,530.4375 27.5,-27.5" + fill="none;" stroke="&fill_color;" stroke-width="10" stroke-linecap="square" /> + </g> +</svg> diff --git a/activity/activity.info b/activity/activity.info new file mode 100644 index 0000000..c498a46 --- /dev/null +++ b/activity/activity.info @@ -0,0 +1,8 @@ +[Activity] +name = Paths +activity_version = 1 +license = GPLv3 +bundle_id = org.sugarlabs.PathsActivity +exec = sugar-activity PathsActivity.PathsActivity +icon = activity-paths +show_launcher = yes @@ -0,0 +1,69 @@ +#Copyright (c) 2009-11 Walter Bender + +# 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 3 of the License, or +# (at your option) any later version. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the +# Free Software Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. + +import gtk +from sprites import Sprite + +N = 0 +E = N + 1 +S = E + 1 +W = S + 1 + + +class Card: + + def __init__(self, sprites, svg_string, card_type='tile'): + self.spr = Sprite(sprites, 0, 0, svg_str_to_pixbuf(svg_string)) + self.connections = [] # [N, E, S, W] + self.card_type = card_type + + def set_connections(self, connections): + self.connections = connections[:] + + def get_connections(self): + return self.connections + + def rotate_clockwise(self): + """ rotate the card and its connections """ + west = self.connections[W] + self.connections[W] = self.connections[S] + self.connections[S] = self.connections[E] + self.connections[E] = self.connections[N] + self.connections[N] = west + self.spr.images[0] = self.spr.images[0].rotate_simple(270) + self.spr.draw() + + def show_card(self): + self.spr.set_layer(2000) + self.spr.draw() + + def hide_card(self): + self.spr.hide() + + +# +# Load pixbuf from SVG string +# +def svg_str_to_pixbuf(svg_string): + pl = gtk.gdk.PixbufLoader('svg') + pl.write(svg_string) + pl.close() + pixbuf = pl.get_pixbuf() + return pixbuf + +# +# Create an error card +# +from genpieces import generate_x + +def error_card(sprites): + return Sprite(sprites, 0, 0, svg_str_to_pixbuf(generate_x(0.5))) @@ -0,0 +1,140 @@ +#Copyright (c) 2009-11 Walter Bender + +# 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 3 of the License, or +# (at your option) any later version. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the +# Free Software Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. + +import gtk +from random import randrange + +from card import Card +from genpieces import generate_tile_1_line, generate_tile_2_lines, \ + generate_board + + +class Deck: + ''' Class for defining deck of card ''' + + def __init__(self, sprites, scale): + ''' Create the deck of cards. ''' + self.cards = [] + for a in range(16): + self.cards.append(Card(sprites, generate_tile_1_line( + -1, 0, 0, 0, scale))) + self.cards[-1].set_connections([0, 0, 0, 1]) + for a in range(4): + self.cards.append(Card(sprites, generate_tile_1_line( + -1, 0, 1, 0, scale))) + self.cards[-1].set_connections([0, 1, 0, 1]) + for a in range(12): + self.cards.append(Card(sprites, generate_tile_2_lines( + -1, 0, 1, 0, 0, 0, 0, 1, scale))) + self.cards[-1].set_connections([0, 1, 1, 1]) + for a in range(16): + self.cards.append(Card(sprites, generate_tile_2_lines( + -1, 0, 0, 0, 0, -1, 0, 0, scale))) + self.cards[-1].set_connections([1, 0, 0, 1]) + for a in range(4): + self.cards.append(Card(sprites, generate_tile_2_lines( + -1, 0, 1, 0, 0, -1, 0, 1, scale))) + self.cards[-1].set_connections([1, 1, 1, 1]) + for a in range(8): + self.cards.append(Card(sprites, generate_tile_2_lines( + -1, 0, 0, 1, 0, -1, 1, 0, scale))) + self.cards[-1].set_connections([1, 1, 1, 1]) + for a in range(4): + self.cards.append(Card(sprites, generate_tile_2_lines( + -1, 0, 0, 0, 0, -1, 1, 0, scale))) + self.cards[-1].set_connections([1, 1, 0, 1]) + # Remember the current position in the deck. + self.index = 0 + + # And a playing surface + self.board = Card(sprites, generate_board(scale), card_type='board') + self.board.spr.set_layer(1) + + def shuffle(self): + ''' Shuffle the deck (Knuth algorithm). ''' + decksize = self.count() + # Hide all the cards. + for c in self.cards: + c.hide_card() + # Randomize the card order. + for n in range(decksize): + i = randrange(decksize - n) + self.swap_cards(n, decksize - 1 - i) + # Reset the index to the beginning of the deck after a shuffle, + self.index = 0 + self.hide() + return + + def restore(self, saved_deck_indices): + ''' Restore the deck upon resume. ''' + decksize = len(saved_deck_indices) + # If we have a short deck, then we need to abort. + if self.count() < decksize: + return False + _deck = [] + for i in saved_deck_indices: + _deck.append(self.index_to_card(i)) + for i in range(decksize): + self.cards[i] = _deck[i] + return True + + def swap_cards(self,i,j): + ''' Swap the position of two cards in the deck. ''' + tmp = self.cards[j] + self.cards[j] = self.cards[i] + self.cards[i] = tmp + return + + def spr_to_card(self, spr): + ''' Given a sprite, find the corresponding card in the deck. ''' + if spr == self.board.spr: + return self.board + for c in self.cards: + if c.spr == spr: + return c + return None + + def index_to_card(self, i): + ''' Given a card index, find the corresponding card in the deck. ''' + for c in self.cards: + if c.index == i: + return c + return None + + def deal_next_card(self): + ''' Return the next card from the deck. ''' + if self.empty(): + return None + next_card = self.cards[self.index] + self.index += 1 + return next_card + + def empty(self): + ''' Is the deck empty? ''' + if self.cards_remaining() > 0: + return False + else: + return True + + def cards_remaining(self): + ''' Return how many cards are remaining in the deck. ''' + return(self.count()-self.index) + + def hide(self): + ''' Hide the deck. ''' + for c in self.cards: + if c is not None: + c.hide_card() + + def count(self): + ''' Return the length of the deck. ''' + return len(self.cards) @@ -0,0 +1,256 @@ +# -*- coding: utf-8 -*- +#Copyright (c) 2011 Walter Bender + +# 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 3 of the License, or +# (at your option) any later version. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the +# Free Software Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. + +import gtk +from gettext import gettext as _ +import logging +_logger = logging.getLogger('paths-activity') + +try: + from sugar.graphics import style + GRID_CELL_SIZE = style.GRID_CELL_SIZE +except ImportError: + GRID_CELL_SIZE = 0 + +from grid import Grid +from deck import Deck +from card import error_card +from sprites import Sprites + +N = 0 +E = N + 1 +S = E + 1 +W = S + 1 +CARD_WIDTH = 55 +CARD_HEIGHT = 55 +ROW = 8 +COL = 8 + + +class Game(): + + def __init__(self, canvas, parent=None): + self.activity = parent + + # Starting from command line + if parent is None: + self.sugar = False + self.canvas = canvas + # Starting from Sugar + else: + self.sugar = True + self.canvas = canvas + parent.show_all() + + self.canvas.set_flags(gtk.CAN_FOCUS) + self.canvas.add_events(gtk.gdk.BUTTON_PRESS_MASK) + self.canvas.add_events(gtk.gdk.BUTTON_RELEASE_MASK) + self.canvas.connect("expose-event", self._expose_cb) + self.canvas.connect("button-press-event", self._button_press_cb) + self.canvas.connect("button-release-event", self._button_release_cb) + self.canvas.connect("key_press_event", self._keypress_cb) + self.width = gtk.gdk.screen_width() + self.height = gtk.gdk.screen_height()-GRID_CELL_SIZE + self.scale = self.height / (8.0 * CARD_HEIGHT) + self.card_width = CARD_WIDTH * self.scale + self.card_height = CARD_HEIGHT * self.scale + self.sprites = Sprites(self.canvas) + self.last_spr_moved = [] + self.errormsg = [] + + for i in range(4): + self.errormsg.append(error_card(self.sprites)) + + def new_game(self, saved_state=None, deck_index=0): + ''' Start a new game. ''' + + # If there is already a deck, hide it. + if hasattr(self, 'deck'): + self.deck.hide() + + # Initialize the grid and create a deck of cards. + if not hasattr(self, 'grid'): + self.grid = Grid(self.width, self.height, self.card_width, + self.card_height) + + if not hasattr(self, 'deck'): + self.deck = Deck(self.sprites, self.scale) + self.deck.board.spr.move((self.grid.left, self.grid.top)) + + # Shuffle the deck and deal a hand of tiles. + self.deck.shuffle() + self.grid.deal(self.deck) + self.last_spr_move = [None] + self._hide_errormsgs() + + def _button_press_cb(self, win, event): + win.grab_focus() + x, y = map(int, event.get_coords()) + self.start_drag = [x, y] + + self._hide_errormsgs() + + spr = self.sprites.find_sprite((x, y)) + if spr is None or spr == self.deck.board.spr: + self.press = None + self.release = None + return True + if self.grid.spr_to_hand(spr) is not None: + self.last_spr_moved.append(spr) + if spr != self.last_spr_moved[-1]: + self.press = None + self.release = None + return True + self.press = spr + return True + + def _button_release_cb(self, win, event): + win.grab_focus() + + if self.press is None: + return + + x, y = map(int, event.get_coords()) + spr = self.sprites.find_sprite((x, y)) + + if spr is None: # Returning tile to hand + i = self.grid.find_empty_slot() + if i is not None: + card = self.deck.spr_to_card(self.press) + card.spr.move(self.grid.hand_to_xy(i)) + self.grid.hand[i] = card + if self.grid.spr_to_grid(self.press) is not None: + self.grid.grid[self.grid.spr_to_grid(self.press)] = None + if spr in self.last_spr_moved: + self.last_spr_moved.remove(spr) + self.press = None + self.release = None + return True + + self.release = spr + if self.press == self.release: + + card = self.deck.spr_to_card(spr) + card.rotate_clockwise() + + if self.last_spr_moved[-1] != card.spr: + self.last_spr_moved.append(card.spr) + + elif self.release == self.deck.board.spr: + card = self.deck.spr_to_card(self.press) + card.spr.move(self.grid.grid_to_xy(self.grid.xy_to_grid(x, y))) + + i = self.grid.spr_to_grid(self.press) + if i is not None: + self.grid.grid[i] = None + self.grid.grid[self.grid.xy_to_grid(x, y)] = card + + i = self.grid.spr_to_hand(self.press) + if i is not None: + self.grid.hand[i] = None + + if self.last_spr_moved[-1] != card.spr: + self.last_spr_moved.append(card.spr) + + self._test_for_bad_paths() + self.press = None + self.release = None + + if self.grid.cards_in_hand() == 0: + self.grid.redeal(self.deck) + return True + + def _game_over(self): + pass + + def _test_for_bad_paths(self): + ''' Is there a path to no where? ''' + i = self.grid.spr_to_grid(self.press) + if i is not None: + self._check_north(i) + self._check_east(i) + self._check_south(i) + self._check_west(i) + + def _check_north(self, i): + # Is it in the top row? + if int(i / COL) == 0: + if self.grid.grid[i].connections[N] == 1: + self._error(i, N) + else: + if self.grid.grid[i-COL] is not None: + if self.grid.grid[i].connections[N] != \ + self.grid.grid[i-COL].connections[S]: + self._error(i, N) + + def _check_east(self, i): + # Is it in the right column? + if int(i % ROW) == ROW - 1: + if self.grid.grid[i].connections[E] == 1: + self._error(i, E) + else: + if self.grid.grid[i+1] is not None: + if self.grid.grid[i].connections[E] != \ + self.grid.grid[i+1].connections[W]: + self._error(i, E) + + def _check_south(self, i): + # Is it in the bottom row? + if int(i / COL) == COL - 1: + if self.grid.grid[i].connections[S] == 1: + self._error(i, S) + else: + if self.grid.grid[i+COL] is not None: + if self.grid.grid[i].connections[S] != \ + self.grid.grid[i+COL].connections[N]: + self._error(i, S) + + def _check_west(self, i): + # Is it in the left column? + if int(i % ROW) == 0: + if self.grid.grid[i].connections[W] == 1: + self._error(i, W) + else: + if self.grid.grid[i-1] is not None: + if self.grid.grid[i].connections[W] != \ + self.grid.grid[i-1].connections[E]: + self._error(i, W) + + def _error(self, i, direction): + ''' Display an error message where and when appropriate. ''' + offsets = [[0.375, -0.125], [0.875, 0.375], [0.375, 0.875], + [-0.125, 0.375]] + x, y = self.press.get_xy() + self.errormsg[direction].move( + (x + offsets[direction][0] * self.card_width, + y + offsets[direction][1] * self.card_height)) + self.errormsg[direction].set_layer(3000) + + def _hide_errormsgs(self): + ''' Hide all the error messages. ''' + for i in range(4): + self.errormsg[i].move((self.grid.left, self.grid.top)) + self.errormsg[i].set_layer(0) + + # + # Callbacks + # + def _keypress_cb(self, area, event): + return True + + def _expose_cb(self, win, event): + self.sprites.redraw_sprites() + return True + + def _destroy_cb(self, win, event): + gtk.main_quit() diff --git a/genpieces.py b/genpieces.py new file mode 100755 index 0000000..d365e78 --- /dev/null +++ b/genpieces.py @@ -0,0 +1,208 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +#Copyright (c) 2009,10 Walter Bender + +# 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 3 of the License, or +# (at your option) any later version. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the +# Free Software Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. + +import os + +# +# SVG generators +# +class SVG: + def __init__(self): + self._scale = 1 + self._stroke_width = 1 + self._fill = "#FF0000" + self._stroke = "A00000" + + def _svg_style(self, extras=""): + return "%s%s%s%s%s%f%s%s%s" % ("style=\"fill:", self._fill, ";stroke:", + self._stroke, ";stroke-width:", + self._stroke_width, ";", extras, + "\" />\n") + + def _svg_line(self, x1, y1, x2, y2): + svg_string = "<line x1=\"%f\" y1=\"%f\" x2=\"%f\" y2=\"%f\"\n" % \ + (x1, y1, x2, y2) + svg_string += self._svg_style("stroke-linecap:square;") + return svg_string + + def _svg_rect(self, w, h, rx, ry, x, y): + svg_string = " <rect\n" + svg_string += " width=\"%f\"\n" % (w) + svg_string += " height=\"%f\"\n" % (h) + svg_string += " rx=\"%f\"\n" % (rx) + svg_string += " ry=\"%f\"\n" % (ry) + svg_string += " x=\"%f\"\n" % (x) + svg_string += " y=\"%f\"\n" % (y) + self.set_stroke_width(1.0) + svg_string += self._svg_style() + return svg_string + + def _svg_x(self, w, h): + self.set_stroke_width(10.0) + svg_string = self._svg_line(0, 0, w, h) + svg_string += self._svg_line(0, h, w, 0) + return svg_string + + def _background(self, scale): + return self._svg_rect(54.5 * scale, 54.5 * scale, 4, 4, 0.25, 0.25) + + def header(self, scale=1, background=True): + svg_string = "<?xml version=\"1.0\" encoding=\"UTF-8\"" + svg_string += " standalone=\"no\"?>\n" + svg_string += "<!-- Created with Emacs -->\n" + svg_string += "<svg\n" + svg_string += " xmlns:svg=\"http://www.w3.org/2000/svg\"\n" + svg_string += " xmlns=\"http://www.w3.org/2000/svg\"\n" + svg_string += " version=\"1.0\"\n" + svg_string += "%s%f%s" % (" width=\"", scale * 55 * self._scale, + "\"\n") + svg_string += "%s%f%s" % (" height=\"", scale * 55 * self._scale, + "\">\n") + svg_string += "%s%f%s%f%s" % ("<g\n transform=\"matrix(", + self._scale, ",0,0,", self._scale, + ",0,0)\">\n") + if background: + svg_string += self._background(scale) + return svg_string + + def footer(self): + svg_string = "</g>\n" + svg_string += "</svg>\n" + return svg_string + + # + # Utility functions + # + def set_scale(self, scale=1.0): + self._scale = scale + + def set_colors(self, colors): + self._stroke = colors[0] + self._fill = colors[1] + + def set_stroke_width(self, stroke_width=1.0): + self._stroke_width = stroke_width + + # + # Card pattern generators + # + + def path(self, a, b, c, d): + x1 = a * 27.5 + 27.5 + y1 = b * 27.5 + 27.5 + x2 = c * 27.5 + 27.5 + y2 = d * 27.5 + 27.5 + self.set_stroke_width(10) + svg_string = self._svg_line(x1, y1, x2, y2) + return svg_string + +# +# Card generators +# +def generate_x(scale=1): + svg = SVG() + svg.set_scale(scale) + svg.set_colors(["#FF0000", "#FF0000"]) + svg_string = svg.header(background=False) + svg_string += svg._svg_x(55, 55) + svg_string += svg.footer() + return svg_string + +def generate_board(scale=1): + svg = SVG() + svg.set_scale(scale) + svg.set_colors(["#000000", "#FFFFFF"]) + svg_string = svg.header(scale=8) # board is 8x8 tiles + svg_string += svg.footer() + return svg_string + +def generate_tile_1_line(a, b, c, d, scale=1): + svg = SVG() + svg.set_scale(scale) + svg.set_colors(["#000000", "#FFFFFF"]) + svg_string = svg.header() + svg_string += svg.path(a, b, c, d) + svg_string += svg.footer() + return svg_string + +def generate_tile_2_lines(a, b, c, d, e, f, g, h, scale=1): + svg = SVG() + svg.set_scale(scale) + svg.set_colors(["#000000", "#FFFFFF"]) + svg_string = svg.header() + svg_string += svg.path(a, b, c, d) + svg_string += svg.path(e, f, g, h) + svg_string += svg.footer() + return svg_string + +# +# Command line utilities used for testing purposed only +# +def open_file(datapath, filename): + return file(os.path.join(datapath, filename), "w") + +def close_file(f): + f.close() + +def generator(datapath): + i = 0 + filename = "tile-%d.svg" % (i) + f = open_file(datapath, filename) + f.write(generate_tile_1_line(-1, 0, 0, 0)) + close_file(f) + i += 1 + filename = "tile-%d.svg" % (i) + f = open_file(datapath, filename) + f.write(generate_tile_1_line(-1, 0, 1, 0)) + i += 1 + close_file(f) + filename = "tile-%d.svg" % (i) + f = open_file(datapath, filename) + f.write(generate_tile_2_lines(-1, 0, 1, 0, 0, 0, 0, 1)) + i += 1 + close_file(f) + filename = "tile-%d.svg" % (i) + f = open_file(datapath, filename) + f.write(generate_tile_2_lines(-1, 0, 0, 0, 0, -1, 0, 0)) + i += 1 + close_file(f) + filename = "tile-%d.svg" % (i) + f = open_file(datapath, filename) + f.write(generate_tile_2_lines(-1, 0, 1, 0, 0, -1, 0, 1)) + i += 1 + close_file(f) + filename = "tile-%d.svg" % (i) + f = open_file(datapath, filename) + f.write(generate_tile_2_lines(-1, 0, 0, 1, 0, -1, 1, 0)) + i += 1 + close_file(f) + filename = "tile-%d.svg" % (i) + f = open_file(datapath, filename) + f.write(generate_tile_2_lines(-1, 0, 0, 0, 0, -1, 1, 0)) + i += 1 + close_file(f) + f = open_file(datapath, 'x.svg') + f.write(generate_x()) + i += 1 + close_file(f) + +def main(): + return 0 + +if __name__ == "__main__": + if not os.path.exists(os.path.join(os.path.abspath('.'), 'images')): + os.mkdir(os.path.join(os.path.abspath('.'), 'images')) + generator(os.path.join(os.path.abspath('.'), 'images')) + main() @@ -0,0 +1,147 @@ +#Copyright (c) 2009-11 Walter Bender + +# 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 3 of the License, or +# (at your option) any later version. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the +# Free Software Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. + +import gtk + +from deck import Deck + +ROW = 8 +COL = 8 + + +class Grid: + ''' Class for managing ROWxCOL matrix of cards ''' + + def __init__(self, width, height, card_width, card_height): + # the playing surface + self.grid = [] + + # the tiles in your hand + self.hand = [] + + for i in range(ROW * COL): + self.grid.append(None) + + for i in range(COL): + self.hand.append(None) + + # card spacing + self.left_hand = int(card_width / 2) + self.left = int((width - (card_width * COL)) / 2 + card_width) + self.xinc = int(card_width) + self.top = 0 + self.yinc = int(card_height) + + def deal(self, deck): + ''' Deal an initial set of cards to the hand ''' + for i in range(COL): + self.hand[i] = deck.deal_next_card() + self.place_a_card(self.hand[i], self.hand_to_xy(i)[0], + self.hand_to_xy(i)[1]) + + # and empty the grid + for i in range(ROW * COL): + self.grid[i] = None + + def redeal(self, deck): + ''' Deal another set of cards to the hand ''' + for i in range(COL): + self.hand[i] = deck.deal_next_card() + self.place_a_card(self.hand[i], self.hand_to_xy(i)[0], + self.hand_to_xy(i)[1]) + + def find_empty_slot(self): + ''' Is there an empty slot in the hand? ''' + for i in range(COL): + if self.hand[i] == None: + return i + return None + + def cards_in_hand(self): + ''' How many cards are in the hand? ''' + return COL - self.hand.count(None) + + def cards_in_grid(self): + ''' How many cards are on the grid? ''' + return ROW * COL - self.grid.count(None) + + def restore(self, deck, saved_card_index): + ''' Restore cards to grid upon resume or share. ''' + # TODO: restore hand too + self.hide() + j = 0 + for i in saved_card_index: + if i is None: + self.grid[j] = None + else: + self.grid[j] = deck.index_to_card(i) + j += 1 + self.show() + + def place_a_card(self, c, x, y): + ''' Place a card at position x,y and display it. ''' + if c is not None: + c.spr.move((x, y)) + c.spr.set_layer(2000) + + def xy_to_grid(self, x, y): + ''' Convert from sprite x,y to grid index. ''' + return COL * int((y - self.top) / self.yinc) + \ + int((x - self.left) / self.xinc) + + def xy_to_hand(self, x, y): + ''' Convert from sprite x,y to hand index. ''' + return int((y - self.top) / self.yinc) + + def grid_to_xy(self, i): + ''' Convert from grid index to sprite x,y. ''' + return (int((self.left + i % COL * self.xinc)), + int((self.top + (i / COL) * self.yinc))) + + def hand_to_xy(self, i): + ''' Convert from hand index to sprite x,y. ''' + return ((self.left_hand, (self.top + i * self.yinc))) + + def grid_to_spr(self, i): + ''' Return the sprite in grid-position i. ''' + return self.grid[i].spr + + def hand_to_spr(self, i): + ''' Return the sprite in hand-position i. ''' + return self.hand[i].spr + + def spr_to_grid(self, spr): + ''' Return the index of a sprite in grid. ''' + for i in range(ROW * COL): + if self.grid[i] is not None and self.grid[i].spr == spr: + return(i) + return None + + def spr_to_hand(self, spr): + ''' Return the index of a sprite in hand. ''' + for i in range(COL): + if self.hand[i] is not None and self.hand[i].spr == spr: + return(i) + return None + + def hide(self): + ''' Hide all of the cards on the grid. ''' + for i in range(ROW * COL): + if self.grid[i] is not None: + self.grid[i].hide_card() + + def show(self): + ''' Restore all card on the grid to their x,y positions. ''' + for i in range(ROW * COL): + self.place_a_card(self.grid[i],self.grid_to_xy(i)[0], + self.grid_to_xy(i)[1]) + @@ -0,0 +1,68 @@ +#!/usr/bin/env python + +#Copyright (c) 2009,10 Walter Bender + +# 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 3 of the License, or +# (at your option) any later version. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the +# Free Software Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. + +import gtk + +from gettext import gettext as _ +import os + +from game import Game + + +class PathMain: + def __init__(self): + self.r = 0 + self.tw = None + + # create a new window + self.win = gtk.Window(gtk.WINDOW_TOPLEVEL) + self.win.maximize() + self.win.set_title("%s: %s" % (_("Paths"), + _("Move tiles to make a path."))) + self.win.connect("delete_event", lambda w,e: gtk.main_quit()) + + # A vbox to put a menu and the canvas in + vbox = gtk.VBox(False, 0) + self.win.add(vbox) + vbox.show() + + canvas = gtk.DrawingArea() + vbox.pack_end(canvas, True, True) + canvas.show() + + self.win.show_all() + + # Join the activity + self.vmw = Game(canvas, os.path.join(os.path.abspath('.'), + 'images/')) + self.vmw.win = self.win + self.vmw.activity = self + self.vmw.level = 12 + + self.vmw.new_game() + + def set_title(self, title): + self.win.set_title(title) + + def _new_game_cb(self, widget, game): + self.vmw.new_game() + return True + +def main(): + gtk.main() + return 0 + +if __name__ == "__main__": + PathMain() + main() diff --git a/sprites.py b/sprites.py new file mode 100644 index 0000000..c633397 --- /dev/null +++ b/sprites.py @@ -0,0 +1,453 @@ +# -*- coding: utf-8 -*- + +#Copyright (c) 2007-8, Playful Invention Company. +#Copyright (c) 2008-10 Walter Bender + +#Permission is hereby granted, free of charge, to any person obtaining a copy +#of this software and associated documentation files (the "Software"), to deal +#in the Software without restriction, including without limitation the rights +#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +#copies of the Software, and to permit persons to whom the Software is +#furnished to do so, subject to the following conditions: + +#The above copyright notice and this permission notice shall be included in +#all copies or substantial portions of the Software. + +#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +#THE SOFTWARE. + +""" + +sprites.py is a simple sprites library for managing graphics objects, +'sprites', on a canvas. It manages multiple sprites with methods such +as move, hide, set_layer, etc. + +There are two classes: + +class Sprites maintains a collection of sprites. +class Sprite manages individual sprites within the collection. + +Example usage: + # Import the classes into your program. + from sprites import Sprites Sprite + + # In your expose callback event handler, call refresh + def _expose_cb(self, win, event): + self.sprite_list.refresh(event) + return True + + # Create a new sprite collection for a gtk Drawing Area. + my_drawing_area = gtk.DrawingArea() + self.sprite_list = Sprites(my_drawing_area) + + # Create a "pixbuf" (in this example, from SVG). + my_pixbuf = svg_str_to_pixbuf("<svg>...some svg code...</svg>") + + # Create a sprite at position x1, y1. + my_sprite = sprites.Sprite(self.sprite_list, x1, y1, my_pixbuf) + + # Move the sprite to a new position. + my_sprite.move_relative((dx, dy)) + + # Create another "pixbuf". + your_pixbuf = svg_str_to_pixbuf("<svg>...some svg code...</svg>") + + # Create a sprite at position x2, y2. + your_sprite = sprites.Sprite(self.sprite_list, x2, y2, my_pixbuf) + + # Assign the sprites to layers. + # In this example, your_sprite will be on top of my_sprite. + my_sprite.set_layer(100) + your_sprite.set_layer(200) + + # Now put my_sprite on top of your_sprite. + my_sprite.set_layer(300) + +# method for converting SVG to a gtk pixbuf +def svg_str_to_pixbuf(svg_string): + pl = gtk.gdk.PixbufLoader('svg') + pl.write(svg_string) + pl.close() + pixbuf = pl.get_pixbuf() + return pixbuf +""" + +import pygtk +pygtk.require('2.0') +import gtk +import pango + + +class Sprites: + """ A class for the list of sprites and everything they share in common """ + + def __init__(self, canvas, area=None, gc=None): + """ Initialize an empty array of sprites """ + self.canvas = canvas + if area == None: + self.area = self.canvas.window + self.gc = self.area.new_gc() + else: + self.area = area + self.gc = gc + self.cm = self.gc.get_colormap() + self.list = [] + + def get_sprite(self, i): + """ Return a sprint from the array """ + if i < 0 or i > len(self.list) - 1: + return(None) + else: + return(self.list[i]) + + def length_of_list(self): + """ How many sprites are there? """ + return(len(self.list)) + + def append_to_list(self, spr): + """ Append a new sprite to the end of the list. """ + self.list.append(spr) + + def insert_in_list(self, spr, i): + """ Insert a sprite at position i. """ + if i < 0: + self.list.insert(0, spr) + elif i > len(self.list) - 1: + self.list.append(spr) + else: + self.list.insert(i, spr) + + def remove_from_list(self, spr): + """ Remove a sprite from the list. """ + if spr in self.list: + self.list.remove(spr) + + def find_sprite(self, pos, alpha=True): + """ Search based on (x, y) position. Return the 'top/first' one. """ + list = self.list[:] + list.reverse() + for spr in list: + if spr.hit(pos): + if not alpha or spr.get_pixel(pos)[3] == 255: + return spr + return None + + def refresh(self, event): + """ Handle expose event refresh """ + self.redraw_sprites(event.area) + + def redraw_sprites(self, area=None): + """ Redraw the sprites that intersect area. """ + for spr in self.list: + if area == None: + spr.draw() + else: + intersection = spr.rect.intersect(area) + if intersection.width > 0 or intersection.height > 0: + spr.draw() + + +class Sprite: + """ A class for the individual sprites """ + + def __init__(self, sprites, x, y, image): + """ Initialize an individual sprite """ + self._sprites = sprites + self.rect = gtk.gdk.Rectangle(int(x), int(y), 0, 0) + self._scale = [12] + self._rescale = [True] + self._horiz_align = ["center"] + self._vert_align = ["middle"] + self._fd = None + self._bold = False + self._italic = False + self._color = None + self._margins = [0, 0, 0, 0] + self.layer = 100 + self.labels = [] + self.images = [] + self._dx = [] # image offsets + self._dy = [] + self.set_image(image) + if self._sprites is not None: + self._sprites.append_to_list(self) + + def set_image(self, image, i=0, dx=0, dy=0): + """ Add an image to the sprite. """ + while len(self.images) < i + 1: + self.images.append(None) + self._dx.append(0) + self._dy.append(0) + self.images[i] = image + self._dx[i] = dx + self._dy[i] = dy + if isinstance(self.images[i], gtk.gdk.Pixbuf): + w = self.images[i].get_width() + h = self.images[i].get_height() + else: + w, h = self.images[i].get_size() + if i == 0: # Always reset width and height when base image changes. + self.rect.width = w + dx + self.rect.height = h + dy + else: + if w + dx > self.rect.width: + self.rect.width = w + dx + if h + dy > self.rect.height: + self.rect.height = h + dy + + def move(self, pos, visible=True): + """ Move to new (x, y) position """ + if visible: + self.inval() + self.rect.x, self.rect.y = int(pos[0]), int(pos[1]) + if visible: + self.inval() + + def move_relative(self, pos, visible=True): + """ Move to new (x+dx, y+dy) position """ + if visible: + self.inval() + self.rect.x += int(pos[0]) + self.rect.y += int(pos[1]) + if visible: + self.inval() + + def get_xy(self): + """ Return current (x, y) position """ + return (self.rect.x, self.rect.y) + + def get_dimensions(self): + """ Return current size """ + return (self.rect.width, self.rect.height) + + def get_layer(self): + """ Return current layer """ + return self.layer + + def set_shape(self, image, i=0): + """ Set the current image associated with the sprite """ + self.inval() + self.set_image(image, i) + self.inval() + + def set_layer(self, layer): + """ Set the layer for a sprite """ + if self._sprites is None: + return + self._sprites.remove_from_list(self) + self.layer = layer + for i in range(self._sprites.length_of_list()): + if layer < self._sprites.get_sprite(i).layer: + self._sprites.insert_in_list(self, i) + self.inval() + return + self._sprites.append_to_list(self) + self.inval() + + def set_label(self, new_label, i=0): + """ Set the label drawn on the sprite """ + self._extend_labels_array(i) + if type(new_label) is str or type(new_label) is unicode: + # pango doesn't like nulls + self.labels[i] = new_label.replace("\0", " ") + else: + self.labels[i] = str(new_label) + self.inval() + + def set_margins(self, l=0, t=0, r=0, b=0): + """ Set the margins for drawing the label """ + self._margins = [l, t, r, b] + + def _extend_labels_array(self, i): + """ Append to the labels attribute list """ + if self._fd is None: + self.set_font('Sans') + if self._color is None: + self._color = self._sprites.cm.alloc_color('black') + while len(self.labels) < i + 1: + self.labels.append(" ") + self._scale.append(self._scale[0]) + self._rescale.append(self._rescale[0]) + self._horiz_align.append(self._horiz_align[0]) + self._vert_align.append(self._vert_align[0]) + + def set_font(self, font): + """ Set the font for a label """ + self._fd = pango.FontDescription(font) + + def set_label_color(self, rgb): + """ Set the font color for a label """ + self._color = self._sprites.cm.alloc_color(rgb) + + def set_label_attributes(self, scale, rescale=True, horiz_align="center", + vert_align="middle", i=0): + """ Set the various label attributes """ + self._extend_labels_array(i) + self._scale[i] = scale + self._rescale[i] = rescale + self._horiz_align[i] = horiz_align + self._vert_align[i] = vert_align + + def hide(self): + """ Hide a sprite """ + if self._sprites is None: + return + self.inval() + self._sprites.remove_from_list(self) + + def inval(self): + """ Force a region redraw by gtk """ + if self._sprites is None: + return + self._sprites.area.invalidate_rect(self.rect, False) + + def draw(self): + """ Draw the sprite (and label) """ + if self._sprites is None: + return + for i, img in enumerate(self.images): + if isinstance(img, gtk.gdk.Pixbuf): + self._sprites.area.draw_pixbuf(self._sprites.gc, img, 0, 0, + self.rect.x + self._dx[i], + self.rect.y + self._dy[i]) + elif img is not None: + self._sprites.area.draw_drawable(self._sprites.gc, img, 0, 0, + self.rect.x + self._dx[i], + self.rect.y + self._dy[i], + -1, -1) + if len(self.labels) > 0: + self.draw_label() + + def hit(self, pos): + """ Is (x, y) on top of the sprite? """ + x, y = pos + if x < self.rect.x: + return False + if x > self.rect.x + self.rect.width: + return False + if y < self.rect.y: + return False + if y > self.rect.y + self.rect.height: + return False + return True + + def draw_label(self): + """ Draw the label based on its attributes """ + if self._sprites is None: + return + my_width = self.rect.width - self._margins[0] - self._margins[2] + if my_width < 0: + my_width = 0 + my_height = self.rect.height - self._margins[1] - self._margins[3] + for i in range(len(self.labels)): + pl = self._sprites.canvas.create_pango_layout(str(self.labels[i])) + self._fd.set_size(int(self._scale[i] * pango.SCALE)) + pl.set_font_description(self._fd) + w = pl.get_size()[0] / pango.SCALE + if w > my_width: + if self._rescale[i]: + self._fd.set_size( + int(self._scale[i] * pango.SCALE * my_width / w)) + pl.set_font_description(self._fd) + w = pl.get_size()[0] / pango.SCALE + else: + j = len(self.labels[i]) - 1 + while(w > my_width and j > 0): + pl = self._sprites.canvas.create_pango_layout( + "…" + self.labels[i][len(self.labels[i]) - j:]) + self._fd.set_size(int(self._scale[i] * pango.SCALE)) + pl.set_font_description(self._fd) + w = pl.get_size()[0] / pango.SCALE + j -= 1 + if self._horiz_align[i] == "center": + x = int(self.rect.x + self._margins[0] + (my_width - w) / 2) + elif self._horiz_align[i] == 'left': + x = int(self.rect.x + self._margins[0]) + else: # right + x = int(self.rect.x + self.rect.width - w - self._margins[2]) + h = pl.get_size()[1] / pango.SCALE + if self._vert_align[i] == "middle": + y = int(self.rect.y + self._margins[1] + (my_height - h) / 2) + elif self._vert_align[i] == "top": + y = int(self.rect.y + self._margins[1]) + else: # bottom + y = int(self.rect.y + self.rect.height - h - self._margins[3]) + self._sprites.gc.set_foreground(self._color) + self._sprites.area.draw_layout(self._sprites.gc, x, y, pl) + + def label_width(self): + """ Calculate the width of a label """ + max = 0 + for i in range(len(self.labels)): + pl = self._sprites.canvas.create_pango_layout(self.labels[i]) + self._fd.set_size(int(self._scale[i] * pango.SCALE)) + pl.set_font_description(self._fd) + w = pl.get_size()[0] / pango.SCALE + if w > max: + max = w + return max + + def label_safe_width(self): + """ Return maximum width for a label """ + return self.rect.width - self._margins[0] - self._margins[2] + + def label_safe_height(self): + """ Return maximum height for a label """ + return self.rect.height - self._margins[1] - self._margins[3] + + def label_left_top(self): + """ Return the upper-left corner of the label safe zone """ + return(self._margins[0], self._margins[1]) + + def get_pixel(self, pos, i=0, mode='888'): + """ Return the pixel at (x, y) """ + x, y = pos + x = x - self.rect.x + y = y - self.rect.y + if isinstance(self.images[i], gtk.gdk.Pixbuf): + if y > self.images[i].get_height() - 1: + return(-1, -1, -1, -1) + array = self.images[i].get_pixels() + if array is not None: + try: + if self.images[i].get_has_alpha(): + offset = (y * self.images[i].get_width() + x) * 4 + a = ord(array[offset + 3]) + else: + offset = (y * self.images[i].get_width() + x) * 3 + a = 255 + r = ord(array[offset]) + g = ord(array[offset + 1]) + b = ord(array[offset + 2]) + return(r, g, b, a) + except IndexError: + """ + print "Index Error: %d %d (%d, %d) (w: %d, h: %d) (%dx%d)"\ + % (len(array), offset, x, y, + self.images[i].get_width(), + self.images[i].get_height(), + self.rect.width, self.rect.height) + """ + pass + return(-1, -1, -1, -1) + else: + w, h = self.images[i].get_size() + if x < 0 or x > (w - 1) or y < 0 or y > (h - 1): + return(-1, -1, -1, -1) + image = self.images[i].get_image(x, y, 1, 1) + pixel = image.get_pixel(0, 0) + visual = self.images[i].get_visual() + r = int((pixel & visual.red_mask) >> visual.red_shift) + g = int((pixel & visual.green_mask) >> visual.green_shift) + b = int((pixel & visual.blue_mask) >> visual.blue_shift) + # Rescale to 8 bits + if mode == '565': + r = r << 3 + g = g << 2 + b = b << 3 + return(r, g, b, 0) |