Browsing Tag

# games

A couple of weeks ago we talked about the game of Tic Tac Toe. I thought it would be fit to talk about another game, Battleships. Both games are excellent beginner games for programming and Python makes it very easy to implement them without a lot of hassle. You will notice that this article is a little different from my regular work. Normally I have the output of the function embedded. Since this is a game, like Tic Tac Toe, it is hard to capture the game in a single screen shoot. I will have some screen shoots toward the end of the article when I talk about printing out the board. Before we begin let us lay some ground rules for the game:

1. This is a modified version of the game Battleships.
2. The coordinates used for this game are all numeric.
3. The game will be played against a random computer player.
4. The computer will place random ships that are hidden from the user.
5. The turns alternate between player regards of hit or miss.

Let us begin by laying out our main logic for the game. Here is some pseudo code that will help keep us inline:

1. Setup the boards for both computer and user
2. Place ships
3. Alternate turns asking for coordinates and check win condition.

That is just a very rough outline. Of course the game itself is a lot more complex than this. So let’s get started. First we need to think about data representation. We need to have 2 boards that are 10 X 10. That is easy, it is just a list of lists with 10 lists. We will say that a cell is empty if it contains a “-1” value. So we just need to initiate this list of lists like this:

```
#setup blank 10x10 board
board = []
for i in range(10):
board_row = []
for j in range(10):
board_row.append(-1)
board.append(board_row)

#setup user and computer boards
user_board = copy.deepcopy(board)
comp_board = copy.deepcopy(board)

```

Notice that we had to use the Python copy module to make sure we are not pointing to the same memory. There are other ways to this, like having the code duplicated. This is a topic for another day, in the mean time, here is a nice article about Deep and Shallow copies in Python. You may want to note that some of this principles are actually global and have parallels in other languages. So we have the boards, but what about this ships? Well, that is easy, what if we have a dictionaries attached to our list of list as the final element. The dictionary will look like this:

```
ships = {"Aircraft Carrier":5,
"Battleship":4,
"Submarine":3,
"Destroyer":3,
"Patrol Boat":2}

```

Ok. So we have the easy part taken away. Now we have to look at how to place the ships. That is a little more complex. Lets first think about our logic. We need to figure out first that the coordinates entered by the user are valid. Then we have to ask orientation of the ship. After that we need to make sure that a ship can be actually placed. For example, what if we are trying to place a Patrol Boat at row: 10 col: 8, vertically? We cannot place the ship there. Horizontally we can, but that is another entry. So we will need to validate the request. If all is good, we will use the first letter of the ship name as a place holder on the board. Seeing the code might hep a little, so here it is:

```
def user_place_ships(board,ships):

for ship in ships.keys():

#get coordinates from user and vlidate the postion
valid = False
while(not valid):

print_board("u",board)
print "Placing a/an " + ship
x,y = get_coor()
ori = v_or_h()
valid = validate(board,ships[ship],x,y,ori)
if not valid:
print "Cannot place a ship there.\nPlease take a look at the board and try again."
raw_input("Hit ENTER to continue")

#place the ship
board = place_ship(board,ships[ship],ship[0],ori,x,y)
print_board("u",board)

raw_input("Done placing user ships. Hit ENTER to continue")
return board

```

You will notice that we call 5 functions we did not talk about yet. So let us talk about them. We will talk about the printing function in a little bit. We will take a look first at the get_coor() function. Before you get cold feet I do warn you that this function is lengthy. Keep in mind that the function accounts for user errors and will make sure that the only input it will accept and return is 2 values that are between 1 - 10. Nothing more, nothing less.

```
def get_coor():

while (True):
user_input = raw_input("Please enter coordinates (row,col) ? ")
try:
#see that user entered 2 values seprated by comma
coor = user_input.split(",")
if len(coor) != 2:
raise Exception("Invalid entry, too few/many coordinates.");

#check that 2 values are integers
coor[0] = int(coor[0])-1
coor[1] = int(coor[1])-1

#check that values of integers are between 1 and 10 for both coordinates
if coor[0] > 9 or coor[0] < 0 or coor[1] > 9 or coor[1] < 0:
raise Exception("Invalid entry. Please use values between 1 to 10 only.")

#if everything is ok, return coordinates
return coor

except ValueError:
print "Invalid entry. Please enter only numeric values for coordinates"
except Exception as e:
print e

```

Ok. So that was a mouth full. How about the next one, the v_or_h()? That is a little more straight forward, it only return V or H. This should be simple:

