PyGame Tutorial: Graphical Hi-Lo Game in Python

Hi Lo Pygame Featured Image

PyGame is a 2D game development library in Python. It contains specific functions and classes required for a programmer to create a simple or complex game from scratch.

In this tutorial, we will be creating our own Hi-Lo game using PyGame Library. Hi-Lo is a very straightforward Casino game where the player has to guess whether the next card in the deck is higher or lower than the current card.

Suggested: If this is the first time you’re creating a game, try the command-line implementation of the hi-lo game in Python. It’s easier and will get you a better idea of the game mechanics. However, if you are already well-versed with Python, continue to read ahead!

The ranking system for cards starts at Ace, the lowest-ranked card, and ends at King, the highest-ranked card.


GUI High Low Game in Python

PyGame : Hi-Lo Game

Importing PyGame

Before using any pygame’s modules, we need to import the library.

import pygame

All the PyGame functions can be accessed using pygame followed by '.' and the name of the function.


Declaring Game Constants

Every game design requires some constants that are used to specify key features of the game.

# Margins
MARGIN_LEFT = 230
MARGIN_TOP = 150

# WINDOW SIZE
WIDTH = 800
HEIGHT = 600

# COLORS
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
GRAY = (110, 110, 110)
GREEN = (0, 255, 0)
LIGHT_GREEN = (0, 120, 0)
RED = (255, 0, 0)
LIGHT_RED = (120, 0, 0)

The types and values of the constants vary for programmers. It is just a good habit to define such constants beforehand so that in case the values change, we do not have to correct them everywhere.


Initializing the Game Modules

To make use of the PyGame modules, we first need to initialize them by:

# Initializing PyGame
pygame.init()

Every game is played in a certain game window, which can be altered as per the programmer’s needs. This game window needs size parameters.

# WINDOW SIZE
WIDTH = 800
HEIGHT = 600

# Setting up the screen and background
screen = pygame.display.set_mode((WIDTH, HEIGHT))
screen.fill(GRAY)

Using the inbuilt set_mode() function, we define the window size. One thing to keep in mind while working with PyGame is that size parameters are passed in as a tuple of two values: width and height.

After setting up the window, we set the background color using the fill() command.


Setting Up The Caption and Icon

Our game needs a title as well as an icon to represent itself.

# Setting up caption
pygame.display.set_caption("Hi-Lo Game")

# Loading image for the icon
icon = pygame.image.load('icon.jpeg')

# Setting the game icon
pygame.display.set_icon(icon)

The set_caption() function takes a String as an argument and places it as the caption. In order to set the icon, we first need to load the image using the load() function that takes in the name of the image file.

The set_icon() function sets the image as the game icon.

Note: If the image file is not present in the same directory as the python game file, then we need to add the relative path along with the name of the image.


Defining Game Fonts

Before rendering text on the screen, we need to define certain fonts.

# Types of fonts to be used
small_font = pygame.font.Font(None, 32)
large_font = pygame.font.Font(None, 50)

The Font() function takes two arguments: the font-type, (None for default font), and the font-size.


Set Up the Text for Game Buttons

There are two buttons in our game: High and Low. Placing text for button requires multiple steps:

  1. Render font to the text
  2. Get the rectangular covering of the text
  3. Place the rectangle on the screen
# Hign and Low Game Buttons
high_button = large_font.render("HIGH", True, WHITE)

# Gets_rectangular covering of text
high_button_rect = high_button.get_rect()

# Places the text
high_button_rect.center = (280, 400)

low_button = large_font.render("LOW", True, WHITE)
low_button_rect = low_button.get_rect()
low_button_rect.center = (520, 400)

The render() function takes in the following parameters:

  • The text – “HIGH”
  • Apply anti-aliasing for smooth edges for text? – True
  • The text color – WHITE

The get_rect() function returns the rectangular covering of the provided text.

The next line specifies the position of the center of the rectangular covering, thereby placing the text.


Define our deck of cards

