Python Kivy – press a button made from the .kv file using the Python code itself

Question:

I have a simple application of a Tic-Tac-Toe game.
Each time I press a button it calls a presser method that changes the current state of who’s turn it is, and checks if there’s a winner etc..

The buttons are all made from the .kv file.

I want to create a bot that chooses a random position – it returns a tuple of two random values between 0-2 (including).

So if for example it returned 1-1, I want it to press the middle button:

enter image description here

The presser method gets the button itself as a callback from the .kv file:

    def presser(self, btn):
        if self.turn == Players.PLAYER_X:
            if self.bot_starting:
                # Here I am stuck :(
                bot_move = Bot.play_move()
                
            btn.text = Players.PLAYER_X
            btn.disabled = True
            self.root.ids.score.text = Messages.TURN.format(player=Players.PLAYER_O)
            self.turn = Players.PLAYER_O
        else:
            btn.text = Players.PLAYER_O
            btn.disabled = True
            self.root.ids.score.text = Messages.TURN.format(player=Players.PLAYER_X)
            self.turn = Players.PLAYER_X

See the comment "Here I am stuck"

The .kv file looks like this (with 9 different buttons)

        Button:
            id: btn7
            text: ""
            font_size: "45sp"
            on_release: app.presser(btn7)

This is the bot.py file:

from random import randint


class Bot:
    @staticmethod
    def play_move():
        return randint(0, 2), randint(0, 2)

How can I use the output (such as (1,1,)) to simulate the pressing of the middle button (for example)?

I thought it would be easy but then I realized I am using a .kv file and had no idea how to continue thinking about it, because it’s the one that has the callback function for every button press, so simulating it would be impossible no?

Minimal working reproducible example:

https://pastebin.com/Y4n3hQRa – this is the .py file of the main logic
https://pastebin.com/S53Xibhm – this is the .kv file
https://pastebin.com/e2pn1qyx – constants file
I would appreciate your kind help! Thank you

Asked By: JetLeg

||

Answers:

You could try automatically calling the Bot.play_move method upon completion of the users turn after it checks to make sure that the end of the game has been reached. Then in the play_move method you would iterate through the ids and find buttons that have not yet been selected and randomly choose one of those remaining squares.

For example: I only included the methods where I made changes.

bot.py

import random

class Bot:
    @staticmethod
    def play_move(widget):
        vals = []
        for key, value in widget.ids.items():
            if "btn" in key and not value.text:
                vals.append(value)
        chosen = random.choice(vals)
        chosen.text = Players.PLAYER_O
        chosen.disabled = True

app file

class MainApp(MDApp):

    def presser(self, btn):
        if self.turn == self.bot_symbol:
            Bot.play_move(self.root)
            self.root.ids.score.text = Messages.TURN.format(player=self.bot_symbol)
            self.turn = Players.PLAYER_X
            self.win()
        else:
            btn.text = Players.PLAYER_X
            btn.disabled = True
            self.root.ids.score.text = Messages.TURN.format(player=Players.PLAYER_O)
            self.turn = Players.PLAYER_O
            if not self.win():
                self.presser(None)

    def no_winner(self):
        if self.winner:
            return False
        if self.root.ids.btn1.disabled and 
            self.root.ids.btn2.disabled and 
            self.root.ids.btn3.disabled and 
            self.root.ids.btn4.disabled and 
            self.root.ids.btn5.disabled and 
            self.root.ids.btn6.disabled and 
            self.root.ids.btn7.disabled and 
            self.root.ids.btn8.disabled and 
            self.root.ids.btn9.disabled:
            self.root.ids.score.text = Messages.TIE
            return False
        return True

    def win(self):
        # this method stays the same except the last line
        return not self.no_winner()

def no_winner(self):
    if self.winner:
       return False
    if all([value.disabled for (key, value) in self.root.ids.items() if 'btn' in key]):
        self.root.ids.score.text = Messages.TIE        
        return False
    return True 
Answered By: Alexander
Categories: questions Tags: , , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.