Can you import a pygame script without opening a pygame window (until you want to?)

So, I have a game called run.py that I made with pygame, and for the most part, it works fine. And I'm trying to make a launcher for all of my pygames. But when I import it, it immediately opens a blank window. What I want to happen is nothing, until I call run.main()

import tkinter as tk
import run

WIN = tk.Tk()


WIN.mainloop()
run.main()

I understand this is happening because in my pygame scripts I set the window outside of a function, and when I open a tkinter window it leaves the pygame window open until I close the tkinter one. But I want the original games to work without this launcher as well as with it.

Here is the code for my pygame:

import pygame , random

pygame.init()

# This is where it opens the window
WIDTH , HEIGHT = 900 , 500
WIN = pygame.display.set_mode((WIDTH , HEIGHT))
pygame.display.set_caption('game')

BLOCK_MOVEMENT_TIMER_EVENT = pygame.USEREVENT + 1
BLOCK_MOVEMENT_TIME_DELAY = 15
pygame.time.set_timer(BLOCK_MOVEMENT_TIMER_EVENT , BLOCK_MOVEMENT_TIME_DELAY)

BLOCK_CREATE_TIMER_EVENT = pygame.USEREVENT + 2
BLOCK_CREATE_TIME_DELAY = 1500
pygame.time.set_timer(BLOCK_CREATE_TIMER_EVENT, BLOCK_CREATE_TIME_DELAY)

FPS = 60

#colors
WHITE = (255 , 255 ,255)
BLACK = (0 , 0 , 0)
RED = (255 , 0 , 0)

BIGFONT = pygame.font.Font('Retro.ttf' , 65)
SMALLFONT = pygame.font.Font('Retro.ttf' , 20)

FLOOR = pygame.Rect(0 , 400 , 900 , 5)

class Player:
    def __init__(self):
        self.size = 20
        self.x = 75
        self.y = 380
        self.jumping = False
        self.fall = False
        self.update_rect()
        self.score = 0

    def update_rect(self):
        self.rect = pygame.Rect(self.x , self.y , self.size , self.size)

    def jump(self):
        pressed = pygame.key.get_pressed()
        if pressed[pygame.K_SPACE] and not self.fall:
            self.jumping = True
        
        if self.jumping:
            self.y -= 3
            if self.y < 300:
                self.y = 300
                self.fall = True
                self.jumping = False

        if self.fall:
            self.y += 3
            if self.y > 380:
                self.y = 380
                self.fall = False
        
        self.update_rect()

player = Player()

class Obstacles:
    def __init__(self , width , height):
        self.x = 900
        self.y = HEIGHT - height - 100
        self.width = width
        self.height = height
        self.update_rect()

    def update_rect(self):
        self.rect = pygame.Rect(self.x , self.y , self.width , self.height)

def move_obstacles(obstacle_list):
    for obstacle in obstacle_list:
        obstacle.x -= 3
        if obstacle.x < -100:
            obstacle_list.pop(0)
            player.score += 1
        
        obstacle.update_rect()

def add_obstacle(obstacle_list):
    obstacle1 = Obstacles(20 , 40)
    obstacle2 = Obstacles(75 , 20)
    obstacle3 = Obstacles(35 , 35)
    obstacle4 = Obstacles(50 , 25)
    obstacle5 = Obstacles(80 , 10)
    obstacle6 = Obstacles(40 , 20)
    obstacle7 = Obstacles(20 , 30)

    obstacle_options = [obstacle1 , obstacle2 , obstacle3 , obstacle4 , obstacle5 , obstacle6 , obstacle7]

    obstacle_list.append(obstacle_options[random.randint(0,6)])

def is_game_over(obstacle_list):
    for obstacle in obstacle_list:
        if player.rect.colliderect(obstacle):
            return True

def game_over():
    running = False
    while not running:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = True
                quit()
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_r:
                    player.score = 0
                    main()
                    break
                if event.key == pygame.K_e:
                    running = True
                    quit()

        WIN.fill(BLACK)
        game_over = BIGFONT.render('GAME OVER', False, WHITE)
        WIN.blit(game_over , (200 , 175))

        instructions = SMALLFONT.render(f'You scored {player.score}. Press R to restart or E to exit' , False , WHITE)
        WIN.blit(instructions , (145 , 290))

        pygame.display.flip()

def draw_window(obstacle_list , is_game_over):
    WIN.fill(BLACK)
    pygame.draw.rect(WIN , WHITE , FLOOR)

    pygame.draw.rect(WIN , WHITE , player.rect)

    for obstacle in obstacle_list:
        pygame.draw.rect(WIN , RED , obstacle.rect)
    
    score_counter = BIGFONT.render(f'SCORE  {player.score}', False, (255, 255, 255))
    WIN.blit(score_counter , (240 , 100))

    pygame.display.flip()

def main():
    clock = pygame.time.Clock()
    
    obstacle_list = []

    done = False
    while not done:
        clock.tick(FPS)
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                done = True
                quit()
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_e:
                    done = True
                    quit()
            if event.type == BLOCK_MOVEMENT_TIMER_EVENT:
                move_obstacles(obstacle_list)
            if event.type == BLOCK_CREATE_TIMER_EVENT:
                add_obstacle(obstacle_list)

        draw_window(obstacle_list , is_game_over(obstacle_list))
        player.jump()
        is_game_over(obstacle_list)

        if is_game_over(obstacle_list):
            game_over()
            done = True
        pygame.display.flip()

if __name__ == '__main__':
    main()

I'm sorry if this question doesn't make sense, but any help would be appreciated.


Yes. First off pygame doesn't initialize a game window until you call pygame.display.set_mode((width, height)).

So you can either move all your game logic inside of run.py into a function say for example...

def my_game_name():
    pygame.init()

    WIDTH , HEIGHT = 900 , 500
    WIN = pygame.display.set_mode((WIDTH , HEIGHT))
    ...

    class Player:
        ...

    def move_obstacles(obstacle_list):
        ...

    def main():
        ...

And then inside you main.py where TK is you can do this.

import run

# When you're ready to run the game
run.my_game_name()

Your second option that I would recommend not to do is this...

Inside main.py

#TK button clicked to start game
def start_my_game():
    import run # Window is shown here
    run.main() # Game logic begins

I wouldn't recommend this as your relying on the import to setup stuff in the background which is the reason why you are having the issue you are having now.