Search code examples
pythonimagebuttonkivykivy-language

Attach kivy button image source to python class attribute


I have been working on this problem for a long time and I cannot figure out a solution. I am making a game of tic tac toe, and I have created a gridlayout of 9 buttons. When a button is pressed, I want it to change from a placeholder image to an image of an x or an o. I have assigned the button image source as a python class attribute, and I can call a function so the attribute updates when the button is pressed. I know it's working because I get an output from the function every time a button is pressed. The problem is that the image does not update. I've looked everywhere for a solution but I can't find one.

from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.image import Image
from kivy.uix.gridlayout import GridLayout

class Game(GridLayout):
    empty = str('C:/Users/codin/Desktop/tictactoe/walpaper.png')
    states = ['button1', 'button2', 'button3', 'button4', 'button5',
              'button6', 'button7', 'button8', 'button9']
    button1 = empty
    button2 = empty
    button3 = empty
    button4 = empty
    button5 = empty
    button6 = empty
    button7 = empty
    button8 = empty
    button9 = empty

    def change_state(self, state):
        O = str('C:/Users/codin/Desktop/tictactoe/just_o.png')
        X = str('C:/Users/codin/Desktop/tictactoe/just_x.png')
        empty = str('C:/Users/codin/Desktop/tictactoe/walpaper.png')

        if getattr(self, state) == empty:
            setattr(self, state, X)
        elif getattr(self, state) == X:
            setattr(self, state, O)
        elif getattr(self, state) == O:
            setattr(self, state, X)
        else:
            setattr(self, state, empty)
        print(self.__dict__[state])

    pass


# Create an app class to do the handling
class TicTacToe(App):
    def build(self):
        return Game()


if __name__ == "__main__":
    TicTacToe().run()

.kv file:

<Game>
    cols: 3
    rows: 4

    Button:
        on_press: root.change_state(root.states[0])
        Image:
            id: button1
            source: root.button1
            center_x: self.parent.center_x
            center_y: self.parent.center_y

    Button:
        on_press: root.change_state(root.states[1])
        Image:
            id: button2
            source: root.button2
            center_x: self.parent.center_x
            center_y: self.parent.center_y

more buttons below

I am fully aware that I can write a function for every button, and change the image source via self.ids.image_id.source in the python file, but I'm trying to keep it short. Any help is greatly appreciated.


Solution

  • I was able to figure it out using a combination of ApuCoder's answer and information from the kivy.lang module outline here: https://kivy.org/doc/stable/api-kivy.lang.html

    My python code is very short, simple variables assigned to str() filepath for images in a game class, and an app class to compile:

    from kivy.app import App
    from kivy.uix.button import Button
    from kivy.uix.image import Image
    from kivy.uix.gridlayout import GridLayout
    
    
    # class for image sources and layout
    class Game(GridLayout):
        O = str('just_o.png')
        X = str('just_x.png')
        empty = str('wallpaper.png')
    
    
    # App class to do the handling
    class TicTacToe(App):
        def build(self):
            return Game()
    
    
    if __name__ == "__main__":
        TicTacToe().run()
    

    In kv, you can write conditional statements. The requirements are that each statement is on its own line. if/elif/else as well as for and while loops are all allowed.

    <Game>
        cols: 3
        rows: 3
    
        Button:
            on_press:
                if button1.source == root.empty: button1.source = root.X
                elif button1.source == root.X: button1.source = root.O
                else: button1.source = root.X
            Image:
                id: button1
                source: root.empty
                center_x: self.parent.center_x
                center_y: self.parent.center_y
    

    This code basically says, "image source assigned at start, on_press if the image is this, change it to that, and vice versa"

    This is likely the least amount of code needed to accomplish this task.