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 (Week3)

Good Morning from my Robotics Lab! This is Shadow_8472 and today, the development cycle continues on my Discord dice bot. Be sure to catch the last two weeks’ posts before reading this one. Let’s get started!

The Development Cycle

My bot will never truly be “done.” In the past, once software was released, it was typically tied to physical media. New features and bug fixes were either integrated into fresh copies or sold as expansion packs. In more recent times, your license to run a piece of proprietary software is tracked by an online service like Steam, and updates are usually free of charge. When you’re developing a piece of software internally, versions mean less and less until some deadline is reached or it’s “good enough” to stop work for a while.

When I stopped work last week, I was not “good enough.” It was pointed out to me that challenge dice for Star Trek Adventures, the game I’ll be playing, count their challenge pips as normal pips as well. My dice engine actually already supported multiple pip types on the same side naively, so I adjusted the challenge die definition instead of bodging a solution.

Various other improvements behind the scenes included matching the double space from the engine file over in the bot’s main file, making the challenge dice roll command use an icon picture, and implementing the RegEx OR operator for screening input. And then I went to adding or subtracting constants. I must have spent an hour or two trying to shove it through the engine side of things. I lost count at four places they needed special handling along the way from parsing to tallying. Just as I was about to finish, I had the brilliant, but tardy idea to code constants as single-sided, single-valued dice. Another good chunk of coding time summed up in one critical line of code. I was sure to leave a comment.

As part of my little constant episode, I made several changes to the code in lots of hard-to-remember places. Fortunately for me, most or all of my unrelated work was on the bot side of things. Because I split the file between bot and dice engine, I was able to pull up an old copy of the engine from the “production” branch and make the relatively few needed changes.

Takeaway

At this point, I need to plan on finishing the month with this bot. The engine’s code is messy, and I’d rather put it away clean so I don’t have to start over or feel the need to black box it next time I need dice.

Final Question

RegEx gave me a bit of trouble working on it. I used https://regex101.com/ to design my expression, but some of the statements they used threw exceptions in Python. Have you ever had a version mismatch give you a bit of grief?

I Upgraded My Python Discord Bot (Week2)

Good Morning from my Robotics Lab! This is Shadow_8472 and today, I am adding some features to my Discord dice bot. Be sure to catch last week’s post before reading this one. Let’s get started!

My Golden Standard

Dice come in many forms. Outside of specialty die collecting, the hobby of role play games will tend to land you with the widest variety to determine success/failure of fictional characters as their real-world players throw them into the hands of the equally fictional Random Number God (RNG*). Most people are familiar with cubic dice with faces valued one to six, but both the shape (read: number of sides) and the faces can be changed.

The role play system Gensys and its Star Wars themed ancestors go so far as to use as many as three types of pips on their narrative dice – it’s even possible to get both a “success” and “advantage” from the same face! There exists a dice roller by SkyJedi on GitHub for this system, but I would like a bot I can look at and understand. It serves as my gold standard, but I can settle for bronze.

*(officially RNG means Random Number Generator)

About My Bot

This bot constitutes my first major project where I have a discrete engine I will likely re-use later, so I’ve done my best to keep the engine clean at the expense of no time to redo the main bot’s .py file. The biggest question I had to ask myself over and over again was this: Engine or implementation? Is this feature specific to this function, or does it belong in the engine? I defined my engine’s role to begin after input has been screened, and lasting until it’s returned the structured results. I also included a separate tally function for summing the results.

Part way through testing, one of my friends tried subtracting one term from another. I had to re-write a good chunk of the otherwise mostly working engine, but I came out with a product that can pass common mathematical operators without choking.

I finished this week with a presentable bot. I didn’t get all the features I was after, but it will work for my upcoming Star Trek themed game. My main must-have feature is a set of “challenge dice” with the faces 1, 2, 0, 0, C, C. Multiple pip types was an important feature on this engine

Tricks Learned Along the Way

In no particular order, I taught myself a trick or two.

Space-indented comments:
Debugging is all about wall of text. At one point, I had a function with a triple for-loop to split input strings into tokens another function could evaluate one at a time. Six hours I bickered with it until success. That victory is in part thanks to my idea to indent debug messages based on how many nested loops each one was in. On my way to a break, inspiration hit and within five minutes, I had replaced the whole thing with only three lines of code.

When to Spaghetti Code:
Programming is an art form. Just as sketch work is drawn/painted over, so too are new features written quickly and sloppily, made to work, added to, tweaked, extended, and eventually depreciated as early code is replaced by a more elegant solution. Messy code is hard to develop, but a little more isn’t going to ruin your day if it’s that little bit more to round off a major version before a deadline. As you code, you will pick up better ways to do things, and you will want to go over your old code as you are able.

Clean Your Code Regularly:
I don’t know why I need to remind myself this, but I have a hard time deleting massive code blocks once a functional replacement has been coded. It feels good being able to revert quickly, but I’m working over SSH in a terminal window, and scrolling through uncollapsible blocks of code all the time isn’t fun. My advice is to comment out the old code being removed and make sure your program still works before removing it all together and verifying that it still works. The same goes for massive chunks of debug code built up over a long debugging session. If a whole file needs a major quality bump, I found it helpful to make a big block of comment I can move functions above as I perfect them.

Git Is a Tool to Practice:
I am using Git locally on this project. Right now, my skill level is git add and git commit. Meaningful comments will come with time as will checking out a previous version will come with time. I’m not working with an online repository, so I don’t need to worry about push or pull, though I am aware of them. In the meantime, I keep a special place for my most recent stable version.

The Development Cycle Loops:
The development cycle doesn’t end with release. Early versions will be flawed. Even now, I’m considering using those picture icons to the dice output. Always a new feature. Support code can take even longer to wrestle into existence if the clean implementation re-writes half the engine.

Takeaway

I would like to stress that I’m finding it difficult to write about writing code. Tangible progress is often non-linear. There are a lot more options than when you’re working with someone else’s program. In some ways, writing a program is more like writing a story than wrestling a Windows game into working on Linux. Once you’ve figured out the interface to set up your canvass, you are in full command of the ideas you express.

Final Question

What do you think of this “Lessons Learned” format?