Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/balls_generator.py
blob: 85d6d16ec4afd9d173654f039dff9d72185cea7b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# -*- coding: utf-8 -*-

"""
HitTheBalls : hit the ball(s) with the good result.
Copyright (C) 2013  Laurent Bernabe <laurent.bernabe@gmail.com>

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.

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, see <http://www.gnu.org/licenses/>.
"""

"""
Generates balls.
"""

from random import randint
from ball import Ball
from operation import Operation, OPER_ADD, OPER_SUB, OPER_MUL, OPER_DIV


class OperationConfig(object):

    """
    Stores configuration about a type of operation (OPER_ADD for example) :
    what is the limit for its operands ?
    """

    def __init__(self, op_type, fst_op, snd_op, res_limit=-1):
        """
        Constructor:
        op_type : operation type => either OPER_ADD, OPER_SUB, OPER_MUL
        or OPER_DIV
        fst_op : limit for first operand => integer
        snd_op : limit for second operand => integer
        res_limit : limit on the result, unless we pass negative or zero value,
        in which case there is no limit (useful for division) => integer
        """
        if op_type == OPER_SUB or op_type == OPER_DIV:
            # First operand can't have less digits than second operand in such
            # cases.
            if fst_op < snd_op:
                raise ValueError("fst_op(%d) can't be less than snd_op(%d)" %
                                 (fst_op, snd_op))
        if fst_op <= 0 or snd_op <= 0:
            raise ValueError(
                "fst_op(%d) and snd_op(%d) must be greater than O." %
                (fst_op, snd_op))
        self._op_type = op_type
        self._fst_op = fst_op
        self._snd_op = snd_op
        self._res_limit = res_limit

    def get_operation_type(self):
        """
        Accessor to the operation type.
        => either OPER_ADD, OPER_SUB, OPER_MUL or OPER_DIV
        """
        return self._op_type

    def get_first_operand_limit(self):
        """
        Accessor to the limit of the first operand
        => integer
        """
        return self._fst_op

    def get_second_operand_limit(self):
        """
        Accessor to the limit of the second operand
        => integer
        """
        return self._snd_op

    def get_result_limit(self):
        """
        Accessor to the result limit.
        => negative or zero value if there is no restriction : integer
        => positive value if there is a limit : integer
        """
        return self._res_limit


class BallsGenerator(object):

    """
    Generates a list of balls
    """

    def generate_list(self, balls_number, oper_configs, move_area,
                      txt_font, txt_color):
        """
        Generates a list of Ball instances.
        Firstly, notice that it is not necessary to pass all four operations.
        In such cases only configured operations type will be used for generation.
        Secondly, notice that if you pass several configurations for a same
        operation, only the first configuration matching this operation will be
        used.
        balls_number : number of balls to build => integer
        oper_configs : configurations for the wanted operations => list of
        OperationConfig.
        move_area : space where balls are allowed to be => Tuple of 4 integers (
              left side x, top side y, right side x, bottom side y)
        txt_font : font common to all balls => olpcgames.pangofont
        txt_color : color common to all balls text => tuple of 3 integers between
                  [0,255]
        """
        # We take only one configuration for a given kind of operation.
        filtered_oper_configs = []
        registered_operations = set()
        for config in oper_configs:
            if not config.get_operation_type()["txt"] in registered_operations:
                filtered_oper_configs.append(config)
                registered_operations.add(config.get_operation_type()["txt"])
        # Then we generates balls
        balls = []
        op_type, fst_operand, snd_operand = None, None, None
        for counter in range(balls_number):
            while True:
                op_type_index = randint(1, len(filtered_oper_configs)) - 1
                operation_config = filtered_oper_configs[op_type_index]
                op_type = operation_config.get_operation_type()
                lim_1 = operation_config.get_first_operand_limit()
                lim_2 = operation_config.get_second_operand_limit()
                lim_res = operation_config.get_result_limit()
                fst_operand = randint(2, lim_1)
                snd_operand = randint(2, lim_2)
                result = Operation(fst_operand, snd_operand, op_type).\
                    get_result()
                if lim_res > 0:
                    if result > lim_res:
                        continue
                if result <= 1:
                    continue
                if op_type == OPER_ADD or op_type == OPER_MUL:
                    break
                elif op_type == OPER_SUB:
                    if fst_operand > snd_operand:
                        break
                elif op_type == OPER_DIV:
                    if fst_operand % snd_operand == 0:
                        break
                else:
                    raise Exception("Unkown operation type")
            ball_bg_color = self._generate_color()
            ball_velocity = self._generate_velocity()
            balls.append(Ball(txt_font, txt_color, ball_bg_color,
                              Operation(fst_operand, snd_operand, op_type),
                              move_area, ball_velocity))
        return balls

    def _generate_color(self):
        """
        Generates a color
        => Tuple of 3 integers between [10, 245[
        """
        red = randint(10, 245)
        green = randint(10, 245)
        blue = randint(10, 245)
        return (red, green, blue)

    def _generate_velocity(self):
        """
        Generates ball velocity
        => Tuple of 2 integers.
        """
        while True:
            x = (200 - randint(0, 400)) / 100  # between -2.0 and 2.0
            y = (200 - randint(0, 400)) / 100  # between -2.0 and 2.0
            velocity = (x, y)
            if abs(velocity[0]) >= 0.5 and abs(velocity[1]) >= 0.5:
                break
        return velocity