```
def v_or_h():

#get ship orientation from user
while(True):
user_input = raw_input("vertical or horizontal (v,h) ? ")
if user_input == "v" or user_input == "h":
return user_input
else:
print "Invalid input. Please only enter v or h"

```

Recall that we are doing all this to place the user ships. So at this point we have a valid coordinate from the user and an orientation. Now we go back to our placement issue. We need to make sue that the type of ship can be placed where the user wants it. To do this we just need to check that it can physically be placed within the board limits and that the cells are empty (-1).

```
def validate(board,ship,x,y,ori):

#validate the ship can be placed at given coordinates
if ori == "v" and x+ship > 10:
return False
elif ori == "h" and y+ship > 10:
return False
else:
if ori == "v":
for i in range(ship):
if board[x+i][y] != -1:
return False
elif ori == "h":
for i in range(ship):
if board[x][y+i] != -1:
return False

```

You should note that this function returns a boolean value of True or False. This is so we can use it when we write the computer version of place ships, we can use the validate() function without writing a new one. Now all we have look at, excluding the print function is the actual placement of the ships. Recall that all we need to do at this point is put the intial character of the ships name.

```
def place_ship(board,ship,s,ori,x,y):

#place ship based on orientation
if ori == "v":
for i in range(ship):
board[x+i][y] = s
elif ori == "h":
for i in range(ship):
board[x][y+i] = s

return board

```

You will notice that this looks very much like a function that we can reuse, just like the validate() function. Perhaps now is a good time to look at the ship placement for the computer agent.

```
def computer_place_ships(board,ships):

for ship in ships.keys():

#genreate random coordinates and vlidate the postion
valid = False
while(not valid):

x = random.randint(1,10)-1
y = random.randint(1,10)-1
o = random.randint(0,1)
if o == 0:
ori = "v"
else:
ori = "h"
valid = validate(board,ships[ship],x,y,ori)

#place the ship
print "Computer placing a/an " + ship
board = place_ship(board,ships[ship],ship[0],ori,x,y)

return board

```

The main difference between the user and computer ship placement is that the computer uses randomization to place the ships while the user enters the information. At this point we have our setup complete and can play the game. The 2 functions that will handle the game are user_move and computer_move. Let us take a look at both:

```
def user_move(board):

#get coordinates from the user and try to make move
#if move is a hit, check ship sunk and win condition
while(True):
x,y = get_coor()
res = make_move(board,x,y)
if res == "hit":
print "Hit at " + str(x+1) + "," + str(y+1)
check_sink(board,x,y)
board[x][y] = '\$'
if check_win(board):
return "WIN"
elif res == "miss":
print "Sorry, " + str(x+1) + "," + str(y+1) + " is a miss."
board[x][y] = "*"
elif res == "try again":

if res != "try again":
return board

def computer_move(board):

#generate user coordinates from the user and try to make move
#if move is a hit, check ship sunk and win condition
while(True):
x = random.randint(1,10)-1
y = random.randint(1,10)-1
res = make_move(board,x,y)
if res == "hit":
print "Hit at " + str(x+1) + "," + str(y+1)
check_sink(board,x,y)
board[x][y] = '\$'
if check_win(board):
return "WIN"
elif res == "miss":
print "Sorry, " + str(x+1) + "," + str(y+1) + " is a miss."
board[x][y] = "*"

if res != "try again":

return board

```

Fist thing you will notice is the infomus function get_coor(). We have used it before and it will work for us again. Now we have 3 more function we need to look at in order to understand how this works. We will start with make_move() this function looks like this:

```
def make_move(board,x,y):

#make a move on the board and return the result, hit, miss or try again for repeat hit
if board[x][y] == -1:
return "miss"
elif board[x][y] == '*' or board[x][y] == '\$':
return "try again"
else:
return "hit"

```

All this function really does is looks at the board in a particular cell and returns a value at that cell. Based of the result the board will be updated if needed. Now we need to check if a ship got sunk. This is easy. If you recall we have a dictionary containing the ships and the amount of hits they can take. This is the last element in the board data representation. So this is not as bad as you might think. We just need to figure out what ship got hit and if it has more damage points.

```
def check_sink(board,x,y):

#figure out what ship was hit
if board[x][y] == "A":
ship = "Aircraft Carrier"
elif board[x][y] == "B":
ship = "Battleship"
elif board[x][y] == "S":
ship = "Submarine"
elif board[x][y] == "D":
ship = "Destroyer"
elif board[x][y] == "P":
ship = "Patrol Boat"

#mark cell as hit and check if sunk
board[-1][ship] -= 1
if board[-1][ship] == 0:
print ship + " Sunk"

```

