I Upgraded My Python Discord Bot (Week5)

Good Morning from my Robotics Lab! This is Shadow_8472 and today, the month of bot development concludes. Be sure to catch the previous posts before reading this one. Let’s get started!

Measures of Progress

Progress this week towards the bot while substantial, wasn’t anything special. In fact, the last day I had to work on it was mostly a flop of me goofing off on other pursuits instead of focusing. Instead, I’d like to focus on what I’ve accomplished over the month.

This longer push on the bot built upon a dice bot I programmed up several months ago that understood how to roll X dice with N sides. Over the month, I achieved multiple personal firsts and worked on my coding style. It was always satisfying when I came up with a new or clever trick to reduce code complexity for a given job. I learned a thing or two about coding the “right” way and practiced with Git version control software.

Memorable Milestones and Personal Firsts

The first big milestone that sticks out in retrospect was rewriting the dice rolling engine from scratch. I will likely want to make another dice bot geared for another game in the future. By splitting out related functions of a larger project into separate files, I can almost trivially re-use the code in another project or re-implement the whole thing. Shorter source files are also much easier to navigate and maintain.

Code condensation is fun. It hurts to see a piece of code you spent all day hacking together (successfully) to be replaced in five minutes by a stupidly more efficient approach, but simpler is easier to maintain. I found myself going through my code more than a few times as I learned about the likes of fstrings and the enumerate function to make sure they were being used when they made more sense than old standards or contextually strayed code, respectively.

This week in particular, I noticed how my relationship with exceptions has changed over the long term. As a beginning programmer in high school, exceptions and tracebacks always meant the existence of a bug. As I became more experienced (one of my early blog projects), I encountered a programming problem where the only way I could solve it was to try/catch an exception. On this project, I have a few places where I reached for try/except almost as though it were an if/else statement. I’ve even made use of Python’s ability to manage different kinds of exceptions with different code blocks in a new feature I worked on this week (but haven’t finished as of writing).

Saving and Loading Data

I have next to zero practical knowledge of writing programs with save data. I was able to bodge a webcam subtitle program involving a second webcam and writing text to a file, but that’s it from before this week.

Fancy dice bots have the ability to store information about players and characters involved in a game. My group don’t strictly need this feature to play, but it is high up on the nice-to-have list. Plus this is a skill I am after; it was just so intimidatingly long to research! Modules like Pickle and Shelve that dump data directly into a file warn in their documentation that clever users can inject arbitrary code through clever wording if the programmer isn’t careful. I started with a module called “configParser,” before realizing this wasn’t exactly its intended use case. It’s close enough though, but it still took a while to gain a mental model of how it operates internally – with an object in RAM, and not directly on the disk. Write the object to essentially a plaintext file, and it can be read back fairly simply. As an important bonus, the saved files are human readable.

Again, saving data to files is perfectly safe. It’s important though to be careful when loading naively stored user-generated data. While researching, I learned about a 15 year-old, trivially exploited vulnerability in the tarfile module that circulated in the news last week. The moral: know what you’re loading, and sanitize incoming data from users or directly from the Internet before saving it.

Takeaway

Sometimes, you need to look at something ten or more times before things start making more sense than not. Programming takes time to learn. One new technique I tried out this week (but wasn’t able to use) is called Ternary Operators. These look like if/else statements nestled into a single line, but they’re more for when you have multiple variations on a single command. My to-do list on this project is quite lengthy as I still need to diagnose why my newest feature collapsed on me. I wish I could end the month with everything in a working state, but such as it is, I have other things I need to work on too.

Final Question

What is your most ambitious personal project to date?

I Upgraded My Python Discord Bot (Week4)

Good Morning from my Robotics Lab! This is Shadow_8472 and today, the month of bot development continues. Be sure to catch the previous posts before reading this one. Let’s get started!

Iteration vs. Recursion

My first major milestone this week was coding a recursive dice tally function to replace the iterative one. Iteration and recursion are two approaches to solving the same types of problems.

