Page 1 of 1

Learning to Program Python Learn the fundamentals with the Black Jack game

#1 mattman059  Icon User is offline

  • Epic Awesomeness
  • member icon

Reputation: 15
  • View blog
  • Posts: 538
  • Joined: 23-October 06

Post icon  Posted 05 September 2009 - 12:21 PM

This is going to be an introduction to the Python programming language. You may have already started learning the language yourself, or have never really thought about it before. This tutorial is going to help you decide if python is the language for you (i hope lol). I really like the ease of use that python provides programmers to either do something really small and quick (such as perform calculations like 2^1014, which would take a while in other languages) to very detailed and specialized programs such as a disassembler or reverse engineering tool.

One of the things I wanted to go for in this tutorial was to create a program that incorporated a lot of the different mechanisms that you use in python without going over the beginners head. This tutorial requires absolutely no prior knowledge of Python, or even programming at all for that matter. It goes without saying then that this tutorial will not be Object Oriented. Also with this tutorial I wanted to take those foundations and apply them to something familiar, so I created the game Black Jack (you may know it as 21). Many people are familiar with the game, and it is simple enough to program (however I left out the gambling aspect, but if you enjoy this tutorial and python, feel free to implement it yourself).

So let's get down to business! Let's talk about some prerequisites to working Python. First thing, you have to have it installed, if you're on Windows then you may need to download and install it (just let it install into the default C:\Python25 directory) I recommend the Python IDLE because it's simple. If you're on Linux/Unix, congratulations the install work has been done for you (most likely). Just fire up a terminal and type "python" and if you get an error, either use your package manager, or run a quick apt-get install for python. Okay once you've got that done open up your python editor and lets go.

One of the first things you learn in any programming language is how to print something to the user. So if you're in your python environment type the following and hit enter:

print "Not another Hello World exercise"



And you should get a nice message printed to your screen. One thing you may notice is that python does not require a semicolon or period or any form of punctuation to end this statement as in some other languages. However some things will require it (later). The next thing you will want to learn is how to get user input. Sometimes this can be tricky but not in the case of python which makes it very simple. All you have to do is use this one line of code to ask for input and assign that input to a variable.

name = raw_input ("What is your name? : ")



