Page 1 of 1

A Highscore Module for Pygame

#1 DK3250  Icon User is offline

  • Pythonian
  • member icon

Reputation: 287
  • View blog
  • Posts: 908
  • Joined: 27-December 13

Posted 04 August 2016 - 11:47 AM

Attached File  score_file.txt (31bytes)
Number of downloads: 108

A Highscore Module for Pygame

This tutorial is supplemental to Modules in Python posted January 2009 by atik97.

So, you have made this fantastic game using Pygame. It works well, You are proud and you want to show it to your friends.
But, your game lacks this little 'extra' that will inspire for competition and continued play; you need a high score module.

In Python the word 'module' is used for a piece of code that you import to your code for direct use. Probably, you have used 'import random' or 'import Pygame'.
You can import specific part of a module with the 'from' command, for example: 'from math import cos' – you can simplify the notation using the 'as' keyword: 'from random import randint as ri'.
Note: If you import the full module, you'll need to call the module functions using dot notation; i.e. <module name>.<module function>() - Example: random.randint(1, 100)

Basically you don't need to know how the module operates, but in this tutorial we will work behind the curtain and see how the module is build. This serves two purposes: Hopefully it will add to your general understanding of Python/Pygame and secondly it should allow you to modify the module code to better serve you needs (specifically size, colors, etc.)

When you import a module, your code needs to know where to find the module. The official Python library modules has default locations known by the Python core.
Homebuild modules, however, are generally better placed in the same directory as your code.
As you get more advanced, you can make your own module library and place it in site-packages, but this is outside the scope of this tutorial.

In this tutorial we want to build a highscore module. Naturally, we need a file to save previous highscore data (score and name – maybe even date, time, ..). The name of this file is up to you, here I use the name 'score_file.txt'. The score_file should be located in the same directory as your game.

All in all, you'll end up with a game directory holding (minimum) three files: The game file, the highscore module and the the score_file.

Let's start with a very basic example. Below is shown two files, first the 'main code' and then the 'module code'

# An example of a basic main program calling the module code
import pygame
from Module import add_rectangle as module
pygame.init()

X = 600  # window width
Y = 400  # window height
WHITE = (255, 255, 255)

screen = pygame.display.set_mode((X, Y))
screen.fill(WHITE)

module(screen, X, Y)  # call of module code

pygame.display.flip()




# An example of a module code
import pygame
pygame.init()

GREY = (200, 200, 200)

