Reputation: 11
I'm currently attempting to implement a card game in python, and my plan was to try to be user-friendly by compiling the main application to not require the user having a python installation, but to also support user modifications in a reasonably-accessible way by allowing the compiled application to load non-compiled python files from specific subfolders. This is so far working fine in the non-compiled application.
However, I can't get pyinstaller to stop including the python files that need to be separate from the compiled application.
Files:
main.py
gameobj.py
data\gamedata.py
data\ais\ai.py
data\ais\dummy.py
data\effects\effect.py
data\effects\haunt.py
data\powers\power.py
data\powers\lore.py
data\spells\spell.py
data\spells\bottomlesspit.py
There are also __init__.py files in each subfolder, but they're just to facilitate importing stuff and don't contain anything.
Ideally, anything in the data subfolder should not be compiled into the executable, but should be included as data files; gamedata.py handles loading stuff in, including extra stuff the user adds. Currently, with the following pyinstaller command (or variations on it), gamedata.py and anything it imports is being compiled in, as far as I can tell:
pyinstaller -n Game --exclude-module gamedata --add-data "res;res" --add-data "data;data" --noconfirm main.py
After running this command, I could go into dist\Game\data\ais\dummy.py and change the value assigned to the AI's name, and it'd still print the pre-compiled version.
I've attempted excluding data, gamedata, ais, effects, powers, spells. I'm not sure excluding all individual data files (files like lore.py or haunt.py) would be viable as I do plan on having many more files like those set up later on, and as long as gamedata.py seems to be getting compiled in anyway it's kind of moot; user wouldn't be able to specific more things to load in. If I could get pyinstaller to ignore that, I'd have the (maybe hacky) solution of compiling without most of the data folder, then copying the rest in to the folder under dist.
mnd.py contains mostly testing stuff at the moment.
from gameobj import *
import data.gamedata as gamedata
# Testing power
lore_test = gamedata.lore.Lore()
print(lore_test.get_desc())
# Testing effect
haunt_test = gamedata.haunt.Haunt()
print(haunt_test.get_desc())
# Testing spell
bottomlesspit_test = gamedata.bottomlesspit.BottomlessPit()
print(bottomlesspit_test.name)
# testing AI
dummy_ai = gamedata.dummy.Dummy()
print(dummy_ai.name)
gamedata.py is where the user should be able to specify files to import from. For the above, I'm currently having it import this.
import sys
sys.path.append(".\data")
from powers import lore
from effects import haunt
from spells import bottomlesspit
from ais import dummy
As an example of one of the files it imports, they're structured like this:
from .ai import AI
class Dummy(AI):
def __init__(self):
super().__init__("DummyAI")
def phase_setup(self):
super().phase_setup()
def phase_energize(self):
super().phase_energize()
def phase_prs_one(self):
super().phase_prs_one()
def phase_attack(self):
super().phase_attack()
def phase_creatures(self):
super().phase_creatures()
def phase_prs_two(self):
super().phase_prs_two()
def phase_draw(self):
super().phase_draw()
Which gets AI from ai.py:
from abc import ABC, abstractmethod
class AI(ABC):
@abstractmethod
def __init__(self, name):
self.name = name
self.player = None
@abstractmethod
def phase_setup(self):
# draw up to 5 cards
# intended to be called after AI chooses whether to take starter cards
while len(self.player.hand) < 5:
self.player.deck.draw()
self.player.match.next_phase()
@abstractmethod
def phase_energize(self):
if self.player.match.turn != 0:
self.player.magi.energize()
self.player.match.next_phase()
@abstractmethod
def phase_prs_one(self):
self.player.match.next_phase()
@abstractmethod
def phase_attack(self):
self.player.match.next_phase()
@abstractmethod
def phase_creatures(self):
self.player.match.next_phase()
@abstractmethod
def phase_prs_two(self):
self.player.match.next_phase()
@abstractmethod
def phase_draw(self):
self.player.draw(2)
self.player.match.next_phase()
gameobj.py contains game objects. Not sure it's relevant, doesn't import anything from the project itself. Can edit the contents in if anyone thinks it'd be relevant, though.
Upvotes: 0
Views: 892
Reputation: 11
Ultimately, what I had to do to get it working was:
Move the entire data folder to a separate project, stopping pyinstaller from picking it up. Pyinstaller only has access to main.py and gameobj.py.
Change how main.py looks for imports:
import sys
sys.path.append(".\data")
sys.path.append(".\data\powers")
sys.path.append(".\data\effects")
sys.path.append(".\data\spells")
sys.path.append(".\data\ais")
from gameobj import *
import data.gamedata as gamedata
May not all be strictly necessary. Everything else is as it was previously.
from data.powers import lore
from data.effects import haunt
from data.spells import bottomlesspit
from data.ais import dummy
Run pyinstaller with following command:
pyinstaller -n Game --exclude-module gamedata --noconfirm main.py
After running pyinstaller, copy the data folder into dist\Game and run Game.exe. Though gamedata.py isn't frozen in with main.py and gameobj.py, the application will still run through the tests that use things from the data folder, and will pick up changes made to those files.
Upvotes: 1