Search code examples
python-3.6tic-tac-toe

Python 3.6 user-defined board size win check with 2 variables


I have somewhat a general question for more experienced programmers. I'm somewhat new to programming, but still enjoy it quite a bit. I've been working with Python, and decided to try to program a tic tac toe game, but with variable board size that can be decided by the user (all the way up to a 26x26 board). Here's what I've got so far:

    print("""Welcome to tic tac toe!
    You will begin by determining who goes first;
    Player 2 will decide on the board size, from 3 to 26.
    Depending on the size of the board, you will have to
    get a certain amount of your symbol (X/O) in a row to win.
    To place symbols on the board, input their coordinates;
    letter first, then number (e.g. a2, g10, or f18).
    That's it for the rules. Good luck!\n""")

    while True:
    ready = input("Are you ready? When you are, input 'yes'.")
    if ready.lower() == 'yes': break

    def printboard(n, board):
      print() #print board in ranks of n length; n given later
      boardbyrnk = [board[ind:ind+n] for ind in range(0,n**2,n)]
      for rank in range(n):
        rn = f"{n-rank:02d}" #pads with a 0 if rank number < 10
        print(f"{rn}|{'|'.join(boardbyrnk[rank])}|") #with rank#'s
      print("  ",end="") #files at bottom of board
      for file in range(97,n+97): print(" "+chr(file), end="")
      print()

    def sqindex(prompt, n, board, syms): #takes input & returns index
      #ss is a list/array of all possible square names
      ss = [chr(r+97)+str(f+1) for r in range(n) for f in range(n)]
      while True: #all bugs will cause input to be taken for same turn
        sq = input(prompt)
        if not(sq in ss): print("Square doesn't exist!"); continue
        #the index is found by multiplying rank and adding file #'s
        index = n*(n-int(sq[1:])) + ord(sq[0])-97
        if board[index] in syms: #ensure it contains ' '
          print("The square must be empty!"); continue
        return index

    def checkwin(n, w, board, sm): #TODO
      #check rows, columns and diagonals in terms of n and w;
      #presumably return True if each case is met
      return False

    ps = ["Player 1", "Player 2"]; syms = ['X', 'O']
    #determines number of symbols in a row needed to win later on
    c = {3:[3,3],4:[4,6],5:[7,13],6:[14,18],7:[19,24],8:[25,26]}
    goagain = True
    while goagain:
      #decide on board size
      while True:
        try: n=int(input(f"\n{ps[1]}, how long is the board side? "))
        except ValueError: print("Has to be an integer!"); continue
        if not(2<n<27): print("Has to be from 3 to 26."); continue
        break
      board = (n**2)*" " #can be rewritten around a square's index

      for num in c:
        if c[num][0] <= n <= c[num][1]: w = num; break
      print(f"You'll have to get {w} symbols in a row to win.")

      for tn in range(n**2): #tn%2 = 0 or 1, determining turn order
        printboard(n, board)
        pt = ps[tn%2]
        sm = syms[tn%2]
        prompt = f"{pt}, where do you place your {sm}? "
        idx = sqindex(prompt, n, board, syms)
        #the index found in the function is used to split the board string
        board = board[:idx] + sm + board[idx+1:]
        if checkwin(n, w, board, sm):
          printboard(n, board); print('\n' + pt + ' wins!!\n\n')
          break
        if board.count(" ") == 0:
          printboard(n, board); print("\nIt's a draw!")

      while True: #replay y/n; board size can be redetermined
        rstorq = input("Will you play again? Input 'yes' or 'no': ")
        if rstorq in ['yes', 'no']:
          if rstorq == 'no': goagain = False
          break
        else: print("Please only input lowercase 'yes' or 'no'.")

    print("Thanks for playing!")

So my question to those who know what they're doing is how they would recommend determining whether the current player has won (obviously I have to check in terms of w for all cases, but how to program it well?). It's the only part of the program that doesn't work yet. Thanks!


Solution

  • You can get the size of the board from the board variable (assuming a square board).

    def winning_line(line, symbol):
        return all(cell == symbol for cell in line)
    
    def diag(board):
        return (board[i][i] for i in range(len(board)))
    
    def checkwin(board, symbol):
        if any(winning_line(row, symbol) for row in board):
            return True
        transpose = list(zip(*board))
        if any(winning_line(column, symbol) for column in transpose):
            return True
        return any(winning_line(diag(layout), symbol) for layout in (board, transpose))
    

    zip(*board) is a nice way to get the transpose of your board. If you imagine your original board list as a list of rows, the transpose will be a list of columns.