1

I made a game using pygame module of Python. I thought to make an executable installer of the file so that anyone could play that game without even having to install python or pygame. I used the module cx-freeze to create an executable for my game file. I stored the code for making an executable installer in file called setup.py and saved it in the same directory as of the game file and all other files which are required for the game like images, sounds, etc. When I executed the command python setup.py build to create an executable I started to get warnings and at the end this error occurred:

Traceback (most recent call last):
  File "setup.py", line 5, in <module>
    cx_Freeze.setup(
  File "C:\Users\Armaan Barak\AppData\Local\Programs\Python\Python38\lib\site-packages\cx_Freeze\dist.py", line 342, in setup
    distutils.core.setup(**attrs)
  File "C:\Users\Armaan Barak\AppData\Local\Programs\Python\Python38\lib\distutils\core.py", line 148, in setup
    dist.run_commands()
  File "C:\Users\Armaan Barak\AppData\Local\Programs\Python\Python38\lib\distutils\dist.py", line 966, in run_commands
    self.run_command(cmd)
  File "C:\Users\Armaan Barak\AppData\Local\Programs\Python\Python38\lib\distutils\dist.py", line 985, in run_command
    cmd_obj.run()
  File "C:\Users\Armaan Barak\AppData\Local\Programs\Python\Python38\lib\distutils\command\build.py", line 135, in run
    self.run_command(cmd_name)
  File "C:\Users\Armaan Barak\AppData\Local\Programs\Python\Python38\lib\distutils\cmd.py", line 313, in run_command
    self.distribution.run_command(command)
  File "C:\Users\Armaan Barak\AppData\Local\Programs\Python\Python38\lib\distutils\dist.py", line 985, in run_command
    cmd_obj.run()
  File "C:\Users\Armaan Barak\AppData\Local\Programs\Python\Python38\lib\site-packages\cx_Freeze\dist.py", line 217, in run
    freezer.Freeze()
  File "C:\Users\Armaan Barak\AppData\Local\Programs\Python\Python38\lib\site-packages\cx_Freeze\freezer.py", line 645, in Freeze
    self._WriteModules(fileName, self.finder)
  File "C:\Users\Armaan Barak\AppData\Local\Programs\Python\Python38\lib\site-packages\cx_Freeze\freezer.py", line 536, in _WriteModules
    sourcePackageDir = os.path.dirname(module.file)
  File "C:\Users\Armaan Barak\AppData\Local\Programs\Python\Python38\lib\ntpath.py", line 223, in dirname
    return split(p)[0]
  File "C:\Users\Armaan Barak\AppData\Local\Programs\Python\Python38\lib\ntpath.py", line 185, in split
    p = os.fspath(p)
TypeError: expected str, bytes or os.PathLike object, not NoneType

The warning shown was:

running build
running build_exe
pygame 1.9.6
Hello from the pygame community. https://www.pygame.org/contribute.html
C:\Users\Armaan Barak\AppData\Local\Programs\Python\Python38\lib\site-packages\setuptools\distutils_patch.py:25: UserWarning: Distutils was imported before Setuptools. This usage is discouraged and may exhibit undesirable behaviors or errors. Please use Setuptools' objects directly or at least import Setuptools first.
  warnings.warn(
C:\Users\Armaan Barak\AppData\Local\Programs\Python\Python38\lib\site-packages\pygame\examples\chimp.py:32: SyntaxWarning: "is" with a literal. Did you mean "=="?
  if colorkey is -1:
C:\Users\Armaan Barak\AppData\Local\Programs\Python\Python38\lib\site-packages\pygame\tests\test_utils\__init__.py:162: SyntaxWarning: "is not" with a literal. Did you mean "!="?
  (rect.left is not 0 and [(rect.left-1, rect.top)] or []) +
copying c:\users\armaan barak\appdata\local\programs\python\python38\python38.dll -> build\exe.win-amd64-3.8\python38.dll
copying c:\users\armaan barak\appdata\local\programs\python\python38\vcruntime140.dll -> build\exe.win-amd64-3.8\vcruntime140.dll
copying c:\users\armaan barak\appdata\local\programs\python\python38\python3.dll -> build\exe.win-amd64-3.8\python3.dll
copying C:\Users\Armaan Barak\AppData\Local\Programs\Python\Python38\lib\site-packages\cx_Freeze\bases\Console.exe -> build\exe.win-amd64-3.8\SpaceInvader2.exe
*** WARNING *** unable to create version resource
version must be specified

Code in the Setup file:

import cx_Freeze

executable = [cx_Freeze.Executable('SpaceInvader2.py')]

cx_Freeze.setup(
    name="Space Invaders 2",
    options={
        "build_exe": {
            "packages": [
                "pygame",
                "random",
                "math",
                "pygame.mixer",
                "time"
            ],
            "include_files": [
                "spaceship.png",
                "space.png",
                "home_screen.png",
                "ammunition.png",
                "alien.png",
                "alien2.png",
                "alien3.png",
                "alien4.png",
                "player.png",
                "settings.png",
                "controls.png",
                "game_over.png",
                "background.wav",
                "laser.wav",
                "explosion.wav"
            ]
        }
    },
    executables=executable
)

Code in the game file:

import pygame 
import random
import math
from pygame import mixer
import time

# Game Screen Initialization
pygame.init()
screen = pygame.display.set_mode((800, 600))

# Loading Images
icon = pygame.image.load('spaceship.png')
game_background = pygame.image.load('space.png')
home_screen_img = pygame.image.load('home_screen.png')
bullet = pygame.image.load('ammunition.png')
alien_img = pygame.image.load('alien.png')
alien_img2 = pygame.image.load('alien2.png')
alien_img3 = pygame.image.load('alien3.png')
alien_img4 = pygame.image.load('alien4.png')
player_img = pygame.image.load('player.png')
settings_img = pygame.image.load('settings.png')
controls_img = pygame.image.load('controls.png')
game_over_img = pygame.image.load('game_over.png')

# Loading sounds
mixer.music.load('background.wav')
mixer.music.play(-1)

bullet_sound = mixer.Sound('laser.wav')
explosion_sound = mixer.Sound('explosion.wav')

# Title and Icon
pygame.display.set_caption('Space Invader')
pygame.display.set_icon(icon)

# Bullet variables
bullet_x = 0
bullet_y = 480
bullet_x_change = 0
bullet_y_change = 4
bullet_state = 'ready'

# Player variables
player_x = 370
player_y = 480
player_x_change = 0

# Enemy variables
enemy_img = []
enemy_x = []
enemy_y = []
enemy_x_change = []
enemy_y_change = []
num_of_enemies = 13

# Creating 10 enemy objects
for i in range(num_of_enemies):

    enemy_img.append(random.choice([alien_img, alien_img2, alien_img3, alien_img4]))
    enemy_x.append(random.randint(0, 735))
    enemy_y.append(random.randint(50, 200))
    enemy_x_change.append(2)
    enemy_y_change.append(40)

# Score variables
score_value = 0
font = pygame.font.Font('freesansbold.ttf', 24)
over_font = pygame.font.Font('freesansbold.ttf', 80)

text_X = 10
text_y = 10

# Game Functions
def show_score(x, y):
    score = font.render('Score: ' + str(score_value), True, (165,240,67))
    screen.blit(score, (x, y))

def game_over_text():
    over_text = over_font.render('GAME OVER!', True, (255, 0, 0))
    screen.blit(over_text, (160, 250))

def player(x, y):
    screen.blit(player_img, (x, y))

def enemy(x, y, i):
    screen.blit(enemy_img[i], (x, y))

def fire_bullet(x, y):
    global bullet_state
    bullet_state = 'fire'
    screen.blit(bullet, (x + 16, y + 10))

def is_collision(enemyX, enemyY, bulletX, bulletY):
    distance = math.sqrt((math.pow(enemyX - bulletX, 2)) + (math.pow(enemyY-bulletY, 2)))
    return distance < 27

# Button colors and fonts
color = (0, 0, 0)
color_light = (100,149,237)
color_dark = (138,43,226)

# Fonts
game_name_font = pygame.font.SysFont("Chiller", 140)
btn_font = pygame.font.SysFont('Corbel Bold', 34)
random_font = pygame.font.SysFont('Arial', 60)
info_btn = pygame.font.SysFont('Century Gothic', 24)
controls_font = pygame.font.SysFont('Consolas', 15)
credits_btn = pygame.font.SysFont('Consolas', 18)

# Texts
game_text = game_name_font.render('Space Invaders!', True, (255,255,0))
play_text = btn_font.render('Play Game', True, color)
controls_text = btn_font.render('Controls', True, color)
credits_text = btn_font.render('Credits', True, color)
settings_text = btn_font.render('Settings', True, color)
quit_text = btn_font.render('Quit Game', True, color)
version_text = btn_font.render('Version 2.8', True, (255, 255, 255))
back_text = btn_font.render('Go Back', True, color)
under_development_text = random_font.render('This area is under development', True, (0, 0, 0))

# Controls text
controls_1 = controls_font.render('Left Arrow   :   Spaceship moves Left', True, (124, 252, 0))
controls_2 = controls_font.render('Right Arrow  :  Spaceship moves Right', True, (124, 252, 0))
controls_3 = controls_font.render('Spacebar     :         Shoots bullets', True, (124, 252, 0))

# Credits text
credits_line_1 = credits_btn.render("The games first version was made while learning from the pygame video", True, (255, 255, 255))
credits_line_2 = credits_btn.render("of FreeCodeCamp.org. The successive updates in version 1 and 2 are", True, (255, 255, 255))
credits_line_3 = credits_btn.render("made by Armaan Barak. No external code was copied. But help was taken", True, (255, 255, 255))
credits_line_4 = credits_btn.render("from google. Images and icons shown this game are taken from freepik.com", True, (255, 255, 255))
credits_line_5 = credits_btn.render("and flaticon.com respectively. The images were resized from", True, (255, 255, 255))
credits_line_6 = credits_btn.render("reduceimages.com and the sounds are imported from github account of", True, (255, 255, 255))
credits_line_7 = credits_btn.render("Mr. Attreya Bhatt. Direct Link to repository:", True, (255, 255, 255))
credits_line_8 = credits_btn.render("https://github.com/attreyabhatt/Space-Invaders-Pygame.", True, (255, 255, 255))
credits_line_9 = credits_btn.render("This game is free and open-source. User is independent for", True, (255, 255, 255))
credits_line_10 = credits_btn.render("manipulating code but it's encouraged that you avoid cheating.", True, (255, 255, 255))
credits_line_11 = credits_btn.render("Creator and Developer: Armaan Barak", True, (255, 255, 255))

# Game loop variables
running = True
home_screen = True
settings = False
controls = False
game_screen = False
credits_screen = False
game_over_screen = False

while running:

    # To loop through events untill event is QUIT
    for event in pygame.event.get():

        # QUIT Button
        if event.type == pygame.QUIT:
            running = False

        # Mouse key controls
        if event.type == pygame.MOUSEBUTTONDOWN:
            
            # Game is Over
            if game_over_screen:
                
                if 250 <= mouse[0] <= 385 and 520 <= mouse[1] <= 560:
                    home_screen = True
                    settings = False
                    controls = False
                    game_screen = False
                    credits_screen = False
                    game_over_screen = False

                    for i in range(num_of_enemies):
                        enemy_y[i] = random.randint(50, 200)
                    
                    player_y = 480
                    bullet_y = 480

                if 420 <= mouse[0] <= 560 and 520 <= mouse[1] <= 560:
                    quit()

            # Play button clicked
            if 230 <= mouse[0] <= 370 and 270 <= mouse[1] <= 310:
                home_screen = False
                settings = False
                controls = False
                game_screen = True
                credits_screen = False

            # Instructions button clicked
            if 400 <= mouse[0] <= 540 and 270 <= mouse[1] <= 310:
                home_screen = False
                settings = False
                controls = True
                game_screen = False
                credits_screen = False

            # Settings button clicked
            if 230 <= mouse[0] <= 370 and 350 <= mouse[1] <= 390:
                home_screen = False
                settings = True
                controls = False
                game_screen = False
                credits_screen = False
            
            # Credits button clicked
            if 400 <= mouse[0] <= 540 and 350 <= mouse[1] <= 390:
                home_screen = False
                settings = False
                controls = False
                game_screen = False
                credits_screen = True

            # Quit button clicked
            if 315 <= mouse[0] <= 455 and 430 <= mouse[1] <= 470:
                quit()

            if settings or controls or credits_screen:

                if 50 <= mouse[0] <= 165 and 30 <= mouse[1] <= 70:
                    home_screen = True
                    settings = False
                    controls = False
                    game_screen = False
                    credits_screen = False

        # KeyBoard Controls
        if event.type == pygame.KEYDOWN:

            if event.key == pygame.K_LEFT:
                player_x_change = -3

            if event.key == pygame.K_RIGHT:
                player_x_change = 3
            
            if event.key == pygame.K_SPACE:
                if bullet_state == 'ready':
                    
                    bullet_sound.play()
                    bullet_x = player_x
                    fire_bullet(bullet_x, bullet_y)

        # Reseting player's x axis speed when left or right key is lifted
        if event.type == pygame.KEYUP:

            if event.key == pygame.K_LEFT or event.key == pygame.K_RIGHT:
               player_x_change = 0
    
    # Home Screen
    if home_screen:
        
        # Displaying main screen
        screen.blit(home_screen_img, (0,0))

        # Getting Mouse Position
        mouse = pygame.mouse.get_pos()

        # Changing color of text boxes on the basis of cursor position
        # play game text
        if 230 <= mouse[0] <= 370 and 270 <= mouse[1] <= 310:
            pygame.draw.rect(screen, color_light, [230, 270, 140, 40])
        
        else:
            pygame.draw.rect(screen, color_dark,[230,270,140,40])

        # instructions text
        if 400 <= mouse[0] <= 540 and 270 <= mouse[1] <= 310:
            pygame.draw.rect(screen, color_light, [400, 270, 140, 40])
        
        else:
            pygame.draw.rect(screen, color_dark, [400, 270, 140, 40])

        # settings text
        if 230 <= mouse[0] <= 370 and 350 <= mouse[1] <= 390:
            pygame.draw.rect(screen, color_light, [230, 350, 140, 40])
        
        else:
            pygame.draw.rect(screen, color_dark, [230, 350, 140, 40])

        # credits text
        if 400 <= mouse[0] <= 540 and 350 <= mouse[1] <= 390:
            pygame.draw.rect(screen, color_light, [400, 350, 140, 40])
        
        else:
            pygame.draw.rect(screen, color_dark, [400, 350, 140, 40])

        # quit game text
        if 315 <= mouse[0] <= 455 and 430 <= mouse[1] <= 470:
            pygame.draw.rect(screen, color_light, [315, 430, 140, 40])
        
        else:
            pygame.draw.rect(screen, color_dark, [315, 430, 140, 40])

        # Displaying text
        screen.blit(game_text, (90, 100))

        # Displaying buttons
        screen.blit(play_text, (240, 280))

        screen.blit(controls_text, (421, 280))

        screen.blit(settings_text, (253, 360))

        screen.blit(credits_text, (427, 360))

        screen.blit(quit_text, (325, 440))

        # Version Display
        screen.blit(version_text, (325, 580))

        # Updating Screen
        pygame.display.update()

    # Settings Screen
    elif settings:

        screen.blit(settings_img, (0, 0))

        mouse = pygame.mouse.get_pos()

        if 50 <= mouse[0] <= 165 and 30 <= mouse[1] <= 70:
            pygame.draw.rect(screen, color_light, [50, 30, 115, 40])
        else:
            pygame.draw.rect(screen, color_dark, [50, 30, 115, 40])

        screen.blit(back_text, (60, 40))

        screen.blit(under_development_text, (60, 250))

        pygame.display.update()
    
    # Controls Screen
    elif controls:
        
        screen.blit(controls_img, (0, 0))

        mouse = pygame.mouse.get_pos()

        if 50 <= mouse[0] <= 165 and 30 <= mouse[1] <= 70:
            pygame.draw.rect(screen, color_light, [50, 30, 115, 40])
        else:
            pygame.draw.rect(screen, color_dark, [50, 30, 115, 40])

        screen.blit(back_text, (60, 40))

        screen.blit(controls_1, (255, 240))
        screen.blit(controls_2, (255, 290))
        screen.blit(controls_3, (255, 340))

        pygame.display.update()

    # Credits Screen
    elif credits_screen:

        screen.fill((85, 107, 47))

        mouse = pygame.mouse.get_pos()

        if 50 <= mouse[0] <= 165 and 30 <= mouse[1] <= 70:
            pygame.draw.rect(screen, color_light, [50, 30, 115, 40])
        else:
            pygame.draw.rect(screen, color_dark, [50, 30, 115, 40])

        screen.blit(back_text, (60, 40))

        screen.blit(credits_line_1, (40, 150))
        screen.blit(credits_line_2, (40, 180))
        screen.blit(credits_line_3, (40, 210))
        screen.blit(credits_line_4, (40, 240))
        screen.blit(credits_line_5, (40, 270))
        screen.blit(credits_line_6, (40, 300))
        screen.blit(credits_line_7, (40, 330))
        screen.blit(credits_line_8, (40, 360))
        screen.blit(credits_line_9, (40, 390))
        screen.blit(credits_line_10, (40, 420))
        screen.blit(credits_line_11, (40, 450))

        pygame.display.update()

    # Game Screen
    else:

        # Adding background image
        screen.blit(game_background, (0,0))

        # Player Movement
        player_x += player_x_change

        if player_x <= 0:
            player_x = 0
        elif player_x >= 736:
            player_x = 736

        # Handling movement of enemies
        for i in range(num_of_enemies):

            # Game Over
            if enemy_y[i] > 450:
                
                game_over_screen = True

                # Hiding Characters
                for j in range(num_of_enemies):
                    enemy_y[j] = 2000

                player_y = -2000
                bullet_y = -4000

                screen.blit(game_over_img, (0, 0))
                
                # Back button
                if 250 <= mouse[0] <= 385 and 520 <= mouse[1] <= 560:
                    pygame.draw.rect(screen, color_light, [250, 520, 140, 40])
                else:
                    pygame.draw.rect(screen, color_dark, [250, 520, 140, 40])

                # Quit Button
                if 420 <= mouse[0] <= 560 and 520 <= mouse[1] <= 560:
                    pygame.draw.rect(screen, color_light, [420, 520, 140, 40])
                else:
                    pygame.draw.rect(screen, color_dark, [420, 520, 140, 40])

                mouse = pygame.mouse.get_pos()

                screen.blit(back_text, (270, 530))

                screen.blit(quit_text, (430, 530))

            # Enemy Movement
            enemy_x[i] += enemy_x_change[i]

            if enemy_x[i] <= 0:
                enemy_x_change[i] = 2
                enemy_y[i] += enemy_y_change[i]
            elif enemy_x[i] >= 736:
                enemy_x_change[i] = -2
                enemy_y[i] += enemy_y_change[i]

            # Collision control
            collision = is_collision(enemy_x[i], enemy_y[i], bullet_x, bullet_y)

            if collision:
                
                explosion_sound.play()
                bullet_y = 480
                bullet_state = 'ready'
                score_value += 1
                enemy_x[i] = random.randint(0, 735)
                enemy_y[i] = random.randint(50, 200)

            # drawing enemy
            enemy(enemy_x[i], enemy_y[i], i)
        
        # Bullet Movement
        if bullet_y <= 0:
            bullet_y = 480
            bullet_state = 'ready'

        if bullet_state == "fire":
            fire_bullet(bullet_x, bullet_y)
            bullet_y -= bullet_y_change

        # drawing player
        player(player_x, player_y)

        # Version Display
        screen.blit(version_text, (325, 580))

        # drawing score card
        show_score(text_X, text_y)

        # Updates screen
        pygame.display.update()

I tried using the command python setup.py bdist_msi but same thing happened.

I'm using python version 3.8.6rc1 and I have also tried using pyinstaller but whenever I try to run the executable made by pyinstaller it says failed to execute game file.

I know this is a lot to read and would be difficult to understand. But I seriously need some help here. If anyone can suggest what to do or how to solve this problem it would be really helpful. Please tell me if you need some more info to provide me with the solution.

P.S. All the game files are kept in the same directory and the game runs without any errors. The only trouble which I face is when creating this executable installer for this question.

Thanks in Advance

Armaan
  • 13
  • 1
  • 1
  • 5

1 Answers1

0

I have not used cxFreeze before, so I cannot directly help with your issue, but I would really recommend using pyinstaller to package python programs. I have used it with pygame before and it has worked fine, and doesn't even require a setup file.

Pyinstaller can be installed using pip, and to package a file you call: pyinstaller file.py, along with any flags you may need. In general, I use pyinstaller file.py --onefile --noconsole, which creates a single executable file.

Pyinstaller does not package asset files, so you must supply them with the project, but all python files used by the project (including libraries) are packaged, so no code needs to be supplied with the project.

Pyinstaller can be temperamental sometimes, so I'd recommend first creating a simple hello world file, and packaging it using pyinstaller helloworld.py --onefile, and then running the file in the dist/ folder.

The docs are not always very helpful for pyinstaller, but I found that the best way to learn to package projects is slowly increase the complexity of the projects you try packaging, and check each project works after packaging.

If you use relative script and asset imports (e.g. pygame.image.load('image.png')), you'll need to move the resulting .exe to the original script's location (the .exe is normally created in the dist/ folder, which will not work if you use relative imports and run the .exe from there.

If you want to add an icon, you can use the --icon flag like this: pyinstaller file.py --icon=image.ico (the icon file must be a .ico file - you can always you online converters if need be)

ThePythonator
  • 154
  • 2
  • 13
  • I'm sorry for not mentioning but I actually tried pyinstaller and did all the things you mentioned but The file didn't run. Every time I tried to run the file it said that Failed to execute file. – Armaan Sep 29 '20 at 15:06
  • @Armaan did you manage to get a basic file working first? And did you move the executable up to the location of the original script if you had assets? – ThePythonator Sep 29 '20 at 15:08
  • I did move the file but hadn't tried with a basic file. I'll do that know to make sure if its working fine. Thanks – Armaan Sep 29 '20 at 15:27
  • I started writing the code again from scratch but as soon as the code starts getting complex or say a bit long the exe file crashes everytime. It always says Failed to execute game file. I'm using Python version 3.8.6rc1. Can you suggest me something which may fix this issue? – Armaan Sep 30 '20 at 04:04
  • If you run your executable from a console, you may see the traceback which will help you track down the error. I suspect your issue is with data files in which case [this answer](https://stackoverflow.com/a/48696953/7675174) may help you create a solution with pyinstaller. – import random Sep 30 '20 at 06:09
  • 1
    If you need to run it in console, you must leave out the `--noconsole` flag – ThePythonator Sep 30 '20 at 06:49