6

Advertisements

Hello everyone,

Michael here, and you may be wondering what’s up with this blog title? What could I possibly be covering? The joys of the number six perhaps?

First of all, this post marks the 6th anniversary of Michael’s Programming Bytes (known as Michael’s Analytics Blog until June 2022). Yes, dear readers, I have officially been blogging for six years now, with 166 posts to this blog’s name covering things from data analytics and Python coding to web development and GitHub.

Now, how might I style my anniversary post for year #6? Will I use it as an excuse to show you all something cool. Yes!

In fact, today I’ll be showing you how to work with basic text encryption in Python. Let’s begin!

What is encryption?

Now, before we dive into some Python encryption, let’s explain the concept of encryption as it relates to data.

Let’s use the example of sending an Instagram DM (direct message for those unfamiliar) to one of your friends. Instagram has the option to enable end-to-end encryption for any DMs you send, which means when you send a DM to your friend encryption would encode the text into something called ciphertext while the message is being sent to your friend’s device. Once the message reaches your friend’s device, it will be decrypted (or decoded) back to plain text.

Why is encryption important? When there’s a transfer of data from one point to another (like one person’s Instagram account to another’s), encryption essentially keeps any hackers from intercepting the contents of that data before it reaches its destination. After all, what good is ciphertext to a hacker?

There are two types of encryption I’ll show you-asymmetric-key encryption and symmetric-key encryption.

Symmetric-key encryption

The first type of encryption we’ll explore is symmetric-key encryption. In symmetric-key encryption, the text is encoded and decoded (or encrypted and decrypted) with the same key.

Although this is an easier method of encryption than asymmetric-key encryption (which we’ll discuss later in this post), it is also a less secure method because the same key is being used to encrypt and decrypt the message. As long as anyone has the encryption/decryption key, they can read the message.

Here’s an illustration of the idea of symmetric-key encryption.

In this picture, “the key” represents the key needed to both encrypt and decrypt the message.

Now, let’s explore symmetric-key encryption in a Pythonic sense. First, please pip install cryptography before beginning.

Next, let’s see some symmetric-key encryption in action:

from cryptography.fernet import Fernet

message = "Thank you for six wonderful years!"

theKey = Fernet.generate_key()

theFernetKey = Fernet(theKey)

encryptedMessage = theFernetKey.encrypt(message.encode())

print("The original message is: " , message)
print("The encrypted message is: " , encryptedMessage)

decryptedMessage = theFernetKey.decrypt(encryptedMessage).decode()

print("The decrypted message is: " , decryptedMessage)

print("The encryption key is: " , theKey)

And here’s our output:

The original message is:  Thank you for six wonderful years!
The encrypted message is:  b'gAAAAABmaQb-Ft-ws6utm0lw8S7Vl-ZHeW0MKyYLdYbGrrV-t04xzjg4ftpQ_0oOegR2MzQ8KWeOsfV2-UMzZdR10CM_UeWmpnlkSOW6kBnYn4KYE9bV-f0mBrco9zWS-OvePTvkGU4F'
The decrypted message is:  Thank you for six wonderful years!
The encryption key is:  b'TA5gu709UF8GEw6zIVvq77sWaOzQHPShZaMlUmA17ls='

So, how did I accomplish all this. Let’s walk through the code, shall we?

  • I imported the Fernet class from the cryptography package. Fernet encryption is a type of symmetric-key encryption that ensures a message can’t be read or manipulated without the encryption/decryption key.
  • I also had a message that I wanted to encrypt and decrypt using Fernet encryption.
  • Before encrypting my message, I utilized the Fernet.generate_key() method to generate an encryption/decryption key (storing it in the theKey variable) and then ensured the key utilized Fernet encryption by instantiating it as an object of the Fernet class (using the line Fernet(theKey) and storing it in the theFernetKey variable).
  • I then encrypted my message by using the Fernet key’s encrypt() method and passing in message.encode() as the method’s parameter. This parameter will encode the original message using Fernet encryption.
  • After printing out my original message and encrypted message, I then decrypted the message using the Fernet key’s decrypt() method while passing in the encryptedMessage as the parameter. I then followed up the call to the decrypt() method with a call to the decode() method.
  • Finally, I printed out my decrypted message and (non-Fernet) encryption key. Granted, I didn’t build this script with high security in mind, but assuming someone had the encryption key, they could read and mess around with my message.

Now, the fun thing about encryption in Python is that, if we try to encrypt the same message using symmetric-key encryption, we’ll get a different key created each time. Here’s the key that’s generated when I run this script again:

b'OkncdE57Dvq42ODTxSMdLbpEIJeZWr5b2_Gbej1LevU='

And now for some asymmetric-key encryption

Now that we’ve explored symmetric-key encryption, let’s explore asymmetric-key encryption! Unlike symmetric-key encryption, asymmetric-key encryption uses different keys to encrypt and decrypt data. A public key encrypts the data while a private key decrypts the data-the great thing about this setup is that since no one has the private key, no one can access the data you are trying to transmit. If it helps, think of asymmetric-key encryption like two-factor authentication (you know when you have to use both your password and a second code to login to something), where the different keys add two layers of protection for your data.

Just as I did with symmetric-key encryption, here’s an illustration of asymmetric-key encryption:

And now, let’s code! For this example, please pip install rsa to be able to follow along with this lesson.

Here’s our code for the asymmetric-key encryption:

import rsa

publicKey, privateKey = rsa.newkeys(512)

message = 'Thank you loyal readers for six amazing years'

encryptedMessage = rsa.encrypt(message.encode(), publicKey)

print('Original message: ', message)
print('Encrypted message: ', encryptedMessage)

decryptedMessage = rsa.decrypt(encryptedMessage, privateKey).decode()

print('Decrypted message: ', decryptedMessage)

print('Public key: ', publicKey)
print('Private key: ', privateKey)

And here are the outputs:

Original message:  Thank you loyal readers for six amazing years
Encrypted message:  b'X\xaa\xc5\x98\xe2\xc8\xd1"\xd5\x94\xd0\xc2l\x92\xe3\xc4^\xe9\xef\x83\x18\xab\xdc\xfb\xea\xbb\x1a9\x06\x8e"\xa1\x08\xcc:\xa6n\xc3\xa4\xc2\x14F\xe5i\x96\xd4\x0e\xb6B\x9c-\x85"\xd9\xde\x15\xd8S\xba\xb8\xc8s\x88m'
Decrypted message:  Thank you loyal readers for six amazing years
Public key:  PublicKey(8756745001992373161285778726645083782004419876731866636961799474661459252554364385770004594397922925180145618274212925790191421654715585611349812414582633, 65537)
Private key:  PrivateKey(8756745001992373161285778726645083782004419876731866636961799474661459252554364385770004594397922925180145618274212925790191421654715585611349812414582633, 65537, 2577171637371696805390544273914435753655206228274169456852147462765616764969150310852001220742439294959750117227094790559647443735723327989042277493248225, 7128026561941571600154499219580762398618969604207116659373371614354183291477318639, 1228495001512334734540841979883773428960324113167485626876315020518609447)

So, how did I accomplish all of this? Let’s walk through the code, shall we?

  • The RSA module that we used here utilizes the RSA encryption algorithm, which is a type of encryption algorithm that uses two different but linked keys (one public and one private). The public key encrypts the message while the private key decrypts the message.
  • Quick historical fact: the name RSA comes from the surnames of the creators of this algorithm-computer scientists Ron Rivest, Adi Shamir and Leonard Adleman, who first developed this algorithm in 1977 (all of them are still alive as of June 2024)
  • I used the rsa.newkeys() method to create the publicKey and privateKey and passed in 512 as this method’s parameter-the 512 represents the number of bits each key should have. Trying to figure out a good number of bits to utilize is a little trial-and-error process.
  • I then used the rsa.encrypt() method to encrypt my message and passed in both message.encode() and my publicKey as parameters.
  • After printing out the original and encrypted message, I then used the rsa.decrypt() method to decrypt my message and passed in the encryptedMessage and privateKey as this method’s parameters.
  • I finally printed out the decryptedMessage, publicKey and privateKey.