To define our deck of cards, we first have to define an individual card. We will take the help of Python classes for this task.

# Card class definition
class Card:
	def __init__(self, suit_type, value):
		self.suit_type = suit_type
		self.value = value

Any card has two characteristics: The type of suit and its face value. Moving on to the card definitions, we use three defining data structures:

# The type of suit
suits = ["Spades", "Hearts", "Clubs", "Diamonds"]

# The type of card
cards = ["A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"]

# The card value
cards_values = {"A": 1, "2":2, "3":3, "4":4, "5":5, "6":6, "7":7, "8":8, "9":9, "10":10, "J":11, "Q":12, "K":13}

The storing of these cards is done inside a list of Python objects.

# The deck of cards - List of Objects
deck = []

# Loop for every type of suit
for suit in suits:

	# Loop for every type of card in a suit
	for card in cards:

		# Adding the card to the deck
		deck.append(Card(suit, card))

Set up card images

Similar to the icon image, we first need to load the card images before rendering them on the game surface. For this purpose, we need an image for every card in the deck. Thankfully, it can be easily obtained from the internet.

This is what the cards look like:

Hi Lo Pygame Cards
The images for playing cards

As we can see the naming convention is a necessity since the cards are to be loaded when the Python script picks it up from the deck. The naming convention is simple: Card value followed by the first letter of the suit.

# Load the card image
prev_card = pygame.image.load(r'./cards/card_cover.png')

# Scale the loaded image 
prev_card = pygame.transform.scale(prev_card , (100,160))

# Choose the starting card from the deck
current_card = random.choice(deck)

# Keep choosing until it is not the highest or lowest
while current_card.value == "A" or current_card.value == "K":
	current_card = random.choice(deck)

# Load the card image	
cur_card =  pygame.image.load(r'./cards/' + current_card.value + current_card.suit_type[0] + '.png')

# Scale the loaded card image
cur_card = pygame.transform.scale(cur_card , (100,160))

# Remove the card from the deck
deck.remove(current_card)

# Loading the card image
next_card =  pygame.image.load(r'./cards/card_cover.png')

# Scaling the loaded image
next_card = pygame.transform.scale(next_card , (100,160))

Placing a raw image file might cover the entire screen, therefore, we need to scale the image according to the screen’s width and height. In PyGame, it is done by scale() function that takes an input image and the target size for transformation.

According to the rules of the Hi-Lo Game, the starting card can not be the highest or the lowest card, that is Ace or King respectively. We run a loop, until the card picked from the deck is neither one of them.

After the card is picked, we need to load that specific card image to display on the screen. This is done by load() function, that takes in the relative path followed by the name of the image.


Declaring the Game Variables

There are few game variables that are necessary for the game:

# Number of chances left
chances = 3

# The current score
score = 0

# User's choice initialized
choice = -1

# Used to stop game functioning, if True
over = False

The main focus in the above variables is the over variable that is used to stop the functioning of the game, for example, the button-clicks.


The Game Loop

The Game Loop is the forever running part of the code, that takes care of maintaining the game window, its components, and the game logic as well.

# The GAME LOOP
while True:

	# Tracking the mouse movements
	mouse = pygame.mouse.get_pos()

The first agenda of the game loop is tracking the mouse movements. This comes in handy for recognizing the position of mouse-clicks, and other things.

The get_pos() function returns a Python tuple of the position of the mouse on the screen as (X-axis, Y-axis).


Handling PyGame Events

The most important part of PyGame development is handling the events that occur inside the game window.

PyGame registers every events occurring into a list of Event objects. We will go through each Event object to handle it.

# Loop events occuring inside the game window 
for event in pygame.event.get():

	# Qutting event
	if event.type == pygame.QUIT:
		pygame.quit()
		quit()

	# Left-mouse clicked event	
	if not over and event.type == pygame.MOUSEBUTTONDOWN:

		# Clicked on the High Button 
		if 220 <= mouse[0] <= 220+125 and 370 <= mouse[1] <= 370+60: 
			choice = 1

		# Clicked on the Low Button	
		if 460 <= mouse[0] <= 460+120 and 370 <= mouse[1] <= 370+60:
			choice = 0

