Reputation: 47
I am programming a Tic-Tac-Toe game in Python 3. I have made the boards as 2D arrays, and have functions for printing the board as a grid, a tutorial, and taking and putting input.
It has the bare-bones abilities to properly display the boards, and process the inputs, along with processing, but I have two major issues in my algorithms. They are:
I have some rough concept of using a "for loop" for the turn switch and game-win mechanics, but the game could break in the event of a tie if that is not implemented.
If anyone has any solution for the following problems or general suggestions, please post them here.
from array import *
class board:
def __init__(self, row_1, row_2, row_3):
self.row_1 = row_1
self.row_2 = row_2
self.row_3 = row_3
# full_board is two-dimensional array
self.full_board = row_1, row_2, row_3
dash = "-"
main_board = board([dash, dash, dash], [dash, dash, dash], [dash, dash,
dash,])
def board_print(board):
for row in board:
for item in row:
print(item, end = " ")
print()
def take_input():
player = input("What space would you like to mark?(From 1 through 9):")
input_process(player)
# Algorithm for player input
# Make a dictionary of player inputs as keys, and 2d array coordinates as values
# if player's input equals a dictionary value:
# take the string and split it at ":"
# assign the two numbers of the 2d dictionary value as part_1 and part_2
# change the value of the matching coordinate to a "X" or "O", on the player(sorry, I can't make good CPU.)
# else:
# print a notification that their input was invalid and recall the function
def input_process(pl_choice):
possible_inputs = {"1" : "0:0", "2" : "0:1", "3" : "0:2", "4" : "1:0", "5" : "1:1", "6" : "1:2", "7": "2:0", "8" : "2:1", "9" : "2:2"}
if pl_choice in possible_inputs:
confirm = input("Are you sure you want to select %s? y/n: "%pl_choice)
if confirm == "y":
choice = possible_inputs[pl_choice].split(":")
answer_p1 = choice[0]
answer_p2 = choice[1]
choice_parts = [answer_p1, answer_p2]
elif confirm == "n":
print("Oh. Well you can try again.\n")
take_input()
elif pl_choice not in possible_inputs or not pl_choice.isdigit():
print("Your choice was invalid. Please try again.")
take_input()
def change_board(play_board, player_id, input_p1, input_p2):
if player_id == 1:
play_board[input_p1][input_p2] = "X"
elif player_id == 2:
play_board[input_p1][input_p2] = "O"
def tutorial():
print("Welcome to the Tic-Tac-Toe tutorial.\n")
print("This version of Tic-Tac-Toe is two-player only, although CPU may be added in the future. To play, \nYou input a number, 1-9. The numbers bind like so: ")
tutorial_board = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
print(board_print(tutorial_board))
print("Otherwise, this plays like on paper; get 3 in a row vertically, horizontally, or diagonally before your opponent, and you win.")
# Placeholder for main game
def main_game():
print("Hello, and welcome to Tic-Tac-Toe.\nWould you like to see the tutorial? y/n:")
tutorial_needed = input
if tutorial_needed == "y":
tutorial()
elif tutorial_needed == "n":
print("Okay. Let's start!")
game_end = False
actual_board = main_board.full_board
board_print(actual_board)
take_input()
Upvotes: 1
Views: 92
Reputation: 912
We define a "win" for player X as a case where one of the following is True:
A row is just a sub-list in your tuple (that's the types you used), so we can just write board[row]
to get a row. To check if any
(notice the code format) row consists of "X", we have to check that all
its value are equal to "X". So, we can write any(all(field == 'X' for field in row) for row in board)
.
Columns are more complicated, because we have to work with indexes. But, basically, it's just the same thing, only the other way round: any
column with all
fields "X":
any(all(board[row][column] == 'X' for row in range(3)) for column in range(3))
Diagonals: We only have one line each, so we only need one loop.
For TL->BR, both indices are equal. So, we want all
of the fields with equal indices to be equal to "X". all(board[i][i] == 'X' for i in range(3))
for BL->TR, one index is 2-other_index
(not 3, since sequences are zero-indexed). We want that all
fields with the two indices to be equal to "X" all(board[i][2-i] for i in range(3))
Question: what happens if you invert i
and 2-i
? If you don't know, let it be printed out.
You will probably want to write a check_win(board, player)
function to do all those above for a generic player
-- here "X" or "O", but, if you decide to include three players on a bigger board later on...
If you really want to create a CPU, this is the wrong place to ask. If you want to create a (simple) AI, read on. If you want it to be complex, ask a new question.
Think about what it should do:
While in this case it is probably simpler to just simulate a few turns and choose the ones with the best possibilities of winning, asking yourself the questions above and writing something that will also work with (much) bigger boards, will be very interesting.
One possibility is:
You have a sequence (str, list or tuple) of the players, like so: "XO"
and a round_counter
variable that is always increased. You then use PLAYERS[round_counter % len(PLAYERS)]
to get the current player. Use the modulus operator %
to always get a valid index.
Alternative:
Use itertools.cycle
:
cycle('ABCD') --> A B C D A B C D A B C D ...
This is easier (for current_player in itertools.cycle("XO"):
), but with the first alternative you have the benefit of a number you would have to call enumerate(itertools.cycle("XO"))
for and you limit library usage (if this code is to shown to someone, you might want to explain how it works).
A draw occurs when all
fields are not
empty (in your case, !=
dash); this is equivalent to say that there is not
any
field with a dash.
See list comprehension to flatten the board:
>>> # flatten a list using a listcomp with two 'for'
>>> vec = [[1,2,3], [4,5,6], [7,8,9]]
>>> [num for elem in vec for num in elem]
[1, 2, 3, 4, 5, 6, 7, 8, 9]
-- second-last example
Upvotes: 1