One interesting thing to note is the similarities between the publicKey and privateKey. Remember how I mentioned that these two keys are opposite, albeit linked? Notice how both keys start with 8756745001992373161285778726645083782004419876731866636961799474661459252554364385770004594397922925180145618274212925790191421654715585611349812414582633, 65537. However, the privateKey is considerably longer than the publicKey, likely to make it harder to access.

Also, similar to symmetric-key encryption, this script will generate different keys each time its run. Here’s the keys we get after another run of the script:

PublicKey(7855075279758572094336135232248306022642803736898164846214110092559099915389472776042423258530713061811745645535500011244055637229684973871741305772152203, 65537)

PrivateKey(7855075279758572094336135232248306022642803736898164846214110092559099915389472776042423258530713061811745645535500011244055637229684973871741305772152203, 65537, 999608279798991276176257195736009768967773672364171305025034380150798682275873600573017565727711649304440962386673322170031172276957264982184527359310433, 6478929829895505402335617053533719083904398041145802512468037497716457622395698959, 1212403203305762871531606015835489318412721597481677658992522607891186117)

You’ll also notice that whenever I run this script, the RSA keys I obtain always have the number 65537 in them. Why might that be? It’s what’s known as a public exponent in the RSA algorithm, which is a crucial part of the public key that is utilized for verifying both encryption of the data and access signatures for anyone trying to access the data.

Dear coder, thank you

However you decide to encode this message, I just want to make one thing clear-thank you, thank you, thank you for following along this journey with me for six wonderful years. Thank you for reading everything I’ve done over the past six years (and perhaps learning a trick of the trade along the way)? I hope to keep coding along for as long as possible but I’ll admit, I’ve certainly come a long way since my early posts (remember R Lesson 1: Basic R commands as my first-ever tutorial and second overall post?). I’ve also certainly learned quite a bit about running this publication over the last six years, and to be honest, I feel like my programming has come very very far since that first post in summer 2018.

In short, keep calm and code along fellow devs! I’ll be back with another great year of programming content (and perhaps another cool coding demo for the 7th anniversary).

Also, I would be remiss not to acknowledge the two furry friends that have been around since the early days of this blog:

Orange Boy/Simba and Pretty Girl/Marbles (seen here eagerly awaiting their Christmas presents in 2017):

Michael

R Lesson 35: Let’s Plot Some Inverse Trigonometric Functions

Advertisements

Hello everybody,

Michael here, and in today’s post, I’ll show you to how to plot some inverse trigonometric functions with R!

In the previous post, we explored how to create R plots of the three basic trigonometric functions-sine, cosine and tangent. This time, we’ll explore how to create R plots of the three basic inverse trigonometric functions-arcsine, arccosine and arctangent. Let’s begin!

First off, the arcsine:

To start off our exploration of plotting inverse trigonometric functions, let’s explore how we can plot the arcsine:

> x <- seq(-3*pi, 3*pi, length.out=100)
> y <- asin(x)
Warning message:
In asin(x) : NaNs produced
> plot(x, y, type='l')

As you can see, we can’t quite use the same approach to plotting the arcsine function that we used to plot the sine function since our sequence of 100 values from -3pi to 3pi yielded all nulls when trying to calculate the arcsine of each value. Let’s try a slightly different approach to plotting the arcsine function, shall we?

> x <- seq(-1, 1, length.out=100)
> y <- asin(x)
> plot(x, y, type='l')

The only modification I made from the previous example was to use a sequence from -1 to 1 (still maintaining 100 equally spaced variables).

Why did I stick with the (-1, 1) sequence? Simply put, the arcsine function is only defined within the range (-1, 1). In other words, it’s not possible to calculate the arcsine of any value outside of the range (-1, 1)-trying to do so will give you an NaN or not a number in R.

Next up, the arccosine

And for our next R plot, let’s graph the arccosine function! Here’s the code to use for a sample arccosine function:

> x <- seq(-1, 1, length.out=100)
> y <- acos(x)
> plot(x, y, type='l')

Aside from using the acos() function, we used the same logic to create this plot that we used for the arcsine plot. Both the arcsine and arccosine functions are only defined for the range (-1, 1), meaning that you will get an NaN in R if you try calculating the arcsine or arccosine for any value outside of this range.

Now, you may have noticed that our arccosine plot looks like a vertical reflection of the arcsine plot. How could that be? The range of x-axis values is the same for both plots, but notice the difference in the range of y-axis values between the two plots. The arcsine plot’s y-axis value range is (-1.5, 1.5) while the arccosine plot’s y-axis value range is (0, 3).

Why do the y-axes in both plots have different value ranges? An easy explanation would be that the arccosine plot is the vertical reflection of the arcsine plot shifted pi/2 radians (or 90 degrees) upward, hence why the arccosine’s y-axis value ranges are higher.

Last but not least, the arctangent

Saving the best for last, let’s plot an arctangent function in R! Here’s the code for a sample arctangent plot:

> x <- seq(-30, 30, length.out=100)
> y <- atan(x)
> plot(x, y, type='l')

For creating the arctangent plot, we used similar logic (aside from the atan() function) that we used to create the arcsine and arccosine plots. However, notice that I didn’t use the (-1, 1) sequence range but rather the range of (-30, 30).

You might be thinking, wouldn’t using a sequence outside of the (-1, 1) range give you a bunch of NaNs? In the case of the arctangent function, no. This is because arctangent, unlike arcsine and arccosine, is defined for the range (-infinity, +infinity). In other words, arctangent functions have no finite range, so you could use any sequence of values you want when creating an arctangent plot (I kept it simple with the -30, 30 range).

However, one interesting thing you’ll notice with the arctangent plot is that its y-axis has a range from (-1.5, 1.5). How is that possible? Even though you could a sequence of literally any two numbers, the range of possible arctangent values will range from approximately -1.5 to 1.5.

Another interesting thing about the arctangent function is that the lower part of the function (the part pointing towards -1.5) represents -infinity in the arctangent function while the upper part of the function (the part pointing away from 1.5) represents +infinity.

Thanks for reading,

Michael

R Lesson 34: Let’s Plot Some Trigonometric Functions

Advertisements

Hello everybody,

Michael here, and in the last two posts we discussed the basics of trigonometry-both with R and basic trig in general. This time, we’ll explore how to plot the three basic trigonometric functions with R-sine, cosine and tangent!

A basic R sine plot

To start our lesson, we’ll create a basic R plot of a sine function. Here’s the code we’ll utilize to create our basic R sine plot:

> x <- seq(0,7*pi,length.out=100)
> y <- sin(x)
> plot(x, y, type='l')

As you can see, these three lines of R code gave us a very simple R sine plot. How did the code accomplish this graph creation? Let’s explain!

  • The seq() function used for the x variable takes 3 parameters-a starting point for the sequence (0), an ending point for the sequence (7*pi), and a value for length.out which indicates how many equally spaced values you want in the sequence (I opted for 100 in this case). The sequence itself will be represented on the x-axis
  • The y variable takes the sine of all 100 values genereated from the sequence in the x variable-these sine values are then plotted on the y-axis.
  • The plot() function takes both the x and y variables along with a type parameter, which indicates the style of graph you want to plot. In this case, I set type to l, indicating I want to plot the sine function with a [solid] linear style.

Now, what exactly does 7*pi mean here? In this case, it indicates that the sequence will end at 7*pi, or roughly 21.98. Something else to note whenever you use pi in trigonometric function plots-sine functions have these things called periods, which in plain English represent the point in a function where it repeats its values. Sine functions have a period of 2*pi which means that they repeat their values every 2*pi-or 6.28-units. Since the endpoint of this sequence is 7*pi, there are 3 1/2 periods in this graph as shown by the 3 1/2 low and high points on this graph.

A basic R cosine plot

Now that we’ve explored sine plots with R, let’s turn our attention to cosine plots! Here’s the code to create a basic cosine plot in R:

> x <- seq(0, 10*pi, length.out=100)
> y <- cos(x)
> plot(x, y, type='l')

Conceptually, the cosine plot works the same way as the sine plot, as both plots have periods of 2*pi (represented by the peaks and valleys in this graph). Since the endpoint of the sequence is 10*pi, this cosine plot will have five periods. Both plots will also generate a sequence of X equally spaced values (X being the number specified in the length.out parameter).

