Update state from the UI

Question:

I’m coding a bot in Python that plays tic-tac-toe. The game is a Web app written in React.js and is equipped with an AI of its own that utilizes minimax. The user (which the Python bot simulates) is always X, the AI is always O, and the user always moves first. The AI obviously keeps updated on the state of the board, but the Python bot currently only keeps track of it’s own clicked squares and will not select a square that it has already selected, but it does not keep track of the board as such.

How can I update the state of the board in Python through the UI? I’m using Selenium to interact with the Web app through the browser. This is a follow-up to another post: python method not being called.

Edit 1:

import pytest
import time
import logging
from random import randint

from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

LOGGER = logging.getLogger(__name__)

class Tags():
    square1 = "(//div[contains(@class, 'board-row')]//button[contains(@class, 'square')])[1]"
    square2 = "(//div[contains(@class, 'board-row')]//button[contains(@class, 'square')])[2]"
    square3 = "(//div[contains(@class, 'board-row')]//button[contains(@class, 'square')])[3]"
    square4 = "(//div[contains(@class, 'board-row')]//button[contains(@class, 'square')])[4]"
    square5 = "(//div[contains(@class, 'board-row')]//button[contains(@class, 'square')])[5]"
    square6 = "(//div[contains(@class, 'board-row')]//button[contains(@class, 'square')])[6]"
    square7 = "(//div[contains(@class, 'board-row')]//button[contains(@class, 'square')])[7]"
    square8 = "(//div[contains(@class, 'board-row')]//button[contains(@class, 'square')])[8]"
    square9 = "(//div[contains(@class, 'board-row')]//button[contains(@class, 'square')])[9]"
    exSquare = "//div[contains(@class, 'board-row')]//button[contains(text(), 'X')]"
    ohSquare = "//div[contains(@class, 'board-row')]//button[contains(text(), 'O')]"
    resultOh = "//div[contains(@class, 'game-info')]//div[contains(text(), 'Winner: O')]"
    resultEx = "//div[contains(@class, 'game-info')]//div[contains(text(), 'Winner: X')]"
    resultTie = "//div[contains(@class, 'game-info')]//div[contains(text(), 'tie')]"

class TestCase_PlayTTT():
    
    URL = "http://localhost:3000"
    
    @pytest.fixture
    def load_browser(self, browser):
        browser.get(self.URL)
        yield browser
    
    def test_playTTT(self, load_browser):

        squares = [Tags.square1,Tags.square2,Tags.square3,
                   Tags.square4,Tags.square5,Tags.square6,
                   Tags.square7,Tags.square8,Tags.square9]
        
        clickedSquares = []
        random_square = randint(1,9)
        time.sleep(5)
        winner = ''

        if not clickedSquares:
            LOGGER.debug("I made it into the first if statement")
            element = load_browser.find_element(By.XPATH, squares[random_square])
            element.click()
            clickedSquares.append(random_square)   
        
        for i in range(1,9):
            if clickedSquares[i] == Tags.exSquare:
                clickedSquares.append(i)
            if clickedSquares[i] == Tags.ohSquare:
                clickedSquares.append(i)
        
        for i in clickedSquares:
            LOGGER.debug("I made it into the for loop")
            if i == random_square:
                LOGGER.debug("I made it into the second if statement")
                self.test_playTTT(load_browser)
            else:
                LOGGER.debug("I made it into the first else statement")
                clickedSquares.append(random_square)
        element = load_browser.find_element(By.XPATH, squares[random_square])
        element.click()

This is a segment I added to my code to check for squares already filled by either X or O:

    for i in range(1,9):
        if clickedSquares[i] == Tags.exSquare:
            clickedSquares.append(i)
        if clickedSquares[i] == Tags.ohSquare:
            clickedSquares.append(i)

But I get a "list index out of range" error. I think the problem is that I’m trying to do a string comparison on an XPATH. How can I fix this?

Answers:

        clickedSquares = []

        if not clickedSquares:
            clickedSquares.append(random_square)   
        
        for i in range(1,9):
            if clickedSquares[i] == Tags.exSquare:
                clickedSquares.append(i)
            if clickedSquares[i] == Tags.ohSquare:
                clickedSquares.append(i)

            # What if it is neither X or O?

I think this is the crux of your issue. You initialize an array, add 1 item, then try to iterate through 9 items. Seems out of range to me.

I think its kinda strange that you are manipulating clickedSquares within a loop over essentially the items of clickedSquares. I would probably separate the list into separate lists, or you actually meant to loop over squares instead.

Answered By: Drise
for i in range(x, y)

means we will take a range starting and including x up to but not including y, so I think your range should be (0,9).
Also, you’re correct about your comparison. If I’m reading your code correctly, it’s not clickedSquares[i] you should be using but squares[i].

        for i in range(0,9):
            if squares[i] == Tags.exSquare:
                clickedSquares.append(i)
            if squares[i] == Tags.ohSquare:
                clickedSquares.append(i)

This should work because squares is a list of XPATH variables.

Answered By: Trevor