We check the type of Events and do the required task. It must be noted that, before quitting the python code, we quit the PyGame modules.


The Game Logic

The game logic involves:

  • Place the current card into the place of the previous card.
  • Choose a new card from the deck.
  • Remove the chosen card from the deck.
  • Check if the new card is higher or lower.
  • If a lower card, then reduce the chances left.
  • If a higher card, then increase the score.
  • Reset the player choice
# If a valid choice, the game logic	
if choice != -1:	

	# Change current card to previous
	previous_card = current_card
	prev_card = pygame.image.load(r'./cards/' + previous_card.value + previous_card.suit_type[0] + '.png')
	prev_card = pygame.transform.scale(prev_card , (100,160))
	
	# Set up the current card
	current_card = random.choice(deck)
	deck.remove(current_card)

	cur_card =  pygame.image.load(r'./cards/' + current_card.value + current_card.suit_type[0] + '.png')
	cur_card = pygame.transform.scale(cur_card , (100,160))

	# Check the result, that is, High or Low
	if cards_values[current_card.value] > cards_values[previous_card.value]:
		result = 1
	elif cards_values[current_card.value] < cards_values[previous_card.value]:
		result = 0
	else:
		result = -1	 	

	# Manage the game variables
	if result == -1:
		continue
	elif result == choice:
		score = score + 1
	else:
		chances = chances - 1		

	# End the game if chances are finished
	if chances == 0:
		over = True	

	# Reset the choice
	choice = -1	

Button animation

Using the tracked mouse movements, we can create a button animation whenever the mouse hovers over the button.

# Manage the button hovering animation
if 220 <= mouse[0] <= 220+125 and 370 <= mouse[1] <= 370+60: 
	pygame.draw.rect(screen,LIGHT_GREEN,[220,370,125,60])  
else: 
	pygame.draw.rect(screen,GREEN,[220,370,125,60]) 

if 460 <= mouse[0] <= 460+120 and 370 <= mouse[1] <= 370+60: 
	pygame.draw.rect(screen,LIGHT_RED,[460,370,120,60]) 
else: 
	pygame.draw.rect(screen,RED,[460,370,120,60]) 

In this code snippet, we first check whether the mouse position lies inside the button. If it does, then we draw a rectangle on the screen with a lighter color than the original button color, otherwise the original button color.

The pygame.draw.rect() function here takes three parameters, the display surface (our game window), the color of the rectangle, the dimensions of the box [starting x-coordinate, starting y-coordinate, width, height].


Display the scoreboard

We need to display a scoreboard with the current score and the number of chances left.

