Esempio n. 1
0
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        # self.pos_to_switch is a dictionary that maps position values in a binary number to range(8) represented
        # as 3-digit binary strings:
        #     {1: '000', 2: '001', 4: '010', 8: '011', 16: '100', 32: '101', 64: '110', 128: '111'}
        # The three digits are the rule components and the keys to the switches.
        # To see it, try: print(self.pos_to_switch) after executing the next line.
        # The function bin_str() is defined in utils.py

        # The following two lines do the same thing. Explain how both work.
        pos_to_switch_a = {2**i: bin_str(i, 3) for i in range(8)}
        pos_to_switch_b = dict(zip([2**i for i in range(8)], CA_World.bin_0_to_7))
        assert pos_to_switch_a == pos_to_switch_b
        self.pos_to_switch = ...  # pos_to_switch_a or pos_to_switch_b

        # The rule number used for this run, initially set to 110 as the default rule.
        # (You might also try rule 165.)
        # The following sets the local variable self.rule_nbr. It doesn't change the 'Rule_nbr' slider widget.
        self.rule_nbr = 110
        # Set the switches and the binary representation of self.rule_nbr.
        self.set_switches_from_rule_nbr()
        self.set_binary_nbr_from_rule_nbr()
        self.init = None

        # self.ca_lines is a list of lines, each of which is a list of 0/1. Each line represents
        # a state of the CA, i.e., all the cells in the line. self.ca_list contains the entire
        # history of the CA.
        self.ca_lines: List[List[int]] = []
        # gui.WINDOW['rows'].update(value=len(self.ca_lines))
        SimEngine.gui_set('rows', value=len(self.ca_lines))
Esempio n. 2
0
    def set_slider_and_binary_nbr_from_rule_nbr(self):
        """
        Translate self.rule_nbr into a binary string and put it into the
        gui.WINDOW['bin_string'] widget. For example, if self.rule_nbr is 110,
        the string '(01101110)' is stored in gui.WINDOW['bin_string']. Include
        the parentheses around the binary number.

        Use gui_set('bin_string', value=new_value) to update the value of the widget.
        """
        gui_set('Rule_nbr', value=self.rule_nbr)
        binary_rule_nbr = bin_str(self.rule_nbr, 8)
        new_bin_value = binary_rule_nbr + ' (binary)'
        gui_set('bin_string', value=new_bin_value)
Esempio n. 3
0
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # self.pos_to_switch is a dictionary that maps positions in a binary number to range(8) represented
        # as 3-digit binary strings:
        #     {1: '000', 2: '001', 4: '010', 8: '011', 16: '100', 32: '101', 64: '110', 128: '111'}
        # The three digits are the rule components and the keys to the switches.
        # To see it, try: print(self.pos_to_switch) after executing the next line.
        # The function bin_str() is defined in utils.py
        self.pos_to_switch = {2**i: bin_str(i, 3) for i in range(8)}
        # print(self.pos_to_switch)

        # The rule number used for this run, initially set to 110 as the default rule.
        # You might also try rule 165.
        self.rule_nbr = 110
        # Set the switches and the binary representation of self.rule_nbr.
        self.set_switches_from_rule_nbr(self.rule_nbr)
        self.set_binary_nbr_from_rule_nbr(self.rule_nbr)

        self.ca_list = [0] * CA_World.ca_display_size
        self.rows = 0
