A Python dice implementation by ChatGPT

My first thought was what are the GUI options for Python, so I asked:

How to generate a gui interface from python code?

What resulted from this request was:

There are several libraries that can be used to generate a GUI interface from Python code, some of the most popular ones include:

Tkinter: This is a built-in Python library that provides a simple way to create graphical user interfaces. It is a powerful tool for creating desktop applications.

PyQt: This is a set of Python bindings for the Qt application framework and runs on all platforms supported by Qt including Windows, OS X, Linux, iOS, and Android.

wxPython: This is a set of Python bindings for the wxWidgets toolkit, which allows you to create graphical user interfaces that are compatible with Windows, OS X, and Linux.

PyGTK: PyGTK is a set of Python bindings for the GTK toolkit. It is the foundation for the GNOME desktop environment and is compatible with Windows, OS X, and Linux.

PySide: PySide is a set of Python bindings for the Qt toolkit and runs on all platforms supported by Qt including Windows, OS X, Linux, iOS, and Android.

Each of these libraries has its own strengths and weaknesses, so you should choose the one that best suits your needs.

That was a pretty useful overview of the options, so I thought I’d just let it take a crack at it and asked:

write a graphic user interface in python that rolls two dice

What I got back definitely worked:

import tkinter as tk
import random

def roll_dice():
    dice1 = random.randint(1, 6)
    dice2 = random.randint(1, 6)
    result_label.config(text=f"Dice 1: {dice1}\nDice 2: {dice2}")

root = tk.Tk()
root.title("Dice Roller")
result_label = tk.Label(root, text="Welcome to Dice Roller", font=("Helvetica", 20))
result_label.pack()
roll_button = tk.Button(root, text="Roll Dice", command=roll_dice)
roll_button.pack()
root.mainloop()

This GUI has a button labeled "Roll Dice" and a label that displays the results of the dice roll (e.g. "Dice 1: 4\nDice 2: 5"). When the button is clicked, the roll_dice() function is called, which generates random numbers between 1 and 6 for each dice and updates the label with the results.

This is just a simple example, you can customize it as you like and add more functionality, for example you can add a history of rolls or a picture of the dices, etc.

No comments, but when I ran it, the GUI generated showed:

And when I clicked Roll Dice it displayed a window:

And new random numbers were displayed every time I clicked Roll Dice. Not too bad with the level of direction I gave it. So, I asked it to improve upon the design, by stating:

add to the GUI the ability to have pictures of the dice

It already knew about the previous program so I was hoping it would just build upon that result. This was what was generated.

import tkinter as tk
from PIL import Image, ImageTk
import random

def roll_dice():
    dice1 = random.randint(1, 6)
    dice2 = random.randint(1, 6)
    dice1_image = ImageTk.PhotoImage(Image.open(f"dice{dice1}.png"))
    dice2_image = ImageTk.PhotoImage(Image.open(f"dice{dice2}.png"))
    dice1_label.config(image=dice1_image)
    dice2_label.config(image=dice2_image)
    dice1_label.image = dice1_image
    dice2_label.image = dice2_image
root = tk.Tk()
root.title("Dice Roller")
result_label = tk.Label(root, text="Welcome to Dice Roller", font=("Helvetica", 20))
result_label.pack()
dice1_label = tk.Label(root)
dice1_label.pack(side=tk.LEFT)
dice2_label = tk.Label(root)
dice2_label.pack(side=tk.RIGHT)
roll_button = tk.Button(root, text="Roll Dice", command=roll_dice)
roll_button.pack()
root.mainloop()

Here, I'm using the PIL library to open and display images of dice. I'm assuming that the images are in the same folder as the script, and are named dice1.png, dice2.png, etc.

In the roll_dice() function, I'm generating random numbers between 1 and 6 for each dice, and then creating PhotoImage objects for each dice using the ImageTk.PhotoImage method. I'm then configuring the dice1_label and dice2_label to display the corresponding images and updating the labels' images.

You can use your own images of the dices, just make sure that the images are in the same folder as the script and the name of the images match the code, for example if you have a dice with the number 3, the image should be named dice3.png

Keep in mind that the images must be in a format that the PIL library can open, like PNG, JPG, etc.