Cool. Now for the last function, check_win(). Now this can be done better, but I was tired at this point so I went for simple. If you think about it, a board is in a win condition if all the ships are gone. That means that in our representation, all the cells must conation a -1 for empty, a \$ for a hit or a * for a miss. Any other character is a ship and there fore not a win. So the function can look like this:

```
def check_win(board):

#simple for loop to check all cells in 2d board
#if any cell contains a char that is not a hit or a miss return false
for i in range(10):
for j in range(10):
if board[i][j] != -1 and board[i][j] != '*' and board[i][j] != '\$':
return False
return True

```

So now we have our puzzle almost complete. The last pice we need is a good print function. This function is built upon the Tic Tac Toe function, but is a little more complex. Recall that there are 2 printing functions that we need. When the users sees the computer board, the ships should be hidden. However, when the user is presented with their own board, they should see all the ships. In wither case, the hits and misses should be displayed and the ‘-1’ should not be printed. This took me a while to get working properly. Mostly because I wanted it to look good, for me. Here is the printing function:

```
def print_board(s,board):

# WARNING: This function was crafted with a lot of attention. Please be aware that any
#          modifications to this function will result in a poor output of the board
#          layout. You have been warn.

#find out if you are printing the computer or user board
player = "Computer"
if s == "u":
player = "User"

print "The " + player + "'s board look like this: \n"

#print the horizontal numbers
print " ",
for i in range(10):
print "  " + str(i+1) + "  ",
print "\n"

for i in range(10):

#print the vertical line number
if i != 9:
print str(i+1) + "  ",
else:
print str(i+1) + " ",

#print the board values, and cell dividers
for j in range(10):
if board[i][j] == -1:
print ' ',
elif s == "u":
print board[i][j],
elif s == "c":
if board[i][j] == "*" or board[i][j] == "\$":
print board[i][j],
else:
print " ",

if j != 9:
print " | ",
print

#print a horizontal line
if i != 9:
print "   ----------------------------------------------------------"
else:
print
```

So to sum it all up, here is a link to the complete code Battleships in Python Command Line. Here are some screen shoots I took from the game play:

Have Fun!

Computer games and python are like a perfect match, it just works. Python is simple enough and powerful to provide game designer and programers a common ground for them to meet up and create awesome stuff. In fact, Python has the PyGame library that is dedicated to creating games. There are many games for Python, buy I thought it would be beneficial to start off with a simple version of Hangman. As before, we will be using Python 2.7 for our examples.

Before we take off with it, let us agree to the rules of the game:

• The computer will randomly chose a secret word.
• For each user guess, all occurrences of the same letter in the word will be revealed to the player.
• The computer will draw the “hangman digram” as outlined bellow for each turn.
• The user may guess letters at each turn until the word is completed or the “hangman digram” is complete.
• We can assume that all user input and word letters are lowercase alpha numeric without errors.

The last one is made to simplify the code and the constrains for the problem. In reality we should always account for errors, but I am going to et this one slide. For now. Let us first look and the “hangman digram”. Some people think this is the hardest thing, but it is actually the easiest. Here are the 7 possibilities of the board in ASCII art form.

```  +---+
|   |
|
|
|
|
=========

+---+
|   |
0   |
|   |
|
|
=========

+---+
|   |
0   |
/|   |
|
|
=========

+---+
|   |
0   |
/|\  |
|
|
=========

+---+
|   |
0   |
/|\  |
/    |
|
=========

+---+
|   |
0   |
/|\  |
/ \  |
|
=========
```

This was generated by this short Python code:

```
board = [
'  +---+   \n  |   |   \n      |   \n      |   \n      |   \n      |   \n========= \n',
'  +---+   \n  |   |   \n  0   |   \n      |   \n      |   \n      |   \n========= \n',
'  +---+   \n  |   |   \n  0   |   \n  |   |   \n      |   \n      |   \n========= \n',
'  +---+   \n  |   |   \n  0   |   \n /|   |   \n      |   \n      |   \n========= \n',
'  +---+   \n  |   |   \n  0   |   \n /|\\  |   \n      |   \n      |   \n========= \n',
'  +---+   \n  |   |   \n  0   |   \n /|\\  |   \n /    |   \n      |   \n========= \n',
'  +---+   \n  |   |   \n  0   |   \n /|\\  |   \n / \\  |   \n      |   \n========= \n'
]

print board[i]

```