The one difference between the cosine and sine plots? The former calculates out the cosine for all sequence values while the latter calculates the sine for all sequence values.

Last but not least, let’s explore tangent plots!

A basic R tangent plot:

Just as we explored basic R sine and cosine plots, let’s explore how to create a basic R tangent plot! Here’s the code for one such plot:

> x <- seq(-5*pi, 3*pi, length.out=100)
> y <- tan(x)
> plot(x, y, type='l')

Creating the tangent plot follows the same logic as creating the sine and cosine plots, with the exception that you’re looking for the tangent of all the equally spaced values in the sequence.

You may also be wondering why the tangent plot looks so different from the sine and cosine plots. One main reason for this is because tangent functions, unlike sine and cosine functions, has a little something called asymptotes.

What are asymptotes? To explain this concept, I feel it is important to mention that sine and cosine functions have a range of values between -1 and 1, which explains why we get smooth, wave-like plots as shown earlier in this post. However, tangent functions have a range of values between negative infinity and positive infinity. Asymptotes are straight imaginary lines that approach various curves on the tangent plot but never fully meet them.

Still a little confused? Allow me to illustrate:

This is the tangent plot we just created. Pay attention to the two curves on this graph with the red line borders (aka the asymptotes). The asymptotes on the first curve (the one that appears to have its lowest point at -60 on the y-axis) appears to be going down to negative infinity, yet the asymptotes will never touch the curve no matter how far down it goes. Likewise for the second curve (the one that appears to have its highest point at 60), which appears to be going up to positive infinity but similar to the first curve, the asymptotes will never touch the curve no matter how high it goes.

Thanks for reading,

Michael

R Lesson 33: Inverse Trigonometric Ratios in R

Advertisements

Hello everybody,

Michael here, and in today’s post, we’ll be expanding our knowledge of R trigonometry by learning inverse trigonometric ratios in R!

In the previous post, we learned some basics of R trigonomtery (and trigonometry in general). However, let’s explore some more advanced R trigonometrical concepts!

Inverse Trigonometric Functions

In the previous post R Lesson 32: Basic Trigonometry, R Style, we learned about the three basic trigonometric concepts-sine, cosine and tangent. Today, we’ll explore inverse trigonometric functions such as the arc-sine, arc-cosine and arc-tangent.

What do these trigonometric concepts represent? Well, let’s go back to our triangle illustration from the previous post:

This illustration gives a visual representation of the three most basic trigonometric concepts-sine, cosine, and tangent with the classic SOHCAHTOA mnemonic.

Now, I did mention that the sine of an angle in a right triangle is the ratio of the opposite side’s length to the hypotenuse’s length. With that said, arcsine is simply the inverse of sine-meaning arcsine is the ratio of the hypotenuse’s length to the opposite side’s length.

The same logic applies for arccosine and arctangent, as these ratios are simply the inverse of the cosine and tangent ratios, respectively. Arccosine would be the ratio of the hypotenuse’s length to the adjacent side’s length while arctangent would be the ratio of the adjacent side’s length to the opposite side’s length.

How would these inverse trigonometric ratios affect our calculations in this triangle? Let’s find out using the 38 degree angle as an example:

RATIONUMERIC FORM
sine7/12.2~0.57
arcsine12.2/7~1.74
cosine10/12.2~0.82
arccosine12.2/10=1.22
tangent7/10=0.7
arctangent10/7~1.43

As you can see here, the three regular trigonometric ratios yielded values less than 1 while the three inverse trigonometric ratios yiedled values greater than 1. Interesting, isn’t it?

And now, let’s explore how to work with more advanced trigonometry in R!

Advanced Trigonometry, R style

Now that we’ve seen how to use the basic trigonometric ratios in R, let’s see how we can utilize these more advanced trigonometric ratios!

> asin(31)
[1] NaN
Warning message:
In asin(31) : NaNs produced
> acos(54)
[1] NaN
Warning message:
In acos(54) : NaNs produced
> atan(14)
[1] 1.499489

As I did when first testing out the sine, cosine, and tangent functions in R, I tested the three inverse trigonometric functions (arcsine, arccosine and arctangent) in R using whole numbers as parameters. However, you can see that for the arcsine and arccosine functions (asin() and acos() respectively), that didn’t quite work out. Interestingly, using a whole number for the atan() function worked just fine.

How can that be? Well, just like the regular trigonometric functions in R, these inverse trigonometric functions calculate the ratios using radians (more on those here: R Lesson 32: Basic Trigonometry, R Style). However, the asin() and acos() functions only take input values ranging from -1 to 1 because they only work with a limited range of angles. Arcsine only works with angles ranging from -π/2 to π/2 radians (-90 to 90 degrees) while arccosine only works with angles ranging from 0 to π radians (or 0 to 180 degrees). Arctangent, on the other hand, can take a wider range of numerical inputs since it works with angles of any length (in fact, the angle lengths arctangent works with encompass negative infinity to positive infinity).

Let’s try executing our inverse trigonometric functions in R with the new inverse trigonometric ratio information that we learned!

> asin(0.5)
[1] 0.5235988
> acos(0.43)
[1] 1.126304
> atan(22)
[1] 1.525373

As you can see, with the rules we discussed above, we’re now able to obtain valid outputs for the asin(), acos() and atan() functions!

Thanks for reading,

Michael

Michael’s Lost Posts

Advertisements

Hello readers,

Michael here, and boy do I have some exciting news for you!

So, as you all may know, I’ve been writing this blog for nearly 6 years now, having covered 8 different programming tools over the course of 161 posts (that’s a lot when you think about it)

However, today I have a new exiciting announcement! Look, I know you were all expecting another post on R trigonometry or Python game-design, but I thought today would be the perfect time to announce the release of Michael’s Lost Posts.

What is Michael’s Lost Posts you ask? It’s a collection of never-before-seen-or-finished posts, straight from the Drafts folder of this great blog.

What kinds of posts might you see in Michael’s Lost Posts? Let’s preview this great collection, shall we?

First off, have you ever wanted to learn more about the wonders of the Raspberry Pi mini-computer? Now you can with Michael’s Lost Posts:

I mean, look at this well-written post-it’s certainly been kept in pristine condition in the Drafts folder since March 2022. After all, if you know me, you know I only release quality content to you all.

Learn about how to build your own Raspberry PI, up until the point where you hook up all the cords and cables. After that, well…good luck:

But wait, there’s more! Ever wonder if you could build a neural-network machine learning translator that’s BETTER than Google Translate? This Lost Post on Seq2Seq neural-network machine learning models will teach you to do just that…

…up until the point of actually implementing the model:

A post so nice, I didn’t even have a dataset for it.

If you really want to see some gems from Michael’s Lost Posts, check out this lesson on Image Arithmetic (with Python):

Now this post takes the cake. One of the finer posts in Michael’s Lost Posts, and unlike many of the other posts in this collection, it’s so amazing that I didn’t even finish writing the introductory paragraph. In fact, I even ended the introductory paragraph mid-sentence.

Now that you’ve seen a preview of Michael’s Lost Posts, you can find them along with other Lost Posts at the following link:

APRIL FOOL’S!

R Lesson 32: Basic Trigonometry, R Style

Advertisements

Hello everybody,

So far this year, we’ve explored the basics on making our own Python game with the pygame module-pretty cool right! After all, it was the first time this blog delved into game development.

However, building a good game takes time; this includes the BINGO game we’ve been developing. With that said, I’ll need to spend a little more time fine-tuning the BINGO game and planning out the game development series going forward. I’d hate to go too long without keeping fresh content on this blog, so with that in mind, in today’s post, we’ll explore something a little different-R trigonometry (you may recall I did a number of R mathematics posts last year)

Last year, we explored some basic R calculus-this year, we’ll explore basic R trigonometry. For those who don’t know what I’m talking about, trigonometry is a branch of mathematics that deals with the study of triangles and their angles.

Trigonometry basics

Before we get into the fun of exploring trig with R, let’s explore some basic trigonometry terms using this handy-dandy illustration:

Here we have a simple right triangle with a 90 degree angle, a 52 degree angle, and a 38 degree angle along with two sides of lengths 10 and 7 and a hyptoensue of length 12.2 (remember the Pythagorean theorem for right triangles-to find the length of the hypotenuse, a squared + b squared = c squared).