This prints the line "What is your name? : " to the screen and waits for you to type your name (or anyone elses for that matter) and hit enter, then assigns that name to the "name" variable. Note that we did not have to declare name as a string, or character, or int. In fact we dont ever HAVE to declare the variables type in python when working on simpler programs (it's nice to include the type definition if you're going to be doing something that must be maintained, and if you're sure the type wont change...more on this later.)

Let's put these two concepts together and form your first small program : The Greeter

name = raw_input ("What is your name? : ")
print "Hello " + name + " it is nice to meet you."



Pretty straight forward, we ask for their name, and tell them it is nice to meet them. What is the "+" you may ask? well that + is a concatenation symbol it can be used to combine two or more strings together to form a compound string (You can also assign the resulting string to a variable, just by putting the variable name and = before your string)

Okay now for a little side track. Remember before I mentioned something about variable types changing? Well that's because python doesnt really care what the type of data is that you store into a variable..lets see an example eh?

myName = "matt"
print myName   # Prints matt
myName = "12345"
print myName   # Prints 12345



Notice how the myName variable was initialized to "matt" at first but was changed to 12345. This causes no errors, no warnings so be careful. The only time you will get an error is if you try to assign something to a variable that does not exist.

A = L * W



This snipet results in an error because neither the variable "L" nor the variable "W" exist.

Lets talk briefly about arrays. Arrays in Python are also called Lists. (note to C++ vets. Try not to let this confuse you with Linked Lists, they are not the same.). The easiest way to create a List is to declare one like this

myList = []
myList2 = [1,2,3,4,5,6]
myList3 = ["Monday", "Tuesday", "Thursday"]



In the above code, we created three lists, the first was an empty list in which we can add things to later on if we like using the .append() method of lists. The second list has been initialized to a group of six integers, and the third list was created with three days of the week. Note again that we never had to tell Python that the lists contained strings, or integers, it just knows.

Just a few more fundamentals and then I promise we'll get down to the game part of this tutorial. Defining your own functions can be fun and exciting (nerd alert) and easy with python

def greeter(name):
	 print "Hello " + name



Very simple function I know but it demonstrates a few fundamentals. The function declaration includes the keyword "def" short for "define" then the function name and a list of parameters that you want to be able to pass to the function. In this example we only want to send one, "name" to the function. The function takes "name" and concatenates it onto the string "Hello " which in turn prints a warm welcoming message. One thing to notice in this example is the colon. This is very important and must always be included in function declarations, it's like the sign that tells Python, hey this is where my function starts. Also important in Python is indentation. In some languages it is unimportant as to the level of indentation but in python it is the difference between a program running, and an error message. Each function or conditional statement will always have one indentation level per statement and if you to back one level, then you are out of the corresponding statement...it's a little difficult to explain, so let me show you.

def myFunction():
	 #function indentation level
	 if x > y:
		  #If statment indentation level
		  print y
	 if y > x:
		  #New if statement indentation level
		  print x
	#outside both if statements but still in the function
	return
#Now i'm outside the function



As you can see each statement in the function was given its own indentation level and anything inside that level belongs to the previous statement. I hope that is fairly clear.

The last thing I would like to mention before we get to the game is the Import statement. In languages like C++ you sometimes need functions, or variables from other programs or modules. In python you can include these variables and functions by using the following code

import "your module here"



Where of course "your module here" is to be replaced with the module you want to include.


Okay so we should have a fairly good grip on things right now, and they will become much clearer as we move through our game of Black Jack.

The first thing we are going to want our program to do are clear the screen of previous games, which is different depending on the operating system you are working with

import os #Imports functions that are used with your specific operating system (os)

os.system("clear") #Unix/Linux based systems use this
os.system("cls")	#Windows based systems use this



Make sure that if you use a Windows machine to include the Windows code above. You can however include both and just comment out the other command by using a #.

I personally really like breaking things down into different functions and allowing each part to do it's own work so my version of this program will have eight (8) functions each with their own specific job. they are as follows:

game() - This is the main function of our game, everything is going to be called here, and this is where the action happens
message() - This is our "end of game" function which asks the user if they want to play again
deal() - This function returns an Array (List) that will be used as our hand in the game
computeScore(hand) - This function takes 1 parameter and computes the score of a players hand (ideally we would want scores between 2 and 21)
hit(hand) - Takes a parameter and returns the same parameter with one "card" appended to it
computeStrategy(hand) - Generally always used for the computer, to decide if it wants to Hit or Stay...but you could incorporate a "Hint" system using this for the Human player
winner(hand1,hand2) - Takes two "hands" and determines who won based on the scores of the hands
bust(hand) - Decides if the player has gone over 21, and has "Busted" their hand

Again, this is my personal preference, i like how it breaks down the work for each function rather than having one "super-function" do it all. but that choice is up to you.

This setup will however allow me to describe the workings of this program piece by piece by explaining each function separately. So let's look at the game function
def game():
	
			#os.system("clear") # *nix Terminal Only
			os.system("cls")   # Windows Command Prompt Only
			print "WELCOME TO PYTHON BLACKJACK"
			Cards = [1,2,3,4,5,6,7,8,9,10,11,12,13,14]
			computerHand = deal()
			playerHand = deal()

			print "-------START GAME-------"
			print "Computer Hand: " + str(computerHand[0])
			print "Player Hand: " + str(playerHand)
			
			print "\n-------Current Score-----"
			if(computeScore(playerHand) == 21):
				print "21! You win!"
				message()
			elif(computeScore(computerHand) == 21):
				print "21! Computer Wins!"
				message()
			print computeScore(playerHand)
			cardHit = raw_input("Do you want to hit (Y/N) : ")
			while(cardHit == "y" or cardHit == "Y" ):
				hit(playerHand)
				
				print "\n------NEW HAND SCORE------"
				print playerHand
				print computeScore(playerHand)
				if(len(playerHand) == 5 and computeScore(playerHand) < 21):
					print "Less than 5 and Under 21:  YOU WIN "
					message()
				elif(bust(playerHand) == 1):
					print "BUST. You lose"
					message()
				else:
					cardHit = raw_input("Do you want to hit (Y/N): ")

										   
			print "\n-------Computer Turn------"
			if(computeStrategy(computerHand) == "Hit"):
				hit(computerHand)
				print computerHand
			else:
				print "I'm done."
				print computerHand

			print "\n--------DETERMINE WINNER------"
			print "COMPUTER: " + str(computeScore(computerHand))
			print "PLAYER  : " + str(computeScore(playerHand))
			
			if(winner(computerHand,playerHand) == "hand1" and bust(computerHand)==0):
			   print "Computer won"
			   
			elif(winner(computerHand,playerHand) == "hand2" and bust(playerHand) == 1):
			   print "Computer won"
			   
			elif(winner(computerHand,playerHand) == "hand1" and bust(computerHand) == 1):
			   print "Player Won"
			   
			elif(winner(computerHand,playerHand) == "hand2" and bust(playerHand) == 0):
			   print "Player Won"
			   
			message()




As you can see, the function begins by clearing the screen of previous games and dealing a new hand of cards. This is another interesting feature of Python (as well as other languages) that you can assign the return value of a function to a variable for later use. In this game, computerHand is assigned the List of cards from the deal() function.

Let's talk for a minute about this line:
 print "Computer Hand: " + str(computerHand[0])

It looks familiar to what we saw earlier where we concatenated two strings together, but it is different in a few places. the str() function is built into python and converts anything..yes anything, into a string which allows you to add it to the end of another string, had we left computerHand[0] as an integer we would have recieved an error message. What does computerHand[0] do you might ask? Well both computerHand and playerHand have two elements (two cards if you will) and computerHand[0] prints only the first card, computerHand[1] prints only the second card, while computerHand prints both cards. And since this is black jack, we only want to see one of the dealers cards.

	 if(computeScore(playerHand) == 21):
				print "21! You win!"
				message()
	 elif(computeScore(computerHand) == 21):
				print "21! Computer Wins!"
				message()


Sometimes you might get really lucky, and end up with 21 on the opening hand, unfortunately so can the dealer, so this little piece determines if your starting score is equal to 21. This is much different than just one = sign. Think of one equals sign as assignment and literally translates to "equals" : a = 2 which always evaluates to true, seeing as you just told the compiler that it was true. Using two equals signs literally says "is equal to" so : a == 2 may or may not evaluate to true depending on the value of a. In our game, we want to check if the score IS EQUAL TO 21 so we use two equal signs. Finally in either case, we call game over, and ask if they want to play again. One more thing to note about this is the structure of our if statement. Notice the colon after the end of the if statement, again, very important to remember this. Also something new is the 'elif' that is located below that if statement. This tells the compiler that if the previous statement is false, try me next. You can have as many elif's as you want after an if statement, and you could also use an 'else' at the end for a default statement.

				if(len(playerHand) == 5 and computeScore(playerHand) < 21):
					print "Less than 5 and Under 21:  YOU WIN "
					message()
				elif(bust(playerHand) == 1):
					print "BUST. You lose"
					message()
				else:
					cardHit = raw_input("Do you want to hit (Y/N): ")



This peice of code evaluates the players hand to see if they have 5 cards and a score less than 21 which automatically guarentees them a win. However, if the bust function returns that they are over 21 then the game is over. But if none of these cases are true, then we ask the player if they want to "hit" or do they want another card. This code snippet is located within a "while" loop that will continue to loop until "cardHit" is equal to "N" or "n" signifying that the player does not want another card.

That is basically the gist of the game function. Now we move onto the smaller functions which are called upon to do to "dirty work" for the game() function.

First up message():
def message():
		again = raw_input("Do you want to play again? (Y/N) : ")
		if(again == "Y" or again == "y"):
			game()
		else:
			print "\n\n-------Thank you for playing!--------\n\n"
			exit()



Very straight forward if you've been reading through this far. Ask for user input, if it's 'Y' or 'y' then start over by calling the game() function. Otherwise, exit the game.


One important function is the deal() function, because it not only sets up the entire game, but does so using pseudo-random numbers. So one thing we must do before using random numbers is import the random number module that has been written for us (arent they nice):

import random



this code goes at the top of your program near the other import statement. now you're ready to use random numbers :).

def deal():
	random1 = random.randint(1,14)
	random2 = random.randint(1,14)

	if (random1 == 11):random1 = "J"
	if (random1 == 12):random1 = "Q"
	if (random1 == 13):random1 = "K"
	if (random1 == 14):random1 = "A"

	if (random2 == 11):random2 = "J"
	if (random2 == 12):random2 = "Q"
	if (random2 == 13):random2 = "K"
	if (random2 == 14):random2 = "A"
	
	hand = [random1,random2]
	return hand



Again, very basic, gets two random numbers between 1 and 14 inclusive and assigns it to the two variables. To give it a little more of a realistic touch, i threw in some if statements to give the Face cards their respective letter value. And finally a list is created with the two variables as elements, and is returns to game(). We now have our hands!

Next we will look at how the game computes the scores for the computer and human players

def computeScore(hand):
	total = 0
	for cards in hand:
		if cards == "J" or cards == "Q" or cards == "K":
			total+= 10
		elif cards == "A":
			if total >= 11: total+= 1
			else:total+= 11
		else:
			total += cards

	return total



The first thing we want to check is face cards J,Q,and K they are worth 10 points apiece. Normally a player would get to choose whether they want the Ace to count as 1 or 11 but I have taken that choice away, and decided that if their total so far is >= (Greater than or equal to) 11 then they want it to add 1 and other wise add 11. If we dont have a face card or an ace, then we just add the numeric value to total.


Lets look at what happens when the player wants to "hit" their hand

def hit(hand):
	newCard = random.randint(1,14)

	if (newCard == 11):newCard = "J"
	if (newCard == 12):newCard = "Q"
	if (newCard == 13):newCard = "K"
	if (newCard == 14):newCard = "A"

	hand.append(newCard)

	return hand



Generate a new random number, assign if a letter if its a face card, then use the .append() method of Lists to add the card to the hand. then it returns the hand with it's new card.

When dealing with a computer player it's always a good idea to incorporate some kind of strategy to the computer. This is probably one of the easiest parts of this program to change to your liking.

def computeStrategy(hand):
	strategy = ""
	if (computeScore(hand) > 15): strategy = "Stay"
	if (computeScore(hand) <=15): strategy =  "Hit"

	return strategy



Want to always win? change the 15 to a 1 to make them always "Stay"
Want the computer to bust a lot? change the 15 to a 17 and swap "Stay" and "Hit"
Really you can change this to whatever threshold you want.


The last two functions just run over some basic comparisons, they both call on the computeScore function to get the score for each hand So i'll just post them both together, they are very simple, and easy to follow if you've been following all this time.

def winner(hand1,hand2):
	score1 = computeScore(hand1)
	score2 = computeScore(hand2)
	
	if score1 > 21: return "hand2"
	if score2 > 21: return "hand1"

	if(score1 > score2): return "hand1"
	else: return "hand2"

def bust(hand):
	if computeScore(hand) > 21:
		return 1
	else:
		return 0



I do recommend that you run these inside your command prompt or terminal just because of the calls to os.system("clear") it runs better (and actually clears the screen) if you run them inside the command prompts.

Windows Users:
[windows key] + R : type "cmd" enter
type : cd C:\Python25
python
import BlackJack
BlackJack.game()

Unix/Linux Users:
open terminal
python
import BlackJack
BlackJack.game()

And you can play for yourself. You can also change the name of the program if you want to anything that you want.

Here is the full program:

import os
import random

def game():
	
			#os.system("clear") # *nix Terminal Only
			os.system("cls")   # Windows Command Prompt Only
			print "WELCOME TO PYTHON BLACKJACK"
			Cards = [1,2,3,4,5,6,7,8,9,10,11,12,13,14]
			computerHand = deal()
			playerHand = deal()

			print "-------START GAME-------"
			print "Computer Hand: " + str(computerHand[0])
			print "Player Hand: " + str(playerHand)
			
			print "\n-------Current Score-----"
			if(computeScore(playerHand) == 21):
				print "21! You win!"
				message()
			elif(computeScore(computerHand) == 21):
				print "21! Computer Wins!"
				message()
			print computeScore(playerHand)
			cardHit = raw_input("Do you want to hit (Y/N): ")
			while(cardHit == "y" or cardHit == "Y" ):
				hit(playerHand)
				
				print "\n------NEW HAND SCORE------"
				print playerHand
				print computeScore(playerHand)
				
				if(len(playerHand) == 5 and computeScore(playerHand) < 21):
					print "Less than 5 and Under 21:  YOU WIN "
					message()
				elif(bust(playerHand) == 1):
					print "BUST. You lose"
					message()
				else:
					cardHit = raw_input("Do you want to hit (Y/N): ")
										   
			print "\n-------Computer Turn------"
			if(computeStrategy(computerHand) == "Hit"):
				hit(computerHand)
				print computerHand
			else:
				print "I'm done."
				print computerHand

			print "\n--------DETERMINE WINNER------"
			print "COMPUTER: " + str(computeScore(computerHand))
			print "PLAYER  : " + str(computeScore(playerHand))
			
			if(winner(computerHand,playerHand) == "hand1" and bust(computerHand)==0):
			   print "Computer won"
			   
			elif(winner(computerHand,playerHand) == "hand2" and bust(playerHand) == 1):
			   print "Computer won"
			   
			elif(winner(computerHand,playerHand) == "hand1" and bust(computerHand) == 1):
			   print "Player Won"
			   
			elif(winner(computerHand,playerHand) == "hand2" and bust(playerHand) == 0):
			   print "Player Won"
			   
			message()
	

def message():
		again = raw_input("Do you want to play again? (Y/N) : ")
		if(again == "Y" or again == "y"):
			game()
		else:
			print "\n\n-------Thank you for playing!--------\n\n"
			exit()
	
def deal():
	random1 = random.randint(1,14)
	random2 = random.randint(1,14)

	if (random1 == 11):random1 = "J"
	if (random1 == 12):random1 = "Q"
	if (random1 == 13):random1 = "K"
	if (random1 == 14):random1 = "A"

	if (random2 == 11):random2 = "J"
	if (random2 == 12):random2 = "Q"
	if (random2 == 13):random2 = "K"
	if (random2 == 14):random2 = "A"
	
	hand = [random1,random2]
	return hand


def computeScore(hand):
	total = 0
	for cards in hand:
		if cards == "J" or cards == "Q" or cards == "K":
			total+= 10
		elif cards == "A":
			if total == 11: total+= 1
			else:total+= 11
		else:
			total += cards

	return total

def hit(hand):
	newCard = random.randint(1,14)

	if (newCard == 11):newCard = "J"
	if (newCard == 12):newCard = "Q"
	if (newCard == 13):newCard = "K"
	if (newCard == 14):newCard = "A"

	hand.append(newCard)

	return hand


def computeStrategy(hand):
	strategy = ""
	if (computeScore(hand) > 15): strategy = "Stay"
	if (computeScore(hand) <=15): strategy =  "Hit"

	return strategy

def winner(hand1,hand2):
	score1 = computeScore(hand1)
	score2 = computeScore(hand2)
	
	if score1 > 21: return "hand2"
	if score2 > 21: return "hand1"

	if(score1 > score2): return "hand1"
	else: return "hand2"

def bust(hand):
	if computeScore(hand) >= 21:
		return 1
	else:
		return 0




Is This A Good Question/Topic? 2
  • +

Replies To: Learning to Program Python

#2 Cerafem  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 2
  • Joined: 05-April 08

Posted 16 January 2010 - 01:31 PM

made it, and it ran fine. However, I got a hand of two aces (12), then was dealt a jack. I believe that both the aces should have become ones, and I think it happened because the first ace it checked didn't need to be a one, but after adding the second, I busted. Personally, I might make it add everything up unmodified (aces=11), and if it busts, subtract as many tens as you can until you aren't over 21 (provided you have the aces to back it up), but that may be a little overkill.


WELCOME TO PYTHON BLACKJACK
-------START GAME-------
Computer Hand: 7
Player Hand: ['A', 'A']

-------Current Score-----
12
Do you want to hit (Y/N): y

------NEW HAND SCORE------
['A', 'A', 'J']
22
BUST. You lose
Do you want to play again? (Y/N) :
Was This Post Helpful? 0
  • +
  • -

#3 Guest_Trob33*


Reputation:

Posted 26 April 2010 - 06:10 PM

when i tell python to run the module nothing even displays in the shell just >>>
Wht did i do wrong?
Was This Post Helpful? 0

#4 dasker  Icon User is offline

  • D.I.C Head

Reputation: 1
  • View blog
  • Posts: 88
  • Joined: 10-November 10

Posted 16 August 2011 - 03:16 PM

This was really helpful.
I too was not able to run it though as described above. Any help would be appreciated.
Was This Post Helpful? 0
  • +
  • -

#5 JacksonD  Icon User is offline

  • D.I.C Head

Reputation: 9
  • View blog
  • Posts: 215
  • Joined: 18-October 11

Posted 24 October 2011 - 05:30 AM

I am learning python, but I don't really get it. Good you possibly give me a tutor via Messenger (D.I.C messenger) It would mean heaps. I get print and stuff, but it got a bit confusing. Could we take it bit by bit,

Thanks in advance and best regards.
Jackson
Was This Post Helpful? 0
  • +
  • -

#6 machv5  Icon User is offline

  • New D.I.C Head

Reputation: 1
  • View blog
  • Posts: 16
  • Joined: 23-June 12

Posted 02 August 2012 - 11:25 AM

View PostCerafem, on 16 January 2010 - 01:31 PM, said:

made it, and it ran fine. However, I got a hand of two aces (12), then was dealt a jack. I believe that both the aces should have become ones, and I think it happened because the first ace it checked didn't need to be a one, but after adding the second, I busted. Personally, I might make it add everything up unmodified (aces=11), and if it busts, subtract as many tens as you can until you aren't over 21 (provided you have the aces to back it up), but that may be a little overkill.


re: being dealt A A in a hand then after one or more bust values are dealt changing both A's values to 1. I'm trying to figure the same thing out too (after reading your reply was what prompted me to) I think if you added something along the lines of if the hand has A A or A A A or A A A A or in it and a bust value is drawn then -10 from the hand would work. I am trying to wrap my head around how to write it. Once I get it right I will post it. For testing I want to be dealt A A so that I can see if it works without having to wait for random to get around to dealing me two aces. Instead of specifying two aces three aces etc...

If cards == "A" "A" or >= 9
          total -10


I have to give it some thought on how to write it properly though as it is the first time I am having to create code from scratch to modify another program. I think that the snippet above would continue to -10 from the total after each subsequent hit. I don't know how to make it only do this once, or if I got the syntax right.

If cards == "A" "A" or >= 9
          "A" = 1


I think that changing the value of aces to only count as 1 would negate having to figure a way to make the -10 form the total score only run once per hand.

Anyway back to completing the tutorial I have a few more line to go before I need to work on this. You could just give the user the choice back instead of letting the program make the best decision. I think I will try to make both ways possible for something to do.

I realize that this is a reply to a 2010 post. I hope it doesn't get be barred for necroing. If you do it will really hurt my ability to move forward when I hit a problem I need help with or I want to share my own experiences with this or any other tutorial.
Was This Post Helpful? 0
  • +
  • -

#7 machv5  Icon User is offline

  • New D.I.C Head

Reputation: 1
  • View blog
  • Posts: 16
  • Joined: 23-June 12

Posted 20 September 2012 - 09:25 AM

I have tried everything (except the thing that works) to get the game to run and I can't seem to get it to. I am running Windows7. I don't know how to get it to run in cmd.exe.

I have tried using the python GUI, python commandline, typing
C:\dic\tuts\blackjack>python pyblackjack.py
and other things to get it to run and I have run out of ideas. And as I am a nub I did also try all the above from a full copy of the original too so as to negate the possibility that it maybe a typo or two in my copy that's to blame and it isn't that. I may also be blind and just didn't see the instructions for getting it to run in the cmd console. I also have tried to open it in the commandline but can't seem to get the syntax right for opening files or running them. I tried open and file to no avail I get "file not found" type errors or outside the py interpreter "is not an internal command" type ones. And I can't seem to find anything via Google due to my likely incorrect search parameters. Anywho moving on.

My question is how do I get the dang thing to run? Thanks for any and all help with this.
Was This Post Helpful? 0
  • +
  • -

#8 pietomb00  Icon User is offline

  • D.I.C Head

Reputation: 12
  • View blog
  • Posts: 68
  • Joined: 28-June 11

Posted 20 February 2013 - 05:28 AM

Great tutorial thanks.

The only problem was that like others I couldn't get it to run. However, (minimal amount of) other code I've seen has the following at the bottom, I've added that and it works, for me, I'm just reading up on what that actually does.

if __name__ == "__main__":
   game()


Was This Post Helpful? 0
  • +
  • -

Page 1 of 1