This will take care of any of the board configurations. All we need to worry about is how many letters the user has got wrong and we can immediately print out the corresponding “hangman diagram”. Let us make it a little more nice and wrap it in a class. Classes are objects in Python, very similar to Objects in Java. We can encapsulate the board configuration, word to be guessed, missed letters and guessed letters in 1 object. Lets take a look:

```
board = [
'  +---+   \n  |   |   \n      |   \n      |   \n      |   \n      |   \n========= \n',
'  +---+   \n  |   |   \n  0   |   \n      |   \n      |   \n      |   \n========= \n',
'  +---+   \n  |   |   \n  0   |   \n  |   |   \n      |   \n      |   \n========= \n',
'  +---+   \n  |   |   \n  0   |   \n /|   |   \n      |   \n      |   \n========= \n',
'  +---+   \n  |   |   \n  0   |   \n /|\\  |   \n      |   \n      |   \n========= \n',
'  +---+   \n  |   |   \n  0   |   \n /|\\  |   \n /    |   \n      |   \n========= \n',
'  +---+   \n  |   |   \n  0   |   \n /|\\  |   \n / \\  |   \n      |   \n========= \n'
]

class Hangman:
def __init__(self,word):
self.word = word
self.missed_letters = []
self.guessed_letters = []

```

Sweet. Let us move on and create a print function. Print functions are useful for debugging and will take care of our user interface. When we want to print our Hangman object, we really want to know all the information, how the board looks like (or how much of the diagram is complete), the letter we missed, the blank splits for the words and letters we already found. Let us take a look:

```
def print_game_status(self):
print board[len(self.missed_letters)]
print 'Word: ' + self.hide_word()
print 'Letters Missed: ',
for letter in self.missed_letters:
print letter,
print
print 'Letters Guessed: ',
for letter in self.guessed_letters:
print letter,
print

```

And the output for an empty board:

Upon close inspection, you will notice that there is another method I am calling name hide_word(). By the concatenation with the string before, you can assume the function is defined and returns a string. What we really need, is a function that can take a word and print out only the letters the user has already guessed. Let’s take a look:

```
def hide_word(self):
rtn = ''
for letter in self.word:
if letter not in self.guessed_letters:
rtn += '_'
else:
rtn += letter
return rtn

```

That seems good enough. Before we can move on to the game play and logic, we need a method in the Hangman object to update a current guess. That is straight forward and looks like so:

```
def guess(self,letter):
if letter in self.word and letter not in self.guessed_letters:
self.guessed_letters.append(letter)
elif letter not in self.word and letter not in self.missed_letters:
self.missed_letters.append(letter)
else:
return False
return True

```

So if the letter is in the word we are trying to guess and we haven’t tried to guess it before, we are going to mark it and return 1 for successful update or if the letter is not in our word, but has not been asked again. If either of these conditions is a failure, we have a repeat input we have already accounted for and we will return 0 to notify the game handler of a problem. The last method we are really missing is to check for win condition. Here is how we can check for hangman win condition in our case:

```
def hangman_won(self):
if '_' not in self.hide_word():
return True
return False

```

Notice that we are reusing methods we already have, making things simple. Now our Hangman game object is complete. You will think that we are over, but there is one more method we are missing for the game to be complete. We have a method to check if the game has been won, but we do not have a method to check if the game is over. What happens when the “hangman digram” is complete? For this we will add just a one liner method:

```
def hangman_over(self):
return self.hangman_won() or len(self.missed_letters) == 7
```

Sweet. Now all we need is a main driver and some helper function and we are all done. Our main method can look like this:

```
def main():

game = Hangman(rand_word())
while not game.hangman_over():
game.print_game_status()
user_input = raw_input('\nEnter a letter: ')
game.guess(user_input)

game.print_game_status()
if game.hangman_won():
print '\nCongratulations! You are the winner of Hangman!'
else:
print '\nSorry, you have lost in the game of Hangman...'
print 'The word was ' + game.word

print '\nGoodbye!\n'
```

You will be now notice that we are almost done, just 1 more function and we are clear, the rand_word() function. Now you can get very fancy with this, read from a file and randomly choose and such. However, I want to keep things simple, so I limited the word bank into a small list from which I randomly choose 1 word. Check it out:

```
def rand_word():