The experience of iteration is the more native one to think in. Your program has a path to walk that loops back in on itself as needed. Its downside is that this path can grow beyond the comprehension of the programmer fairly quickly when using in loops within loops within loops if need be. From experience, I found myself guessing more often than not when I was finishing the iterative tally function, and even then I had to teach myself to indent debug text.

To get the idea of recursion, imagine having an army of clones. Each recursion clone has a tiny job, but half that job is working out what it’s supposed to do, and most of the time, that it’s subdividing the problem and summoning one or more clones to work on each one. Recursion has a higher learning curve, but it’s a lot simpler to maintain. I was able to identify and squash a bug involving my pip tally missing secondary pips within seconds to a minute as opposed to fixing the same bug in minutes to an hour and a lot print() statements.

Code Polishing

The more time I spend on this project, the more I discover or rediscover tidier ways to code. It’s not always a hundred lines crunched down into two or three, but it can still feel validating to get a whole chunk of debug code onto a single line I can easily comment out.

In terms of memorable condensations this week, I had two functions –cleanInput() and tokenizeInput() (the later of which underwent around an eighteen:three line reduction last week)– in my dice engine file used for turning raw input into a format my roller can understand. Upon closer inspection, they combined neatly. Later, I solved a bug introduced when adding support for counting dice sets against the total [subtraction] by re-arranging lines of code from tokenizeInput in before lines from cleanInput.

Another thing that’s been bothering me has been the inability to pass off the context from Discord messages to other functions. As far back as the first stint on this bot, I wanted a function so I wouldn’t have to repeat myself in-code when sending a copy of returned messages to the command line. I achieved this with a function that logs, then returns strings I pass it.

print(“Hello World!”)
await ctx.send(“Hello World!”)

becomes

await ctx.send(broadcast(“Hello World!”))

Furthermore, I also played around with the ‘or’ operator. As I began work on implementing more features, of a trio of lines used for grabbing a user’s server nickname – or baring one, a user’s global account name. The way Python works, I was able to use

(ctx.message.author.nick or ctx.message.author.name)

straight in my broadcast liner function.

My general philosophy for this project has been to come up with a creative approach instead of just patching problems as they pop up. The readability of code will go down sharply as efforts to limit the amount of guesswork patching are neglected. As it stands right now with the next section’s features unfinished, I’d say this should be something I can come back to in a few months and make sense of faster than recoding it from scratch.

The Pythonic Way

Change of plan: implementing a save system can wait. While researching save file systems, I happened across an article on askpython.com about so-called “pythonic coding” [1]. Conventions change over time. I’ve noticed this first hand over this project as my style has noticeably drifted as I’ve consulted resources from over the past decade. Looks like I’ll be going through my code again and bringing my code up to standard where I can.

My first task was converting to using fstrings (f-strings – I cannot find anyone saying for sure what the f stands for, but I will be thinking of it as “format” until someone corrects me) when putting variables into strings. It went pretty smoothly, though I did have to be careful about using single vs double quotes when dealing with string data, such as keys in a dictionary. I’m not going for total conversion. If a print statement contains just one string and one variable, I can separate them with a comma. Anything more, and it’s time for fstrings.

The enumerate() function for getting loop indexes was one I’ve read about before, but didn’t appreciate until now. Similar to fstrings, enumerate has its place – specifically anywhere I used pointer variables. Now that I look at my code, my most annoying use of pointers was in that ugly function I’ve already replaced. Nevertheless, I was able to combine a pointer and its supporting code into for-loop statements. Similarly, I employed range() in a while-loop being used for a similar indexing job.

The AskPython article also brought up naming conventions, but I’m putting a hard pass on that one for this project. Variable names are fragile, and only work if they’re all converted at once. I’m not up to that level of potential breakage right now.

Takeaway