If you’ve ever taken at least precalculus with basic trigonometry, you’ll certainly recognize the mnemonic on the upper-right hand corner of the screen-SOHCAHTOA. If you’re not familiar with this mnemonic, here’s what it means:

  • SOH: To find the sine of an angle in the triangle, take the ratio of the length of the side opposite the angle to the length of the hypotenuse
  • CAH: To find the cosine of an angle in the triangle, take the ratio of the length of the side adjacent to the hypotenuse to the length of the hypotenuse
  • TOA: To find the tangent of an angle in the triangle, take the ratio of the length of the side opposite to the hypotenuse to the length of the side adjacent to the hypotenuse

Trigonometry, R style

Now that we’ve discussed the basics of trigonometry, let’s discuss trigonometry, R style. Here are some examples of the three basic trigonometric functions executed in R:

> cos(20)
[1] 0.4080821
> tan(20)
[1] 2.237161
> sin(20)
[1] 0.9129453

In this example, I used R’s built-in cos(), tan() and sin() functions to calculate the cosine, tangent, and sine of a given angle, respectively. Since these functions are built-in to R, there’s no need to install any extra packages to utilize these functions

You may be wondering if these functions can calculate the sine/cosine/tangent of an angle given specific side lengths (as shown in the illustration above). The answer to that is no, but then again, you can easily calculate these trigonometric ratios using simple division. For instance, in the illustration above, the sine of the 52 degree angle in the triangle is ~0.82 (rounded to two decimal places) because the opposite/hypotenuse length ratio is 10/12.2.

So, how does R calculate trigonometric ratios of certain angles? They use a little something called radians, which I’ll explain more right here.

Radians

What are radians, exactly? Well, when measuring angles in shapes, there are two metrics we use-degrees (which I’m sure you’re familiar with) and radians.

Let’s take this illustration of a circle:

As you see, the length of this circle’s radius is 8-the arc of the circle that is formed also has a length of 8. Therefore, the angle that is formed at the circle’s center has a length of 1 radian.

Now, how do you convert radians to degrees. Easy-1 degree is equal to pi/180 radians.

Why are radians expressed as a ratio of pi? This is because, for one, the circumference of a circle is 2pi times the circle’s radius. For two, the length of a full circle is 360 degrees-or 2pi radians; similarly, the length of a half-circle is 180 degrees-or pi radians.

Now, let’s analyze how radians work in the context of our R examples (all of which used 20 degree angles).

I mentioned earlier that 1 degree equals pi/180 radians, so 20 degrees would be 20*(pi/180) radians, which when converted to simplest form, equals pi/9 radians, which is then used to calculate various trigonometric ratios for any given angle.

The degrees-to-radians formula is so versatile that in R, it can be used on any integer, positive or negative. Check out some of these examples:

> cos(-3)
[1] -0.9899925
> sin(355)
[1] -3.014435e-05
> tan(15000)
[1] -1.98891
> cos(412)
[1] -0.8998537
> sin(-1333)
[1] -0.8218865
> tan(10191)
[1] -0.338695

Yes, I can use R’s basic trigonometric functions on a wide variety of integers and obtain valid results from each integer (although let’s be real, where will you ever find a 15000 degree angle)?

Thanks for reading,

Michael

Let’s Make Our Own Python Game Part Two: Creating The Bingo Card (Python Lesson 50)

Advertisements

Hello everyone,

Michael here, and in today’s post, I’ll pick up where we left off last time. This time, we’ll create the BINGO board!

In case you forgot where we left off last time, here’s the code from the previous post:

Now let’s continue making our BINGO game. In today’s post, we’ll focus on generating the BINGO card.

Outlining the BINGO board

As you may recall from the previous post, all we really did was create one giant lime green square:

It’s a good start, but quite frankly, it won’t work for our BINGO board. How can we improve the look of our BINGO card? Add some borders!

Here’s the code to add the borders:

for xcoord in x:

for ycoord in y:
screen.blit(square1.surf, (xcoord, ycoord))
pygame.draw.rect(screen, pygame.Color("Red"), (xcoord, ycoord, 75, 75), width=3)

Pay attention to the last line in these nested for loops-the one that contains the pygame.draw.rect() method. What this method does is draw a square border around each square in the BINGO card. This method takes four parameters-the game screen (screen in this case), the color you want for your border (this could be a color name or hex code), a four-integer tuple that contains the x and y-coordinates for the square along with the square’s size, and the thickness of the border in pixels. Let’s see what we get!

The BINGO card already looks much better-now we need to fill it up!

  • Just a tip-for the border generation process to work, make the dimensions of the border the same as the dimensions of the square generated. In this case, we used 75×75 borders since the squares are 75×75 [pixels].

B-I-N-G-O

Now, what does every good BINGO card need. The B-I-N-G-O on the top row, of course!

Here’s how we can implement that:

font = pygame.font.Font('ComicSansMS3.ttf', 32) 

if ycoord == 75:
if xcoord == 40:
text = font.render('B', True, (0,0,0), (50,205,50))
textRect = text.get_rect()
textX = (75 - textRect.width) // 2
textY = (75 - textRect.height) // 2
screen.blit(text, (xcoord + textX, ycoord + textY))
elif xcoord == 115:
text = font.render('I', True, (0,0,0), (50,205,50))
textRect = text.get_rect()
textX = (75 - textRect.width) // 2
textY = (75 - textRect.height) // 2
screen.blit(text, (xcoord + textX, ycoord + textY))
elif xcoord == 180:
text = font.render('N', True, (0,0,0), (50,205,50))
textRect = text.get_rect()
textX = (75 - textRect.width) // 2
textY = (75 - textRect.height) // 2
screen.blit(text, (xcoord + textX, ycoord + textY))
elif xcoord == 255:
text = font.render('G', True, (0,0,0), (50,205,50))
textRect = text.get_rect()
textX = (75 - textRect.width) // 2
textY = (75 - textRect.height) // 2
screen.blit(text, (xcoord + textX, ycoord + textY))
elif xcoord == 330:
text = font.render('O', True, (0,0,0), (50,205,50))
textRect = text.get_rect()
textX = (75 - textRect.width) // 2
textY = (75 - textRect.height) // 2
screen.blit(text, (xcoord + textX, ycoord + textY))

And how does all of this code work? Let me explain.

The B-I-N-G-O letters will usually go on the top row of the card. The line if ycoord == 75 will ensure that the letters are only drawn on the top row of the card since point 75 on the y-axis (on our gamescreen) corresponds to the top row of the card.

Since there are five letters in B-I-N-G-O, there are also five conditional statements that account for the five letters we’ll be drawing onto the top five squares. There are also five x-coordinates to account for (40, 115, 180, 255, 330).

But before we actually start drawing the text, I want you to take note of this line-font = pygame.font.Font('ComicSansMS3.ttf', 32). This line allows you to set the font you wish to use for text along with its pixel size-in this case, I wanted to use Comic Sans with a 32 pixel size (hey, this isn’t a business project, so I can have some fun with fonts). Unforntuantely, if you want custom fonts for your game, you’ll need to download a TFF (TrueType font) file and save it to your local drive.

  • Another tip-if the TFF file is saved in the same directory as your game file, you just need the TFF file name as the first parameter of the pygame.font.Font() method. Otherwise, you’ll need the whole filepath as the first parameter.

As for the five conditional statements, you’ll notice that they each have the same five lines. Let’s explain them one-by-one.

First off we have our text variable, which contains the text we want to write to the square. The value of this variable is stored as the results of the font.render() method, which takes four parameters-the text you wish to display, whether you want to antialias the text (True will antialias the text, which simply results in a smoother text appearance), a 3-integer tuple representing the color of the text in RGB form, and another 3-integer tuple representing the color of the background where you wish to apply the text-also in RGB form.

  • For a good look, be sure to make the backgound color the same as the square’s color.

Next we have our textRect variable, which represents the invisible rectangle (or square) that contains the text we will render.

Upon initial testing of the text rendering, I noticed that my text wasn’t being centered in the appropriate square. The testX and testY variables are here to fix it by using the simple formula (square size-rectangle width/height) // 2 (use width for the x-center point and height for the y-center point). What this does is help gather the textRect x-center and y-center to in turn help center the text within the square. However, these two variables alone won’t center the text correctly, and I’ll explain why shortly.

  • In Python, the // symbol indicates that the result of the division will be rounded down to the nearest whole number, which helps when dealing with coordinates and text centering.