def add_rectangle(display, X, Y):
    surf = pygame.surface.Surface((X//2, Y//2))
    surf.fill(GREY)
    display.blit(surf, [X//4, Y//4])



In the main code, two lines are of special interest:
Line 3 is where the module is imported, for demonstration purpose I used the 'from x import y as z' form.
Line 13 executes the call of the module code; as the Pygame display name and size may vary, the actual name and size parameters are included in the call. Try experimenting with the size parameters (line 6 and 7).

The module code is very simple, it places a grey rectangle centered on the white Pygame window.

A Highscore Module

Now, let's look on what is required to build a highscore module.
I suggest the following step:
1. Read the present score list from a file (score_file)
2. Sort the score list
3. If user beats the highscore, - show a special welcome
4. Ask the user of his/her name
5. Write (append) user data to the file
6. Show the (updated) top-10 list

The score_file used here holds all data ever inputted; only top-10 is shown but data is taken for other type of statistics and analysis.

Actually step 1 and 5 are associated with Python file handling.
Step 2 and 3 are standard Python issues and will only be commented on in the code.
Step 4 (keyboard interaction with Pygame) is somewhat challenging and will be detailed described.

The core of our highscore module may look like this:

def highscore():
    read_from_file_and_find_highscore()
    special_welcome()
    enterbox()  # get user name
    write_to_file()
    show_top10()


Actually, I include the special welcome as a parameter when calling the enterbox, so the real code becomes:

def highscore(screen, file_name, your_points):
    high_name, high_score = read_from_file_and_find_highscore(file_name)

    if your_points > high_score:
        your_name = enterbox(screen, "YOU HAVE BEATEN THE HIGHSCORE - What is your name?")
    
    elif your_points == high_score:
        your_name = enterbox(screen, "YOU HAVE SAME AS HIGHSCORE - What is your name?")
    
    elif your_points < high_score:
        st1 = "Highscore is "
        st2 = " made by "
        st3 = "   What is your name?"
        txt = st1+str(high_score)+st2+high_name+st3
        your_name = enterbox(screen, txt)

    if your_name == None or len(your_name) == 0:
        return  # do not update the file unless a name is given

    write_to_file(file_name, your_name, your_points)
    show_top10(screen, file_name)
    return


The three parameters,: screen, file_name and your_points are transferred from the main program. screen is the Pygame window (as above), file_name is here score.file.txt, your_points is the value obtained in the game.
Data in the score_file.txt has the format of {<name>, <points> /n}

Now, we just need to write the remaining 4 functions.

The two file handling functions are quite straightforward, they look like this:

def read_from_file_and_find_highscore(file_name):
    file = open(file_name, 'r')
    lines=file.readlines()
    file.close
       
    high_score = 0
    
    for line in lines:
        name, score = line.strip().split(",")
        score = int(score)

        if score > high_score:
            high_score = score
            high_name = name

    return high_name, high_score


def write_to_file(file_name, your_name, points):
    score_file = open(file_name, 'a')
    print (your_name+",", points, file=score_file)
    score_file.close()
    


Using Python, all files are read as text, even if numbers are written to the file. So, upon reading the file, I first strip() (get rid of tabs, line shift, etc.) and then split into two string variables, - the latter one is converted to integer.

The show_top10() function runs after the user has inputted his/her data.
The show_top10() function reads and sorts the file; this time, however, we save the sorted data for later presentation.
The size of the box (bx and by right after the function definition) are here fixed to fit with the 10 line output. If you need a flexible size, the values can be transferred from the main program, - just like I did in the first example. Further comments are located in the code:

def show_top10(screen, file_name):
    bx = 480  # x-size of box
    by = 400  # y-size of box
    
    file = open(file_name, 'r')
    lines=file.readlines()
       
    all_score = []
    for line in lines:
        sep = line.index(',')
        name = line[:sep]
        score = int(line[sep+1:-1])
        all_score.append((score, name))
    file.close
    all_score.sort(reverse=True)  # sort from largest to smallest
    best = all_score[:10]  # top 10 values

    # make the presentation box
    box = pygame.surface.Surface((bx, by)) 
    box.fill(GREY)
    pygame.draw.rect(box, WHITE, (50, 12, bx-100, 35), 0)
    pygame.draw.rect(box, WHITE, (50, by-60, bx-100, 42), 0)
    pygame.draw.rect(box, BLACK, (0, 0, bx, by), 1)
    txt_surf = font.render("HIGHSCORE", True, BLACK)  # headline
    txt_rect = txt_surf.get_rect(center=(bx//2, 30))
    box.blit(txt_surf, txt_rect)
    txt_surf = font.render("Press ENTER to continue", True, BLACK)  # bottom line
    txt_rect = txt_surf.get_rect(center=(bx//2, 360))
    box.blit(txt_surf, txt_rect)

    # write the top-10 data to the box
    for i, entry in enumerate(best):
        txt_surf = font.render(entry[1] + "  " + str(entry[0]), True, BLACK)
        txt_rect = txt_surf.get_rect(center=(bx//2, 30*i+60))
        box.blit(txt_surf, txt_rect)
    
    screen.blit(box, (0, 0))
    pygame.display.flip()
    
    while True:  # wait for user to acknowledge and return
        for event in pygame.event.get():
            if event.type == pygame.KEYDOWN and event.key in [pygame.K_RETURN, pygame.K_KP_ENTER]:
                return
        pygame.time.wait(20)


Finally, the enterbox() function need a few words. The enterbox() function uses two local functions: blink() and show_name(). The use of local functions are somewhat disputed, but I find it reasonable here as the functions will never be uses outside the scope of enterbox().
The first part of enterbox() is simply building the box; again, here, using hard sized values.
More interesting is the input-loop.
In Pygame every key is associated with a number: a-z takes up the values 97-122, the two enter keys are 13 and 271, backspace is 8.
Now, what if you want to input CAPITAL letters? Well you will use one or more of the modifiers (Shift, Ctrl, Alt). As single input they are numbered above 300. We, however, need to see then in combination with a normal key.
Using the Pygame function pygame.key.get_mods() returns a single integer representing a bitmask of all the modifier keys being held. Using bitwise operators you can test if specific shift keys are pressed. This is quite tricky and took me quite some time to understand and implement. You can see it in action in line 124 of the code. As the capital letters ASCII value are shifted by -32 compared to lower case letters, this value (32) is subtracted when a shift (upper case) is detected.

The user input is initialized as an empty string, and grows letter by letter until a return is detected.
The blink() function indicates that no input is given.
The show_name() function updates the screen as letters are detected.

Putting it all together, the module look like this:

import pygame, sys
pygame.init()

GREY = (150, 150, 150)
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
font = pygame.font.SysFont("Arial", 16)


def read_from_file_and_find_highscore(file_name):
    file = open(file_name, 'r')
    lines=file.readlines()
    file.close
       
    high_score = 0
    
    for line in lines:
        name, score = line.strip().split(",")
        score = int(score)

        if score > high_score:
            high_score = score
            high_name = name

    return high_name, high_score


def write_to_file(file_name, your_name, points):
    score_file = open(file_name, 'a')
    print (your_name+",", points, file=score_file)
    score_file.close()
    

def show_top10(screen, file_name):
    bx = 480  # x-size of box
    by = 400  # y-size of box
    
    file = open(file_name, 'r')
    lines=file.readlines()
       
    all_score = []
    for line in lines:
        sep = line.index(',')
        name = line[:sep]
        score = int(line[sep+1:-1])
        all_score.append((score, name))
    file.close
    all_score.sort(reverse=True)  # sort from largest to smallest
    best = all_score[:10]  # top 10 values

    # make the presentation box
    box = pygame.surface.Surface((bx, by)) 
    box.fill(GREY)
    pygame.draw.rect(box, WHITE, (50, 12, bx-100, 35), 0)
    pygame.draw.rect(box, WHITE, (50, by-60, bx-100, 42), 0)
    pygame.draw.rect(box, BLACK, (0, 0, bx, by), 1)
    txt_surf = font.render("HIGHSCORE", True, BLACK)  # headline
    txt_rect = txt_surf.get_rect(center=(bx//2, 30))
    box.blit(txt_surf, txt_rect)
    txt_surf = font.render("Press ENTER to continue", True, BLACK)  # bottom line
    txt_rect = txt_surf.get_rect(center=(bx//2, 360))
    box.blit(txt_surf, txt_rect)

    # write the top-10 data to the box
    for i, entry in enumerate(best):
        txt_surf = font.render(entry[1] + "  " + str(entry[0]), True, BLACK)
        txt_rect = txt_surf.get_rect(center=(bx//2, 30*i+60))
        box.blit(txt_surf, txt_rect)
    
    screen.blit(box, (0, 0))
    pygame.display.flip()
    
    while True:  # wait for user to acknowledge and return
        for event in pygame.event.get():
            if event.type == pygame.KEYDOWN and event.key in [pygame.K_RETURN, pygame.K_KP_ENTER]:
                return
        pygame.time.wait(20)
    

def enterbox(screen, txt):

    def blink(screen):
        for color in [GREY, WHITE]:
            pygame.draw.circle(box, color, (bx//2, int(by*0.7)), 7, 0)
            screen.blit(box, (0, by//2))
            pygame.display.flip()
            pygame.time.wait(300)

    def show_name(screen, name):
        pygame.draw.rect(box, WHITE, (50, 60, bx-100, 20), 0)
        txt_surf = font.render(name, True, BLACK)
        txt_rect = txt_surf.get_rect(center=(bx//2, int(by*0.7)))
        box.blit(txt_surf, txt_rect)
        screen.blit(box, (0, by//2))
        pygame.display.flip()
        
    bx = 480
    by = 100

    # make box
    box = pygame.surface.Surface((bx, by))
    box.fill(GREY)
    pygame.draw.rect(box, BLACK, (0, 0, bx, by), 1)
    txt_surf = font.render(txt, True, BLACK)
    txt_rect = txt_surf.get_rect(center=(bx//2, int(by*0.3)))
    box.blit(txt_surf, txt_rect)

    name = ""
    show_name(screen, name)

    # the input-loop
    while True:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()
            elif event.type == pygame.KEYDOWN:
                inkey = event.key
                if inkey in [13, 271]:  # enter/return key
                    return name
                elif inkey == 8:  # backspace key
                    name = name[:-1]
                elif inkey <= 300:
                    if pygame.key.get_mods() & pygame.KMOD_SHIFT and 122 >= inkey >= 97:
                        inkey -= 32  # handles CAPITAL input
                    name += chr(inkey)

        if name == "":
            blink(screen)
        show_name(screen, name)


def highscore(screen, file_name, your_points):
    high_name, high_score = read_from_file_and_find_highscore(file_name)

    if your_points > high_score:
        your_name = enterbox(screen, "YOU HAVE BEATEN THE HIGHSCORE - What is your name?")
    
    elif your_points == high_score:
        your_name = enterbox(screen, "YOU HAVE SAME AS HIGHSCORE - What is your name?")
    
    elif your_points < high_score:
        st1 = "Highscore is "
        st2 = " made by "
        st3 = "   What is your name?"
        txt = st1+str(high_score)+st2+high_name+st3
        your_name = enterbox(screen, txt)

    if your_name == None or len(your_name) == 0:
        return  # do not update the file unless a name is given

    write_to_file(file_name, your_name, your_points)
    show_top10(screen, file_name)
    return



As the 'screen' is defined in the main program, this variable needs to be passed around between the functions where the display is affected.
Please remember to add a score_file.txt with minimum one line of data in the format {<name>, <points> /n}. I'll include one as attachment, but your firewall may prohibit a download.
The code can be expanded to making the score_file.txt if it dosn't exist, - I'll leave this to you.

All we now need is a program to call our module. In real life this is your program after the user has finalized the game. Here I include a minimalistic code:

import pygame
from High_Score_Module import highscore
pygame.init()

X = 480
Y = 400
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)

font = pygame.font.SysFont("Arial", 16)
screen = pygame.display.set_mode((X, Y))
screen.fill(WHITE)

my_score = 12345

highscore(screen, 'score_file.txt', my_score)

txt_surf = font.render("Ready to continue...", True, BLACK)
txt_rect = txt_surf.get_rect(center=(X//2, Y//2))
screen.blit(txt_surf, txt_rect)
pygame.display.flip()


Post Scriptum:
Throughout this tutorial, I have assumed that high values are better than low ones. Some games, however, run on time and smaller time values are better than larger ones. I feel confident leaving to you to modify the highscore function (and the show_top10 function) into a time_score function based on data in ascending order.

As for my other tutorials:
Please leave a feedback – just one line will be fine: What you liked and/or what you disliked.

I'll be happy to answer any questions...

Is This A Good Question/Topic? 0
  • +

Page 1 of 1