# Displaying scoreboard
pygame.draw.rect(screen, WHITE, [270, 40, 255, 90])
score_text = small_font.render("Score = "+str(score), True, BLACK)
score_text_rect = score_text.get_rect()
score_text_rect.center = (WIDTH//2, 70)


chances_text = small_font.render("Chances = "+str(chances), True, BLACK)
chances_text_rect = chances_text.get_rect()
chances_text_rect.center = (WIDTH//2, 100)	

We use the similar text rendering as we did for the button text.


Set up the entire display

After all the display components are initialized, we can finally place them on our game window using the blit() function.

# Setting up all the buttons, images and texts on the screen
screen.blit(high_button, high_button_rect)
screen.blit(low_button, low_button_rect)
screen.blit(score_text, score_text_rect)
screen.blit(chances_text, chances_text_rect)
screen.blit(prev_card, (MARGIN_LEFT,MARGIN_TOP))
screen.blit(cur_card, (MARGIN_LEFT+120, MARGIN_TOP))
screen.blit(next_card, (MARGIN_LEFT+240, MARGIN_TOP))	

The blit() function takes in the game object like image or text, and the position of its placement.


Manage the end game

In the game logic, when the chances are finished, the over variable is changed to True. Its effect is shown here.

# If the game is finished, display the final score
if over == True:
	pygame.draw.rect(screen, WHITE, [270, 40, 255, 90])
	score_text = small_font.render("Final Score = "+str(score), True, BLACK)
	score_text_rect = score_text.get_rect()
	score_text_rect.center = (WIDTH//2, 85)
	screen.blit(score_text, score_text_rect)

After the game has ended, we display the final score in the scoreboard.


Updating the game display

The final thing that is to be done, is updating the game display at the end of the game loop.

# Update the display after each game loop
pygame.display.update()

The Complete Code

import pygame
import random

# Card class definition
class Card:
	def __init__(self, suit_type, value):
		self.suit_type = suit_type
		self.value = value

if __name__ == '__main__':

	# Margins
	MARGIN_LEFT = 230
	MARGIN_TOP = 150

	# WINDOW SIZE
	WIDTH = 800
	HEIGHT = 600

	# COLORS
	BLACK = (0, 0, 0)
	WHITE = (255, 255, 255)
	GRAY = (110, 110, 110)
	GREEN = (0, 255, 0)
	LIGHT_GREEN = (0, 120, 0)
	RED = (255, 0, 0)
	LIGHT_RED = (120, 0, 0)


	# The type of suit
	suits = ["Spades", "Hearts", "Clubs", "Diamonds"]

	# The type of card
	cards = ["A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"]

	# The card value
	cards_values = {"A": 1, "2":2, "3":3, "4":4, "5":5, "6":6, "7":7, "8":8, "9":9, "10":10, "J":11, "Q":12, "K":13}

	# The deck of cards - List of Objects
	deck = []

	# Loop for every type of suit
	for suit in suits:

		# Loop for every type of card in a suit
		for card in cards:

			# Adding the card to the deck
			deck.append(Card(suit, card))

	# Initializing PyGame
	pygame.init()


	# Setting up the screen and background
	screen = pygame.display.set_mode((WIDTH, HEIGHT))
	screen.fill(GRAY)

	# Setting up caption
	pygame.display.set_caption("Hi-Lo Game")

	# Loading image for the icon
	icon = pygame.image.load('icon.jpeg')

	# Setting the game icon
	pygame.display.set_icon(icon)

	# Types of fonts to be used
	small_font = pygame.font.Font(None, 32)
	large_font = pygame.font.Font(None, 50)

	# Hign and Low Game Buttons
	high_button = large_font.render("HIGH", True, WHITE)

	# Gets_rectangular covering of text
	high_button_rect = high_button.get_rect()

	# Places the text
	high_button_rect.center = (280, 400)

	low_button = large_font.render("LOW", True, WHITE)
	low_button_rect = low_button.get_rect()
	low_button_rect.center = (520, 400)
	
	# Load the card image
	prev_card = pygame.image.load(r'./cards/card_cover.png')

	# Scale the loaded image 
	prev_card = pygame.transform.scale(prev_card , (100,160))

	# Choose the starting card from the deck
	current_card = random.choice(deck)

	# Keep choosing until it is not the highest or lowest
	while current_card.value == "A" or current_card.value == "K":
		current_card = random.choice(deck)

	# Load the card image	
	cur_card =  pygame.image.load(r'./cards/' + current_card.value + current_card.suit_type[0] + '.png')

	# Scale the loaded card image
	cur_card = pygame.transform.scale(cur_card , (100,160))

	# Remove the card from the deck
	deck.remove(current_card)

	# Loading the card image
	next_card =  pygame.image.load(r'./cards/card_cover.png')

	# Scaling the loaded image
	next_card = pygame.transform.scale(next_card , (100,160))

	# Number of chances left
	chances = 3

	# The current score
	score = 0

	# User's choice initialized
	choice = -1

	# Used to stop game functioning, if True
	over = False

	# The GAME LOOP
	while True:

		# Tracking the mouse movements
		mouse = pygame.mouse.get_pos()

		# Loop events occuring inside the game window 
		for event in pygame.event.get():

			# Qutting event
			if event.type == pygame.QUIT:
				pygame.quit()
				quit()

			# Left-mouse clicked event	
			if not over and event.type == pygame.MOUSEBUTTONDOWN:

				# Clicked on the High Button 
				if 220 <= mouse[0] <= 220+125 and 370 <= mouse[1] <= 370+60: 
					choice = 1

				# Clicked on the Low Button	
				if 460 <= mouse[0] <= 460+120 and 370 <= mouse[1] <= 370+60:
					choice = 0

				# Finish the game if the deck is finished
				if len(deck) == 1:
					over = True	

				# If a valid choice, the game logic	
				if choice != -1:	

					# Change current card to previous
					previous_card = current_card
					prev_card = pygame.image.load(r'./cards/' + previous_card.value + previous_card.suit_type[0] + '.png')
					prev_card = pygame.transform.scale(prev_card , (100,160))
					
					# Set up the current card
					current_card = random.choice(deck)
					deck.remove(current_card)

					cur_card =  pygame.image.load(r'./cards/' + current_card.value + current_card.suit_type[0] + '.png')
					cur_card = pygame.transform.scale(cur_card , (100,160))

					# Check the result, that is, High or Low
					if cards_values[current_card.value] > cards_values[previous_card.value]:
						result = 1
					elif cards_values[current_card.value] < cards_values[previous_card.value]:
						result = 0
					else:
						result = -1	 	

					# Manage the game variables
					if result == -1:
						continue
					elif result == choice:
						score = score + 1
					else:
						chances = chances - 1		

					# End the game if chances are finished
					if chances == 0:
						over = True	

					# Reset the choice
					choice = -1	
		
		# Manage the button hovering animation
		if 220 <= mouse[0] <= 220+125 and 370 <= mouse[1] <= 370+60: 
			pygame.draw.rect(screen,LIGHT_GREEN,[220,370,125,60])  
		else: 
			pygame.draw.rect(screen,GREEN,[220,370,125,60]) 

		if 460 <= mouse[0] <= 460+120 and 370 <= mouse[1] <= 370+60: 
			pygame.draw.rect(screen,LIGHT_RED,[460,370,120,60]) 
		else: 
			pygame.draw.rect(screen,RED,[460,370,120,60]) 

		# Displaying scoreboard
		pygame.draw.rect(screen, WHITE, [270, 40, 255, 90])
		score_text = small_font.render("Score = "+str(score), True, BLACK)
		score_text_rect = score_text.get_rect()
		score_text_rect.center = (WIDTH//2, 70)


		chances_text = small_font.render("Chances = "+str(chances), True, BLACK)
		chances_text_rect = chances_text.get_rect()
		chances_text_rect.center = (WIDTH//2, 100)	
		
		# Setting up all the buttons, images and texts on the screen
		screen.blit(high_button, high_button_rect)
		screen.blit(low_button, low_button_rect)
		screen.blit(score_text, score_text_rect)
		screen.blit(chances_text, chances_text_rect)
		screen.blit(prev_card, (MARGIN_LEFT,MARGIN_TOP))
		screen.blit(cur_card, (MARGIN_LEFT+120, MARGIN_TOP))
		screen.blit(next_card, (MARGIN_LEFT+240, MARGIN_TOP))	


		# If the game is finished, display the final score
		if over == True:
			pygame.draw.rect(screen, WHITE, [270, 40, 255, 90])
			score_text = small_font.render("Final Score = "+str(score), True, BLACK)
			score_text_rect = score_text.get_rect()
			score_text_rect.center = (WIDTH//2, 85)
			screen.blit(score_text, score_text_rect)

		# Update the display after each game loop
		pygame.display.update()

Conclusion

Creating our own Hi-Lo game using PyGame seems like a easy task. We hope this tutorial forms the basis of reader’s future PyGame trials and adventures.

Thank you for reading. Feel free to comment below for queries or suggestions.