Last but not least, we have our wonderful screen.blit() method. In this context, the screen.blit() method takes two parameters-the text you want to display (text) and a 2-integer tuple denoting the coordinates where you wish to place the text.

Simple enough, right? However, take note of the coordinates I’m using here-(xcoord + testX, ycoord + testY). What addind the testX and testY coordinates will do is help center the text within the square.

After all our text rendering, how does the game look now?

Wow, our BINGO game is starting to come together. And now, let’s generate some BINGO numbers!

B….1 to 15, I….16 to 30 and so on

Now that the B-I-N-G-O letters are visible on the top of our card, the next thing we should do is fill our card up with the appropriate numbers.

For anyone who’s played BINGO, you’ll likely be familiar with which numbers end up on which spots on the card. In any case, here’s a refresher on that:

  • B (1 to 15)
  • I (16 to 30)
  • N (31 to 45)
  • G (46 to 60)
  • O (61 to 75)

With these numbering rules in mind, let’s see how we can implement them into our code and show them on the card! First, inside the game while loop, let’s create five different arrays to hold our BINGO numbers, appropriately titled B, I, N, G, and O.

from random import seed, randint 

[meanwhile, inside the while game loop...]

    B = []
seed(10)
for n in range(5):
value = randint(1, 15)
B.append(value)

I = []
seed(10)
for n in range(5):
value = randint(16, 30)
I.append(value)

N = []
seed(10)
for n in range(5):
value = randint(31, 45)
N.append(value)

G = []
seed(10)
for n in range(5):
value = randint(46, 60)
G.append(value)

O = []
seed(10)
for n in range(5):
value = randint(61, 75)
O.append(value)

Also, don’t forget to include the line from random import seed, randint (I’ll explain why this is important) on the top of the script and out of the while game loop.

As for the array creation, please keep that inside the while game loop! How does the BINGO array creation work? First of all, I first created five empty arrays-B, I, N, G, and O-which will soon be filled with five random numbers according to the BINGO numbering system (B can be 1 to 15, I can be 16 to 30, and so on).

Now, you’ll notice that there are five calls to the [random].seed() method, and all of the calls take 10 as the seed. What does the seed do? Well, in Python (and many other programming languages), random number generation isn’t truly “random”. The seed value can be any positive integer under the sun, but your choice of seed value determines the sequence of random numbers that will be generated-hence why random number generation (at least in programming) is referred to as a deterministic algorithm since the seed value you choose determines the sequence of numbers generated.

  • If you don’t have a specific range of random number you want to generate, you’ll get a sequence of random numbers Python’s random number generator chooses to generate.
  • You simply need to write seed() to initialize the random seed generator-the random. part is implied.

After the random seed generators are set up, there are five different loops that append five random numbers to each array-the line for n in range(5) ensures that each array has a length of 5. Inside each loop I have utilized the [random].randint() function and passed in two integers as parameters to ensure that I only recieve random numbers in a specific range (such as 1 to 15 for array B).

Now, let’s display our numbers on the BINGO card! Here’s the code to run (and yes, keep it in the while game loop):

 if ycoord != 75:

if xcoord == 40:
for num in B:
text = font.render(str(num), True, (0,0,0), (50,205,50))
textRect = text.get_rect()
textX = (75 - textRect.width) // 2
textY = (75 - textRect.height) // 2
screen.blit(text, (xcoord + textX, ycoord + textY))

if xcoord == 115:
for num in I:
text = font.render(str(num), True, (0,0,0), (50,205,50))
textRect = text.get_rect()
textX = (75 - textRect.width) // 2
textY = (75 - textRect.height) // 2
screen.blit(text, (xcoord + textX, ycoord + textY))

if xcoord == 180:
for num in N:
text = font.render(str(num), True, (0,0,0), (50,205,50))
textRect = text.get_rect()
textX = (75 - textRect.width) // 2
textY = (75 - textRect.height) // 2
screen.blit(text, (xcoord + textX, ycoord + textY))

if xcoord == 255:
for num in G:
text = font.render(str(num), True, (0,0,0), (50,205,50))
textRect = text.get_rect()
textX = (75 - textRect.width) // 2
textY = (75 - textRect.height) // 2
screen.blit(text, (xcoord + textX, ycoord + textY))

if xcoord == 330:
for num in O:
text = font.render(str(num), True, (0,0,0), (50,205,50))
textRect = text.get_rect()
textX = (75 - textRect.width) // 2
textY = (75 - textRect.height) // 2
screen.blit(text, (xcoord + textX, ycoord + textY))

Confused at what this code means? The last five lines in each if statement essentially do the same thing we were doing when we were rendering the B-I-N-G-O on the top row of the card (rendering and centering the text on each square). However, since we don’t want the numbers on the top row of the card, we include the main if statement if ycoord != 75 because this represents all squares that aren’t on the top row of the card.

Oh, one thing to note about rendering the numbers on the card-simply cast the number variable (num in this case) as a string/str because pygame won’t render anything other than text of type str.

With all that said, let’s see what our BINGO card looks like:

Well, we did get correct number ranges, but this isn’t the output we want. Time for some debugging!

D-E-B-U-G

Now, how do we fix this board to get distinct numbers on the card? Here’s the code for that!

First, let’s fix the BINGO number array creation process:

    B = []


value = sample(range(1, 15), 5)
for v in value:
B.append(v)

I = []

value = sample(range(16, 30), 5)
for v in value:
I.append(v)

N = []

value = sample(range(31, 45), 5)
for v in value:
N.append(v)

G = []

value = sample(range(46, 60), 5)
for v in value:
G.append(v)

O = []

value = sample(range(61, 75), 5)
for v in value:
O.append(v)

In this code, I first added the sample method to the from random import ... line as we’ll need this method to ensure we get an array of distinct random numbers.

   else:

y2 = [150, 225, 300, 375, 450]
if xcoord == 40:
for num, ycoord in zip(B, y2):
text = font.render(str(num), True, (0,0,0), (50,205,50))
textRect = text.get_rect()
textX = (75 - textRect.width) // 2
textY = (75 - textRect.height) // 2
screen.blit(text, (xcoord + textX, ycoord + textY))

if xcoord == 115:
for num, ycoord in zip(I, y2):
text = font.render(str(num), True, (0,0,0), (50,205,50))
textRect = text.get_rect()
textX = (75 - textRect.width) // 2
textY = (75 - textRect.height) // 2
screen.blit(text, (xcoord + textX, ycoord + textY))

if xcoord == 180:
for num, ycoord in zip(N, y2):
text = font.render(str(num), True, (0,0,0), (50,205,50))
textRect = text.get_rect()
textX = (75 - textRect.width) // 2
textY = (75 - textRect.height) // 2
screen.blit(text, (xcoord + textX, ycoord + textY))

if xcoord == 255:
for num, ycoord in zip(G, y2):
text = font.render(str(num), True, (0,0,0), (50,205,50))
textRect = text.get_rect()
textX = (75 - textRect.width) // 2
textY = (75 - textRect.height) // 2
screen.blit(text, (xcoord + textX, ycoord + textY))

if xcoord == 330:
for num, ycoord in zip(O, y2):
text = font.render(str(num), True, (0,0,0), (50,205,50))
textRect = text.get_rect()
textX = (75 - textRect.width) // 2
textY = (75 - textRect.height) // 2
screen.blit(text, (xcoord + textX, ycoord + textY))

Remember the else block where we were rendering the text? Well, I made a few changes to the code. I first added another array of y-coordinates (y2) that is essentially the same as the y array without the number 75. Why did I remove the 75? I simply wanted to ensure that no numbers are drawn on the top row of the card, and 75 represents the y-coordinate that displays the top row of the card.

While iterating through our five BINGO number arrays aptly titled B, I, N, G and O, we’re also iterating through the y2 array to ensure that the correct numbers are rendered in the correct squares.

  • In case you’re wondering about the zip() line in our for loop, the zip() function allows us to iterate through multiple arrays at once in a for loop. However, the zip() function only works if the arrays you’re looping through are the same length.
  • If you want to iterate through multiple arrays of unequal lengths, include this import at the beginning of your script-from itertools import zip_longest. The zip_longest() function will allow you to iterate through multiple arrays of unequal length. Also remember to pip install the itertools package if you don’t have it on your laptop already.

