patternpythonModerate
Command-line noughts and crosses
Viewed 0 times
lineandcommandnoughtscrosses
Problem
I've just got a Raspberry Pi, so I thought I'd try my hand at some Python. This is my first ever Python program (hurrah!).
It's a very simple command-line implementation of noughts and crosses. The computer opponent's strategy is simply to pick at random, the code is very verbose in places and there's no way to restart the game without closing and restarting the program, but my aim wasn't to make a working game; I just wanted to practice using the language.
Really, I'm just looking for help making this more Python-y, and any features of the language I've missed that would have been more effective. I've come from several years of C# and Java, so the language feels a little jarring to me right now.
P.S. this is Python 3.2
```
import random
import sys
#### Initialising ####
boxes = [[0, 0, 0], # 0 - empty
[0, 0, 0], # 1 - nought
[0, 0, 0]] # 2 - cross
#### Functions ####
#Converts the box's magic number into a character
def intToText(num):
if (num == 0): return ' '
if (num == 1): return 'O'
if (num == 2): return 'X'
#This outputs the grid, with the correct symbols in the boxes
def printGrid():
print('┌─┬─┬─┐' '\n'
'│' + intToText(boxes[0][0]) + '│' + intToText(boxes[1][0]) + '│' + intToText(boxes[2][0]) + '│' '\n'
'├─┼─┼─┤' '\n'
'│' + intToText(boxes[0][1]) + '│' + intToText(boxes[1][1]) + '│' + intToText(boxes[2][1]) + '│' '\n'
'├─┼─┼─┤' '\n'
'│' + intToText(boxes[0][2]) + '│' + intToText(boxes[1][2]) + '│' + intToText(boxes[2][2]) + '│' '\n'
'└─┴─┴─┘')
def checkVictory():
#This method looks at each box in the grid and checks the two boxes
#in each of the four directions defined as vectors below
for i in range(0, 3):
for j in range(0, 3):
if (boxes[i][j] == 0):
continue
for vector in [[1, 0], [1, 1], [0, 1], [-1, 1]]: #The four directions to check for a complete line in
try:
It's a very simple command-line implementation of noughts and crosses. The computer opponent's strategy is simply to pick at random, the code is very verbose in places and there's no way to restart the game without closing and restarting the program, but my aim wasn't to make a working game; I just wanted to practice using the language.
Really, I'm just looking for help making this more Python-y, and any features of the language I've missed that would have been more effective. I've come from several years of C# and Java, so the language feels a little jarring to me right now.
P.S. this is Python 3.2
```
import random
import sys
#### Initialising ####
boxes = [[0, 0, 0], # 0 - empty
[0, 0, 0], # 1 - nought
[0, 0, 0]] # 2 - cross
#### Functions ####
#Converts the box's magic number into a character
def intToText(num):
if (num == 0): return ' '
if (num == 1): return 'O'
if (num == 2): return 'X'
#This outputs the grid, with the correct symbols in the boxes
def printGrid():
print('┌─┬─┬─┐' '\n'
'│' + intToText(boxes[0][0]) + '│' + intToText(boxes[1][0]) + '│' + intToText(boxes[2][0]) + '│' '\n'
'├─┼─┼─┤' '\n'
'│' + intToText(boxes[0][1]) + '│' + intToText(boxes[1][1]) + '│' + intToText(boxes[2][1]) + '│' '\n'
'├─┼─┼─┤' '\n'
'│' + intToText(boxes[0][2]) + '│' + intToText(boxes[1][2]) + '│' + intToText(boxes[2][2]) + '│' '\n'
'└─┴─┴─┘')
def checkVictory():
#This method looks at each box in the grid and checks the two boxes
#in each of the four directions defined as vectors below
for i in range(0, 3):
for j in range(0, 3):
if (boxes[i][j] == 0):
continue
for vector in [[1, 0], [1, 1], [0, 1], [-1, 1]]: #The four directions to check for a complete line in
try:
Solution
- Introduction
If this is your first Python program, then it's not too bad at all. There are a whole host of things that can be improved, but there are also a good few things you got right. For example, your code is pretty well-commented. Also, I liked the response "Invalid input. Type 'help' if you're stuck" which came at just the right time.
On to the review!
- Bugs
-
The computer doesn't play very well:
┌─┬─┬─┐
│ │ │X│
├─┼─┼─┤
│X│O│X│
├─┼─┼─┤
│O│X│O│
└─┴─┴─┘
Computer's turn:
┌─┬─┬─┐
│ │O│X│
├─┼─┼─┤
│X│O│X│
├─┼─┼─┤
│O│X│O│
└─┴─┴─┘It's one thing not to have any strategic look-ahead, but quite another to miss an easy win like this!
-
There's a bug in the win determination:
Your turn. Make your move:
1 1
┌─┬─┬─┐
│X│O│X│
├─┼─┼─┤
│X│O│X│
├─┼─┼─┤
│O│X│O│
└─┴─┴─┘
You win!This happens because your win determination code starts from each of the nine squares and walks for two squares in each of four directions checking to see if the three cells form a line. You rely on getting an
IndexError if this walks off the side of the array of boxes. But that's not always going to happen, because Python is perfectly happy for you to supply negative array indices: these count backwards from the end of the array, so that a[-1] is the last element, a[-2] is the second-to-last, and so on.One of these searches starts at box (0, 0), and then walks in the direction (−1, 1), getting to (−1, 1) which is the same as (2, 1), and then to (−2, 2) which is the same as (1, 2). All three cells contain an X, so it thinks that you have won!
So you really do need to check that the indices are in range here.
-
Here's another bug. The program raises an exception if the game is drawn.
Your turn. Make your move:
2 1
┌─┬─┬─┐
│X│O│X│
├─┼─┼─┤
│X│X│O│
├─┼─┼─┤
│O│X│O│
└─┴─┴─┘
Traceback (most recent call last):
...
File "./cr15631.py", line 78, in chooseComputerMove
return emptyBoxes[random.randint(1, len(emptyBoxes) - 1)]
...
ValueError: empty range for randrange() (1,0, -1)You need to check for draws as well as wins.
- Coding style and technique
-
Python has a common set of coding conventions, which are written down in "PEP8". The majority of Python developers follow these conventions, so if you learn them too then you will find it easier to collaborate with other Python programmers. Some of these you follow already, but you might take a look at "Function names should be lowercase, with words separated by underscores as necessary to improve readability."
-
Your code has good descriptions of each function in the form of comments. In Python it's usual to put the description of a function into a docstring which can users can retrive using the built-in
help function.Often you'll find that the discipline of writing documentation for a user helps you choose a better name for your function. For example, instead of
#Converts the box's magic number into a character
def intToText(num):
if (num == 0): return ' '
if (num == 1): return 'O'
if (num == 2): return 'X'you could write:
def box_display(num):
"""
Return the character to display for a box containing `num`.
"""
return ' OX'[num]-
Instead of using
+ to join lots of strings together, you can use Python's powerful string formatting mechanism. Your printing code could be written:def print_grid():
"""
Print the grid of boxes.
"""
print('┌─┬─┬─┐\n'
'│{}│{}│{}│\n'
'├─┼─┼─┤\n'
'│{}│{}│{}│\n'
'├─┼─┼─┤\n'
'│{}│{}│{}│\n'
'└─┴─┴─┘\n'
.format(*[box_display(boxes[i][j])
for i in range(3)
for j in range(3)]))(I've used a couple of advanced features in the last three lines to avoid the repetition of
box_display(boxes[0][0]), box_display(boxes[0][1]), ... and so on: first, a list comprehension to build the list of characters to pass to the format method, and secondly the *args syntax to pass arguments to the function as a list instead of individual parameters.)-
range(0, 3) can be abbreviated to range(3).-
Python doesn't need parentheses around the test in an
if statement.-
In the win determination code, you test for the last time around a loop with your test
if x == 2:. Python has syntax for handling the last time around a loop with the else: clause to the for statement.-
You statement
sys.exit doesn't actually call the function: you'd need to write sys.exit() for that. But there's no point in doing so: when a Python program comes to an end it exits automatically without you having to tell it to do so.- Design improvements
-
There's no particular need to store the box contents as numbers (and then to convert them to letters for display). Why not just store
O and X?-
You'll find that a lot of things become easier if you don't try to keep the board in a 3-by-3 array, but in a 9-element list. That way, you only need on
Code Snippets
┌─┬─┬─┐
│ │ │X│
├─┼─┼─┤
│X│O│X│
├─┼─┼─┤
│O│X│O│
└─┴─┴─┘
Computer's turn:
┌─┬─┬─┐
│ │O│X│
├─┼─┼─┤
│X│O│X│
├─┼─┼─┤
│O│X│O│
└─┴─┴─┘Your turn. Make your move:
1 1
┌─┬─┬─┐
│X│O│X│
├─┼─┼─┤
│X│O│X│
├─┼─┼─┤
│O│X│O│
└─┴─┴─┘
You win!Your turn. Make your move:
2 1
┌─┬─┬─┐
│X│O│X│
├─┼─┼─┤
│X│X│O│
├─┼─┼─┤
│O│X│O│
└─┴─┴─┘
Traceback (most recent call last):
...
File "./cr15631.py", line 78, in chooseComputerMove
return emptyBoxes[random.randint(1, len(emptyBoxes) - 1)]
...
ValueError: empty range for randrange() (1,0, -1)#Converts the box's magic number into a character
def intToText(num):
if (num == 0): return ' '
if (num == 1): return 'O'
if (num == 2): return 'X'def box_display(num):
"""
Return the character to display for a box containing `num`.
"""
return ' OX'[num]Context
StackExchange Code Review Q#15631, answer score: 17
Revisions (0)
No revisions yet.