I now had to find some dice images for it to display, which I just pulled off the internet into the folder with the file names stated. I dropped those into the folder, so now the display looked like:

This is better, so now I realize it is not compatible with Android, one platform where I would like this to run. I asked it:

write a graphic user interface in python that rolls two dice PyQt

and the following was generated:

import sys
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QPixmap
from PyQt5.QtWidgets import QApplication, QLabel, QPushButton, QVBoxLayout, QWidget
from random import randint

class DiceRoller(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Dice Roller")
        self.initUI()
        
    def initUI(self):
        self.dice1_label = QLabel()
        self.dice1_label.setAlignment(Qt.AlignCenter)
        self.dice2_label = QLabel()
        self.dice2_label.setAlignment(Qt.AlignCenter)

        roll_button = QPushButton("Roll Dice")
        roll_button.clicked.connect(self.roll_dice)

        layout = QVBoxLayout()
        layout.addWidget(self.dice1_label)
        layout.addWidget(self.dice2_label)
        layout.addWidget(roll_button)
        
        self.setLayout(layout)

    def roll_dice(self):
        dice1 = randint(1, 6)
        dice2 = randint(1, 6)
        self.dice1_label.setPixmap(QPixmap(f"dice{dice1}.png"))
        self.dice2_label.setPixmap(QPixmap(f"dice{dice2}.png"))

if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = DiceRoller()
    window.show()
    sys.exit(app.exec_())

This GUI has a button labeled "Roll Dice" and two labels that displays the results of the dice roll (dice images). When the button is clicked, the roll_dice() function is called, which generates random numbers between 1 and 6 for each dice, loads the images using the QPixmap class and updates the labels with the images.
As in the previous example, I'm assuming that the images are in the same folder as the script, and are named dice1.png, dice2.png, etc. and you can use your own images, just make sure the name of the images match the code, for example if you have a dice with the number 3, the image should be named dice3.png.
Keep in mind that the images must be in a format that PyQt can open, like PNG, JPG, etc.

If this code is error free (and it should be), that definitely took some of the grunt work out of learning GUI programming with Python. I’ll have to get back to this tomorrow though.

Now that I have had a few people test out the program…

Now that I have had a few people test out Ancient War on a range of devices, it is clear that my understanding of the libGDX user interface was not up to the task.

I am in the process of redefining the UI so it may be a while until I have another test version. My problems are caused by the limitations of skins. Fonts are defined to a fixed size, where everything else can scale to the screen sizes. Fonts need to be kludged with to scale. I have yet to find a way to do it cleanly.

Granted there are ways to get around this, and I knew it was going to be a problem as soon as I saw how fonts were defined — it is a bit odd for a GUI to have elements that are this fixed — IMHO.

In the version currently out for test, if the device has a screen width of less than 1500 pixels, the GUI starts to breakdown.

Now it is time for some experimentation… to make it look better!

Here is a simple example of the error being seen:

The button size is based on the screen size but the font is fixed! Even the button size is behaving a bit odd! This devices screen width is only 854 pixels.

I finally got the Android play store to work

It is not like I have not published something on the Play Store before, but this is the first time I’ve done something with libGDX and the various changes that have taken place in the Play Store recently.

If you want to try ‘open testing‘ for the game, search for ‘Ancient War Bess’ or follow this link and install the program from the play store entry.

There are a few bugs that I am aware of, but if you find something new, I’ll try to fix it. Count on updates coming out over the coming weeks.

For those geeky enough to care about what the changes I had to make to the gradle file in the Android Studio files for this project to work, in the gradle file for android I had to have the following settings:

 release {
            minifyEnabled false
            multiDexEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }

This makes it so the optimization process doesn’t hide some of the code entry points that actually allow the program to work (at least that is my understanding).

I’d say that the whole Android Studio build and promotion to production process has gotten so ‘complex and wonderful’ that it is VERY fragile.

Finally back at getting Ancient War released so someone can see it

Found out a few things over the past couple of weeks.

  1. Once a program is out in the play store, it is impossible to delete — unless NO ONE has ever downloaded it! You can unpublish it though.
  2. Once you loose the key you have used to upload the software to the play store, it is almost impossible to get a new key — it is possible, but for a new program effort just not worth the time and bureaucracy.
  3. You can rename a program that is out in the Play Store so you can use the same name again — but you cannot reuse the package name. The play store folks remember that forever.

I went through all that because no matter what I did, I could not get a Play Store entry that would actually run once installed on the Android device. I started over with a new build and new Play Store entry. I am back to where I was weeks ago with a game in the store ‘review’ process.

Hopefully, I’ll see in a few days if anything is different and the newly uploaded program works.

I think part of the complications I ran into was based on them changing all the rules on August 1st and all the documentation and examples out on the internet are not up to date. At best the instructions on what to do are lacking in detail — at worst, they are totally misleading.

I should have an update in a few days and will let anyone running Android (who cares) know how to install the program from the Play Store.

I did add some features to the program while I was thinking about the problem, like Top Score tracking

and some tactical control during the battle.

I also added a bit of in game help so you could know more about the foundations and the prioritization of populating the ranks of a formation.

Google Play’s release process – a twisty maze of passageways, all alike

The last time I setup a release of a program on Google Play was with QSOSender3 (a program to simulate interactions for ham radio code training). Back in 2017, the process was relatively straightforward.

Today, it took me 3-4 hours to figure out all the settings that needed to be defined — and more importantly where to find them! The Play Store release process kept complaining that I didn’t have release countries defined. I swear this used to be optional, so I skipped that step during the initial deployment definition. It was a convoluted research effort needed to find those settings again and fix it.

The Google Play interface was more than happy to tell me that the country definition was missing, but a little scarce on the details of how to resolve the issue. Eventually, I found a countries tab in the user interface for the app release, clicked on it exposing the options that needed to be checked to define the release countries.

After defining a closed beta, an open testing release and finally resorting to a production release, I found an announcement that once you have ‘released’ your software to the store, it will likely take at least 3 days (and that is only an estimate) before anyone can see it. This review is a safety measure to ensure that the software is relatively safe, before anyone can install it. It appears that no matter what size target audience, the review takes the same amount of time. For a closed beta, I only had 3 names on the list and that review used to be almost instantaneous, but no more.

Maybe by Monday, I’ll see something show up, somewhere (unfortunately, it will probably be all three release types). I can then clean up the mess that I find and limit access to only a few until it is more refined.

The following is the game icon, I created, simple but hopefully effective.

Ancient War icon

Now it’s time to move this into Google Play…

Even though my efforts are not what I would consider a completed game, I am going to move it into Google Play so that others can ‘play’ with an alpha test and make suggestions. There is a whole strategic level for the game that has not been thought out. Most of the work to date has been on a tactical interface.

I’ll need to do several things (e.g., create a logo) before going to the store, but I hope to have it out there soon. I am sure that Ancient War will crash on some machines or at least ‘look ugly’ since I don’t have all that many devices to test it on. Hopefully, a few people will look at it and give some feedback.

Publishing will likely be done over the weekend, so I will probably not do another post until then.

There are also a few things that I’ve been putting off for a while that will not be accomplished before the first alpha test (e.g., dialog backgrounds) but I can work on those while awaiting feedback.

Adding a dialog for army population and turning this into something that can be played

Now that the sound is working, it is about time to turn it into something that can be played. So far, the program has mostly been a demonstration of movement and screen element control. Though this is not the final version of the game structure, it seems like it would be a good time to get a version 1 out so people could play with it.

All that is going to happen right now is that when you first start, there will be a number of resources allocated. For example: 100 tokens. Those can be used to buy army elements like:

Archers: 2 tokens

Warriors: 1 token

Knights: 4 tokens

The largest group you can have for a particular battle will be 18 solders.

You can then select the formation desired, and the priority of how it will be populated.

You can then proceed to the battle and see the result.

After completing a few battles, the winner will be decided based on resources remaining.

The following is a very early screen layout for the resource selection:

I still need to add a continue button…

In the process of putting this dialog together, I was surprised to find that LibGDX really didn’t have a screen actor for radio buttons. It does have ButtonGroup to provide the single selection functionality, but not a separate GUI element.

This can be addressed with skins but I’ll need to dig into that to see if I can have both a CheckBox that works/looks like a traditional check box in the same skin as one that looks like a radio button. The worst case is that I’ll need two skins, one with a square CheckBox GUI and one with a round CheckBox GUI.

Time to add some sound

I was pleasantly surprised that adding sound to the game was straightforward. One of the biggest issues was how much sound to add to improve the game experience. You don’t want too little, but too much is just irritating.

I identified sound for each hit, death, and background. Once the sound was working it was apparent my archers were launching many arrows and the sound was just annoying. I still need to work that out. Turns out that LibGDX doesn’t have a function that lets you know if a sound is still playing. It does for the Music class, but the Sound class as less functionality (read as lightweight, since sounds should be less than 1MB in size).

LibGDX provides tools to both load the sound data asynchronously as well as play the sounds, including in loops – that’s what is needed for background sounds.

When the program starts up, I loaded up all the sounds that are always going to be used, using the AssetManager:

manager = new AssetManager();
manager.load("audio/sounds/ava.wav", Sound.class);
manager.load("audio/sounds/avk.wav",Sound.class);
manager.load("audio/sounds/avw.wav", Sound.class);
manager.load("audio/sounds/wva.wav",Sound.class);
manager.load("audio/sounds/wvk.wav", Sound.class);
manager.load("audio/sounds/wvw.wav",Sound.class);
manager.load("audio/sounds/kva.wav", Sound.class);
manager.load("audio/sounds/kvk.wav",Sound.class);
manager.load("audio/sounds/kvw.wav",Sound.class);
manager.load("audio/sounds/afall.wav",Sound.class);
manager.load("audio/sounds/wfall.wav",Sound.class);
manager.load("audio/sounds/kfall.wav",Sound.class);

At some point later, I can call finishedLoading to ensure that all the assets are loaded before they are used.

   manager.finishedLoading();

When I load up the battle field, the sound specific to that field is loaded, the volume set based on the preferences created earlier and start the sound playing in a loop.

   backgroundMusic = Gdx.audio.newMusic(Gdx.files.internal("audio/music/"+baseBackgroundString+".mp3"));
   AppPreferences myPref = gsm.getPreferences();
   currentBackgroundVolumeLevel = myPref.getMusicVolume();
   backgroundMusic.setVolume(currentBackgroundVolumeLevel); 
   backgroundMusic.setLooping(true);

When a battle takes place and events happen, the resources that were previously loaded can be sounded. I am still working on this part since it is bad form to have the AssetManager be a static class (especially on Android?!?!). I’ll have to post on that a bit more later.

Now I have some sounds going so sound selection becomes important. Right now, the ones being used are sounds I purchased from a Humble Bundle.

Table creation in LibGDX

I think I’ve navigated through the most frustrating couple of days with this project. I’ve created an initial status table to be displayed after the battle is complete.

Battle Status screen

Manipulating Tables and Cells within a Stage is not that well explained, and most of the sample code I could find was very simple. I was also forced to understand manipulating Label and Button location manipulation within a Stage more thoroughly to create the display shown above.

By default, every cell in the table is defined relative to the other cells — The column width of the table is defined by the widest cell within that column. That may not be too bad, if everything is relatively simple, but I had headers, numbers and information that required justification. These widths just needed to be locked down.

The best way that I could find to get a grip on the problem was to put blank header row on the top of the table for alignment purposes. That way all my column alignment issues could be addressed in one place.

Here is an example of the code I added at the top of the table:

        table.row(); // this is my placeholder row
        table.row().height(0);
        table.add().width(160); //for the row label
        table.add().width(140);
        table.add().width(140);
        table.add().width(140);
        table.add().width(10); //a spacer between the main columns
        table.add().width(140);
        table.add().width(140);
        table.add().width(140);
        table.add().width(10); //spacer to prevent the table from getting too close to the edge

I still need to try it out on range of platforms but since LibGDX Stages has a ‘camera’ view, I hope it all scales correctly.

The rest of the table can have the elements added without any width methods employed, since that first row should define everything where it needs to be. If someone knows of a better way, please let me know.

I still need to do background and other design work, but at least now I have a tabular view of what is going on.

It also didn’t help that my Android Studio decided that it was no longer going to work since it lost a plugin. I had to reinstall it from scratch – including flushing out the AppData for Android Studio, since that appeared to be where the corruption was and that information survives a reinstall.