Using our revised code, let’s see what our BINGO card looks like now!

Wow, the BINGO card already looks much better! However, if you’re familiar with BINGO, you know the center square in the N column is considered a “free space”. Let’s reflect this with the addition of one simple line of code:

N[2] = 'Free'

This line will replace the middle element in the N array with the word Free, which in turn will display the word Free on the center of the BINGO card:

Nice work!

Testing the card

Now that we’ve generated quite the good-looking BINGO card, the last thing we’ll need to do is test it to ensure we get a new card each time we open the game!

Here’s what our card currently looks like:

And let’s see what happens we we close and restart the game:

It looks like we got the same card. Now, playing with the same card every time would be a quite boring, right? How do we fix this bug? Here’s some code to do so (and keep in mind this is just one way to solve the problem):

possibleSeeds = []

value = sample(range(1, 10000), 5)
for v in value:
possibleSeeds.append(v)

meanwhile, inside the game loop

...

seed(possibleSeeds[0])

To solve the BINGO card generation bug, I used the same array generating trick I used for generating the BINGO arrays which is gather a specific number of integers from a specific integer range and use a loop to create a 5-element integer array. This time, I used integers ranging from 1 to 10000.

Inside the game loop, I set the random seed to the first element of the possibleSeeds array. Why did I do this? When I set my seed() to 10, I managed to see the same BINGO card each time I started the game because since the seed() value was fixed, the same sequence of random numbers are generated each time because using the same seed() each time you run a random number generator will give you the same sequence of random numbers each time it’s run. However, using the first element of the possibleSeeds array won’t give you the same random number sequence (and in turn, the same card) each time because the possibleSeeds array generates a sequence of five different integers with each iteration. Since you get a different random number sequence each time, the random number generation seed will be different each time, which in turn results in a different BINGO card generated each time the game is run.

  • Keep the seed() method inside the game loop, but keep the possibleSeeds array outside of the game loop because inserting the array into the game loop will generate random 5-integer sequences non-stop, which isn’t a desirable outcome.

Now, let’s see if our little trick worked. Let’s try running the game:

Now let’s close this window and try running the game again!

Awesome-we got a different BINGO card! How about another test run-third time’s the charm after all!

Nice work. Stay tuned for the next part of this game development series where we will create the mechanism to call out different BINGO “balls”.

Also, here’s a Word Doc file with our code so far (WordPress won’t let me upload PY files)-

Thanks for reading,

Michael

Let’s Make Our Own Python Game Part One: Getting Started (Python Lesson 49)

Advertisements

Hello loyal readers,

I hope you all had a wonderful and relaxing holiday with those you love-I know I sure did. I did promise you all new and exciting programming content in 2024, so let’s get started!

My first post of 2024 (and first several posts of 2024 for that matter) will do something I haven’t done on this blog in its nearly 6-year existence-game development! Yup, that’s right, we’ll learn how to make a simple game using Python’s pygame package. And yes, this game will include graphics (so we’re making something way cooler than a simple text-based blackjack game or something like that).

Let’s begin!

Setting ourselves up

Before we even start to design our game, let’s install the pygame package using the following line either on our IDE or command prompt-pip install pygame.

Next, let’s open our IDE. You could technically use Jupyter notebook to start creating the game, but for something like game creation that utilizes graphics (and likely lots of code) I’d suggest an IDE like Spyder.

Now, where do we begin?

To start, here are the first three lines of code we should include in our script:

import pygame
from pygame.locals import *
import sys

What game will I teach you how to program? Well, in this series of posts, we’ll learn to make our own BINGO clone.

Why BINGO? Well, compared to many other games I could possibly teach you to program, BINGO seemed like a relatively easy first game to learn to develop as it doesn’t involve multiple levels, much scoring, health tracking, or final bosses (though we could certainly explore games that involve these concepts later on).

Let’s start coding!

First off, since we are programming a BINGO game, we’ll need to draw squares. 30 of them, to be precise, as simple BINGO games utilize a 5×5 card along with five squares at the top that contain the letters B, I, N, G, and O.

Seems simple enough to understand right? Let’s see how we code it!

class Square(pygame.sprite.Sprite):

def __init__(self):

super(Square, self).__init__()


self.surf = pygame.Surface((75, 75))


self.surf.fill((50, 205, 50))

self.rect = self.surf.get_rect()



pygame.init()



screen = pygame.display.set_mode((800, 600))



square1 = Square()

First of all, to draw the BINGO squares, we’ll first need to create a Square class and pass in the pygame.sprite.Sprite parameter into it like so-class Square(pygame.sprite.Sprite).

What is the Sprite class in pygame? For those who are familiar with fanatsy works (e.g. Shrek, Lord of the Rings), a sprite is a legendary mythical creature such as a pixie, fairy, or elf (among others). In pygame a sprite simply represents a 2D image or animation that is displayed on the game screen-like the squares we’ll need to draw for our BINGO board.

The next line-the one that begins with super-allows the Square class to inherit all of the methods and capabilites of the Sprite class, which is necessary if you want the squares drawn on the game screen.

The following three lines set the drawing surface (and in turn, the size) of the square, set the color of each square on the gameboard using RGB color coding (yes, you can make the squares different colors, but I’m keeping it simple and coloring all the squares lime green), and get the rectangular area of each square, respectively.

The next two lines initiate the instace of the game-using the line pygame.init()-and set the size of the screen (in pixels). In this case, we’ll use an 800×600 pixel screen.

The last line initiates a square object for us to draw. The interesting thing to note here is that even though we’ll ultimately need to draw 30 squares for our BINGO board, we only need one square object since we can draw that same square object 30 different times in 30 different places.

Even with all this code, we’ll still need to actually draw the squares onto our game screen-this code just ensures that we have the ability to do just that (it doesn’t actually take care of the graphics drawing).

Let’s run the game!

Now that we have created the squares for our BINGO game and imported the necessary packages, let’s figure out how to get our game running! Check out this chunk of code that helps us do just that!

gameOn = True


while gameOn:


for event in pygame.event.get():

if event.type == KEYDOWN:


if event.key == K_BACKSPACE:

gameOn = False




elif event.type == QUIT:

gameOn = False

First, we have our boolean variable gameOn, which indicates whether or not our game is currently running (True if it is, False if it isn’t).

The while loop that follows is a great example of event handling (I think this is the first time I mention it on this blog), which is the process of what your program should do in various scenarios, or events. This while loop will keep running as long as gameOn is true (in other words, as long as the game is running).

You’ll notice two event types that will shut the game down, KEYDOWN and QUIT. In the case of KEYDOWN, the game will shut down only if the backspace key is pressed. In the case of the QUIT event, the game will quit if the user presses the X close button on the window. However, something to note about the QUIT event is that pressing X alone doesn’t quit the game-I know because I tried using the X button to quit the game and ran into an unresponsive window that I ended up force-quitting. Don’t worry, I’ll explain how to quit the game properly later in this post.

Drawing the squares

Now that we have a means to keep our game running (or close it if we so choose), let’s now draw the squares onto the gameboard. Here’s the code to do so:

screen.blit(square1.surf, (40, 75))

screen.blit(square1.surf, (115, 75))
screen.blit(square1.surf, (180, 75))
screen.blit(square1.surf, (255, 75))
screen.blit(square1.surf, (330, 75))
screen.blit(square1.surf, (40, 150))
screen.blit(square1.surf, (115, 150))
screen.blit(square1.surf, (180, 150))
screen.blit(square1.surf, (255, 150))
screen.blit(square1.surf, (330, 150))
screen.blit(square1.surf, (40, 225))
screen.blit(square1.surf, (115, 225))
screen.blit(square1.surf, (180, 225))
screen.blit(square1.surf, (255, 225))
screen.blit(square1.surf, (330, 225))
screen.blit(square1.surf, (40, 300))
screen.blit(square1.surf, (115, 300))
screen.blit(square1.surf, (180, 300))
screen.blit(square1.surf, (255, 300))
screen.blit(square1.surf, (330, 300))
screen.blit(square1.surf, (40, 375))
screen.blit(square1.surf, (115, 375))
screen.blit(square1.surf, (180, 375))
screen.blit(square1.surf, (255, 375))
screen.blit(square1.surf, (330, 375))
screen.blit(square1.surf, (40, 450))
screen.blit(square1.surf, (115, 450))
screen.blit(square1.surf, (180, 450))
screen.blit(square1.surf, (255, 450))
screen.blit(square1.surf, (330, 450))

