patternpythonMinor
Tic-Tac-Toe game in Python
Viewed 0 times
toetictacgamepython
Problem
I've decided to use some of my free time to create a simple game in Python. I chose Tic-Tac-Toe, but I decided not to make it too trivial. I made my game support any board size. I would greatly appreciate a code review.
``
def f_coords(board):
"Returns function handling user input for board coordinates"
def f(i):
i = i.split(" ")
if len(i) != 2: return
x = f_positive(i[0])
y = f_positive(i[1])
if x is None or y is None: return
if board[y - 1
``
## Tic-tac-toe game by Mateon1
try:
raw_input
except NameError:
import sys
print >> sys.stderr, "This program supports Python 2 only."
sys.exit(1)
def prompt(question, check_func=lambda i: i.lower() == "y"):
"""Prompts the user with question
Awaits until check_func() returns a non-None value and returns it"""
while True:
value = check_func(raw_input(question))
if value is not None:
return value
def check_win(board):
"Checks if there is a win condition, returns who won or False"
width = len(board[0])
height = len(board)
# Horizontal
for y in xrange(height):
if board[y][0] != 0 and all(board[y][i] == board[y][0] for i in xrange(1, width)):
return board[y][0]
# Vertical
for x in xrange(width):
if board[0][x] != 0 and all(board[i][x] == board[0][x] for i in xrange(1, height)):
return board[0][x]
if width == height: # Diagonals only work if the board is square
# \ diagonal
if board[0][0] != 0 and all(board[i][i] == board[0][0] for i in xrange(1, width)):
return board[0][0]
# / diagonal
if board[0][-1] != 0 and all(board[i][-1 - i] == board[0][-1] for i in xrange(1, width)):
return board[0][-1]
return False
def f_positive(string):
try:
val = int(string)
if val > 0: return val
except ValueError:
pass
# Implicit return None` at the end of the functiondef f_coords(board):
"Returns function handling user input for board coordinates"
def f(i):
i = i.split(" ")
if len(i) != 2: return
x = f_positive(i[0])
y = f_positive(i[1])
if x is None or y is None: return
if board[y - 1
Solution
This won't work:
The program would crash with a
One way of doing this check is to have a file like
This program has to support both versions syntactically but can be much smaller.
Personally, though, it looks trivial to get this running on Python 3; use
swap the usages and you're done. I suggest you do so; Python 3 is way better anyway.
IMHO diagonals shouldn't only work for square boards; this should be a valid win
given that this is also a valid win:
Although this is a gameplay issue so I won't mess with it.
As written, the checks are largely repetitive so I'd suggest extracting it into an operation over indices:
It's also more typical to
As Caridorc says,
Sadly there is not
although you're not checking for large numbers;
Personally I'd use
are bad; if the programmer needs commenting then the user is going to be confused.
The
try:
raw_input
except NameError:
import sys
print >> sys.stderr, "This program supports Python 2 only."
sys.exit(1)The program would crash with a
SyntaxError during compilation before being able run this. I getFile "p.py", line 104
print "".join(text)
^
SyntaxError: invalid syntaxOne way of doing this check is to have a file like
from __future__ import print_function
try:
raw_input
except NameError:
import sys
print("This program supports Python 2 only.", file=sys.stderr)
sys.exit(1)
import the_real_program
the_real_program.main()This program has to support both versions syntactically but can be much smaller.
Personally, though, it looks trivial to get this running on Python 3; use
from __future__ import print_function
try:
input = raw_input
range = xrange
except NameError:
passswap the usages and you're done. I suggest you do so; Python 3 is way better anyway.
prompt's docstring should be indented properly and IMHO you shouldn't use backticks. Being pedantic, it should also be phrased as an imperative. Out of the several valid styles, I prefer:"""
Prompt the user with 'question'.
Await until check_func() returns a non-None value and return it
"""check_win's docstring should use tripple-quotes:"""Check if there is a win condition, return who won or False."""IMHO diagonals shouldn't only work for square boards; this should be a valid win
1 2 3 4
+--+--+--+--+
1 |<>| | |>| |>| |
+--+--+--+--+given that this is also a valid win:
1 2 3 4
+--+--+--+--+
1 |<>| | |>| | |>| | | |
+--+--+--+--+Although this is a gameplay issue so I won't mess with it.
As written, the checks are largely repetitive so I'd suggest extracting it into an operation over indices:
def lines(width, height):
for y in range(width):
yield [(x, y) for x in range(height)]
for x in range(height):
yield [(x, y) for y in range(width)]
if width == height:
yield [(x, x) for x in range(width)]
yield [(x, -x) for x in range(width)]
def check_win(board):
"""Check if there is a win condition, return who won or False."""
width = len(board[0])
height = len(board)
for line in lines(width, height):
x, y = line[0]
first = board[x][y]
if first and all(board[i][j] == first for i, j in line):
return first
return FalseIt's also more typical to
return None on failure.As Caridorc says,
return None is much better than # Implicit 'return None' at the end of the function. f_positive should also use try's else clause to minimize the area under it:def f_positive(string):
try:
val = int(string)
except ValueError:
pass
else:
if val > 0: return val
returnSadly there is not
try ... elif ;). I would personally invert the logic to an early return:def f_positive(string):
try:
val = int(string)
except ValueError:
return
if val <= 0:
return
return valf_coords is too tightly spaced. Lines are cheap, here are a few for free:def f_coords(board):
"""Get function handling user input for board coordinates."""
def f(i):
i = i.split(" ")
if len(i) != 2:
return
x = f_positive(i[0])
y = f_positive(i[1])
if x is None or y is None:
return
if board[y - 1][x - 1] != 0:
return
return (x - 1, y - 1)
return fi is to short, I suggest user_input. f → inner would be better, traditional naming. You can shorten this todef f_coords(board):
"""Get function handling user input for board coordinates."""
def inner(user_input):
try:
x, y = map(int, user_input.split(" "))
except ValueError:
return
x -= 1; y -= 1
if x < 0 or y < 0 or board[y][x]:
return
return x, y
return inneralthough you're not checking for large numbers;
board[y][x] can crash. Add that check:def f_coords(board):
"""Get function handling user input for board coordinates."""
def inner(user_input):
try:
x, y = map(int, user_input.split(" "))
except ValueError:
return
x -= 1; y -= 1
if not (0 <= x < len(board) and 0 <= y < len(board[0])):
return
if board[y][x]:
return
return x, y
return innerprint_board's docstring needs indenting too.Personally I'd use
() for a circle; it looks much rounded and easier to distinguish. Your comments forelif value == 1:
return "<>" # circle
elif value == 2:
return "><" # crossare bad; if the programmer needs commenting then the user is going to be confused.
The
cell function Code Snippets
try:
raw_input
except NameError:
import sys
print >> sys.stderr, "This program supports Python 2 only."
sys.exit(1)File "p.py", line 104
print "".join(text)
^
SyntaxError: invalid syntaxfrom __future__ import print_function
try:
raw_input
except NameError:
import sys
print("This program supports Python 2 only.", file=sys.stderr)
sys.exit(1)
import the_real_program
the_real_program.main()from __future__ import print_function
try:
input = raw_input
range = xrange
except NameError:
pass"""
Prompt the user with 'question'.
Await until check_func(<user_input>) returns a non-None value and return it
"""Context
StackExchange Code Review Q#77083, answer score: 6
Revisions (0)
No revisions yet.