8
\$\begingroup\$

I coded a simulator of the Game Of Life by Conway. I made using Kivy, so that it has a graphical interface.

Any suggestion on how to improve its pythonism or performance would be highly appreciated.

This it my .kv file:

<MenuGrid>:
    id: actions
    cols: 1
    rows: 1

    BoxLayout:
        size_hint_y: None
        height: 30
        spacing: 10

        canvas:
            Color:
                rgba: 1, 1, 1, 1
            Rectangle:
                pos: self.pos
                size: self.size

        Button:
            text: str(actions.clear_stop)
            on_press: actions.clear_action()

        Button:
            text: str(actions.start_pause)
            on_press: actions.start_action()

And this is my .py file:

from kivy.config import Config
Config.set('graphics', 'width',  903)
Config.set('graphics', 'height', 933)
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.togglebutton import ToggleButton
from kivy.clock import Clock
from kivy.properties import StringProperty


class MenuGrid(GridLayout):
    active = False  # Defines whether the game is running, or paused.
    start_pause = StringProperty('Start')
    clear_stop = StringProperty('Clear')
    clock = None

    def start_action(self):

        # If game is paused, run it, making an iteration every half a second
        # Change the labels to Pause and Stop

        if self.active is False:
            for instance in CustomButton.cells:
                instance.disabled = True
            self.clock = Clock.schedule_interval(iteration, 0.5)
            self.active = True
            self.start_pause = 'Pause'
            self.clear_stop = 'Stop'

        # Else, unschedule the game and change the labels to Start and Clear

        elif self.active is True:
            self.clock.cancel()
            for instance in CustomButton.cells:
                instance.disabled = False
            self.active = False
            self.start_pause = 'Start'
            self.clear_stop = 'Clear'

    def clear_action(self):

        # Stop the game if running, and change all instances to their normal
        # state and set them to False, so that they are not considered as
        # living cells in the next game.

        if self.active is False:
            for instance in CustomButton.cells:
                instance.state = 'normal'
                instance.phase = False
        elif self.active is True:
            self.start_action()
            for instance in CustomButton.cells:
                instance.state = 'normal'
                instance.phase = False


class CustomButton(ToggleButton):

    # Class for cells. Give them an id and a False phase.
    # The phase tells the program if the cell is alive or dead.
    # Store them in a list

    cells = []

    def __init__(self, index, **kwargs):
        super(CustomButton, self).__init__(**kwargs)
        self.id = str(index)
        self.phase = False
        CustomButton.cells.append(self)

    def __str__(self):
        return 'Index: {}'.format(self.id)

    def __int__(self):
        return int(self.id)


def iteration(dt):

    # Get the list of cells.
    # Create temporary list

    cells = CustomButton.cells
    next_iteration = []

    # For each cell, count its living neighbours by trying to access them and
    # checking their phase

    for i, instance in enumerate(cells):

        neighbours = [-51, -50, -49, -1, 1, 49, 50, 51]
        living_neighbours = 0

        for x in neighbours:
            try:
                if cells[i + x].phase is True:
                    living_neighbours += 1
            except IndexError:
                pass
            if living_neighbours == 4:
                break

        # Apply the game's rules
        #  1: Any live cell with fewer than two live neighbours dies,
        #     as if caused by underpopulation.
        #  2: Any live cell with two or three live neighbours lives on to
        #     the next generation.
        #  3: Any live cell with more than three live neighbours dies,
        #     as if by overpopulation.
        #  4: Any dead cell with exactly three live neighbours becomes a
        #     live cell, as if by reproduction.

        if instance.phase is True:
            if living_neighbours < 2:
                next_iteration.append(False)
            elif living_neighbours > 3:
                next_iteration.append(False)
            else:
                next_iteration.append(True)
        else:
            if living_neighbours == 3:
                next_iteration.append(True)
            else:
                next_iteration.append(False)

    # Change the state of the buttons according to their phases

    for i, instance in enumerate(cells):
        instance.phase = next_iteration[i]
        if next_iteration[i] is True:
            instance.state = 'down'
        else:
            instance.state = 'normal'


def change_phase(instance):

    # If a cell is clicked, turn it to its opposite phase.
    # True - False
    # Alive - Dead

    if instance.phase is True:
        instance.phase = False
    else:
        instance.phase = True


class GameOfLifeApp(App):

    # Build the game
    # Create a 50x50 grid, and assign a button to each cell.
    # Bind the button to the change_phase() function

    def build(self):

        layout_base = GridLayout(cols=1, row_force_default=True,
                                 row_default_height=30)
        layout_base.add_widget(MenuGrid())

        layout = GridLayout(cols=50, spacing=3, padding=3)
        for i in range(0, 2500):
            button = CustomButton(i, size_hint=[None, None], width=15,
                                  height=15)
            button.bind(on_press=change_phase)
            layout.add_widget(button)

        layout_base.add_widget(layout)

        return layout_base


# Run the game

if __name__ == '__main__':
    GameOfLifeApp().run()

In the comments I explain what everything is supposed to do, but if you have doubts, please tell me and I'll try to be clearer.

\$\endgroup\$
3
\$\begingroup\$

You do not need to check if variable is True/None/False like this if instance.phase is True. You can write just if instance.phase (check if True), or if not instance.phase. (check if False).

next thing, you can write this function in one line using "Conditional expressions" - http://book.pythontips.com/en/latest/ternary_operators.html

def change_phase(instance):

    # If a cell is clicked, turn it to its opposite phase.
    # True - False
    # Alive - Dead

    if instance.phase is True:
        instance.phase = False
    else:
        instance.phase = True

one liner:

def change_phase_2(instance):
    # If a cell is clicked, turn it to its opposite phase.
    # True - False
    # Alive - Dead
    instance.phase = not instance.phase

you can also read about Generators here http://book.pythontips.com/en/latest/generators.html .

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.