Log‐05 | Developing the Prompt - AncientNimbus/rb-chess GitHub Wiki
Developing the Prompt
As hinted in the previous DevLog, this log will mainly focus on how I develop the input handling experience for this project. I will dive into the technical challenges and considerations I made during my iterative process. It is important to note that the project is still work-in-progress, so many elements may be altered over time, but the core approach will likely remain similar.
First, let's start off with one simple question: How do people interact with the Terminal? Well, via user input of course. But how will the computer understand what we have entered? And that's how commands were invented. Most, if not all, Terminal programs' main operation handling is done via keyboard inputs, and through a series of properly arranged keywords, a pattern is formed. When a user enters this special sequence of words, a specific action will be triggered. That's essentially what commands are all about.
Oftentimes, I hear a common phenomenon where people are wary when using the Terminal because it looks scary and they are afraid a wrong command can trigger catastrophic events. Whilst technically possible, good terminal applications usually have safeguards in place to do the following:
- Prevent an operation from taking place due to incorrect values
- Re-prompt the user when incorrect values are found
- Provide help at any point during the usage of the program
- User interactions should not lead to a program crash
The Butterfingers Problem
In my humble opinion, entering a wrong command should not be treated as an exception. Instead, a wrong command event should be part of the core design pipeline from the very beginning and consideration should be made at every prompt request. This may seem like a lot of extra work, but these are mostly thinking/planning works and if done right, it will save you time tremendously over the course of the development process.
From a technical point of view, Ruby has many excellent methods that can help solve this problem easily and they are surprisingly versatile to use.
#match
is heavily used throughout the program. In fact, this very function sits at the heart of my prompt user method. Without it, it simply won't work as effectively as I wanted. For this to work, Regular Expressions plays a huge role in this, as I essentially built a system that caters to this very module.
Regular Expressions may seem like a random string of letters and symbols, but it is actually a pattern. Once you understand how it works, it is like magic. It is no wonder why this framework is heavily used in form validation, account registration, banking and more. With Regular Expressions, you can create a pattern that matches everything, and that's no good. So how do you go about creating a good pattern? The answer to this is simple: Understand the purpose.
Let's say I want to create a file name that should be safe to use in a cross-platform setting, a name that is not likely to cause conflicts.
[\sa-zA-Z0-9._-]+
This pattern accepts whitespace, lowercase and uppercase characters between a to z, 0 to 9 as well as three commonly used symbols .
, _
& -
. The square brackets ensure each character matches this pattern and the plus sign at the very back allows this to take place one or unlimited times. With this pattern, you can gather safe values from the user that's also easy to work with. For example, you may want to perform an operation to trim all the whitespace or make all characters lowercase.
As for my use case, the pattern is a little more complex as it features a method that creates expressions depending on the type of values I am requesting from the user. It also needs to support a set of command patterns I created. The best way to put it into context is by seeing the screenshot below.
What you are seeing is part of the intro sequence, which is also the first entry point of the program. Here I am requesting the user to choose between two modes: Create a new profile or Load a profile. To ensure an accurate return value, I only allowed 1 or 2, but the twist here is that I need to offer a way for the user to exit the program too. That's where I leverage the capture group pattern within the regular expressions framework. The pattern will look something like this:
/\A([1-2]|--(exit|help|info).*?)\z/
This pattern will enable the ability to accept a flag-style command pattern --exit
, and a corresponding function will be triggered. You will notice that when an invalid value is entered, a re-prompt will be triggered with a warning message that suggests a proper format the prompt is expecting. Another aspect worth mentioning is that the icon on the left is coloured and it will change from ?
to !
to provide better user feedback. This is inspired by the prompt styling from GitHub CLI.
The Arrow Keys Problem
During the development, I discovered a minor but noticeable issue when collecting user input and it is the ANSI escape sequence. With the standard gets
, this is what you will normally get when you type -> #=> ^[[c
, and for the most cases, that's not what a user wants when interacting with a chess program.
After some research, I have found the ReadLine
module within the Ruby built-in library, but for some reason, the documentation for this module is quite scattered. That being said, it is a super useful module that returns the regular usage of the arrow keys, where you can navigate between characters to modify your response. It also features history where you can use the upper arrow to get a copy of your previous responses. In short, this module helped elevate the user experience massively when compared to gets
.
I hope you enjoy reading this log. In the upcoming logs, I will start diving into the chess game itself. Stay tuned for my next update.