root/confluence/foreverbubble/foreverbubble.py

Revision 801, 5.9 kB (checked in by confluence, 6 weeks ago)

fixed usage some more

  • Property svn:executable set to *
Line 
1#!/usr/bin/python
2#
3# requires: argparse, networkx.  Works on Linux.  Might work on OSX.  Probably doesn't work on Windows.  Patches appreciated.
4#
5# Copyright 2010 Adrianna Pinska, Simon Cross
6# This program is distributed under the terms of the GNU General Public License v3
7
8import warnings
9warnings.simplefilter("ignore", DeprecationWarning)
10
11import random
12import os
13from copy import deepcopy
14
15from argparse import ArgumentParser
16from networkx import Graph, connected_components
17
18class Ball(object):
19    def __init__(self, ball=None):
20        self.ball = ball
21
22    def __str__(self):
23        if self.ball is None:
24            return '-'
25        else:
26            return str(self.ball)
27
28
29class Row(object):
30    def __init__(self, row_index):
31        self.even = not row_index % 2
32        self.balls = []
33
34    def num_balls(self):
35        return 8 if self.even else 7
36
37    def __str__(self):
38        prefix = '  ' if not self.even else ''
39        return prefix + '   '.join([str(b) for b in self.balls])
40
41
42class RandomRow(Row):
43    def __init__(self, row_index, selection, symmetric):
44        super(RandomRow, self).__init__(row_index)
45        if symmetric:
46            startballs = [Ball(random.choice(selection)) for b in range(4)]
47            self.balls = startballs + list(reversed(deepcopy(startballs)[:self.num_balls()/2]))
48        else:
49            self.balls = [Ball(random.choice(selection)) for b in range(self.num_balls())]
50
51class BlankRow(Row):
52    def __init__(self, row_index):
53        super(BlankRow, self).__init__(row_index)
54        self.balls = [Ball()] * self.num_balls()
55
56class Level(object):
57    BALLS = range(8)
58
59    ASYM, SYM_HORIZ, SYM_BOTH = range(3)
60    SYMMETRIES = {
61        "asym" : ASYM,
62        "horiz" : SYM_HORIZ,
63        "both" : SYM_BOTH,
64    }
65
66    def __init__(self, fill_rows=None, colours=None, emptyspace=None, symmetry=None):
67        if fill_rows is None:
68            fill_rows = random.randint(6,10)
69        if colours is None:
70            colours = random.randint(1,8)
71        if emptyspace is None:
72            emptyspace = random.randint(1,4)
73        if symmetry is None:
74            symmetry = random.randint(0,2)
75
76        self.rows = []
77
78        selection = random.sample(self.BALLS, colours) + [None] * emptyspace
79        symmetric_rows = symmetry > 0
80
81        if symmetry > 1:
82            if not fill_rows % 2:
83                fill_rows -= 1
84            for row in range((fill_rows + 1)/2):
85                self.rows.append(RandomRow(row, selection, symmetric_rows))
86            for rand_row in reversed(self.rows[:fill_rows/2]):
87                self.rows.append(deepcopy(rand_row))
88        else:
89            for row in range(fill_rows):
90                self.rows.append(RandomRow(row, selection, symmetric_rows))
91
92        for row in range(fill_rows, 10):
93            self.rows.append(BlankRow(row))
94
95    def valid(self):
96        g = Graph()
97
98        def add_edge(b1, b2):
99            if b1.ball is not None and b2.ball is not None:
100                g.add_edge(b1, b2)
101
102        # add all balls and horizontal edges
103        for row in self.rows:
104            for ball in row.balls:
105                if ball.ball is not None:
106                    g.add_node(ball)
107            for b1, b2 in zip(row.balls[:-1], row.balls[1:]):
108                add_edge(b1, b2)
109
110        # add inter-row connections
111        for row, above, below in zip(self.rows[1::2], self.rows[::2], self.rows[2::2] + [BlankRow(0)]):
112            for ball, above_left, above_right, below_left, below_right in zip(row.balls, above.balls[:-1], above.balls[1:], below.balls[:-1], below.balls[1:]):
113                add_edge(ball, above_left)
114                add_edge(ball, above_right)
115                add_edge(ball, below_left)
116                add_edge(ball, below_right)
117
118        top_row = set(self.rows[0].balls)
119        valid = True
120        for comp in connected_components(g):
121            if set(comp).isdisjoint(top_row):
122                valid = False
123                break
124
125        return valid
126
127    def __str__(self):
128        return '\n'.join([str(r) for r in self.rows])
129
130
131class LevelSet(object):
132    def __init__(self, num_levels, **kwargs):
133        l = 0
134        self.levels = []
135
136        while l < num_levels:
137            level = Level(**kwargs)
138            if level.valid():
139                self.levels.append(level)
140                l += 1
141
142    def __str__(self):
143        return '\n\n'.join([str(l) for l in self.levels])
144
145if __name__ == '__main__':
146    parser = ArgumentParser()
147    parser.add_argument('-f', '--filename', dest='filename', type=str, default="%s/.frozen-bubble/levels/foreverbubble" % os.getenv('HOME'), metavar='FILE', help='levelset save location (default: %(default)s)')
148    parser.add_argument('-n', '--number', dest='number', type=int, default=20, metavar='NUMBER', help='number of levels to generate (default: %(default)s)')
149
150    parser.add_argument('-r', '--rows', dest='fill_rows', type=int, choices=xrange(1,11), metavar='ROWS', help='number of rows to generate per level (permitted values: %(choices)s; default: random)')
151    parser.add_argument('-c', '--colours', dest='colours', type=int, choices=xrange(1,9), metavar='COLOURS', help='number of colours to use per level (permitted values: %(choices)s; default: random)')
152    parser.add_argument('-e', '--empty-space', dest='emptyspace', type=int, choices=xrange(0,4), metavar='SPACE', help='amount of empty space on each level (permitted values: %(choices)s; default: random)')
153    parser.add_argument('-s', '--symmetry', dest='symmetry', type=str, choices=Level.SYMMETRIES.keys(), metavar='SYMMETRY', help='symmetry of each level (permitted values: %(choices)s; default: random)')
154    args = parser.parse_args()
155
156    symmetry = Level.SYMMETRIES[args.symmetry] if args.symmetry else None
157
158    levelset = LevelSet(args.number, fill_rows=args.fill_rows, colours=args.colours, emptyspace=args.emptyspace, symmetry=symmetry)
159
160    levelsetfile = open(args.filename, "w")
161    levelsetfile.write(str(levelset))
162    levelsetfile.close()
Note: See TracBrowser for help on using the browser.