Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorWalter Bender <walter@sugarlabs.org>2010-09-09 17:48:15 (GMT)
committer Walter Bender <walter@sugarlabs.org>2010-09-09 17:48:15 (GMT)
commit611ceff0d5bf0a53fc596ebd7ad01d7ca43b7f91 (patch)
treeecf45354ac36371f4ad83f475f96167a63bbdd82
parenta37532dc04b42aa05517559946f0a6596a1bbb64 (diff)
added triangle game
-rw-r--r--PukllanapacActivity.py101
-rw-r--r--card.py72
-rw-r--r--constants.py4
-rw-r--r--grid.py88
-rw-r--r--sprites.py5
-rw-r--r--window.py78
6 files changed, 282 insertions, 66 deletions
diff --git a/PukllanapacActivity.py b/PukllanapacActivity.py
index c01a0a1..8247b4e 100644
--- a/PukllanapacActivity.py
+++ b/PukllanapacActivity.py
@@ -17,15 +17,18 @@ import gobject
import sugar
from sugar.activity import activity
-try: # 0.86+ toolbar widgets
+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 ToolbarBox
from sugar.graphics.toolbarbox import ToolbarButton
- _new_sugar_system = True
-except ImportError:
- _new_sugar_system = False
+
from sugar.graphics.toolbutton import ToolButton
from sugar.graphics.menuitem import MenuItem
from sugar.graphics.icon import Icon
@@ -42,6 +45,7 @@ SERVICE = 'org.sugarlabs.PukllanapacActivity'
IFACE = SERVICE
PATH = '/org/augarlabs/PukllanapacActivity'
LEVEL_ICONS = ['level1', 'level2', 'level3']
+GAME_ICONS = ['rectangle', 'hexagon']
def _button_factory(icon_name, tooltip, callback, toolbar, cb_arg=None,
@@ -93,7 +97,8 @@ class PukllanapacActivity(activity.Activity):
super(PukllanapacActivity,self).__init__(handle)
self._play_level = 0
- self._setup_toolbars(_new_sugar_system)
+ self._play_mode = 0
+ self._setup_toolbars(_have_toolbox)
# Create a canvas
canvas = gtk.DrawingArea()
@@ -103,35 +108,42 @@ class PukllanapacActivity(activity.Activity):
canvas.show()
self.show_all()
- # Initialize the canvas
self.tw = Game(canvas, os.path.join(activity.get_bundle_path(),
'images'), self)
# Restore game state from Journal or start new game
- try: # Try reading restored settings from the Journal.
- self._play_level = int(self.metadata['play_level'])
+ if 'play_level' in self.metadata:
+ self.change_play_level_cb(play_level=int(
+ self.metadata['play_level']))
+ if 'play_mode' in self.metadata:
+ self.change_play_mode_cb(play_mode=int(
+ self.metadata['play_mode']))
grid = []
for i in range(24):
- grid.append(int(self.metadata['card'+str(i)]))
- self._play_level = int(self.metadata['play_level'])
- # print "restoring: " + str(grid)
- self.tw.grid.set_grid(grid)
- except KeyError:
- pass
- print "restoring play level %d " % (self._play_level)
+ if 'card' + str(i) in self.metadata:
+ grid.append(int(self.metadata['card' + str(i)]))
+ self.tw.grid.set_grid(grid, self.tw.mode)
+ for i in range(24):
+ if 'rotate' + str(i) in self.metadata:
+ print "restoring %d to %d" % (i,
+ int(self.metadata['rotate' + str(i)]))
+ self.tw.grid.card_table[i].set_orientation(
+ int(self.metadata['rotate' + str(i)]))
self.tw.mask(self._play_level)
def write_file(self, file_path):
""" Write the grid status to the Journal """
+ self.metadata['play_mode'] = self._play_mode
self.metadata['play_level'] = self._play_level
- # print "saving " + str(self.tw.grid.grid)
for i in range(24):
- self.metadata['card'+str(i)] = str(self.tw.grid.grid[i])
+ self.metadata['card' + str(i)] = str(self.tw.grid.grid[i])
+ self.metadata['rotate' + str(i)] = str(
+ self.tw.grid.card_table[self.tw.grid.grid[i]].orientation)
- def _setup_toolbars(self, new_sugar_system):
+ def _setup_toolbars(self, have_toolbox):
""" Setup the toolbars.. """
- if new_sugar_system:
+ if have_toolbox:
toolbox = ToolbarBox()
# Activity toolbar
@@ -157,11 +169,18 @@ class PukllanapacActivity(activity.Activity):
# Add the buttons and labels to the toolbars
self.level_button = _button_factory(LEVEL_ICONS[self._play_level],
_('Set difficulty level.'),
- self.level_cb, toolbar)
+ self.change_play_level_cb, toolbar)
+ mode = self._play_mode
+ mode += 1
+ if mode == len(GAME_ICONS):
+ mode = 0
+ self.game_button = _button_factory(GAME_ICONS[mode],
+ _('Select game.'),
+ self.change_play_mode_cb, toolbar)
_separator_factory(toolbar, True, False)
self.status_label = _label_factory(_("drag to swap"), toolbar)
- if _new_sugar_system:
+ if _have_toolbox:
_separator_factory(toolbox.toolbar, False, True)
stop_button = StopButton(self)
@@ -169,11 +188,39 @@ class PukllanapacActivity(activity.Activity):
toolbox.toolbar.insert(stop_button, -1)
stop_button.show()
- def level_cb(self, button):
+ def change_play_level_cb(self, button=None, play_level=None):
""" Cycle between levels """
- self._play_level += 1
- if self._play_level == len(LEVEL_ICONS):
- self._play_level = 0
+ if self._play_mode > 0:
+ return
+ if play_level is None:
+ self._play_level += 1
+ if self._play_level == len(LEVEL_ICONS):
+ self._play_level = 0
+ else:
+ self._play_level = play_level
self.level_button.set_icon(LEVEL_ICONS[self._play_level])
- self.tw.grid.reset()
+ self.tw.grid.reset(GAME_ICONS[self._play_mode])
self.tw.mask(self._play_level)
+
+ def change_play_mode_cb(self, button=None, play_mode=None):
+ """ Cycle between game modes """
+ if play_mode is None:
+ self._play_mode += 1
+ if self._play_mode == len(GAME_ICONS):
+ self._play_mode = 0
+ else:
+ self._play_mode = play_mode
+ mode = self._play_mode
+ mode += 1
+ if mode == len(GAME_ICONS):
+ mode = 0
+ self.game_button.set_icon(GAME_ICONS[mode])
+ self.tw.mode = GAME_ICONS[self._play_mode]
+ self.tw.grid.initialize_cards(self.tw.sprites, self.tw.path,
+ self.tw.card_dim, self.tw.scale,
+ GAME_ICONS[self._play_mode])
+ if self._play_mode > 0:
+ self._play_level = len(LEVEL_ICONS) - 1
+ self.level_button.set_icon(LEVEL_ICONS[self._play_level])
+ self.tw.mask(self._play_level)
+ self.tw.grid.reset(GAME_ICONS[self._play_mode])
diff --git a/card.py b/card.py
index d7a9fd1..a408ab1 100644
--- a/card.py
+++ b/card.py
@@ -18,19 +18,75 @@ from sprites import Sprite
def load_image(file, w, h):
- """ convert from file to pixbuf at size w, h """
+ """ Convert from file to pixbuf at size w, h """
return gtk.gdk.pixbuf_new_from_file_at_size(file, int(w), int(h))
class Card:
- """ class for defining individual cards """
+ """ Class for defining individual cards """
- def __init__(self, tw, c, x, y):
- """ Load a card from a precomputed svg """
+ def __init__(self, sprites, path, card_dim, scale, c, x, y, shape='circle'):
+ """ Load a card from a precomputed SVG. """
self.images = []
- file = "%s/card-%d.svg" % (tw.path, c)
- self.images.append(load_image(file, tw.card_dim * tw.scale,
- tw.card_dim * tw.scale))
+ self.orientation = 0
+ self.increment = 90
+
+ if shape == 'triangle':
+ file = "%s/triangle-r0-%d.svg" % (path, c)
+ self.increment = 60
+ else:
+ file = "%s/card-%d.svg" % (path, c)
+
+ self.images.append(load_image(file, card_dim * scale,
+ card_dim * scale))
+
+ if shape == 'triangle':
+ file = "%s/triangle-r60-%d.svg" % (path, c)
+ self.images.append(load_image(file, card_dim * scale,
+ card_dim * scale))
+ file = "%s/triangle-r120-%d.svg" % (path, c)
+ self.images.append(load_image(file, card_dim * scale,
+ card_dim * scale))
+ file = "%s/triangle-r180-%d.svg" % (path, c)
+ self.images.append(load_image(file, card_dim * scale,
+ card_dim * scale))
+ file = "%s/triangle-r240-%d.svg" % (path, c)
+ self.images.append(load_image(file, card_dim * scale,
+ card_dim * scale))
+ file = "%s/triangle-r300-%d.svg" % (path, c)
+ self.images.append(load_image(file, card_dim * scale,
+ card_dim * scale))
+ else:
+ for r in range(3):
+ self.images.append(self.images[r].rotate_simple(90))
+
# create sprite from svg file
- self.spr = Sprite(tw.sprites, x, y, self.images[0])
+ self.spr = Sprite(sprites, x, y, self.images[0])
+ self.spr.draw()
+
+ def reset_image(self, tw):
+ """ Reset image to orientation 0. """
+ while self.orientation != 0:
+ self.rotate_ccw()
+
+ def set_orientation(self, r):
+ """ Set orientation to 0, 90, 180, or 270. """
+ if r in [0, 60, 90, 120, 180, 240, 270, 300]:
+ while r != self.orientation:
+ self.rotate_ccw()
+
+ def rotate_180(self):
+ """ Rotate a tile 180 degrees """
+ print "rotate 180"
+ r = 0
+ while r < 180:
+ self.rotate_ccw()
+ r += self.increment
+
+ def rotate_ccw(self):
+ """ Set the card image to correspond to rotation r. """
+ self.orientation += self.increment
+ if self.orientation == 360:
+ self.orientation = 0
+ self.spr.images[0] = self.images[int(self.orientation/self.increment)]
self.spr.draw()
diff --git a/constants.py b/constants.py
index 8ea6eb7..57bd402 100644
--- a/constants.py
+++ b/constants.py
@@ -24,9 +24,7 @@ C = [[Y, B, Y, R], [R, Y, Y, R], [R, R, Y, R], [R, R, R, R], [B, B, B, Y], [Y, B
H = [[G, B, G], [B, Y, R], [B, R, B], [Y, G, Y], [R, G, B], [G, B, B], [R, B, G], [B, B, B], [R, R, R], [R, R, B], [G, R, R], [Y, R, R], [R, G, Y], [G, G, Y], [G, R, Y], [B, Y, G], [Y, R, Y], [G, G, R], [G, G, G], [B, Y, Y], [R, Y, B], [Y, B, G], [Y, Y, Y], [Y, B, B]]
-RT = [[B, B, Y], [Y, B, G], [G, R, B], [Y, G, Y], [R, G, G], [Y, G, B], [B, G, G], [R, B, Y], [R, R, G], [R, Y, G], [B, B, R], [Y, Y, Y]]
-
-LT = [[B, B, G], [R, Y, Y], [G, G, G], [G, Y, G], [G, R, B], [B, Y, Y], [B, B, B], [B, R, R], [Y, R, R], [R, R, R], [R, B, Y], [R, Y, G]]
+RT = [[B, B, Y], [Y, B, G], [G, B, R], [Y, G, Y], [R, G, G], [Y, G, B], [B, G, G], [R, Y, B], [R, R, G], [R, G, Y], [B, B, R], [Y, Y, Y], [B, B, G], [R, Y, Y], [G, G, G], [G, Y, G], [G, R, B], [B, Y, Y], [B, B, B], [B, R, R], [Y, R, R], [R, R, R], [R, B, Y], [R, Y, G]]
MASKS = [[0, 1, 2, 3, 4, 5, 6, 7, 10, 11, 12, 13, 16, 17, 18, 19, 20, 21, 22,
23], [0, 1, 2, 3, 4, 5, 6, 11, 12, 17, 18, 19, 20, 21, 22, 23], []]
diff --git a/grid.py b/grid.py
index f6956c0..0dd2cef 100644
--- a/grid.py
+++ b/grid.py
@@ -16,43 +16,78 @@ import gtk
import gobject
from sprites import Sprite
-from card import Card, load_image
+from card import Card
+HEX_TO_GRID = [-1, 0, 1, -1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
+ 17, 18, 19, 20, 21, 22, -1, 23, 24, -1]
+GRID_TO_HEX = [1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
+ 20, 21, 22, 23, 25, 26]
+HEX_ORIENTATION = [-1, 180, 0, -1, 180, 0, 180, 0, 0, 180, 0, 180, 180, 0, 180,
+ 0, 0, 180, 0, 180, 180, 0, 180, 0, -1, 180, 0, -1]
class Grid:
- """ Class for defining 6x4 matrix of cards """
- def __init__(self, tw):
- """ Grid positions """
+ """ Class for defining matrix of cards """
+
+ def __init__(self, tw, shape='rectangle'):
+ """ Set initial grid positions: either a rectangle or a hexgaon """
self.grid = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
17, 18, 19, 20, 21, 22, 23]
- self.card_table = []
# Stuff to keep around for the graphics
self.w = int(tw.width)
self.h = int(tw.height)
self.d = int(tw.card_dim * tw.scale)
+ self.dx = self.d * 0.85
+ self.dy = self.d * 0.5
self.s = tw.scale
- # Initialize the cards
+ self.initialize_cards(tw.sprites, tw.path, tw.card_dim, tw.scale, shape)
+
+ def initialize_cards(self, sprites, path, card_dim, scale, shape):
+ if hasattr(self, 'card_table'):
+ for c in self.card_table:
+ c.spr.hide()
+ self.card_table = []
for i in self.grid:
- x, y = self.i_to_xy(i)
- self.card_table.append(Card(tw, i, x, y))
+ x, y = self.i_to_xy(i, shape)
+ if shape == 'hexagon':
+ self.card_table.append(Card(sprites, path, card_dim, scale, i,
+ x, y, 'triangle'))
+ self.card_table[i].set_orientation(
+ HEX_ORIENTATION[GRID_TO_HEX[i]])
+ else:
+ self.card_table.append(Card(sprites, path, card_dim, scale, i,
+ x, y))
- def i_to_xy(self, i):
+ def i_to_xy(self, i, shape='rectangle'):
""" Convert a grid index to an x, y position """
- return int((self.w - (self.d * 6)) / 2) + \
- (i % 6) * self.d - 10 + i % 6 * 4, \
- int((self.h - (self.d * 4)) / 2) + \
- int( i / 6) * self.d - 6 + int(i/6)*4
+ if shape == 'hexagon': # 4 x 7 with empty corners
+ return int((self.w - (self.dx * 4)) / 2) + \
+ (GRID_TO_HEX[i] % 4) * self.dx, \
+ int((self.h - (self.dy * 7)) / 2) + \
+ int(GRID_TO_HEX[i] / 4) * self.dy
+ else: # 6 x 4
+ return int((self.w - (self.d * 6)) / 2) + \
+ (i % 6) * self.d - 10 + i % 6 * 4, \
+ int((self.h - (self.d * 4)) / 2) + \
+ int( i / 6) * self.d - 6 + int(i / 6) * 4
- def xy_to_i(self, x, y):
+ def xy_to_i(self, x, y, shape='rectangle'):
""" Convert an x, y position to a grid index """
- return (x - int((self.w - (self.d * 6)) / 2)) / self.d + \
- ((y - int((self.h - (self.d * 4)) / 2)) / self.d) * 6
+ if shape == 'hexagon':
+ return HEX_TO_GRID[(x - int((self.w - (self.d * 6)) / 2)) \
+ / self.d + \
+ ((y - int((self.h - (self.d * 4)) / 2)) / self.d) * 6]
+ else:
+ return (x - int((self.w - (self.d * 6)) / 2)) / self.d + \
+ ((y - int((self.h - (self.d * 4)) / 2)) / self.d) * 6
- def set_grid(self, newgrid):
+ def set_grid(self, newgrid, shape='rectangle'):
""" Move cards to x, y positions specified in grid """
for i, c in enumerate(newgrid):
- x, y = self.i_to_xy(i)
+ x, y = self.i_to_xy(i, shape)
self.card_table[c].spr.move((x, y))
+ if shape == 'hexagon':
+ self.card_table[c].set_orientation(
+ HEX_ORIENTATION[GRID_TO_HEX[c]])
self.grid[i] = c
def show_all(self):
@@ -65,16 +100,21 @@ class Grid:
for i in list:
self.card_table[i].spr.hide()
- def reset(self):
+ def reset(self, shape='rectangle'):
""" Reset everything to initial layout """
self.show_all()
self.grid = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
17, 18, 19, 20, 21, 22, 23]
for i in self.grid:
- x, y = self.i_to_xy(i)
+ x, y = self.i_to_xy(i, shape)
self.card_table[i].spr.move((x, y))
+ if shape == 'hexagon':
+ self.card_table[i].set_orientation(
+ HEX_ORIENTATION[GRID_TO_HEX[i]])
+ else:
+ self.card_table[i].set_orientation(0)
- def swap(self, a, b):
+ def swap(self, a, b, shape='rectangle'):
""" swap grid elements and x,y positions of sprites """
ai = self.spr_to_i(a)
bi = self.spr_to_i(b)
@@ -87,6 +127,12 @@ class Grid:
bx, by = b.get_xy()
a.move((bx, by))
b.move((ax, ay))
+ if shape == 'hexagon':
+ if HEX_ORIENTATION[GRID_TO_HEX[ai]] != \
+ HEX_ORIENTATION[GRID_TO_HEX[bi]]:
+ print 'rotating 180: ', ai, bi, self.grid[ai], self.grid[bi]
+ self.card_table[self.grid[ai]].rotate_180()
+ self.card_table[self.grid[bi]].rotate_180()
def spr_to_i(self, spr):
""" Find a card index from a sprite """
diff --git a/sprites.py b/sprites.py
index f1650db..41b1a59 100644
--- a/sprites.py
+++ b/sprites.py
@@ -127,13 +127,14 @@ class Sprites:
if spr in self.list:
self.list.remove(spr)
- def find_sprite(self, pos):
+ 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):
- return spr
+ if not alpha or spr.get_pixel(pos)[3] == 255:
+ return spr
return None
def refresh(self, event):
diff --git a/window.py b/window.py
index 72de639..d6efc09 100644
--- a/window.py
+++ b/window.py
@@ -28,6 +28,9 @@ from grid import Grid
from sprites import Sprites
from constants import C, MASKS, CARD_DIM
+import logging
+_logger = logging.getLogger('pukllanapac-activity')
+
LEVEL_BOUNDS = [[[1, 2], [0, 1], [2, 3], [1, 2]],
[[1, 2], [0, 1], [1, 4], [0, 3]],
[[0, 3], [-1, 2], [1, 5], [-1, 4]]]
@@ -59,6 +62,7 @@ class Game():
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.card_dim = CARD_DIM
@@ -68,7 +72,8 @@ class Game():
self.sprites = Sprites(self.canvas)
# Initialize the grid
- self.grid = Grid(self)
+ self.mode = 'rectangle'
+ self.grid = Grid(self, self.mode)
self.bounds = LEVEL_BOUNDS[0]
self.level = 0
@@ -102,11 +107,23 @@ class Game():
self.release = spr
# if the same card (click) then rotate
if self.press == self.release:
- # check to see if it was an aborted move
- if distance(self.start_drag, [x, y]) < 20:
- pass
+ if distance(self.start_drag, [x, y]) > 20:
+ print "rotating card ", self.grid.grid[self.grid.spr_to_i(
+ self.press)], 'was (', self.grid.card_table[
+ self.grid.grid[self.grid.spr_to_i(
+ self.press)]].orientation, ')'
+ self.grid.card_table[self.grid.grid[self.grid.spr_to_i(
+ self.press)]].rotate_ccw()
+ if self.mode == 'hexagon': # Rotate a second time
+ self.grid.card_table[self.grid.grid[self.grid.spr_to_i(
+ self.press)]].rotate_ccw()
+ self.press.set_layer(0)
+ self.press.set_layer(100)
else:
- self.grid.swap(self.press, self.release)
+ print "swapping: ", self.grid.grid[self.grid.spr_to_i(
+ self.press)], self.grid.grid[self.grid.spr_to_i(
+ self.release)]
+ self.grid.swap(self.press, self.release, self.mode)
self.press = None
self.release = None
if self.test() == True:
@@ -114,6 +131,15 @@ class Game():
gobject.timeout_add(3000, self.activity.level_cb, None)
return True
+ def _keypress_cb(self, area, event):
+ """ Keypress is used to switch between games """
+ k = gtk.gdk.keyval_name(event.keyval)
+ u = gtk.gdk.keyval_to_unicode(event.keyval)
+ if k == '1':
+ print 'game 1'
+ elif k == '2':
+ print 'game 2'
+
def _expose_cb(self, win, event):
self.sprites.refresh(event)
return True
@@ -144,26 +170,68 @@ class Game():
return False
if C[self.grid.grid[i]][3] != C[self.grid.grid[i - 6]][2]:
return False
+ '''
if row > self.bounds[1][0] and row <= self.bounds[1][1]:
if C[self.grid.grid[i]][1] != C[self.grid.grid[i + 6]][0]:
return False
if C[self.grid.grid[i]][2] != C[self.grid.grid[i + 6]][3]:
return False
+ '''
if col > self.bounds[2][0] and col <= self.bounds[2][1]:
if C[self.grid.grid[i]][3] != C[self.grid.grid[i - 1]][0]:
return False
if C[self.grid.grid[i]][2] != C[self.grid.grid[i - 1]][1]:
return False
+ '''
if col > self.bounds[3][0] and col <= self.bounds[3][1]:
if C[self.grid.grid[i]][0] != C[self.grid.grid[i + 1]][3]:
return False
if C[self.grid.grid[i]][1] != C[self.grid.grid[i + 1]][2]:
return False
+ '''
return True
+ def solver(self):
+ """ Permutate until a solution is found (useless since 24! is >>>) """
+ self.grid.reset(self.mode)
+ counter = 0
+ a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
+ 19, 20, 21, 22, 23]
+ for i in Permutation(a):
+ self.grid.set_grid(i, self.mode)
+ if self.test() is True:
+ return True
+ counter += 1
+ if (counter/10000)*10000 == counter:
+ _logger.debug('%d' % counter)
+ self.activity.status_label.set_text('%d' % (counter))
+ self.activity.status_label.set_text(_("no solution found"))
+ return True
def distance(start, stop):
""" Measure the length of drag between button press and button release. """
dx = start[0] - stop[0]
dy = start[1] - stop[1]
return sqrt(dx * dx + dy * dy)
+
+
+class Permutation:
+ """Permutaion class for checking for all possible matches on the grid """
+
+ def __init__(self, elist):
+ self._data = elist[:]
+ self._sofar = []
+
+ def __iter__(self):
+ return self.next()
+
+ def next(self):
+ for e in self._data:
+ if e not in self._sofar:
+ self._sofar.append(e)
+ if len(self._sofar) == len(self._data):
+ yield self._sofar[:]
+ else:
+ for v in self.next():
+ yield v
+ self._sofar.pop()