pygame.display.flip()

Even though you’ll only need to create one square object, you’ll need to draw that object 30 different times since the BINGO board will consist of 30 squares drawn in a 6×5 matrix. To draw the squares, you’ll need to use the following method-screen.blit(square1.surf, (x-coordinate, y-coordinate). The screen.blit(...) method drawes the squares onto the screen and it takes two parameters-the square1.surf, which is the surface of the square and a two-integer tuple stating the coordinates where you want the square placed (x-coordinate first, then y-coordinate).

After the 30 instances of the screen.blit() method, the pygame.display.flip() method is called, which simply updates the game screen to display the 30 squares. You might’ve thought the screen.blit() method already accomplishes this, but this method simply draws the squares while the pygame.display.flip() method updates the game screen to ensure the squares are present.

Quitting the game

As I mentioned earlier in this post, I’ll show you how to properly quit the game. Here are the two lines of code needed to do so:

pygame.quit()

sys.exit()

To properly end the pygame session, you’ll need to include these two commands in your code. Why do you need them both? Wouldn’t one command or the other work?

You need both commands because the pygame.quit() method simply shuts down the active pygame module while the sys.exit() method proprely shuts down the entire window.

And now, let’s see our work!

Now that we’ve got the basic game outline set up, let’s see our work by running our script!

As you see here, we simply have one giant lime-green square. However, that lime green square consists of the 30 squares we drew earlier-the squares are simply drawn on top of each other, hence why the output looks like one big square. Don’t worry, in the next post we’ll make this square look more like a BINGO board!

A small coding improvement

As you noticed earlier in this post, I was calling the screen.blit() method 30 times while drawing the squares. However, there is a much better way to accomplish this:

x = [40, 115, 180, 255, 330]

y = [75, 150, 225, 300, 375, 450]

for xcoord in x:
for ycoord in y:
screen.blit(square1.surf, (xcoord, ycoord))

In this example, I placed all possible x and y coordinates into arrays and drew each square by looping through the values in both arrays. Here’s the output of this improved approach:

As you see, not only did we improve the process for drawing the squares onto the game screen, but we also got the same result we did when we were calling the screen.blit() method 30 times.

For your reference, the code

Just in case you want it, here’s the code we used for our game development in this post (and we will certainly change it throughout this series of posts). I’m copying the code here since WordPress won’t let me upload .PY files:

import pygame

from pygame.locals import *
import sys

class Square(pygame.sprite.Sprite):
def __init__(self):
super(Square, self).__init__()

self.surf = pygame.Surface((75, 75))

self.surf.fill((50, 205, 50))
self.rect = self.surf.get_rect()

pygame.init()

screen = pygame.display.set_mode((800, 600))

square1 = Square()

gameOn = True

while gameOn:
for event in pygame.event.get():

if event.type == KEYDOWN:

if event.key == K_BACKSPACE:
gameOn = False

# Check for QUIT event
elif event.type == QUIT:
gameOn = False

x = [40, 115, 180, 255, 330]
y = [75, 150, 225, 300, 375, 450]

for xcoord in x:
for ycoord in y:
screen.blit(square1.surf, (xcoord, ycoord))

# Update the display using flip
pygame.display.flip()

pygame.quit()
sys.exit()

Thanks for reading, and I look forward to having you code along with me in 2024!

Michael

And Now Let’s Create Some AI Art (Midjourney Version)(AI pt. 15)

Advertisements

Hello everybody,

Michael here, and for my final post of 2023, I wanted to try something a little different! Usually on this blog, I like to only use tools that are open-source (aka free)-this way, all of you can follow along with my tutorials.

However, for this post I wanted to try something different-Midjourney! Just like DALLE-2, Midjourney is an AI text-to-art generator (you may recall that I explored DALLE-2 in the post /And Now Let’s Create Some AI Art! (AI pt.6)). However, unlike DALLE-2, Midjourney cannot be used for free. But since I wanted to fool around with Midjourney, I thought I could do post on it for all of my loyal readers!

Let’s begin!

Five fast facts about Midjourney

In my intro paragraph, I did mention that Midjourney was an AI text-to-art generator. Here are five more fast facts about Midjourney:

  • It works in a very similar manner to DALLE-2 in the sense that both tools are text-to-art generators.
  • However, unlike DALLE-2, Midjourney wasn’t developed by OpenAI (it was created by Midjourney Labs).
  • As of this writing, Midjourney is currently in open beta mode, as has been the case since its creation in July 2022.
  • Midjourney utilitzes Discord as an interface to generate its AI art.
  • As long as you’re on a paid subscription, you can generate as many images as you want (unlike DALLE-2, where the free trial limits you to a certain number of image generations a month)

Setting up Midjourney

Before we start polaying aroung with the magic of Midjourney, you’ll need two things to set it up:

  • A Midjourney subscription
  • A Discord account (I’ll explain this later)

If you need assistance setting up Midjourney, please follow the stpes in this link-https://docs.midjourney.com/docs/quick-start.

Now, why would you need a Discord account? See, even though Midjourney is separate from Discord, I mentioned earlier that Midjourney currently uses Discord as its interface to generate AI art via a Midjourney Discord bot (which makes Midjourney a bit more convoluted to set up than DALLE-2).

And Now Let’s Make Some Midjourney AI Art

Once you’ve gotten Midjourney set up, let’s get started creating our very own AI art!

First, let’s open up our Discord Midjourney bot:

When you open up the bot, you’ll see the bot’s homepage. To start creating Midjourney art, go to any of the channels that start with newbies.

As you can see here, I am currently in the newbies-122 channel, which is where I can start generating AI art.

To begin with the AI art generation, I will first run the /imagine command and then type the prompt A Christmas card featuring Santa Claus and his reindeer saying "Happy Holidays To You" that is drawn in pencil sketch with lots of color. Let’s see what Midjourney spits out!

As you can see, after a few minutes, Midjourney will (just like DALLE-2) spit out four different images based off the prompt you submitted. Also, if you look at each image closely, you’ll see that Midjourney, like DALLE-2, doesn’t have the hang of generated coherent text (but it sure is good at generating gibberish).

However, you may be wondering what all of these buttons below the generated images do. Allow me to explain:

  • The U1-U4 buttons allow you to output only one of the four generated images. U1 represents the image in the upper left hand corner, U2 represents the image in the upper right hand corner, U3 represents the image in the lower left hand corner and U4 represents the image in the the lower right hand corner.
  • The V1-V4 buttons also represent the four images (V1=upper left hand image, V2=upper right hand image, V3=lower left hand image, V4=lower right hand image) but unlike the U1-U4 buttons, these buttons allow you to modify the prompt on an individual image-or as Midjourney calls it, “remixing” each image.
  • The refresh button allows you to generate four different images with the same prompt.
  • A note about these buttons: you can also use them for other users’ images, not just your own (I mean, it is fun to see other users’ prompts).

A little note about the Midjourney interface

If you’re playing around with Midjourney, you’ll notice that you’re far from the only one generating AI art. In fact, there are certianly going to be thousands of users at any given trying to generate their own AI art. While I think it’s neat that anyone can log onto Midjourney at any time, it also makes the user interface less user-friendly since if you want to find your generated art, you’ll need to do quite a bit of scrolling!

This image was taken at 11:32AM, so this should give you an idea as to how many people are generating Midjourney art at once!

Luckily, if you want to easily find your Midjourney art, head on over to https://www.midjourney.com/explore, log in to your Midjourney account, and navigate to My Images:

In this interface, I have all of the images I generated through Midjouney throughout the duration of my subscription. This way, in case I want to find any image I generated on Midjourney, all I need to do is go here.

  • As you can see from the screenshot above, the functionality to /imagine new prompts from this interface has yet to be implemented as of this writing (December 2023).

You can even click on an image to see its prompt in case you want to use and/or modify that prompt for a future image generation:

As you see here, this image was generated as part of the Christmas card prompt I wrote earlier. Personally, even though I asked the image to generate a Happy Holidays To You card featuring Santa and his reindeer, I instead get whatever this is. Santa has one reindeer in this image (and its wearing what I think is a necklace of leaves for some reason). The reindeer has six legs. There is no text in this image. The other creatures in this images look like two gerbils and two gremlin-things (it would be a stretch to call them elves). At least there’s something resembling a Christmas village in the background.

Let’s try some other scenarios

Next up, let’s try some other Midjourney scenarios! First up, let’s see Midjourney’s capabilities for generating realistic looking photographs.

Since it’s the holidays, let’s try this prompt next-/imagine A Nikon photo of the Avengers at a Christmas party at Avengers tower. Iron Man, Black Widow, Hulk, Captain America, Thor, and Hawkeye are there. 16:9 aspect ratio

Yes, Midjourney can even set aspect ratios and camera model-styles for the AI images it generates. Let’s take a look at the images we got from this prompt:

Throughout these four images, the only character that Midjourney seems to get right each time is Iron Man. Marvel fans like myself will liekly recognize several mistakes throughout these four images:

All of this just goes to show you that Midjourney, like DALLE-2, doesn’t have the best attention to detail when generating AI art.

The images I got when I used this prompt-/imagine A Nikon photo of Kang The Conqueror and Thanos at a Christmas Party, 16:9 aspect ratio-weren’t much better. Here’s one such image:

I mean, at least Midjourney made Thanos purple, but Kang the Conqueror is another story entirely (unless he happens to be one of Kang’s many variants).

Let’s try generating AI people!

So now that we’ve explored some AI art-generation scenarios, let’s try something different! As you may recall from the post And Now Let’s Create Some AI Art! (AI pt.6), DALLE-2 wasn’t the best when it came to generating images of real people. However, let’s see if Midjourney is up for the task!

Here’s the prompt I’ll use-/imagine Stephen Curry drawn in Simpsons style

And here are the generated images:

Not gonna lie, I’m surprised that not only did Midjourney generated a pretty accurate-looking Stephen Curry, but also that it generated the correct Golden State Warriors logo and generated the text Golden State Warriors correctly. However, Midjourney didn’t really replicate the Simpsons art style and in the third image, it got Steph’s jersey number wrong (he wears a #30 jersey, not a #35-which is the number Kevin Durant wore during his stint with the Warriors).

Now, just as I did with my DALLE-2 experiment, I’ll try to generate an image of a female public figure and see where that goes. Here’s the prompt I used-/imagine A drawing of Margot Robbie in colored pencil sketch, and here’s the output:

Not gonna lie, but I’m amazed at how much Midjourney’s generated images actually resemble the real Margot Robbie. Unlike DALLE-2, which didn’t allow me to generate image of female public figures for some reason, Midjourney does allow for these types of image generations and does a scarily accurate job of it too.

And now, let’s go to an AI-generated place!

So, we’ve tested how well Midjourney can replicate pop culture and people, but let’s see how well it knows places. Here’s the prompt I’ll use-/imagine A neon rendering of Bicentennial Capitol Mall State Park in Nashville, TN-and here’s the output:

From these images, I see that Midjourney at least got the Capitol part right (Bicentennial Capitol Mall State Park does have a view of the Tennessee state capitol building from the park), but the four images generated look like they could be a part of Downtown DC, not Downtown Nashville. At least Midjourney included a park in each of these four images.

If you’re wondering what Bicentennial Capitol Mall State Park looks like, here’s a picture of it (more specifically, the amphitheater in all its glory):

This is just a small sampling of the things Midjourney is capable of, and although it doesn’t always have the best attention to detail (or greatest text-generation abilities), it is still an amazing AI tool-though it can never, ever, ever replace human creativity or talent (or your friendly neighborhood coding tutorial writer).

With all that said, thank you all for another wonderful year of programming and development (and I hope you learned something along the way). Have a happy and festive holiday season to you all, and I’ll see you in 2024 for another amazing year of development and learning! Keep calm and code on!

AI-generated Santa wishes you a happy holiday season!

Michael!

Python Lesson 48: Image Borders (AI pt. 14)

Advertisements

Hello everybody,

Michael here, and in today’s post, we’ll explore image border creation using OpenCV!

Let’s create some image borders!

Before we start creating image borders, here’s the image we’ll be using for this lesson:

This is an image of Fry and Bender pumpkins (two main characters from the animated sitcom Futurama if you weren’t familiar) that I created with a few Sharpie markers this past Halloween (creative I know).

Now, let’s read in our image to the IDE in RGB form:

import cv2

import matplotlib.pyplot as plt

pumpkins = cv2.imread(r'C:\Users\mof39\Downloads\20231031_231020.jpg', cv2.IMREAD_COLOR)
pumpkins = cv2.cvtColor(pumpkins, cv2.COLOR_BGR2RGB)

Now that we’ve done that, let’s add a simple yellow border around our image:

pumpkinsNormalized = pumpkins / 255.0


pumpkinsWithYellowBorder = cv2.copyMakeBorder(pumpkinsNormalized, 30, 30, 30, 30, cv2.BORDER_CONSTANT, value=[1, 1, 0])

plt.figure(figsize=(10, 10))
plt.imshow(pumpkinsWithYellowBorder)
plt.show()

To add a simple yellow rectangular border around our image, we’ll need to use the cv2.copyMakeBorder() method and include the following parameters:

  • The image where you will add a border
  • Four integers indicating the border’s thickness (in pixels) on the top, bottom, left, and right sides of the border, respectively
  • One of five possible OpenCV image border modes-in this case, I used the border mode cv2.BORDER_CONSTANT, which adds a simple colored rectangular border to the image.
  • A three-integer array indicating the border color in RGB notation (more on that shortly)

Now, you may see something unfamiliar above-pumpkinNormalized. This indicates that I have normalized the image. What does that mean?

In this case, normalizing an image means scaling all the pixels-and in turn, colors-to ensure that all of the pixels and colors in the image are using the same scale. This is important since I discovered that OpenCV has a bug (as of this writing) where when you try to add a border to an image, it will add a border to the greyscale version of the image (even if you read it into the IDE in RGB scale). Normalizing the image ensures that the border will be added to the RGB version of the image.

  • It likely goes without saying, but if you want the border on the RGB image, please remember to add the border to the normalized image, not the initial image you read into the IDE (even if you did convert it to RGB colorscale).

Now, as for the RGB color array, let’s dive into that. The array works the same way as other forms of RGB notation (e.g. RGB(210, 12, 12) represents the intensity of the red, green and blue colors) but with one key difference-the values used only range from 0 to 1 (including decimals between these two integers). The values still represent the intensity of the red, green and blue colors in the image, respectively, but the representation looks more like the percent of a certain color and less like an integer. In this example, since I wanted a simple yellow border on the image, I used the array [1, 1, 0] which is the same as saying RGB(255, 255, 0) because in both notations creating yellow requires full (or 100%) red and green but no blue.

Other border modes

Now, one thing to keep in mind with OpenCV’s image border modes is that, as of December 2023, there are no ways to make fun dotted/dashed/dotted-and-dashed borders yet (though of course, that could change).

However, aside from the simple cv2.BORDER_CONSTANT mode that creates a simple rectangular border around the image, there are four other OpenCV image border modes. Let’s explore one of them-cv2.BORDER_REFLECT, which adds a reflective border to the image. To change the border from a simple colored border to a reflective one, let’s change this one line of code:

pumpkinsWithReflectiveBorder = cv2.copyMakeBorder(pumpkinsNormalized, 40, 40, 40, 40, cv2.BORDER_REFLECT)

All I had to do to modify the code to get a reflective border was change this one line by changing the border thickness (30 to 40 pixels), removing the color (since this border mode doesn’t require a color), and changing the border mode to cv2.BORDER_REFLECT and, well, check out the image with a reflective border:

In this image, there are a few spots where the reflective border is hard to find, but it’s there (and quite prominent in the bottom side of the image where if you look close enough, you can see the reflections of the pumpkins.

Thank you,

Michael