Esempio n. 4
0
class CA_World(OnOffWorld):

    ca_display_size = 225

    # bin_0_to_7 is ['000' .. '111']
    bin_0_to_7 = [bin_str(n, 3) for n in range(8)]

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        # self.pos_to_switch is a dictionary that maps position values in a binary number to range(8) represented
        # as 3-digit binary strings:
        #     {1: '000', 2: '001', 4: '010', 8: '011', 16: '100', 32: '101', 64: '110', 128: '111'}
        # The three digits are the rule components and the keys to the switches.
        # To see it, try: print(self.pos_to_switch) after executing the next line.
        # The function bin_str() is defined in utils.py

        # self.pos_to_switch0 = {2**i: bin_str(i, 3) for i in range(8)}
        self.pos_to_switch = dict(
            zip([2**i for i in range(8)], CA_World.bin_0_to_7))
        # print(self.pos_to_switch0 == self.pos_to_switch)

        # The rule number used for this run, initially set to 110 as the default rule.
        # (You might also try rule 165.)
        # The following sets the local variable self.rule_nbr. It doesn't change the 'Rule_nbr' slider widget.
        self.rule_nbr = 110
        # Set the switches and the binary representation of self.rule_nbr.
        self.set_switches_from_rule_nbr()
        self.set_slider_and_binary_nbr_from_rule_nbr()

        # self.ca_lines is a list of lines, each of which is a list or string of 0/1. Each line
        # representsa state of the CA, i.e., all the symbols in the line. self.ca_list contains
        # the entire history of the CA.
        self.lists = None
        self.padding_element = None
        self.ca_lines = []
        gui_set('rows', value=len(self.ca_lines))

    def build_initial_line(self):
        """
        Construct the initial CA line.
        It is a random line if gui_get('Random?').
        It is a line (of length ca_display_size) of 0's if gui_get('init_line') == ''.
        Otherwise it is the string in gui_get('init_line') converted into 0's and 1's.
        (' ' and '0' are converted to 0; everything else is converted to 1.)
        However, if the rule includes 000 -> 1,pad the line with 0's on both ends to fill the display.
        How much to put on each end depends on the user-specific initial line and the requested justification.
        """
        if gui_get('Random?'):
            line = [choice([0, 1]) for _ in range(self.ca_display_size)] if self.lists else \
                   ''.join([choice(['0', '1']) for _ in range(self.ca_display_size)])
        else:
            padding = self.padding_element * (self.ca_display_size)
            if gui_get('init_line') == '':
                line = padding
            else:
                line_0 = gui_get('init_line')
                # Convert line_0 to 0's and 1's
                # Treat '0' and ' ' as "not on".
                line = [0 if c in ' 0' else 1 for c in line_0] if self.lists else \
                       ''.join(['0' if c in ' 0' else '1' for c in line_0])
                if gui_get('000'):
                    justification = gui_get('justification')
                    line_len = len(line)
                    actual_padding = padding[line_len:]
                    line = actual_padding + line if justification == 'Right' else \
                           line + actual_padding if justification == 'Left' else \
                           actual_padding[len(actual_padding)//2:] + line + actual_padding[len(actual_padding)//2:]
        return line

    # Used only for the list case
    @staticmethod
    def drop_extraneous_0s_from_ends_of_new_line(new_line):
        """
        Drop the end cell at each end of new_line if it is 0. Keep it otherwise.
        Return the result.
        Args:
            new_line: ca_state with perhaps extraneous 0 cells at the ends

        Returns: trimmed ca_state without extraneous 0 cells at the ends.
        """
        # Drop the 0's at the ends that are 0.
        if not new_line[0]:
            new_line.pop(0)
        if not new_line[-1]:
            new_line.pop(-1)
        return new_line

    # Used only for the list case
    def extend_ca_lines_if_needed(self, new_line):
        """
        new_line is one cell longer at each then than ca_lines[-1]. If those extra
        cells are 0, delete them. If they are 1, insert a 0 cell at the corresponding
        end of each line in ca_lines
        """
        # new_line is longer than the original line by one on each end.
        # Extend the other lines in self.ca_lines for the ends that are non-zero
        if new_line[0] or new_line[-1]:
            for line in self.ca_lines:
                if new_line[0]:
                    line.insert(0, 0)
                if new_line[-1]:
                    line.append(0)

    def generate_new_line_from_current_line(self, prev_line):
        """
        The argument is (a copy of) the current line. We call it prev_line because that's the role
        it plays in this method.

        Generate the new line in these steps.
        1. Add '00' or (0, 0) to both ends of prev_line. (We do that because we want to allow the
        new line to extend the current line on either end. So start with a default extension.
        In addition, we need a triple to generate the symbols at the end of the new line.)
        Strings are immutable; string concatenation (+) does not change the original strings.

        2. Apply the rules (i.e., the switches) to the triples extracted from the line resulting from step 1.

            a. Look up the truth value of each triple. Is its switch on or off?
            b. Convert that boolean first to an int (0 or 1) and then to a character ('0' or '1').
            These two steps are done in a single list comprehension. The result is new_line_chars: List[str].
            Each element of new_line_chars is a string of one character, either '0' or '1'.

            c. Use join to combine that list of characters into a new string.

        This produces a line which is one symbol shorter than the current prev_line on each end.
        That is, it is one symbol longer on each end than the original current line. It may have
        0 or 1 at each end.

        Args:
            prev_line: The current state of the CA.
        Returns: The next state of the CA.
        """
        # Extend the current line two to the left and right.
        # Want to be able to generate one additional value at each end.
        if self.lists:
            prev_line.insert(0, 0)
            prev_line.insert(0, 0)
            prev_line.extend([0, 0])
            triples = [
                ''.join(map(str, prev_line[i:i + 3]))
                for i in range(len(prev_line) - 2)
            ]
            new_line = [int(gui_get(triple)) for triple in triples]
        else:
            prev_line = '00' + prev_line + '00'

            # For each triple of characters in the prev_line, look up the setting of the corresponding switch.
            # (gui_get(prev_line[i:i + 3]))
            # Convert its Truth value (rule is on/off) to an int and then to a one character str.
            new_line_chars = [
                str(int(gui_get(prev_line[i:i + 3])))
                for i in range(len(prev_line) - 2)
            ]

            # Finally, join those strings together into a new string.
            new_line = ''.join(new_line_chars)
        return new_line

    def get_rule_nbr_from_switches(self):
        """
        Translate the on/off of the switches to a rule number.
        This is the inverse of set_switches_from_rule_nbr(), but it doesn't set the 'Rule_nbr' Slider.
        """
        rule_nbr = 0
        (_event, values) = gui.WINDOW.read(timeout=10)
        for (pos, key) in self.pos_to_switch.items():
            if values[key]:
                rule_nbr += pos
        return rule_nbr

    def handle_event(self, event):
        """
        This is called when a GUI widget is changed and the change isn't handled by the system.
        The key of the widget that changed is in event.
        """
        # Handle rule nbr change events, either switches or rule_nbr slider
        if event in ['Rule_nbr'] + CA_World.bin_0_to_7:
            self.make_switches_and_rule_nbr_consistent()

        # When the user checks the 'Random?' box, the Input line area should disappear.
        # When the user unchecks the 'Random?' box, the Input line area should re-appear.
        elif event == 'Random?':
            disabled = gui_get('Random?')
            gui_set('init_line', visible=not disabled, value='1')

        # Handle color change (and any other) requests.
        else:
            super().handle_event(event)

    def make_switches_and_rule_nbr_consistent(self):
        """
        Make the Slider, the switches, and the bin number consistent: all should contain self.rule_nbr.
        """
        new_switches_nbr = self.get_rule_nbr_from_switches()

        # The switches changed
        if self.rule_nbr != new_switches_nbr:
            self.rule_nbr = new_switches_nbr

        # The slider changed
        else:
            self.rule_nbr = gui_get('Rule_nbr')
            self.set_switches_from_rule_nbr()
        self.set_slider_and_binary_nbr_from_rule_nbr()

    def set_slider_and_binary_nbr_from_rule_nbr(self):
        """
        Translate self.rule_nbr into a binary string and put it into the
        gui.WINDOW['bin_string'] widget. For example, if self.rule_nbr is 110,
        the string '(01101110)' is stored in gui.WINDOW['bin_string']. Include
        the parentheses around the binary number.

        Use gui_set('bin_string', value=new_value) to update the value of the widget.
        """
        gui_set('Rule_nbr', value=self.rule_nbr)
        binary_rule_nbr = bin_str(self.rule_nbr, 8)
        new_bin_value = binary_rule_nbr + ' (binary)'
        gui_set('bin_string', value=new_bin_value)

    def set_display_from_lines(self):
        """
        Copy values from self.ca_lines to the patches. There are two issues.
        1. Is self.ca_lines longer/shorter than the number of Patch rows in the display?
        2. Are there more/fewer symbols-per-line than Patches-per-row?
        What do you do in each case?

        This is the most difficult method. Here is the outline I used.
        """
        # Get the current setting of 'justification'.
        justification = gui_get('justification')

        # Get the two relevant widths.
        display_width = gui.PATCH_COLS

        # All the lines in self.ca_lines are the same length.
        ca_line_width = len(self.ca_lines[0])

        # How many blanks must be prepended to a line to be displayed to fill a display row?
        # Will be 0 if the ca_line is at least as long as the display row or the line is left-justified.
        left_padding_needed = 0 if ca_line_width >= display_width or justification == 'Left' else \
                              (display_width - ca_line_width)//2  if justification == 'Center' else \
                              display_width - ca_line_width     # if justification == 'Right'

        # Use [0]*n to get a list of n 0s to use as left padding.
        left_padding = self.padding_element * left_padding_needed

        # Which symbols of the ca_line are to be displayed?
        # More to the point, what is index of the first symbol of the line to be displayed?
        # Will be 0 if left_padding is the empty list. Otherwise compute the values for the other cases.
        left_ca_line_index = 0 if display_width >= ca_line_width or justification == 'Left' else \
                             (ca_line_width - display_width)//2  if justification == 'Center' else \
                             ca_line_width - display_width     # if justification == 'Right'

        # Reverse both self.ca_lines and CA_World.patches_array.
        ca_lines_to_display = reversed(self.ca_lines)
        patch_rows_to_display_on = np.flip(CA_World.patches_array, axis=0)

        # Now we can use zip to match up ca_lines_to_display and patch_rows_to_display on.
        # In both cases we are starting at the bottom and working our way up.
        ca_lines_patch_rows = zip(ca_lines_to_display,
                                  patch_rows_to_display_on)

        # zip is given two iterables and produces a sequence of pairs of elements, one from each.
        # An important feature of zip is that it stops whenever either of its arguments ends.
        # In particular, the two arguments needn't be the same length. Zip simply uses all the elements
        # of the shorter argument and pairs them with the initial elements of the longer argument.

        # We can now step through the corresponding pairs.
        for (ca_line, patch_row) in ca_lines_patch_rows:
            # The values in ca_line are to be displayed on patch_row.
            # The issue now is how to align them.

            # Which symbols of ca_line should be displayed?
            # We display all the symbols starting at left_ca_line_index (computed above).
            # Use a slice to identify these symbols.
            ca_line_portion = ca_line[left_ca_line_index:]

            # For the complete display line and the desired justification,
            # we may need to pad ca_line_portion to the left or right (or both).
            # We need left_padding (computed above) to the left and an arbitrary sequence of 0's to the right.
            # (Use repeat() from itertools for the padding on the right. It doesn't matter if it's too long!)

            # Put the three pieces together to get the full line.
            # Use chain() from itertools to combine the three parts of the line:
            #                   left_padding, ca_line_portion, right_padding.
            padded_line = chain(left_padding, ca_line_portion, repeat('0'))

            # padded_line has the right number of 0's at the left. It then contains the symbols from ca_line
            # to be displayed. If we need more symbols to display, padded_line includes an unlimted number of
            # trailing 0's.

            # Since padded_line will be displayed on patch_row, we can use zip again to pair up the values
            # from padded_line with the Patches in patch_row. Since padded_line includes an unlimited number
            # of 0's at the end, zip will stop when it reaches the last Patch in patch_row.

            ca_values_patchs = zip(padded_line, patch_row)

            # Step through these value/patch pairs and put the values into the associated Patches.
            for (ca_val, patch) in ca_values_patchs:
                # Use the set_on_off() method of OnOffPatch to set the patch based on ca_val.
                patch.set_on_off(int(ca_val))

    def set_switches_from_rule_nbr(self):
        """
        Update the settings of the switches based on self.rule_nbr.
        Note that the 2^i position of self.rule_nbr corresponds to self.pos_to_switch[i]. That is,
        self.pos_to_switch[i] returns the key for the switch representing position  2^i.

        Set that switch as follows: gui_set(self.pos_to_switch[pos], value=new_value).
        (new_value will be either True or False, i.e., 1 or 0.)

        This is the inverse of get_rule_nbr_from_switches().
        """
        rule_nbr = self.rule_nbr
        for pos in self.pos_to_switch:
            gui_set(self.pos_to_switch[pos], value=rule_nbr % 2)
            rule_nbr = rule_nbr // 2

    def setup(self):
        """
        Make the slider, the switches, and the bin_string of the rule number consistent with each other.
        Give the switches priority.
        That is, if the slider and the switches are both different from self.rule_nbr,
        use the value derived from the switches as the new value of self.rule_nbr.

        Once the slider, the switches, and the bin_string of the rule number are consistent,
        set self.ca_lines[0] to the line generated by build_initial_line.
        """
        self.lists = gui_get('lists_or_strings') == 'Lists'
        self.padding_element = [0] if self.lists else '0'

        self.make_switches_and_rule_nbr_consistent()

        for patch in CA_World.patches:
            patch.set_on_off(False)

        initial_line = self.build_initial_line()

        self.ca_lines = [initial_line]

        self.set_display_from_lines()

    def step(self):
        """
        Take one step in the simulation.
        (a) Generate an additional line for the ca. (Use a copy of self.ca_lines[-1].)
        (b) Extend all lines in ca_lines if the new line is longer (with additional 1's) than its predecessor.
        (c) Trim the new line and add it to the end of self.ca_lines.
        (d) Refresh display from values in self.ca_lines.
        """
        # (a)
        new_line: str = self.generate_new_line_from_current_line(
            copy(self.ca_lines[-1]))

        # (b)
        # Extend lines in self.ca_lines at each end as needed. (Don't extend for extra 0's at the ends.)
        if self.lists:
            self.extend_ca_lines_if_needed(new_line)
        else:  # Strings
            line_end = {'1': '0', '0': ''}
            # If either end is '1', add '0' to that end of all strings.
            # Can't drop the 0's first because we would lose track of which end was extended.
            if '1' in (new_line[0], new_line[-1]):
                # Use the line_end dictionary to look up values for left_end and right_end
                left_end = line_end[new_line[0]]
                right_end = line_end[new_line[-1]]
                self.ca_lines = [
                    left_end + line + right_end for line in self.ca_lines
                ]

        # (c)
        if self.lists:
            trimmed_new_line = self.drop_extraneous_0s_from_ends_of_new_line(
                new_line)
        else:  # Strings
            start = 0 if new_line[0] == '1' else 1
            end = len(new_line) if new_line[-1] == '1' else len(new_line) - 1
            trimmed_new_line: str = new_line[start:end]

        # Add trimmed_new_line to the end of self.ca_lines
        self.ca_lines.append(trimmed_new_line)

        # (d)
        # Refresh the display from self.ca_lines
        self.set_display_from_lines()
        # Update the 'rows' widget.
        gui_set('rows', value=len(self.ca_lines))
class CA_World(OnOffWorld):

    ca_display_size = 141

    # bin_0_to_7 is ['000' .. '111']
    bin_0_to_7 = [bin_str(n, 3) for n in range(8)]

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        # self.pos_to_switch is a dictionary that maps position values in a binary number to range(8) represented
        # as 3-digit binary strings:
        #     {1: '000', 2: '001', 4: '010', 8: '011', 16: '100', 32: '101', 64: '110', 128: '111'}
        # The three digits are the rule components and the keys to the switches.
        # To see it, try: print(self.pos_to_switch) after executing the next line.
        # The function bin_str() is defined in utils.py

        # The following two lines do the same thing. Explain how both work.
        # self.pos_to_switch0 = {2**i: bin_str(i, 3) for i in range(8)}
        self.pos_to_switch = dict(
            zip([2**i for i in range(8)], CA_World.bin_0_to_7))

        # The rule number used for this run, initially set to 110 as the default rule.
        # (You might also try rule 165.)
        # The following sets the local variable self.rule_nbr. It doesn't change the 'Rule_nbr' slider widget.
        self.rule_nbr = 110
        # Set the switches and the binary representation of self.rule_nbr.
        self.set_switches_from_rule_nbr()
        self.set_binary_nbr_from_rule_nbr()

        # self.ca_lines is a list of lines, each of which is a list of 0/1. Each line represents
        # a state of the CA, i.e., all the cells in the line. self.ca_list contains the entire
        # history of the CA.
        self.ca_lines: List[List[int]] = []
        SimEngine.gui_set('rows', value=len(self.ca_lines))

    def build_initial_line(self):
        """
        Construct the initial CA line.
        It is a random line if SimEngine.gui_get('Random?').
        It is a line (of length ca_display_size) if SimEngine.gui_get('init_line') == ''.
        Otherwise it is the string in SimEngine.gui_get('init_line') converted into 0's and 1's.
        (' ' and '0' are converted to 0; everything else is converted to 1.) 
        However, if the rule includes 000 -> 1,pad the line with 0's on both ends to fill the display.
        How much to put on each end depends on the user-specific initial line and the requested justification.
        """
        if SimEngine.gui_get('Random?'):
            line = ...
        else:
            # A line of 0's.
            padding = [0] * (self.ca_display_size)
            if SimEngine.gui_get('init_line') == '':
                line = padding
            else:
                line_0 = SimEngine.gui_get('init_line')
                # Convert line_0 to 0's and 1's
                line = [... for c in line_0]
                # If the rule include 000 -> 1, fill out the new line with 0's.
                if SimEngine.gui_get('000'):
                    justification = SimEngine.gui_get('justification')
                    line_len = len(line)
                    actual_padding = padding[line_len:]
                    line = ... if justification == 'Right' else \
                           ... if justification == 'Left' else \
                           ... #  justification == 'Center'
        return line

    @staticmethod
    def drop_extraneous_0s_from_ends_of_new_line(new_line):
        """
        Drop the end cell at each end of new_line if it is 0. Keep it otherwise.
        Return the result.
        Args:
            new_line: ca_state with perhaps extraneous 0 cells at the ends

        Returns: trimmed ca_state without extraneous 0 cells at the ends.
        """
        ...

    def extend_ca_lines_if_needed(self, new_line):
        """
        new_line is one cell longer at each end than ca_lines[-1]. If those extra
        cells are 0, delete them. If they are 1, insert a 0 cell at the corresponding
        end of each line in ca_lines.
        """
        ...

    @staticmethod
    def generate_new_line_from_current_line(prev_line):
        """
        The argument is (a copy of) the current line, i.e., copy(self.ca_lines[-1]).
        We call it prev_line because that's the role it plays in this method.
        Generate the new line in these steps.
        1. Add 0 to both ends of prev_line. (We do that because we want to allow the
        new line to extend the current line on either end. So start with a default extension.
        2. Insert an additional 0 at each end of prev_line. That's because we need a triple
        to generate the cells at the end of the new line. So, by this time the original
        line has been extended by [0, 0] on both ends.
        3. Apply the rules (i.e., the switches) to the result of step 2. This produces a line
        which is one cell shorter than the current prev_line on each end. That is, it is
        one cell longer on each end than the original prev_line.
        4. Return that newly generated line. It may have 0 or 1 at each end.
        Args:
            prev_line: The current state of the CA.
        Returns: The next state of the CA.
        """
        # Extend the current line two to the left and right.
        # Want to be able to generate one additional value at each end.
        prev_line = '00' + prev_line + '00'
        new_line = ...
        return new_line

    def get_rule_nbr_from_switches(self):
        """
        Translate the on/off of the switches to a rule number.
        This is the inverse of set_switches_from_rule_nbr(), but it doesn't set the 'Rule_nbr' Slider.
        """
        ...

    def handle_event(self, event):
        """
        This is called when a GUI widget is changed and the change isn't handled by the system.
        The key of the widget that changed is in event.
        """
        # Handle color change requests.
        super().handle_event(event)

        # Handle rule nbr change events, either switches or rule_nbr slider
        if event in ['Rule_nbr'] + CA_World.bin_0_to_7:
            self.make_switches_and_rule_nbr_consistent()

        # When the user checks the 'Random?' box, the Input line area should disappear.
        # When the user unchecks the 'Random?' box, the Input line area should re-appear.
        elif event == 'Random?':
            ...

    def make_switches_and_rule_nbr_consistent(self):
        """
        Make the Slider, the switches, and the bin number consistent: all should contain self.rule_nbr.
        """
        ...

    def set_binary_nbr_from_rule_nbr(self):
        """
        Translate self.rule_nbr into a binary string and put it into the
        gui.WINDOW['bin_string'] widget. For example, if self.rule_nbr is 110,
        the string '(01101110)' is stored in gui.WINDOW['bin_string']. Include
        the parentheses around the binary number.

        Use SimEngine.gui_set('bin_string', value=new_value) to update the value of the widget.
        """
        ...

    def set_display_from_lines(self):
        """
        Copy values from self.ca_lines to the patches. There are two issues.
        1. Is self.ca_lines longer/shorter than the number of Patch rows in the display?
        2. Are there more/fewer cells-per-line than Patches-per-row?
        What do you do in each case?

        This is the most difficult method. Here is the outline I used.
        """
        # Get the current setting of 'justification'.
        justification = SimEngine.gui_get('justification')

        # Get the two relevant widths.
        display_width = gui.PATCH_COLS

        # All the lines in self.ca_lines are the same length.
        ca_line_width = len(self.ca_lines[0])

        # How many blanks must be prepended to a line to be displayed to fill a display row?
        # Will be 0 if the ca_line is at least as long as the display row or the line is left-justified.
        left_padding_needed = 0 if ca_line_width >= display_width or justification == 'Left' else \
                              ...

        # Use [0]*n to get a list of n 0s to use as left padding.
        left_padding = ...

        # Which elements of the ca_line are to be displayed?
        # More to the point, what is index of the first element of the line to be displayed?
        # Will be 0 if left_padding is the empty list. Otherwise compute the values for the other cases.
        left_ca_line_index = 0 if display_width >= ca_line_width or justification == 'Left' else \
                             ...

        # Reverse both self.ca_lines and CA_World.patches_array.
        ca_lines_to_display = reversed(self.ca_lines)
        patch_rows_to_display_on = np.flip(CA_World.patches_array, axis=0)

        # Now we can use zip to match up ca_lines_to_display and patch_rows_to_display on.
        # In both cases we are starting at the bottom and working our way up.
        ca_lines_patch_rows = zip(ca_lines_to_display,
                                  patch_rows_to_display_on)

        # zip is given two iterables and produces a sequence of pairs of elements, one from each.
        # An important feature of zip is that it stops whenever either of its arguments ends.
        # In particular, the two arguments needn't be the same length. Zip simply uses all the elements
        # of the shorter argument and pairs them with the initial elements of the longer argument.

        # We can now step through the corresponding pairs.
        for (ca_line, patch_row) in ca_lines_patch_rows:
            # The values in ca_line are to be displayed on patch_row.
            # The issue now is how to align them.

            # Which elements of ca_line should be displayed?
            # We display all the elements starting at left_ca_line_index (computed above).
            # Use a slice to identify these elements.
            ca_line_portion = ca_line[...]

            # For the complete display line and the desired justification,
            # we may need to pad ca_line_portion to the left or right (or both).
            # We need left_padding (computed above) to the left and an arbitrary sequence of 0's to the right.
            # (Use repeat() from itertools for the padding on the right. It doesn't matter if it's too long!)

            # Put the three pieces together to get the full line.
            # Use chain() from itertools to combine the three parts of the line:
            #          left_padding, ca_line_portion, right_padding.
            padded_line = chain(..., ca_line_portion, ...)

            # padded_line has the right number of 0's at the left. It then contains the elements from ca_line
            # to be displayed. If we need more elements to display, padded_line includes an unlimted number of
            # trailing 0's.

            # Since padded_line will be displayed on patch_row, we can use zip again to pair up the values
            # from padded_line with the Patches in patch_row. Since padded_line includes an unlimited number
            # of 0's at the end, zip will stop when it reaches the last Patch in patch_row.

            ca_values_patchs = zip(padded_line, patch_row)

            # Step through these value/patch pairs and put the values into the associated Patches.
            for (ca_val, patch) in ca_values_patchs:
                # Use the set_on_off() method of OnOffPatch to set the patch to ca_val.
                patch.set_on_off(ca_val)

    def set_switches_from_rule_nbr(self):
        """
        Update the settings of the switches based on self.rule_nbr.
        Note that the 2^i position of self.rule_nbr corresponds to self.pos_to_switch[i]. That is,
        self.pos_to_switch[i] returns the key for the switch representing position  2^i.

        Set that switch as follows: SimEngine.gui_set(self.pos_to_switch[pos], value=new_value).
        (new_value will be either True or False, i.e., 1 or 0.)

        This is the inverse of get_rule_nbr_from_switches().
        """
        ...

    def setup(self):
        """
        Make the slider, the switches, and the bin_string of the rule number consistent with each other.
        Give the switches priority.
        That is, if the slider and the switches are both different from self.rule_nbr,
        use the value derived from the switches as the new value of self.rule_nbr.

        Once the slider, the switches, and the bin_string of the rule number are consistent,
        set self.ca_lines[0] to the line generated by build_initial_line.
        """
        ...

    def step(self):
        """
        Take one step in the simulation.
        (a) Generate an additional line for the ca. (Use a copy of self.ca_lines[-1].)
        (b) Extend all lines in ca_lines if the new line is longer (with additional 1's) than its predecessor.
        (c) Trim the new line and add to self.ca_lines
        (d) Refresh display from values in self.ca_lines.
        """
        # (a)
        new_line = ...  # The new state derived from self.ca_lines[-1]

        # (b)
        # Extend lines in self.ca_lines at each end as needed.
        # (Don't extend for extra 0's at the ends.)
        # Can't do step (c) first because we would lose track of which end was extended.
        ...

        # (c)
        # Drop extraneous 0s at the end of new_line
        ...
        # Add trimmed_new_line to the end of self.ca_lines
        ...

        # (d)
        # Refresh the display from self.ca_lines
        ...
        # Update the 'rows' widget.
        ...
Esempio n. 6
0
# ############################################## Define GUI ############################################## #
import PySimpleGUI as sg

""" 
The following appears at the top-left of the window. 
It puts a row consisting of a Text widgit and a ComboBox above the widgets from on_off.py
"""
ca_left_upper = [[sg.Text('Initial row:'),
                  sg.Combo(values=['Right', 'Center', 'Left', 'Random', 'All off'], key='init', default_value='Right')],
                 [sg.Text('Rows:'), sg.Text('     0', key='rows')],
                 HOR_SEP(30)] + \
                 on_off_left_upper


# bin_0_to_7 is ['000' .. '111']
bin_0_to_7 = [bin_str(n, 3) for n in range(8)]

# The switches are CheckBoxes with keys from bin_0_to_7 (in reverse).
# These are the actual GUI widgets, which we access through their keys.
# The pos_to_switch dictionary maps positions in the rule number as a binary number
# to these widgets. Each widget corresponds to a position in the rule number.
switches = [sg.CB(n+'\n 1', key=n, pad=((60, 0), (0, 0))) for n in reversed(bin_0_to_7)]

""" 
This  material appears above the screen: 
the rule number slider, its binary representation, and the switch settings.
"""
ca_right_upper = [[sg.Text('Rule number', pad=((250, 0), (20, 10))),
                   sg.Slider(key='Rule_nbr', range=(0, 255), orientation='horizontal', pad=((10, 20), (0, 10))),
                   sg.Text('00000000 (binary)', key='bin_string', pad=((0, 0), (10, 0)))],
Esempio n. 7
0
class CA_World(OnOffWorld):

    ca_display_size = 110

    # bin_0_to_7 is ['000' .. '111']
    bin_0_to_7 = [bin_str(n, 3) for n in range(8)]
    '''
    Design/Strategy:
    generate the dictionary containing the number from 1 to 128 as well as its rule form.
    set the initial rule number so that the switches and binary number can be set from it.
    then generate a list that will contain lists, which will containg the values in the grid lines.

    code: given below

    Review:
        this method makes use of list comprehension to be able to save the values directly into a list, rather than using a separate for loop. this saves lines of code, which make it more readable.
        Other than that, it uses simple lines of code and is very readable

    Testing:
        check that the rule number align with the switches and slider as well as the binary representation at startup.
        Also print the variable pos_to_switch to make sure it has the right key vallue pairs

    '''
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        # self.pos_to_switch is a dictionary that maps position values in a binary number to range(8) represented
        # as 3-digit binary strings:
        #     {1: '000', 2: '001', 4: '010', 8: '011', 16: '100', 32: '101', 64: '110', 128: '111'}
        # The three digits are the rule components and the keys to the switches.
        # To see it, try: print(self.pos_to_switch) after executing the next line.
        # The function bin_str() is defined in utils.py

        # The following two lines do the same thing. Explain how both work.
        # self.pos_to_switch0 = {2**i: bin_str(i, 3) for i in range(8)}
        self.pos_to_switch = dict(
            zip([2**i for i in range(8)], CA_World.bin_0_to_7))

        # The rule number used for this run, initially set to 110 as the default rule.
        # (You might also try rule 165.)
        # The following sets the local variable self.rule_nbr. It doesn't change the 'Rule_nbr' slider widget.
        self.rule_nbr = 110
        # Set the switches and the binary representation of self.rule_nbr.
        self.set_switches_from_rule_nbr()
        self.set_binary_nbr_from_rule_nbr()
        gui_set('Rule_nbr', value=self.rule_nbr)

        # self.ca_lines is a list of lines, each of which is a list of 0/1. Each line represents
        # a state of the CA, i.e., all the cells in the line. self.ca_list contains the entire
        # history of the CA.
        # !  List of ines that is initialized to an empty list. The values from the nested list get copied to patches for each line
        self.ca_lines: List[List[int]] = []
        # ! Sets the rows number based on how many lists are nested inside of self.ca_lines
        gui_set('rows', value=len(self.ca_lines))

        # BY <ABRAN LEZAMA>

    def build_initial_line(self):
        """
        Construct the initial CA line.
        It is a random line if gui_get('Random?').
        It is a line (of length ca_display_size) if gui_get('init_line') == ''.
        Otherwise it is the string in gui_get('init_line') converted into 0's and 1's.
        (' ' and '0' are converted to 0; everything else is converted to 1.) 
        However, if the rule includes 000 -> 1,pad the line with 0's on both ends to fill the display.
        How much to put on each end depends on the user-specific initial line and the requested justification.
        """
        '''
        Design/Strategy:

            first check if the user checked the random box so that a line with random 0's and 1's can be generated
            if rndom was not selected and no input by use was given in box initial row, fill the first line with 0's
            if input was give by user, get it from initial row box and add add them as integer values to a list
            check if rule 000 was checked, because if it was checked we need to generate a padding to fill the line.
            the actual_padding is generated by looking at line_len to see how many places available there are, and that is why line_len depends on rule 000.
            the padding is either placed on the left, center, or right depending on the justification given by user.

        Code: given below

        Review:
            To generate the random line if the random checkbox is clicked a list comprehension isused.
            This allows us to fit multiple line content into a single line.
            The rest of the code implements simple operations and follow appropiate indentations.
        
        Testing:
            the initial line should be testing on multiple justifications to ensure that the padding is 
            being added to the right position based on the specified justification
            
        '''
        # ! OUR EXPLANATION
        # ! if random is clicked, the first line is randomly filled with 0's and 1's
        # ! if random is not clicked and the user does not input a pattern in initial row, the whole line is filled with 0's
        # ! if initial pattern in provided, add it into a list
        # ! when rule 000 is applied we add padding depending on the justification to fill the line
        # ! line_len is made to depend on 000 because if that rule is applied, we have to know how many
        # ! 0's or 1's the user entered so that we can fill the rest of the line with padding depending on the justification
        self.ca_lines.append([])
        if gui_get('Random?'):
            line = [choice([0, 1]) for _ in range(self.ca_display_size)]
            #line = ''.join([choice(['0', '1']) for _ in range(self.ca_display_size)])
        else:
            # A line of 0's.
            padding = [0] * (self.ca_display_size)
            if gui_get('init_line') == '':
                line = padding
            else:
                line_0 = gui_get('init_line')
                # Convert line_0 to 0's and 1's
                line = [0 if c in ' 0' else 1 for c in line_0]
                # If the rule include 000 -> 1, fill out the new line with 0's.
                if gui_get('000'):
                    justification = gui_get('justification')
                    line_len = len(line)
                    actual_padding = padding[line_len:]
                    line = actual_padding + line if justification == 'Right' else \
                           line + actual_padding if justification == 'Left' else \
                           actual_padding[len(actual_padding) // 2:] + line + \
                           actual_padding[len(actual_padding) // 2:] #  justification == 'Center'

        print(line)
        ''' 
        THIS HAPPENS FOR WHEN THE JUSTIFICATION IS CENTERED AND RULE 000 IS CHECKED
        display_size = 10
        line_len = 3  --> 4 + 3 + 4 = 11 --> greater than display_size since the actual padding in odd
        line_len = 2 ---> 4 + 2 + 4 = 10 --> the same as the dipsly_size since the actual padding is even

        '''

        return line

        # BY <ABRAN LEZAMA>

    '''
    Design/Strategy:
        check if the first element and last element in new_line is 0, if they are, remove them

    Code: provided below

    Review:
        this method impleents simple logoc, with good indentation for the method to work properly

    Testing:
        One would test of the first and last line of the new_line ar eactually removed if they are 0
    '''
    # ! the static method measn that an instance creation is not required, they are bound to the class instead of instances of such class.
    # ! this method basically remoes the first and last element of a new list if they are 0
    @staticmethod
    def drop_extraneous_0s_from_ends_of_new_line(new_line):
        """
        Drop the end cell at each end of new_line if it is 0. Keep it otherwise.
        Return the result.
        Args:
            new_line: ca_state with perhaps extraneous 0 cells at the ends

        Returns: trimmed ca_state without extraneous 0 cells at the ends.
        """
        if new_line[0] == 0:
            new_line.pop(0)
        if new_line[-1] == 0:
            new_line.pop(-1)
        return new_line

    # BY <ABRAN LEZAMA>
    '''
    Design/Strategy:
        firch cehck if the first or last elemen in new_list is 1.
        if that is the case, insert a 0 at the end of each line in ca_lines
        this is done by looping through each line in ca_lines

    Code: code is provided below

    Review:
        The method uses simple logic. Other than that, it uses proper indentation for the method to work

    Testing:
        one would test if the if the 0 is inserted at the corresponding end of each line in ca_lines if they are 0.
        otherwise, the method does not does not work properly
    '''

    # ! the static method measn that an instance creation is not required, they are bound to the class instead of instances of such class.
    # ! this method basically extends caline_line if needed
    def extend_ca_lines_if_needed(self, new_line):
        """
        new_line is one cell longer at each end than ca_lines[-1]. If those extra
        cells are 0, delete them. If they are 1, insert a 0 cell at the corresponding
        end of each line in ca_lines.
        """
        if new_line[0] == 1 or new_line[-1] == 1:
            for line in self.ca_lines:
                if new_line[0]:
                    line.insert(0, 0)
                if new_line[-1]:
                    line.append(0)

    # BY <ABRAN LEZAMA>

    @staticmethod
    def generate_new_line_from_current_line(prev_line):
        """
        The argument is (a copy of) the current line, i.e., copy(self.ca_lines[-1]).
        We call it prev_line because that's the role it plays in this method.
        Generate the new line in these steps.
        1. Add 0 to both ends of prev_line. (We do that because we want to allow the
        new line to extend the current line on either end. So start with a default extension.
        2. Insert an additional 0 at each end of prev_line. That's because we need a triple
        to generate the cells at the end of the new line. So, by this time the original
        line has been extended by [0, 0] on both ends.
        3. Apply the rules (i.e., the switches) to the result of step 2. This produces a line
        which is one cell shorter than the current prev_line on each end. That is, it is
        one cell longer on each end than the original prev_line.
        4. Return that newly generated line. It may have 0 or 1 at each end.
        Args:
            prev_line: The current state of the CA.
        Returns: The next state of the CA.
        """
        # Extend the current line two to the left and right.
        # Want to be able to generate one additional value at each end.

        #prev_line = '00' + prev_line + '00'
        #new_line_chars = [str(int(gui_get(prev_line[i:i + 3]))) for i in range(len(prev_line) - 2)]
        #new_line = ''.join(new_line_chars)

        prev_line.insert(0, 0)
        prev_line.insert(0, 0)
        prev_line.append(0)
        prev_line.append(0)
        triples = [
            ''.join(map(str, prev_line[i:i + 3]))
            for i in range(len(prev_line) - 2)
        ]
        new_line = [int(gui_get(triple)) for triple in triples]
        return new_line

    def get_rule_nbr_from_switches(self):
        """
        Translate the on/off of the switches to a rule number.
        This is the inverse of set_switches_from_rule_nbr(), but it doesn't set the 'Rule_nbr' Slider.
        """

        nubers = [1, 2, 4, 8, 16, 32, 64, 128]
        new_rule_nbr = 0
        for i in range(0, 8):
            if gui_get(self.bin_0_to_7[i]):
                new_rule_nbr += nubers[i]
        return new_rule_nbr

    '''
    Design/Strategy:
        this method makes use of the onofworld class to handle the events,
        it checks if there have been changes in the rule number
        if there have been changes, make sure that everything is the same by calling self.make_switches_and_rule_nbr_consistent()
        also check if the random checkbox is clikced, if it is  disappear the initial row box where the user enters the initial pattern.

    Code: given below

    Review:
        this method as well uses simple logic and is formatted well, which makes it readable

    Testing: 
        this method can be tested by making sure that the events are trigerred when there is a change in the rule number
        either thorugh the slider or switches

    '''

    # ! this method basically allows us to make changes based on changes on the slider and switches.
    # ! this method is called from on_off.py file inside the class onOfWorld. I think that is also
    # ! why super is used since this class extends from that class and is implementing a method from its parent in a way.
    def handle_event(self, event):
        """
        This is called when a GUI widget is changed and the change isn't handled by the system.
        The key of the widget that changed is in event.
        """
        # Handle color change requests.
        super().handle_event(event)

        # Handle rule nbr change events, either switches or rule_nbr slider
        if event in ['Rule_nbr'] + CA_World.bin_0_to_7:
            self.make_switches_and_rule_nbr_consistent()

        # When the user checks the 'Random?' box, the Input line area should disappear.
        # When the user unchecks the 'Random?' box, the Input line area should re-appear.
        elif event == 'Random?':
            disabled = gui_get('Random?')
            gui_set('init_line', visible=not disabled, value=1)

    # BY <ABRAN LEZAMA>
    '''
    Design/Strategy:
        the straategy is check if the the current number in the switches is the same as the rule_number
        if it is not, set the rule number to the number in the switches because tha is the dominant one.
        Also set the slider be consistent with the switches
        Also check if the slider changes, if it does, set its value to the rule number and also adjust the switches
        lastly, the binarly number format always changes based on what the current rule numbe is on the slider.
    Code: code is given below

    Review:
        This method as well only depends on some conditional. Additionally, the code is formated well

    Testing:
        To test it, check that all are in sync, meaning that when one thing changes, the rest also change.
    '''

    def make_switches_and_rule_nbr_consistent(self):
        """
        Make the Slider, the switches, and the bin number consistent: all should contain self.rule_nbr.
        """
        current_switches_nbr = self.get_rule_nbr_from_switches()

        # ** switches number and rule number are not the same
        if current_switches_nbr != self.rule_nbr:
            self.rule_nbr = current_switches_nbr
            gui_set('Rule_nbr', value=self.rule_nbr)
        else:
            # ** the slider changed
            self.rule_nbr = gui_get('Rule_nbr')
            self.set_switches_from_rule_nbr()
        # ** update the binary number for any change in rule number
        self.set_binary_nbr_from_rule_nbr()

    # BY <ABRAN LEZAMA>
    '''
    Design/Strategy:
        set the binary number on the gui equal to the binary version of the rule number.

    Code: given below

    Review:
        I think it follows good form this code because it is readable and everything is compacted into a single line
    
    Testing:
        check that the binary representation of the rule number is accurate when the slider or switches are changed.
    '''

    def set_binary_nbr_from_rule_nbr(self):
        """
        Translate self.rule_nbr into a binary string and put it into the
        gui.WINDOW['bin_string'] widget. For example, if self.rule_nbr is 110,
        the string '(01101110)' is stored in gui.WINDOW['bin_string']. Include
        the parentheses around the binary number.

        Use gui_set('bin_string', value=new_value) to update the value of the widget.
        """

        gui_set('bin_string', value=bin(self.rule_nbr)[2:].zfill(8))

    # BY <ABRAN LEZAMA>

    def set_display_from_lines(self):
        """
        Copy values from self.ca_lines to the patches. There are two issues.
        1. Is self.ca_lines longer/shorter than the number of Patch rows in the display?
        2. Are there more/fewer cells-per-line than Patches-per-row?
        What do you do in each case?

        This is the most difficult method. Here is the outline I used.
        """
        # Get the current setting of 'justification'.
        justification = gui_get('justification')

        # Get the two relevant widths.
        display_width = gui.PATCH_COLS

        # All the lines in self.ca_lines are the same length.
        ca_line_width = len(self.ca_lines[0])

        # How many blanks must be prepended to a line to be displayed to fill a display row?
        # Will be 0 if the ca_line is at least as long as the display row or the line is left-justified.
        left_padding_needed = 0 if ca_line_width >= display_width or justification == 'Left' else \
                              (display_width - ca_line_width) // 2 if justification == 'Center' else \
                              display_width - ca_line_width

        # Use [0]*n to get a list of n 0s to use as left padding.
        left_padding = [0] * left_padding_needed

        # Which elements of the ca_line are to be displayed?
        # More to the point, what is index of the first element of the line to be displayed?
        # Will be 0 if left_padding is the empty list. Otherwise compute the values for the other cases.
        left_ca_line_index = 0 if display_width >= ca_line_width or justification == 'Left' else \
                             (ca_line_width - display_width) // 2 if justification == 'Center' else \
                             ca_line_width - display_width

        # Reverse both self.ca_lines and CA_World.patches_array.
        ca_lines_to_display = reversed(self.ca_lines)
        patch_rows_to_display_on = np.flip(CA_World.patches_array, axis=0)

        # Now we can use zip to match up ca_lines_to_display and patch_rows_to_display on.
        # In both cases we are starting at the bottom and working our way up.
        ca_lines_patch_rows = zip(ca_lines_to_display,
                                  patch_rows_to_display_on)

        # zip is given two iterables and produces a sequence of pairs of elements, one from each.
        # An important feature of zip is that it stops whenever either of its arguments ends.
        # In particular, the two arguments needn't be the same length. Zip simply uses all the elements
        # of the shorter argument and pairs them with the initial elements of the longer argument.

        # We can now step through the corresponding pairs.
        for (ca_line, patch_row) in ca_lines_patch_rows:
            # The values in ca_line are to be displayed on patch_row.
            # The issue now is how to align them.

            # Which elements of ca_line should be displayed?
            # We display all the elements starting at left_ca_line_index (computed above).
            # Use a slice to identify these elements.
            ca_line_portion = ca_line[left_ca_line_index:]

            # For the complete display line and the desired justification,
            # we may need to pad ca_line_portion to the left or right (or both).
            # We need left_padding (computed above) to the left and an arbitrary sequence of 0's to the right.
            # (Use repeat() from itertools for the padding on the right. It doesn't matter if it's too long!)

            # Put the three pieces together to get the full line.
            # Use chain() from itertools to combine the three parts of the line:
            #          left_padding, ca_line_portion, right_padding.
            padded_line = chain(left_padding, ca_line_portion, repeat('0'))

            # padded_line has the right number of 0's at the left. It then contains the elements from ca_line
            # to be displayed. If we need more elements to display, padded_line includes an unlimted number of
            # trailing 0's.

            # Since padded_line will be displayed on patch_row, we can use zip again to pair up the values
            # from padded_line with the Patches in patch_row. Since padded_line includes an unlimited number
            # of 0's at the end, zip will stop when it reaches the last Patch in patch_row.

            ca_values_patchs = zip(padded_line, patch_row)

            # Step through these value/patch pairs and put the values into the associated Patches.
            for (ca_val, patch) in ca_values_patchs:
                # Use the set_on_off() method of OnOffPatch to set the patch to ca_val.
                patch.set_on_off(ca_val)

    # BY <ABRAN LEZAMA>
    # Assisted by <Brandon >
    '''
    Design/Strategy:
        so first we need to know the current rule number, then we loop though the post_to_switch because we want to have access to 000....111 to be able to check them in the guy
        we do this by using module on the rule number, it we get a remainder we check the box, else no.
        modles 2 is used because the numbers for each switch is a multiple of 2
        divide the rule number by 2 and repeat the process.
    
    Code:
        given below
    
    review:
        This method also is really simple. It follows good format and everything is very readable

    Testing:    
        Check that the switches are checked correctly based on what the current rule number is
    '''

    def set_switches_from_rule_nbr(self):
        """
        Update the settings of the switches based on self.rule_nbr.
        Note that the 2^i position of self.rule_nbr corresponds to self.pos_to_switch[i]. That is,
        self.pos_to_switch[i] returns the key for the switch representing position  2^i.

        Set that switch as follows: gui_set(self.pos_to_switch[pos], value=new_value).
        (new_value will be either True or False, i.e., 1 or 0.)

        This is the inverse of get_rule_nbr_from_switches().
        """
        ...
        rule_nbr = self.rule_nbr
        for i in self.pos_to_switch:
            gui_set(self.pos_to_switch[i], value=rule_nbr % 2)
            rule_nbr = rule_nbr // 2

    '''
    Design/Strategy:
        after the slider and the switches are all the same, the initial line of the frid is generated when the setup button is clicked.
        Also the set_display_from_line is called to form the initial line

    code: given below

    Review: 
        This method is also really simple. It follow good format which makes it readable

    Testing:
        check that the initial line is generated when the setup button is clicked. Otherwise, something is wrong.
    '''

    def setup(self):
        """
        Make the slider, the switches, and the bin_string of the rule number consistent with each other.
        Give the switches priority.
        That is, if the slider and the switches are both different from self.rule_nbr,
        use the value derived from the switches as the new value of self.rule_nbr.

        Once the slider, the switches, and the bin_string of the rule number are consistent,
        set self.ca_lines[0] to the line generated by build_initial_line.
        """

        # ! elif SimEngine.event == self.simple_gui.SETUP:
        # ! the line above is appear in file sim_engine.py line 149

        # self.make_switches_and_rule_nbr_consistent()
        self.ca_lines = [self.build_initial_line()]
        self.set_display_from_lines()

        # print(gui_get('init_line'))
        # print([choice([0, 1]) for _ in range(self.ca_display_size)])

    def step(self):
        """
        Take one step in the simulation.
        (a) Generate an additional line for the ca. (Use a copy of self.ca_lines[-1].)
        (b) Extend all lines in ca_lines if the new line is longer (with additional 1's) than its predecessor.
        (c) Trim the new line and add to self.ca_lines
        (d) Refresh display from values in self.ca_lines.
        """
        # (a)
        new_line: str = self.generate_new_line_from_current_line(
            copy(self.ca_lines[-1])
        )  # The new state derived from self.ca_lines[-1]

        # (b)
        # Extend lines in self.ca_lines at each end as needed.
        # (Don't extend for extra 0's at the ends.)
        # Can't do step (c) first because we would lose track of which end was extended.
        self.extend_ca_lines_if_needed(new_line)

        # (c)
        # Drop extraneous 0s at the end of new_line
        trimmed_new_line = self.drop_extraneous_0s_from_ends_of_new_line(
            new_line)
        # Add trimmed_new_line to the end of self.ca_lines
        self.ca_lines.append(trimmed_new_line)

        # (d)
        # Refresh the display from self.ca_lines
        self.set_display_from_lines()
        # Update the 'rows' widget.
        gui_set('rows', value=len(self.ca_lines))
Esempio n. 8
0
class CA_World(OnOffWorld):

    ca_display_size = 151

    # bin_0_to_7 is ['000' .. '111']
    bin_0_to_7 = [bin_str(n, 3) for n in range(8)]

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        # self.pos_to_switch is a dictionary that maps position values in a binary number to range(8) represented
        # as 3-digit binary strings:
        #     {1: '000', 2: '001', 4: '010', 8: '011', 16: '100', 32: '101', 64: '110', 128: '111'}
        # The three digits are the rule components and the keys to the switches.
        # To see it, try: print(self.pos_to_switch) after executing the next line.
        # The function bin_str() is defined in utils.py

        # The following two lines do the same thing. Explain how both work.
        pos_to_switch_a = {2**i: bin_str(i, 3) for i in range(8)}
        pos_to_switch_b = dict(zip([2**i for i in range(8)], CA_World.bin_0_to_7))
        assert pos_to_switch_a == pos_to_switch_b
        self.pos_to_switch = ...  # pos_to_switch_a or pos_to_switch_b

        # The rule number used for this run, initially set to 110 as the default rule.
        # (You might also try rule 165.)
        # The following sets the local variable self.rule_nbr. It doesn't change the 'Rule_nbr' slider widget.
        self.rule_nbr = 110
        # Set the switches and the binary representation of self.rule_nbr.
        self.set_switches_from_rule_nbr()
        self.set_binary_nbr_from_rule_nbr()
        self.init = None

        # self.ca_lines is a list of lines, each of which is a list of 0/1. Each line represents
        # a state of the CA, i.e., all the cells in the line. self.ca_list contains the entire
        # history of the CA.
        self.ca_lines: List[List[int]] = []
        # gui.WINDOW['rows'].update(value=len(self.ca_lines))
        SimEngine.gui_set('rows', value=len(self.ca_lines))

    def build_initial_line(self):
        """
        Construct the initial CA line
        """
        self.init = SimEngine.gui_get('init')
        if self.init == 'Random':
            # Set the initial row to random 1/0.
            # You complete this line.
            line = ...
        else:
            line_length = self.ca_display_size if SimEngine.gui_get('000') else 1
            line = [0] * line_length
            col = 0 if self.init == 'Left' else \
                  line_length // 2 if self.init == 'Center' else \
                  line_length - 1  # self.init == 'Right'
            line[col] = 1
        return line

    @staticmethod
    def drop_extraneous_0s_from_ends_of_new_line(new_line):
        """
        Drop the end cell at each end of new_line if it is 0. Keep it otherwise.
        Return the result.
        Args:
            new_line: ca_state with perhaps extraneous 0 cells at the ends

        Returns: trimmed ca_state without extraneous 0 cells at the ends.
        """
        ...

    def extend_ca_lines_if_needed(self, new_line):
        """
        new_line is one cell longer at each then than ca_lines[-1]. If those extra
        cells are 0, delete them. If they are 1, insert a 0 cell at the corresponding
        end of each line in ca_lines
        """
        ...

    @staticmethod
    def generate_new_line_from_current_line(prev_line):
        """
        The argument is (a copy of) the current line, i.e., copy(self.ca_lines[-1]).
        We call it prev_line because that's the role it plays in this method.
        Generate the new line in these steps.
        1. Add 0 to both ends of prev_line. (We do that because we want to allow the
        new line to extend the current line on either end. So start with a default extension.
        2. Insert an additional 0 at each end of prev_line. That's because we need a triple
        to generate the cells at the end of the new line. So, by this time the original
        line has been extended by [0, 0] on both ends.
        3. Apply the rules (i.e., the switches) to the result of step 2. This produces a line
        which is one cell shorter than the current prev_line on each end. That is, it is
        one cell longer on each end than the original prev_line.
        4. Return that newly generated line. It may have 0 or 1 at each end.
        Args:
            prev_line: The current state of the CA.
        Returns: The next state of the CA.
        """
        ...
        return new_line

    def get_rule_nbr_from_switches(self):
        """
        Translate the on/off of the switches to a rule number.
        This is the inverse of set_switches_from_rule_nbr(), but it doesn't set the 'Rule_nbr' Slider.
        """
        ...

    def handle_event(self, event):
        """
        This is called when a GUI widget is changed and isn't handled by the system.
        The key of the widget that changed is in event.
        If the changed widget has to do with the rule number or switches, make them all consistent.

        This is the function that will trigger all the code you write this week
        """
        # Handle color change requests.
        super().handle_event(event)

        if event in ['Rule_nbr'] + CA_World.bin_0_to_7:
            self.make_switches_and_rule_nbr_consistent()

    def make_switches_and_rule_nbr_consistent(self):
        """
        Make the Slider, the switches, and the bin number consistent: all should contain self.rule_nbr.
        """
        ...

    def set_binary_nbr_from_rule_nbr(self):
        """
        Translate self.rule_nbr into a binary string and put it into the
        gui.WINDOW['bin_string'] widget. For example, if self.rule_nbr is 110,
        the string '(01101110)' is stored in gui.WINDOW['bin_string']. Include
        the parentheses around the binary number.

        Use gui.WINDOW['bin_string'].update(value=new_value) to update the value of the widget.
        Use SimEngine.gui_set('bin_string', value=new_value) to update the value of the widget.
        """
        ...

    def set_display_from_lines(self):
        """
        Copy values from self.ca_lines to the patches. There are two issues.
        1. There are more or fewer lines than Patch row.
        2. The ca_lines are longer or shorter than the Patch rows.
        """
        # This is the most difficult method. Here is the outline I used.
        display_width = gui.PATCH_COLS
        ca_line_width = len(self.ca_lines[0])
        # The number of blanks to prepend to the line to be displayed to fill the display row.
        # Will be 0 if the ca_line is at least as long as the display row or the line is left-justified.
        left_padding_needed = 0 if ca_line_width >= display_width or self.init == 'Left' else ...
        # This is the position in the ca_line to be displayed that is displayed in the leftmost display Patch.
        # Will be 0 if the display width is greater than or equal to the line width or we are left-justifying.
        left_ca_line_index = 0 if display_width >= ca_line_width or self.init == 'Left' else ...

        display_height = gui.PATCH_ROWS
        nbr_ca_lines = len(self.ca_lines)
        # The first line from self.ca_lines to display.
        # Will be 0 if display_height >= nbr_ca_lines
        first_ca_line_to_display = 0 if display_height >= nbr_ca_lines else ...
        # The row of the patch display where the first ca_line is displayed.
        # Will be 0 if nbr_ca_lines >= display_height
        first_patches_row_nbr = 0 if nbr_ca_lines >= display_height else ...
        # Zip the indices of ca_lines to be display together with the indices of
        # the Patch rows where they are to be displayed.
        ca_lines_patch_rows = zip(..., ...)

        # Step through the corresponding index pairs.
        for (ca_line_index, patch_row_index) in ca_lines_patch_rows:
            # This is the portion of self.ca_lines[ca_line] to be displayed.
            # What matters is where it starts. It's ok if it's too long!
            ca_line_portion = ...
            # This is the entire line to be displayed. It may have some 0-padding
            # at the left and some 0-padding at the right. (Use repeat from itertools
            # for the padding on the right. It doesn't matter if it's too long!)
            # Use chain from itertools to combine the three parts of the line.
            padded_line = chain(..., ..., ...)
            # Zip the values from the padded_line to be displayed with the indices
            # where they will be displayed. The second parameter to zip limits the number
            # of values to be displayed. That's why it doesn't matter if the first
            # parameter is too long.
            ca_value_patch_index = zip(..., range(display_width))
            # Step through these pairs and put the values into the associated Patches.
            for (ca_val, col_nbr) in ca_value_patch_index:
                # Get the Patch to be updated from the patches_array.
                patch = CA_World.patches_array[patch_row, col_nbr]
                # Use the set_on_off method of OnOffPatch to set the patch to ca_val.
                ...

        # Update the 'rows' widget.
        ...

    def set_switches_from_rule_nbr(self):
        """
        Update the settings of the switches based on self.rule_nbr.
        Note that the 2^i position of self.rule_nbr corresponds to self.pos_to_switch[i]. That is,
        self.pos_to_switch[i] returns the key for the switch representing position  2^i.

        Set that switch as follows: gui.WINDOW[self.pos_to_switch[pos]].update(value=new_value).
        Set that switch as follows: SimEngine.gui_set(self.pos_to_switch[pos], value=new_value).
        (new_value will be either True or False, i.e., 1 or 0.)

        This is the inverse of get_rule_nbr_from_switches().
        """
        ...

    def setup(self):
        """
        Make the slider, the switches, and the bin_string of the rule number consistent with each other.
        Give the switches priority.
        That is, if the slider and the switches are both different from self.rule_nbr,
        use the value derived from the switches as the new value of self.rule_nbr.

        Once the slider, the switches, and the bin_string of the rule number are consistent,
        set self.ca_lines[0] as directed by SimEngine.gui_get('init').

        Copy (the settings on) that line to the bottom row of patches.
        Note that the lists in self.ca_lines are lists of 0/1. They are not lists of Patches.
        """
        ...

    def step(self):
        """
        Take one step in the simulation.
        (a) Generate an additional line for the ca. (Use a copy of self.ca_lines[-1].)
        (b) Extend all lines in ca_lines if the new line is longer than its predecessor.
        (c) Trim the new line and add to self.ca_lines
        (d) Copy self.ca_lines to the display
        """
        # (a)
        new_line = ... # The new state derived from self.ca_lines[-1]

        # (b)
        ... # Extend lines in self.ca_lines at each end as needed.

        # (c)
        trimmed_new_line = ... # Drop extraneous 0s at the end of new_line
        ... # Add trimmed_new_line to the end of self.ca_lines

        # (d)
        ... # Refresh the display from self.ca_lines
Esempio n. 9
0
class CA_World(OnOffWorld):

    ca_display_size = 151

    # bin_0_to_7 is ['000' .. '111']
    bin_0_to_7 = [bin_str(n, 3) for n in range(8)]

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        # self.pos_to_switch is a dictionary that maps position values in a binary number to range(8) represented
        # as 3-digit binary strings:
        #     {1: '000', 2: '001', 4: '010', 8: '011', 16: '100', 32: '101', 64: '110', 128: '111'}
        # The three digits are the rule components and the keys to the switches.
        # To see it, try: print(self.pos_to_switch) after executing the next line.
        # The function bin_str() is defined in utils.py

        self.pos_to_switch = dict(
            zip([2**i for i in range(8)], CA_World.bin_0_to_7))
        # print(self.pos_to_switch)

        # The rule number used for this run, initially set to 110 as the default rule.
        # (You might also try rule 165.)
        # The following sets the local variable self.rule_nbr. It doesn't change the 'Rule_nbr' slider widget.
        self.rule_nbr = 110
        # Set the switches and the binary representation of self.rule_nbr.
        self.set_switches_from_rule_nbr()
        self.set_binary_nbr_from_rule_nbr()
        self.init = None

        # self.ca_lines is a list of lines, each of which is a list of 0/1. Each line represents
        # a state of the CA, i.e., all the cells in the line. self.ca_list contains the entire
        # history of the CA.
        '''self.ca_lines is that cointains the rows of the 1d automaton, the : in pythion denotes the type that will be assigned to the variable
        in this case A list containing a list of integers. It is initializing this list as empty'''
        self.ca_lines: List[List[int]] = []
        # gui.WINDOW['rows'].update(value=len(self.ca_lines))
        '''this line is setting the number of ca_lines, (the number of rows) to the graphical representation of the CA,
        which in this case will be 0 since it is currently empty'''
        SimEngine.gui_set('rows', value=len(self.ca_lines))

    def build_initial_line(self):
        """
        Construct the initial CA line
        """
        self.init = SimEngine.gui_get('init')
        if self.init == 'Random':
            # Set the initial row to random 1/0.
            # You complete this line.
            line = ...
        else:
            line = [0] * self.ca_display_size
            col = 0 if self.init == 'Left' else \
                  CA_World.ca_display_size // 2 if self.init == 'Center' else \
                  CA_World.ca_display_size - 1   # self.init == 'Right'
            line[col] = 1
        return line

    def get_rule_nbr_from_switches(self):
        """
        Translate the on/off of the switches to a rule number.
        This is the inverse of set_switches_from_rule_nbr(), but it doesn't set the 'Rule_nbr' Slider.
        """

        # int_bin = {'000':1, '001':2, '010':4, '011':8, '100':16, '101':32, '110':64, '111':128}
        # r = int_bin['010']
        # output = 0
        # for r in self._rules:
        #     if SimEngine.get_gui_value(r) == True:
        #         output += int_bin[r]
        # self.rule_nbr = output

        output = []

        for rule in self.pos_to_switch:
            output.append(str('1' if SimEngine.gui_get(rule) else '0'))
        output.reverse()
        self.rule_nbr = int("".join(output), 2)

    def handle_event(self, event):
        """
        This is called when a GUI widget is changed and isn't handled by the system.
        The key of the widget that changed is the event.
        If the changed widget has to do with the rule number or switches, make them all consistent.

        This is the function that will trigger all the code you write this week
        """
        # Handle color change requests.
        '''this method is called by the model loop method in the Sim Engine class when
        it decideds that it is an event that SimEngine is not dealing with
        
        this calls the handle_event class from the superclass this world is derived from
        it gets the current event and sends it to that method to change the color of the cell if it was 
        clicked on'''
        super().handle_event(event)

        if event in ['Rule_nbr'] + CA_World.bin_0_to_7:
            self.make_switches_and_rule_nbr_consistent(event)

    def make_switches_and_rule_nbr_consistent(self, event):
        """
        Make the Slider, the switches, and the bin number consistent: all contain self.rule_nbr.
        """
        if event == 'Rule_nbr':
            self.rule_nbr = SimEngine.gui_get(event)
            self.set_switches_from_rule_nbr()
            self.set_binary_nbr_from_rule_nbr()

        if event in self.pos_to_switch:
            self.rule_nbr = self.get_rule_nbr_from_switches()
            self.set_slider_from_rule_nbr()
            self.set_binary_nbr_from_rule_nbr()

    def set_slider_from_rule_nbr(self):
        SimEngine.gui_set('Rule_nbr', value=self.rule_nbr)

    def set_binary_nbr_from_rule_nbr(self):
        """
        Translate self.rule_nbr into a binary string and put it into the
        gui.WINDOW['bin_string'] widget. For example, if self.rule_nbr is 110,
        the string '(01101110)' is stored in gui.WINDOW['bin_string']. Include
        the parentheses around the binary number.

        Use gui.WINDOW['bin_string'].update(value=new_value) to update the value of the widget.
        Use SimEngine.gui_set('bin_string', value=new_value) to update the value of the widget.
        """
        binary = self.int_to_8_bit_binary(self.rule_nbr, False)
        binary_str = ''.join(binary)
        SimEngine.gui_set('bin_string', value=binary)

    def set_display_from_lines(self):
        """
        Copy values from self.ca_lines to the patches. One issue is dealing with
        cases in which there are more or fewer lines than Patch row.
        """

        ...

    def set_switches_from_rule_nbr(self):
        """
        Update the settings of the switches based on self.rule_nbr.
        Note that the 2^i position of self.rule_nbr corresponds to self.pos_to_switch[i]. That is,
        self.pos_to_switch[i] returns the key for the switch representing position  2^i.

        Set that switch as follows: gui.WINDOW[self.pos_to_switch[pos]].update(value=new_value).
        Set that switch as follows: SimEngine.gui_set(self.pos_to_switch[pos], value=new_value).
        (new_value will be either True or False, i.e., 1 or 0.)

        This is the inverse of get_rule_nbr_from_switches().
        """
        for rule_switch, enabled in zip(
                self.pos_to_switch, self.int_to_8_bit_binary(self.rule_nbr)):
            SimEngine.gui_set(rule_switch,
                              value=(True if enabled == '1' else False))

    def int_to_8_bit_binary(self, input, rev=True):
        output = "{0:b}".format(input)
        output2 = list(output)
        output2.reverse()
        while len(output2) < 8:
            output2.append('0')

        #reverse to alignt to rules and index
        if rev:
            return output2
        else:
            output2.reverse()
            return output2

    def setup(self):
        """
        Make the slider, the switches, and the bin_string of the rule number consistent with each other.
        Give the switches priority.
        That is, if the slider and the switches are both different from self.rule_nbr,
        use the value derived from the switches as the new value of self.rule_nbr.

        Once the slider, the switches, and the bin_string of the rule number are consistent,
        set self.ca_lines[0] as directed by SimEngine.gui_get('init').

        Copy (the settings on) that line to the bottom row of patches.
        Note that the lists in self.ca_lines are lists of 0/1. They are not lists of Patches.
        """
        ...

    def step(self):
        """
        Take one step in the simulation.
        o Generate an additional line in self.ca_lines.
        o Copy self.ca_lines to the display
        """
        ...
Esempio n. 10
0
class CA_World(OnOffWorld):

    ca_display_size = 141

    # bin_0_to_7 is ['000' .. '111']
    bin_0_to_7 = [bin_str(n, 3) for n in range(8)]

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        # self.pos_to_switch is a dictionary that maps position values in a binary number to range(8) represented
        # as 3-digit binary strings:
        #     {1: '000', 2: '001', 4: '010', 8: '011', 16: '100', 32: '101', 64: '110', 128: '111'}
        # The three digits are the rule components and the keys to the switches.
        # To see it, try: print(self.pos_to_switch) after executing the next line.
        # The function bin_str() is defined in utils.py

        # The following two lines do the same thing. Explain how both work.
        pos_to_switch_a = {2**i: bin_str(i, 3) for i in range(8)}
        pos_to_switch_b = dict(zip([2**i for i in range(8)], CA_World.bin_0_to_7))
        assert pos_to_switch_a == pos_to_switch_b
        # self.pos_to_switch = ...  # pos_to_switch_a or pos_to_switch_b
        self.pos_to_switch = pos_to_switch_a

        # The rule number used for this run, initially set to 110 as the default rule.
        # (You might also try rule 165.)
        # The following sets the local variable self.rule_nbr. It doesn't change the 'Rule_nbr' slider widget.
        self.rule_nbr = 110
        # Set the switches and the binary representation of self.rule_nbr.
        self.set_switches_from_rule_nbr()
        self.set_binary_nbr_from_rule_nbr()
        self.get_rule_nbr_from_switches()
        self.make_switches_and_rule_nbr_consistent()

        # self.ca_lines is a list of lines, each of which is a list of 0/1. Each line represents
        # a state of the CA, i.e., all the cells in the line. self.ca_list contains the entire
        # history of the CA.
        self.ca_lines: List[List[int]] = []
        # gui.WINDOW['rows'].update(value=len(self.ca_lines))
        SimEngine.gui_set('rows', value=len(self.ca_lines))

    def build_initial_line(self):
        """
        Construct the initial CA line.
        It is a random line if SimEngine.gui_get('Random?').
        It is a line (of length ca_display_size) if SimEngine.gui_get('init_line') == ''.
        Otherwise it is the string in SimEngine.gui_get('init_line') converted into 0's and 1's.
        (' ' and '0' are converted to 0; everything else is converted to 1.)
        However, if the rule includes 000 -> 1,pad the line with 0's on both ends to fill the display.
        How much to put on each end depends on the user-specific initial line and the requested justification.
        """
        absolute = SimEngine.gui_get('absolute')
        if SimEngine.gui_get('Random?'):
            # Random sequence of 0's and 1's to fit the display size.
            line = [choice([0,1]) for _ in range(self.ca_display_size)]
        else:
            # A line of 0's.
            padding = [0] * (self.ca_display_size-1)
            if SimEngine.gui_get('init') == '':
                line = padding
            else:
                line_0 = SimEngine.gui_get('init')
                # Convert line_0 to 0's and 1's
                line = [1 if c == '1' else 0 for c in line_0]
                # If the rule include 000 -> 1, fill out the new line with 0's.
                if SimEngine.gui_get('000'):
                    justification = SimEngine.gui_get('justification')
                    line_len = len(line)
                    actual_padding = padding[line_len:]
                    if absolute:
                        # Pad to the left if we are absolute justifying to the right
                        # Pad the right side if we are absolute justifying the left
                        # Pad both sides if we are centering.
                        line = actual_padding + line if justification == 'Right' else \
                               line + actual_padding if justification == 'Left' else \
                               actual_padding + line + actual_padding #  justification == 'Center'
                    else:
                        # We can add padding to both sides if we are not using absolute. (For all '000').
                        actual_padding = [0] * self.ca_display_size
                        line = actual_padding + line + actual_padding
        return line

    @staticmethod
    def drop_extraneous_0s_from_ends_of_new_line(new_line):
        """
        Drop the end cell at each end of new_line if it is 0. Keep it otherwise.
        Return the result.
        Args:
            new_line: ca_state with perhaps extraneous 0 cells at the ends

        Returns: trimmed ca_state without extraneous 0 cells at the ends.
        """
        # checks to see if the last element of new_line is 0
        # if new_line[-1] == 0:
        #     # removes ending 0 from new_line
        #     new_line.pop(-1)
        # # checks if first element of new_line is 0
        # if new_line[0] == 0:
        #     # removes first element of new_line
        #     new_line.pop(0)

        # checks to see if the first and last elements of new_line are both 0
        if new_line[0] == 0 & new_line[-1] == 0:
            # removes ending 0 from new_line
            new_line.pop(-1)
            # removes 0 from beginning of new_line
            new_line.pop(0)

        return new_line

    def extend_ca_lines_if_needed(self, new_line):
        """
        new_line is one cell longer at each then than ca_lines[-1]. If those extra
        cells are 0, delete them. If they are 1, insert a 0 cell at the corresponding
        end of each line in ca_lines
        """
        # Create a copy of the new line
        nl_copy = new_line.copy()
        # Check if the first and last elements are 0's
        if (nl_copy[0], nl_copy[1]) == (0,0):
            # Return a slice of the copied line without the first and last element
            return nl_copy[1:-1]
        # Check if the first and last elements are 1's
        elif (nl_copy[0], nl_copy[1]) == (1,1):
            # Return a list with 0's inserted to the front and end of the list.
            return [0] + nl_copy + [0]

        # No pairs of 0s or 1s at each end. No changes made.
        return new_line

    @staticmethod
    def generate_new_line_from_current_line(prev_line):
        """
        The argument is (a copy of) the current line, i.e., copy(self.ca_lines[-1]).
        We call it prev_line because that's the role it plays in this method.
        Generate the new line in these steps.
        1. Add 0 to both ends of prev_line. (We do that because we want to allow the
        new line to extend the current line on either end. So start with a default extension.
        2. Insert an additional 0 at each end of prev_line. That's because we need a triple
        to generate the cells at the end of the new line. So, by this time the original
        line has been extended by [0, 0] on both ends.
        3. Apply the rules (i.e., the switches) to the result of step 2. This produces a line
        which is one cell shorter than the current prev_line on each end. That is, it is
        one cell longer on each end than the original prev_line.
        4. Return that newly generated line. It may have 0 or 1 at each end.
        Args:
            prev_line: The current state of the CA.
        Returns: The next state of the CA.
        """
        # Create a copy of the new line
        prev_copy = prev_line.copy()

        # Concatenate 2 zeros to the beginning and end of the copy
        prev_copy = [0, 0] + prev_copy + [0, 0]

        # Create a list for the new line
        new_line = []
        # Loop through each number in the previous line
        # Subtract 2 since we need to check the i'th, i+1, and i+2 indices and stay in bounds.
        for i in range(len(prev_copy)-2):
            # Convert the first, next, and second next elements into strings to obtain the key
            neighbors = str(prev_copy[i]) + str(prev_copy[i+1]) + str(prev_copy[i+2])
            # Get the value of the checkbox using the obtained key and convert it into an int.
            # Append the converted integer onto the new line.
            new_line.append(int(SimEngine.gui_get(key=neighbors)))
        return new_line

    def get_rule_nbr_from_switches(self):
        """
        Translate the on/off of the switches to a rule number.
        This is the inverse of set_switches_from_rule_nbr(), but it doesn't set the 'Rule_nbr' Slider.
        """
        rule_nbr = 0
        for i in range(8):
            # for every element in range(8) -> [0,1,2,3,4,5,6,7]
            # 0, 1, 2, 4, 8, 16, 32 ...
            # 2^0, 2^1, 2^2 ...
            dec_value = 2 ** i

            # switch_key = '000', '001', '002'
            switch_key = self.pos_to_switch[dec_value]
            # 1 or 0, depending on checkbox
            # Implementation of SimEngine.gui_get(switch_key) runs into a None Type error
            switch_value = gui.WINDOW[switch_key].Get()
            # switch_value = SimEngine.gui_get(str(switch_key))

            rule_nbr += (2 ** i) * switch_value

        return rule_nbr

    def handle_event(self, event):
        """
        This is called when a GUI widget is changed and isn't handled by the system.
        The key of the widget that changed is in event.
        If the changed widget has to do with the rule number or switches, make them all consistent.

        This is the function that will trigger all the code you write this week
        """
        # Let OnOffWorld handle color change requests.
        '''
        We call super().handle_event(event) we want the handle_event method associated
        with the superclass 'onOffWorld, rather than the one in this class.
        The handle_event method in the super class handles the events in the color pickers,
        "SELECT_ON_TEXT" and "SELECT_OFF_TEXT" first before we proceed with changing sliders/switches/rule numbers.
        If we don't have super().handle_event(event), the colors wouldn't change unless we added the color handling
        specifically within this method.
        '''
        super().handle_event(event)

        # Handle switches and rule slider
        if event in ['Rule_nbr']:
            self.rule_nbr = SimEngine.gui_get('Rule_nbr')
        # self.bin_0_to_7 is essentially a list of all the checkbox keys
        elif event in self.bin_0_to_7:
            self.rule_nbr = self.get_rule_nbr_from_switches()
        elif event == 'justification':
            self.set_display_from_lines()
        else:
            pass
        self.make_switches_and_rule_nbr_consistent()

    def make_switches_and_rule_nbr_consistent(self):
        """
        Make the Slider, the switches, and the bin number consistent: all should contain self.rule_nbr.
        """
        # gui.WINDOW['Rule_nbr'].update(value=self.rule_nbr)
        SimEngine.gui_set('Rule_nbr', value=self.rule_nbr)
        self.set_switches_from_rule_nbr()
        self.set_binary_nbr_from_rule_nbr()


    def set_binary_nbr_from_rule_nbr(self):
        """
        Translate self.rule_nbr into a binary string and put it into the
        gui.WINDOW['bin_string'] widget. For example, if self.rule_nbr is 110,
        the string '(01101110)' is stored in gui.WINDOW['bin_string']. Include
        the parentheses around the binary number.

        Use SimEngine.gui_set('bin_string', value=new_value) to update the value of the widget.
        """
        binary_list = self.rule_nbr_to_binary_list()
        # [0, 0, 0, 1]
        # ['0', '0', '0', '1']
        binary_list_to_strs = [str(x) for x in binary_list]
        rule_nbr_to_bin_str = ''.join(binary_list_to_strs)
        SimEngine.gui_set('bin_string', value=rule_nbr_to_bin_str)

    def set_display_from_lines(self):
        """
        Copy values from self.ca_lines to the patches. There are two issues.
        1. Is self.ca_lines longer/shorter than the number of Patch rows in the display?
        2. Are there more/fewer cells-per-line than Patches-per-row?
        What do you do in each case?

        This is the most difficult method. Here is the outline I used.
        """
        # Get the current setting of 'justification'.
        justification = SimEngine.gui_get('justification')
        # Get the current setting of 'absolute'
        absolute = SimEngine.gui_get('absolute')

        # Get the two relevant widths.
        display_width = gui.PATCH_COLS

        # All the lines in self.ca_lies are the same length.
        ca_line_width = len(self.ca_lines[0])

        # How many blanks must be prepended to the line to be displayed to fill the display row?
        # Will be 0 if the ca_line is at least as long as the display row or the line is left-justified.
        left_padding_needed = 0 if ca_line_width >= display_width or justification == 'Left' else 0

        # Use [0]*n to get a list of n 0s to use as left padding.
        left_padding = [0]*left_padding_needed

        # Which elements of the ca_line are to be displayed?
        # More to the point, what is index of the first element of the line to be displayed?
        # Will be 0 if the display width is greater than or equal to the line width or we are left-justifying.
        left_ca_line_index = 0 if display_width >= ca_line_width or justification == 'Left' else 1

        # Reverse both self.ca_lines and CA_World.patches_array.
        ca_lines_to_display = reversed(self.ca_lines)
        # Reverse the rows of CA_World.patches_array
        patch_rows_to_display_on = np.flip(CA_World.patches_array, axis=0)

        # Now we can use zip to match up ca_lines_to_display and patch_rows_to_display on.
        # In both cases we are starting at the bottom and working our way up.
        ca_lines_patch_rows = zip(ca_lines_to_display, patch_rows_to_display_on)

        # zip is given two iterables and produces a sequence of pairs of elements, one from each.
        # An important feature of zip is that it stops whenever either of its arguments ends.
        # In particular, the two arguments needn't be the same length. Zip simply uses all the
        # elements of the shorter and pairs them with the initial elements of the longer.

        # We can now step through the corresponding pairs.
        for (ca_line, patch_row) in ca_lines_patch_rows:
            # The values in ca_line are to be displayed on patch_row.
            # The issue now is how to align them.

            # Which elements of ca_line should be displayed?
            # We display the elements starting at left_ca_line_index (computed above).

            # Left absolute justification displays the right half of the line.
            # Center absolute displays everything in the center of the line.
            # Right absolute displays everything in the left half of the line.
            if absolute:
                # Left -> Slice of everything on the right half
                # Center -> Keep as is
                # Right -> Slice of everything on the left half
                ca_line_portion = ca_line[len(ca_line) // 2:] if justification == 'Left' \
                    else ca_line if justification == 'Center' \
                    else ca_line[:(len(ca_line) // 2) + 1]

                # Number of padding to use, generated for each line portion
                num_pad = display_width - len(ca_line_portion)

                if justification == 'Right':
                    if num_pad > 0:
                        # Our line portion is not as large as the number of padding
                        # Pad to fill in the additional space.
                        left_padding = [0] * num_pad
                    else:
                        # Find the center of the line (since ca_line_portion is already halved)
                        # len(ca_line_portion) just gives us the middle of ca_line
                        center_of_line = len(ca_line_portion)
                        # Find where to begin our display
                        # Since our ca_line can be very large, we need to use the center of the line
                        # to calculate where we can begin our slice.
                        start_to_display = center_of_line - display_width
                        # Since the length of our ca_line_portion is large enough,
                        # We no longer need any padding, and can just slice right away.
                        ca_line_portion = ca_line[start_to_display:center_of_line]
                        left_padding = []
            else:
                # Non-absolutes
                # If we are left-justifying, we only care about the left side of the list
                # If we are right-justifying, we only care about the right side of the list.
                # Ca_line[::-1] is the reverse of Ca_line
                ca_line_portion = ca_line[:display_width] if justification == 'Left' else \
                                  ca_line if justification == 'Center' else \
                                  ca_line[::-1][:display_width] if len(ca_line) > display_width and justification == 'Right' else \
                                  ca_line[::-1]
                # We can pad the left side of the line with 0's based off the longest (last) line in the list
                # Divide it by 2 and subtract the length of the current line by 2.
                # This will pad 0s to fill up any difference in length.
                left_padding = [0] * ((len(self.ca_lines[-1]) // 2) - (len(ca_line) // 2))

            # We don't care about absolute or not here, since Center will be the same for both.
            if justification == 'Center':
                # Now we have a different number for padding.
                # Cut the display width in half and subtract the half the length of the portion.
                num_pad = display_width // 2 - len(ca_line_portion) // 2
                if num_pad > 0:
                    # We only add padding based on half the length of the line
                    left_padding = [0] * num_pad
                else:
                    # Once our left side fits the width of half the screen, we can just display a middle slice
                    # that is the width of the display
                    center_of_line = len(ca_line_portion) // 2
                    # Find where to begin and where to end in the slice (to get the middle)
                    start_to_display = center_of_line - display_width // 2
                    # Set the portion to the center slice of the line
                    ca_line_portion = ca_line[start_to_display:center_of_line + (display_width // 2) + 1]
                    left_padding = []

            # For the complete display line and the desired justification,
            # we may need to pad ca_line_portion to the left or right (or both).
            # We need left_padding_needed 0's to the left and an arbitrary sequence of 0's to the right.
            # (Use repeat() from itertools for the padding on the right. It doesn't matter if it's too long!)

            # Put the three pieces together to get the full line.
            # Use chain() from itertools to combine the three parts of the line:
            #          left_padding, ca_line_portion, right_padding.
            # We need this right padding in case we change justification mid-step
            # It will clear up any previous 1's existing on the right side.
            right_padding = [0] * (display_width - len(ca_line))
            padded_line = chain(left_padding, ca_line_portion, right_padding)
            # padded_line has the right number of 0's at the left. It then contains the elements from ca_line
            # to be displayed. If we need more elements to display, padded_line includes an unlimted number of
            # trailing 0's.

            # Since padded_line will be displayed on patch_row, we can use zip again to pair up the values
            # from padded_line with the Patches in patch_row. Since padded_line includes an unlimited number
            # of 0's at the end, zip will stop when it reaches the last Patch in patch_row.

            ca_values_patches = zip(padded_line, patch_row)
            # Reverse the patch row if we're right justifying to make it easier when we are displaying.
            if justification == 'Right' and not absolute:
                ca_values_patches = zip(padded_line, reversed(patch_row))

            # Step through these value/patch pairs and put the values into the associated Patches.
            for (ca_val, patch) in ca_values_patches:
                # Use the set_on_off method of OnOffPatch to set the patch to ca_val.
                patch.set_on_off(ca_val)

    def set_switches_from_rule_nbr(self):
        """
        Update the settings of the switches based on self.rule_nbr.
        Note that the 2^i position of self.rule_nbr corresponds to self.pos_to_switch[i]. That is,
        self.pos_to_switch[i] returns the key for the switch representing position  2^i.
        Set that switch as follows: gui.WINDOW[self.pos_to_switch[pos]].update(value=new_value).
        Set that switch as follows: SimEngine.gui_set(self.pos_to_switch[pos], value=new_value).
        (new_value will be either True or False, i.e., 1 or 0.)
        This is the inverse of get_rule_nbr_from_switches().
        """

        # List of 0s and 1s... based off of rule number (binary)
        # [0, 1, 1, 0, 1, 1, 1, 0]
        rule_nbr_to_binary = list(reversed(self.rule_nbr_to_binary_list()))

        for i in range(8):
            # for every element in range(8) -> [0,1,2,3,4,5,6,7]
            dec_value = 2 ** i
            SimEngine.gui_set(self.pos_to_switch[dec_value], value=rule_nbr_to_binary[i])
            # checks every element in the list if it is a 0
            # If there is a zero, then the method tells the gui to uncheck the switch at
            # the position corresponding to dec_value. If there is a 1, then
            # it is checked

    def rule_nbr_to_binary_list(self):
        # Rule number is a decimal value.. ex.. 110
        # Method takes the decimal value of the rule number and uses a range of 8 to determine
        # if a 1 or a 0 is added to an array
        temp_rule_nbr = self.rule_nbr
        rule_nbr_to_binary = []
        # Range(8) is an iterator 0, 1, 2, 3, ... 7
        # Reverse it to make it 7, 6, 5, ... 0
        for i in reversed(range(8)):
            # use a sequence incrementing down from 7 to 0 with i being an element in the sequence
            dec_value = 2 ** i
            if temp_rule_nbr >= dec_value:
                temp_rule_nbr -= dec_value
                rule_nbr_to_binary.append(1)
            else:
                rule_nbr_to_binary.append(0)
                # ex. let temp_rule_nbr = 150
                # 150 > 128, 150-128 = 22, [1]
                # 22 < 64, 22, [1,0]
                # 22 < 32, 22, [1,0,0]
                # 22 > 16, 6, [1,0,0,1]
                # 6 < 8, 6, [1,0,0,1,0]
                # 6 > 4, 2, [1,0,0,1,0,1]
                # 2 = 2, 0, [1,0,0,1,0,1,1]
                # [1,0,0,1,0,1,1,0]
        return rule_nbr_to_binary

    def setup(self):
        """
        Make the slider, the switches, and the bin_string of the rule number consistent with each other.
        Give the switches priority.
        That is, if the slider and the switches are both different from self.rule_nbr,
        use the value derived from the switches as the new value of self.rule_nbr.

        Once the slider, the switches, and the bin_string of the rule number are consistent,
        set self.ca_lines[0] as directed by SimEngine.gui_get('justification').

        Copy (the settings on) that line to the bottom row of patches.
        Note that the lists in self.ca_lines are lists of 0/1. They are not lists of Patches.
        """
        # Review by: Wilson Weng
        # The method follows standard PEP 8 indentation and spacing rules.
        # The method sets ca_lines with an empty array to reset any changes
        # The method then adds to ca_lines with values from build_initial_line()
        # values from build_initial_line() dependant on the value select from the Initial Row GUI
        # The method finally calls set_display_from_lines() to setup the first line

        # Reset self.ca_lines
        self.ca_lines = []
        # Build initial line and append it to self.ca_lines
        self.ca_lines.append(self.build_initial_line())
        # Update the display based off of self.ca_lines
        self.set_display_from_lines()

    def step(self):
        """
        Take one step in the simulation.
        (a) Generate an additional line for the ca. (Use a copy of self.ca_lines[-1].)
        (b) Extend all lines in ca_lines if the new line is longer (with additional 1's) than its predecessor.
        (c) Trim the new line and add to self.ca_lines
        (d) Refresh display from values in self.ca_lines.
        """
        # (a)
        new_line = self.generate_new_line_from_current_line(self.ca_lines[-1]) # The new state derived from self.ca_lines[-1]

        # (b)
        # Extend lines in self.ca_lines at each end as needed. (Don't extend for extra 0's at the ends.)
        # Can't drop the 0's first because we then lose track of which end was extended.
        new_line = self.extend_ca_lines_if_needed(new_line)

        # (c)
        trimmed_new_line = self.drop_extraneous_0s_from_ends_of_new_line(new_line)
        # Add trimmed_new_line to the end of self.ca_lines
        self.ca_lines.append(trimmed_new_line)

        # (d)
        # Refresh the display from self.ca_lines
        self.set_display_from_lines()
Esempio n. 11
0
class CA_World(OnOffWorld):

    ca_display_size = 151

    # bin_0_to_7 is ['000' .. '111']
    bin_0_to_7 = [bin_str(n, 3) for n in range(8)]

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        # self.pos_to_switch is a dictionary that maps position values in a binary number to range(8) represented
        # as 3-digit binary strings:
        #     {1: '000', 2: '001', 4: '010', 8: '011', 16: '100', 32: '101', 64: '110', 128: '111'}
        # The three digits are the rule components and the keys to the switches.
        # To see it, try: print(self.pos_to_switch) after executing the next line.
        # The function bin_str() is defined in utils.py

        self.pos_to_switch = dict(
            zip([2**i for i in range(8)], CA_World.bin_0_to_7))
        # print(self.pos_to_switch)

        # The rule number used for this run, initially set to 110 as the default rule.
        # (You might also try rule 165.)
        # The following sets the local variable self.rule_nbr. It doesn't change the 'Rule_nbr' slider widget.
        self.rule_nbr = 110
        # Set the switches and the binary representation of self.rule_nbr.
        # self.set_switches_from_rule_nbr()
        # self.set_binary_nbr_from_rule_nbr()
        # self.set_slider_from_rule_nbr()

        self.make_switches_and_rule_nbr_consistent()

        self.init = None

        # self.ca_lines is a list of lines, each of which is a list of 0/1. Each line represents
        # a state of the CA, i.e., all the cells in the line. self.ca_list contains the entire
        # history of the CA.
        self.ca_lines: List[List[int]] = []

        #For testing --- REMOVE
        self.ca_lines.append([])
        self.ca_lines[0] = [0, 0, 1, 0, 0]
        #end of test code

        # gui.WINDOW['rows'].update(value=len(self.ca_lines))
        SimEngine.gui_set('rows', value=len(self.ca_lines))

    def build_initial_line(self):
        """
        Construct the initial CA line
        """
        # self.init = SimEngine.gui_get('init')
        # if self.init == 'Random':
        #     # Set the initial row to random 1/0.
        #     # You complete this line.
        #     line = "".join(random.choice('10') for i in range(self.ca_display_size))
        # else:
        #     line = [0] * self.ca_display_size
        #     col = 0 if self.init == 'Left' else \
        #           CA_World.ca_display_size // 2 if self.init == 'Center' else \
        #           CA_World.ca_display_size - 1   # self.init == 'Right'
        #     line[col] = 1
        # return line

        #if justification is used to display it is unnecessary to make the initial line as wide as the gui except
        #when random
        self.init = SimEngine.gui_get('init')
        if self.init == 'Random':
            # Set the initial row to random 1/0.
            # You complete this line.
            line = "".join(
                random.choice('10') for i in range(self.ca_display_size))

            # check to see if a zero needs to be added to either end
            # useful for the rule 001 or 100
            if line[:2] == [0, 1]:
                line.insert(0, 0)

            if line[-2:] == [1, 0]:
                line.append(0)

            return line

        else:
            return [0, 0, 1, 0, 0]

    def get_rule_nbr_from_switches(self):
        """
        Translate the on/off of the switches to a rule number.
        This is the inverse of set_switches_from_rule_nbr(), but it doesn't set the 'Rule_nbr' Slider.
        """
        output = []

        for rule in CA_World.bin_0_to_7:
            output.append(str('1' if SimEngine.gui_get(rule) else '0'))
        output.reverse()
        self.rule_nbr = int("".join(output), 2)

    def handle_event(self, event):
        """
        This is called when a GUI widget is changed and isn't handled by the system.
        The key of the widget that changed is the event.
        If the changed widget has to do with the rule number or switches, make them all consistent.

        This is the function that will trigger all the code you write this week
        """
        # Handle color change requests.
        super().handle_event(event)

        if event in ['Rule_nbr'] + CA_World.bin_0_to_7:
            if event == 'Rule_nbr':
                self.rule_nbr = SimEngine.gui_get('Rule_nbr')

            if event in CA_World.bin_0_to_7:
                self.get_rule_nbr_from_switches()

            self.make_switches_and_rule_nbr_consistent()

    def make_switches_and_rule_nbr_consistent(self):
        """
        Make the Slider, the switches, and the bin number consistent: all should equal self.rule_nbr.
        """
        self.set_slider_from_rule_nbr()
        self.set_switches_from_rule_nbr()
        self.set_binary_nbr_from_rule_nbr()

    def set_slider_from_rule_nbr(self):
        SimEngine.gui_set('Rule_nbr', value=self.rule_nbr)

    def set_binary_nbr_from_rule_nbr(self):
        """
        Translate self.rule_nbr into a binary string and put it into the
        gui.WINDOW['bin_string'] widget. For example, if self.rule_nbr is 110,
        the string '(01101110)' is stored in gui.WINDOW['bin_string']. Include
        the parentheses around the binary number.

        Use gui.WINDOW['bin_string'].update(value=new_value) to update the value of the widget.
        Use SimEngine.gui_set('bin_string', value=new_value) to update the value of the widget.
        """
        binary = self.int_to_8_bit_binary(self.rule_nbr, False)
        binary_str = ''.join(binary)
        SimEngine.gui_set('bin_string', value=binary_str)

    def int_to_8_bit_binary(self, input, rev=True):
        output = "{0:b}".format(input)
        output2 = list(output)
        output2.reverse()
        while len(output2) < 8:
            output2.append('0')

        #reverse to alignt to rules and index
        if rev:
            return output2
        else:
            output2.reverse()
            return output2

    def set_display_from_lines(self):
        """
        Copy values from self.ca_lines to the patches. One issue is dealing with
        cases in which there are more or fewer lines than Patch row.
        """
        y = 1
        maxlin = CA_World.ca_display_size - 1
        limy = len(self.ca_lines) + maxlin
        for i in self.ca_lines:
            x = 1
            if limy >= maxlin:
                if SimEngine.gui_get('init') == "Right":  # Right
                    limx = len(i) + maxlin + 2
                    for j in range(len(i) - 2):
                        if limx >= maxlin:
                            b = bool(i[j])
                            self.pixel_tuple_to_patch(
                                ((maxlin - len(i) + 2 + x) * 4,
                                 (maxlin - len(self.ca_lines) + y) *
                                 4)).set_on_off(b)
                            x += 1
                        else:
                            limx -= 1
                elif SimEngine.gui_get('init') == "Left":  # Left
                    limx = 0
                    for j in range(len(i) - 2):
                        if limx <= maxlin + 2:
                            b = bool(i[j])
                            self.pixel_tuple_to_patch(
                                ((x - 3) * 4,
                                 (maxlin - len(self.ca_lines) + y) *
                                 4)).set_on_off(b)
                            x += 1
                            limx += 1
                else:  # Center and Random
                    limx = int((len(i) - maxlin) / 2)
                    k = 0
                    for j in range(len(i)):
                        if limx < 0:
                            b = bool(i[j])
                            self.pixel_tuple_to_patch(
                                ((maxlin - len(i) + x - 1 + limx) * 4,
                                 (maxlin - len(self.ca_lines) + y) *
                                 4)).set_on_off(b)
                        else:
                            if k < maxlin + 1:
                                b = bool(i[j + limx])
                                self.pixel_tuple_to_patch(
                                    (k * 4, (maxlin - len(self.ca_lines) + y) *
                                     4)).set_on_off(b)
                        x += 1
                        k += 1
                y += 1
            else:
                limy -= 1

    def set_switches_from_rule_nbr(self):
        """
        Update the settings of the switches based on self.rule_nbr.
        Note that the 2^i position of self.rule_nbr corresponds to self.pos_to_switch[i]. That is,
        self.pos_to_switch[i] returns the key for the switch representing position  2^i.

        Set that switch as follows: gui.WINDOW[self.pos_to_switch[pos]].update(value=new_value).
        Set that switch as follows: SimEngine.gui_set(self.pos_to_switch[pos], value=new_value).
        (new_value will be either True or False, i.e., 1 or 0.)

        This is the inverse of get_rule_nbr_from_switches().
        """
        for rule_switch, enabled in zip(
                CA_World.bin_0_to_7, self.int_to_8_bit_binary(self.rule_nbr)):
            SimEngine.gui_set(rule_switch,
                              value=(True if enabled == '1' else False))

    def setup(self):
        """
        Make the slider, the switches, and the bin_string of the rule number consistent with each other.
        Give the switches priority.
        That is, if the slider and the switches are both different from self.rule_nbr,
        use the value derived from the switches as the new value of self.rule_nbr.

        Once the slider, the switches, and the bin_string of the rule number are consistent,
        set self.ca_lines[0] as directed by SimEngine.gui_get('init').

        Copy (the settings on) that line to the bottom row of patches.
        Note that the lists in self.ca_lines are lists of 0/1. They are not lists of Patches.
        """
        self.ca_lines = []
        self.ca_lines.append(self.build_initial_line())
        self.set_display_from_lines()

    def step(self):
        """
        Take one step in the simulation.
        o Generate an additional line in self.ca_lines.
        o Copy self.ca_lines to the display
        """

        #make a dictionary of rules and which are active
        binary = self.int_to_8_bit_binary(self.rule_nbr)
        binary_str = ''.join(binary)

        active_rules = dict(zip(CA_World.bin_0_to_7, list(binary_str)))

        #variable to store the new computed line
        #first one will always be zero as there is no rule
        #find better explanation
        new_line = [0]

        #compute the new line

        #for each ca triplet to check
        for i in range(len(self.ca_lines[-1]) - 2):
            #make the array of three cells together ex [1,0,0] starting at index one and ending at index 3 before the end
            #then check to see if it is active from the dictionary
            #add the new cell as a 1 if active else set it to 0
            new_line.append(1 if active_rules["".join(
                str(x) for x in self.ca_lines[-1][i:i + 3])] == '1' else 0)

        # variable to store the new computed line
        # first one will always be zero as there is no rule
        # find better explanation
        new_line.append(0)

        self.ca_lines.append(new_line)

        #add leading and trailing zeroes to all entries in the history
        for row in self.ca_lines:
            row.insert(0, 0)
            row.append(0)

        self.set_display_from_lines()