Code takes a lot of work to maintain and ensure readability. Add a seemingly innocent feature or consolidate chunks of code that belonged together all along, and you’re liable for something breaking and going unnoticed. I’ve lost track of all the bugs I’ve squashed, but I try to commit it to Git whenever I have a stable version, documenting my progress since my last commit. I still haven’t looked into using its other features yet, but it’s good practice making improved quality commits, and I know that’s worth something, even if it doesn’t feel like it yet.

Final Question

Have you ever taken up [part of] a set of standards for projects, even mid-project?

I look forward hearing your answers on in the comments below or on my Discord server.

Work Cited

[1] pankaj, “Pythonic Way of Writing Code,” [Online]. Available: https://www.askpython.com/python/examples/pythonic-way-of-coding. [Accessed Sept. 19, 2022].

I Upgraded My Python Discord Bot

Good Morning from my Robotics Lab! This is Shadow_8472 and today, I am tailoring my simple Discord dice bot for an upcoming role play. Let’s get started!

Project History

A while back, I built a very simple dice bot for Discord [1]. In its current form, it can be used for rolling an integer number of dice with another integer number of sides (3d6, for example) provided at runtime. A lot of effort also went into learning the Buildah/Podman OCI container implementation so I could clean up afterwords if something got messy. In the end, I managed a container with a mounted volume I could reprogram on the fly without entering the container.

Containerization with Podman/Cockpit has been treating me very kindly. This week, I was able to add environment variables on container creation, allowing me to work a bot token and branch label into the container itself. I also created separate development/production branches for short-term testing and long-term operation respectively, though in effect, I’ll also be keeping an old version around to act as a “stable” branch which will have its code integrated into the container itself instead of relying on an attached volume.

During this week’s work, I figured out how to test code segments directly in the interpreter. I also played around with separating core functions out of the main .py program file, and found they imported cleanly into the live interpreter. As a result, my debugging efficiency improved quite a bit.

Total Rewrite

There is a meme out there where a programmer can write a program such that only he and God know how it works – after a time, only God will know. A good programmer will return to such a problem with ideas for a more elegant solution, though it usually means rewriting from scratch. The core dice rolling functions for my first dice bot were hacked together and barely able to roll an arbitrary set of XdN dice where X in the number of dice and N is the number of sides.

This time, however, I’m programming for a game with non-standard dice faces. No way was I going to shove that into my old bot core. Instead, I spent around eight to ten hours crafting a new monstrosity I can reuse for other bots in the future and handles multiple types of die pips on a side at once. I focused on hacking it into my existing bot code (which I have yet to fully decipher), but it wasn’t so bad. What I do know is that I obsoleted several bits of code and about half as many comments intended for future work on that ugly line of development.

My new functions begin with the assumption that N can be defined as a list of sides, which in-turn list their respective kinds/counts of pips. For when N is int type (integer), I have some code to build a die face-by-face in that format. All the dice are loaded into a list, and that list is passed off to another function to actually roll the dice and tally up the results.

Worth noting is that I’m starting to make git commits to savestate my progress. I’m not worrying about proper form right now, but I’m counting on improving over time.

Settings Files

The ability to save/load data –while not strictly needed for a simple dice bot– is an important skill to develop. My brief research showed that while there’s any number of ways a Python programmer may implement settings, a somewhat standardized and end-user friendly method is using .ini files. While I may not use them this week in this project, I will want to remember this side goal. I’ve invented my own scheme before, and it wasn’t fun.

Takeaway

It’s really difficult to communicate all the little challenges that come up without looking/feeling like an idiot half the time because I missed something and another half because of the jargon. I’d have to say most of the time I spend coding is trying to understand what I just wrote. I still haven’t completed all the features I need, so the plan is to cover the rest of this revamp in next week’s post.

Final Question

What were you working on the first time you successfully extracted a part of your program into a separate document?

Works Cited

[1] Shadow_8472, “My Discord Bot Understands Strange Dice,” Feb. 28, 2022. letsbuildroboticswithshadow8472, [Online]. Available: https://letsbuildroboticswithshadow8472.com/index.php/2022/02/28/my-discord-bot-understands-strange-dice/ [Accessed Aug. 29, 2022].