0

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!

1 Answers1

0

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.

Patrick Haugh
  • 59,226
  • 13
  • 88
  • 96
  • Thank you @Patrick Haugh! I don't think the winning_line function will be quite enough as it is, since the amount of symbols I need in a row changes depending on the board size; but the zip(*board) will be particularly helpful, I couldn't think of a nice way to check the columns. – Lachlan Shead May 01 '18 at 01:29
  • You can model that with `def winning_line(line, symbol, goal): return sum(cell == symbol for cell in line) >= goal` – Patrick Haugh May 01 '18 at 01:34
  • If I had the line of symbols XXOXX and goal = 4, wouldn't the winning_line function return True in that case, considering the sum is 4? – Lachlan Shead May 01 '18 at 22:13
  • That's a good point. You could instead have a [rolling window](https://stackoverflow.com/questions/6822725/rolling-or-sliding-window-iterator) of length n over the line and check if that window is ever equal to `symbol * n` – Patrick Haugh May 01 '18 at 22:15
  • Thanks for your help, should be fine from here on. :) – Lachlan Shead May 02 '18 at 10:49