Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
November 3, 2020 03:19 am GMT

Can We Fit Rock Paper Scissors in Python in a Tweet?

If youve followed me on this saga to shrink my original behemoth of a solution to Rock Paper Scissors, then you know weve moved 1,389 characters down to 864 by introducing modular arithmetic. Then, we shrunk the program again down to 645 characters through some refactoring. Now, were going to try to get the program down to the size of a tweet or 280 characters. Can it be done?

This article is not clickbait. It is absolutely possible to write Rock Paper Scissors in 280 characters, and I did it! That said, I dont think its possible without making some sacrifices to the original requirements.

At any rate, lets get started!

Where Did We Leave Off?

At this point in time, heres the latest version of the program:

import random# Generate default outcomechoices = ["Rock", "Paper", "Scissors"]pc_index = random.randint(0, 2)pc_choice = choices[pc_index]output = [f"I chose {pc_choice}", "You chose nothing.", "You lose by default."]# Play gameuser_pick = input("Choose Rock (0), Paper (1), or Scissors (2): ")if user_pick.isdecimal() and (user_index := int(user_pick)) in range(3):  user_choice = choices[user_index]  output[1:] = [    f"You chose {user_choice}",     [      "Tie!",       f"{user_choice} beats {pc_choice} - you win!",       f"{pc_choice} beats {user_choice} - I win!"    ][(user_index - pc_index) % 3]]# Share outcomeprint("
".join(output))
Enter fullscreen mode Exit fullscreen mode

Currently, our program sits comfortably at 644 characters, and its still very readable. Ultimately, what I want to do now is exploit a few things that can be found in my obfuscation articlenamely remove spaces and shorten variable names. In addition, were going to try a few tricks in this code golf thread. Lets get started!

Begin Compression

Throughout the remainder of this article, Ill be documenting my entire process for trying to shrink Rock Paper Scissors down to 280 characters (a.k.a. the size of a tweet).

As a quick warning, compressing code by hand is a long and messy process, and there are definitely better ways to go about it. That said, one of the things that I find missing in education is expert rationale. Im not considering myself an expert here, but I think itll be valuable to see my approach to problem solving.

And if nothing else, you can watch me struggle to get this done! Dont worry. I manage to get it down to tweet size not without a few bumps along the way.

Iterable Unpacking

One of the very first suggestions in that code golf thread is to take advantage of iterable unpacking when assigning variables. In our case, we have several variables assignments at the top that we might try merging. For example, we might take the following:

choices = ["Rock", "Paper", "Scissors"]pc_index = random.randint(0, 2)pc_choice = choices[pc_index]output = [f"I chose {pc_choice}", "You chose nothing.", "You lose by default."]
Enter fullscreen mode Exit fullscreen mode

And, turn it into this:

choices, pc_index, pc_choice, output = ["Rock", "Paper", "Scissors"], random.randint(0, 2), choices[pc_index], [f"I chose {pc_choice}", "You chose nothing.", "You lose by default."]
Enter fullscreen mode Exit fullscreen mode

Sadly, this doesnt really have the payoff I was expecting. Perhaps because the answer Im referencing uses a string as the iterable. That said, Im determined to squeeze some sort of payoff out of this, so Im going to try to restructure it:

*choices, pc_index, pc_choice = "Rock", "Paper", "Scissors", random.randint(0, 2), choices[pc_index]output = [f"I chose {pc_choice}", "You chose nothing.", "You lose by default."]
Enter fullscreen mode Exit fullscreen mode

Okay, so this was a bit of a let down, but it might help us later. Lets retain the original program for now and try something else!

Rewriting Input String

Since all of our choices are stored in a list, I figured we could try dynamically generating the input string. Perhaps that would be a bit cleaner. In other words, instead of writing this:

user_pick = input("Choose Rock (0), Paper (1), or Scissors (2): ")
Enter fullscreen mode Exit fullscreen mode

We could write something like this:

user_pick = input(f'{", ".join(choices)}! (0, 1, 2):')
Enter fullscreen mode Exit fullscreen mode

Now, thats some savings! Its not quite as explicit as the original, but were going for compression. Ill take 54 characters over 66 any day. Heres what the program looks like now:

import random# Generate default outcomechoices = ["Rock", "Paper", "Scissors"]pc_index = random.randint(0, 2)pc_choice = choices[pc_index]output = [f"I chose {pc_choice}", "You chose nothing.", "You lose by default."]# Play gameuser_pick = input(f'{", ".join(choices)}! (0, 1, 2):')if user_pick.isdecimal() and (user_index := int(user_pick)) in range(3):  user_choice = choices[user_index]  output[1:] = [    f"You chose {user_choice}",     [      "Tie!",       f"{user_choice} beats {pc_choice} - you win!",       f"{pc_choice} beats {user_choice} - I win!"    ][(user_index - pc_index) % 3]]# Share outcomeprint("
".join(output))
Enter fullscreen mode Exit fullscreen mode

Now, were down to 653! Dont worry; bigger changes are ahead.

Renaming the Import

At this point, I dont think there is any way around using the random library. That said, we can give it a name which could save us a couple characters. In other words, instead of rocking this:

import randompc_index = random.randint(0, 2)
Enter fullscreen mode Exit fullscreen mode

We could try something like this:

import random as rpc_index = r.randint(0, 2)
Enter fullscreen mode Exit fullscreen mode

Unfortunately, a change like this doesnt actually save us any characters: 45 no matter how you slice it! That said, this may have worked if we used random multiple times.

Renaming All Variables

At this point, I dont see any value in trying to play with the existing code. Lets go ahead and shrink all our variables and optimize on the other end if were still out of range. Heres what that would look like:

import random# Generate default outcomea = ["Rock", "Paper", "Scissors"]b = random.randint(0, 2)c = a[b]d = [f"I chose {c}", "You chose nothing.", "You lose by default."]# Play gamee = input(f'{", ".join(a)}! (0, 1, 2):')if e.isdecimal() and (f := int(e)) in range(3):  g = a[f]  d[1:] = [    f"You chose {g}",     [      "Tie!",       f"{g} beats {c} - you win!",       f"{c} beats {g} - I win!"    ][(f - b) % 3]]# Share outcomeprint("
".join(d))
Enter fullscreen mode Exit fullscreen mode

Now, were down to 470 characters! Hows that for savings? Were on our way to tweet size. Up next, lets try removing all the comments and empty lines.

Removing Comments and Empty Lines

Another quick change we can make is the removal off all comments and empty lines. That way, we just get a wall of code like this:

import randoma = ["Rock", "Paper", "Scissors"]b = random.randint(0, 2)c = a[b]d = [f"I chose {c}", "You chose nothing.", "You lose by default."]e = input(f'{", ".join(a)}! (0, 1, 2):')if e.isdecimal() and (f := int(e)) in range(3):  g = a[f]  d[1:] = [    f"You chose {g}",     [      "Tie!",       f"{g} beats {c} - you win!",       f"{c} beats {g} - I win!"    ][(f - b) % 3]]print("
".join(d))
Enter fullscreen mode Exit fullscreen mode

Unfortunately, this only buys us another 58 characters. Now, were sitting at 412 characters. How will we ever cut another 132 characters? Well, we can start trimming away spaces.

Eliminating Extraneous Spaces

At this point, Im starting to grasp at straws, so I figured we could try removing any unnecessary spaces. For example, do we really need spaces around our assignment operators? Of course not! See:

import randoma=["Rock","Paper","Scissors"]b=random.randint(0,2)c=a[b]d=[f"I chose {c}","You chose nothing.","You lose by default."]e=input(f'{", ".join(a)}! (0, 1, 2):')if e.isdecimal() and (f:=int(e)) in range(3):  g=a[f]  d[1:]=[f"You chose {g}",["Tie!",f"{g} beats {c} - you win!",f"{c} beats {g} - I win!"][(f-b)%3]]print("
".join(d))
Enter fullscreen mode Exit fullscreen mode

Now, this really does a number on the total count. Unfortunately, its not quite enough! Were only down to 348 characters. How will we shave off another 68? Well, since were on the topic of removing extra spaces, how about in our winner strings? Take a look:

import randoma=["Rock","Paper","Scissors"]b=random.randint(0,2)c=a[b]d=[f"I chose {c}","You chose nothing.","You lose by default."]e=input(f'{", ".join(a)}! (0, 1, 2):')if e.isdecimal() and (f:=int(e)) in range(3):  g=a[f]  d[1:]=[f"You chose {g}",["Tie!",f"{g} beats {c}you win!",f"{c} beats {g}I win!"][(f-b)%3]]print("
".join(d))
Enter fullscreen mode Exit fullscreen mode

That shaves off another four characters! Now, were just 64 away from freedom (i.e. 344 in total), and I have a couple ideas.

Crushing Branches

One idea I had was to see if we could reduce the if statement into a single line. To do that, well need to remove the creation of g. The result looks like this:

import randoma=["Rock","Paper","Scissors"]b=random.randint(0,2)c=a[b]d=[f"I chose {c}","You chose nothing.","You lose by default."]e=input(f'{", ".join(a)}! (0, 1, 2):')if e.isdecimal() and (f:=int(e)) in range(3):d[1:]=[f"You chose {a[f]}",["Tie!",f"{a[f]} beats {c}you win!",f"{c} beats {a[f]}I win!"][(f-b)%3]]print("
".join(d))
Enter fullscreen mode Exit fullscreen mode

Unfortunately, it seems g was doing a lot of the heavy lifting because this only shaved off a couple characters! Oh well, were down to 341. What else can we do?

Removing Redundant Brackets

At this point, Im really running out of options. That said, one idea I had was to remove any brackets that werent doing anything useful. For example, our a list stores the choices of Rock Paper Scissors. Surely, we can turn that into a tuple, right? Well, heres to saving two more characters:

import randoma="Rock","Paper","Scissors"b=random.randint(0,2)c=a[b]d=[f"I chose {c}","You chose nothing.","You lose by default."]e=input(f'{", ".join(a)}! (0, 1, 2):')if e.isdecimal() and (f:=int(e)) in range(3):d[1:]=[f"You chose {a[f]}",["Tie!",f"{a[f]} beats {c}you win!",f"{c} beats {a[f]}I win!"][(f-b)%3]]print("
".join(d))
Enter fullscreen mode Exit fullscreen mode

Unfortunately, similar ideas cant be used on the d list. That said, the list used in slice assignment can absolutely be trimmed:

import randoma="Rock","Paper","Scissors"b=random.randint(0,2)c=a[b]d=[f"I chose {c}","You chose nothing.","You lose by default."]e=input(f'{", ".join(a)}! (0, 1, 2):')if e.isdecimal() and (f:=int(e)) in range(3):d[1:]=f"You chose {a[f]}",["Tie!",f"{a[f]} beats {c}you win!",f"{c} beats {a[f]}I win!"][(f-b)%3]print("
".join(d))
Enter fullscreen mode Exit fullscreen mode

From here, though, there doesnt appear to be any lists that we can trim. That said, weve saved even more characters. Now were down to 337! Can we achieve 280?

Reducing Redundant Strings

At this point, I had an ephany! What if we referenced d when building the successful game string? In other words, why type out You chose twice when we can extract it from d? Heres what that would look like:

import randoma="Rock","Paper","Scissors"b=random.randint(0,2)c=a[b]d=[f"I chose {c}","You chose nothing.","You lose by default."]e=input(f'{", ".join(a)}! (0, 1, 2):')if e.isdecimal() and (f:=int(e)) in range(3):d[1:]=f"{d[1][:10]}{a[f]}",["Tie!",f"{a[f]} beats {c}you win!",f"{c} beats {a[f]}I win!"][(f-b)%3]print("
".join(d))
Enter fullscreen mode Exit fullscreen mode

Sadly, this bit of trickery actually costs us a character. Even in the best case scenario, wed only break even. So, what if instead we just saved You chose into a variable? Heres the result:

import randoma="Rock","Paper","Scissors"b=random.randint(0,2)c=a[b]g="You chose "d=[f"I chose {c}",f"{g}nothing.","You lose by default."]e=input(f'{", ".join(a)}! (0, 1, 2):')if e.isdecimal() and (f:=int(e)) in range(3):d[1:]=f"{g}{a[f]}",["Tie!",f"{a[f]} beats {c}you win!",f"{c} beats {a[f]}I win!"][(f-b)%3]print("
".join(d))
Enter fullscreen mode Exit fullscreen mode

Again, we lose a couple characters! Perhaps if these strings werent so short, wed get some type of savings, but this has been a huge letdown so far. Lets try something else!

Removing Function Calls

With 57 characters to shave, Im not sure well meet our goal. However, we can keep trying. For instance, I already know a place where we can trim a couple characters. And, I might even let it serve double duty! Lets go ahead and remove our call to range():

import randoma="Rock","Paper","Scissors"g=0,1,2b=random.choice(g)c=a[b]d=[f"I chose {c}","You chose nothing.","You lose by default."]e=input(f'{", ".join(a)}! {g}:')if e.isdecimal() and (f:=int(e)) in g:d[1:]=f"You chose {a[f]}",["Tie!",f"{a[f]} beats {c}you win!",f"{c} beats {a[f]}I win!"][(f-b)%3]print("
".join(d))
Enter fullscreen mode Exit fullscreen mode

By storing our choices as a tuple, we were able to delete our call to range(). At the same time, I saw an opportunity to replace part of the input string with our new tuple. Even better, we no longer have to use the randint() function of random. Instead, we can pull a random choice from our tuple. Talk about triple duty!

While this is very exciting, we only managed to save 8 characters (i.e. 329 in total). With 49 to go, Im not sure were going to hit our goal, but we can keep trying!

Converting Lists to Strings

One thing I thought we could try that might be slightly more drastic would be to overhaul d, so its a string rather than a list. In other words, if we can somehow get rid of the need for lists, we can drop the call to join() and print out the string directly. I think its worth a shot! Heres what that would look like:

import randoma="Rock","Paper","Scissors"g=0,1,2b=random.choice(g)c=a[b]d=f"I chose {c}
You chose nothing.
You lose by default."e=input(f'{", ".join(a)}! {g}:')if e.isdecimal() and (f:=int(e)) in g:d=f"{d[0:9+len(c)]}You chose {a[f]}
{['Tie!',f'{a[f]} beats {c}you win!',f'{c} beats {a[f]}I win!'][(f-b)%3]}"print(d)
Enter fullscreen mode Exit fullscreen mode

Despite this change, we only manage to save a single character. Instead, lets try something else!

Another idea I had was to try using a string list instead of a numeric list of g. One of the main problems with this program is that we have to validate the input. Perhaps the easiest way to validate it is to check for the three values we expect directly. In other words, make g store strings and convert them back to integers as needed:

import randoma="Rock","Paper","Scissors"*g,='012'b=random.randint(0,2)c=a[b]d=[f"I chose {c}","You chose nothing.","You lose by default."]e=input(f'{", ".join(a)}! {g}:')if e in g:d[1:]=f"You chose {(f:=a[int(e)])}",["Tie!",f"{f} beats {c}you win!",f"{c} beats {f}I win!"][(int(e)-b)%3]print("
".join(d))
Enter fullscreen mode Exit fullscreen mode

Surprisingly, this actually works! By cleaning up our if statement, we managed to save another 14 characters. Now, were down to 315. Can we remove 35 more?

Using Walrus Operators Everywhere

Another idea I had was to use walrus operators in the place of traditional assignment. Unfortunately, this doesnt seem to actually save any characters because the walrus operator has an extra character. In addition, it often has to be embedded in parentheses to work. That said, I gave it a shot for fun!

import random*g,='012'd=[f"I chose {(c:=(a:=('Rock','Paper','Scissors'))[(b:=random.randint(0,2))])}","You chose nothing.","You lose by default."]if (e:=input(f'{", ".join(a)}! {g}:')) in g:d[1:]=f"You chose {(f:=a[int(e)])}",["Tie!",f"{f} beats {c}you win!",f"{c} beats {f}I win!"][(int(e)-b)%3]print("
".join(d))
Enter fullscreen mode Exit fullscreen mode

Now, this is a complete nightmare! But, surprisingly, theres not a ton of additional baggage. As far as I can tell, this brings us back to 321 characters, and it works. So, lets backtrack!

Taking Advantage of the Import

While reading through that code golf thread, I found this gem. Rather than importing random and using it, I can import all things random and save a character:

from random import*a="Rock","Paper","Scissors"*g,='012'b=randint(0,2)c=a[b]d=[f"I chose {c}","You chose nothing.","You lose by default."]e=input(f'{", ".join(a)}! {g}:')if e in g:d[1:]=f"You chose {(f:=a[int(e)])}",["Tie!",f"{f} beats {c}you win!",f"{c} beats {f}I win!"][(int(e)-b)%3]print("
".join(d))
Enter fullscreen mode Exit fullscreen mode

Its not much, but were totally in not much territory. In other words, with 34 characters to go, a single character might be all we need!

Revisiting String Manipulation

A few sections ago I had mentioned that converting the lists to strings didnt pay off. Well, I found a way to make it work!

from random import*a="Rock","Paper","Scissors"*g,='012'b=randint(0,2)c=a[b]h=" chose "d=f"I{h}{c}
You{h}nothing
You lose by default"e=input(f'{", ".join(a)}{g}:')if e in g:d=f"I{h}{c}
You{h}{(f:=a[int(e)])}
{['Tie',f'{f} beats {c}you win',f'{c} beats {f}I win'][(int(e)-b)%3]}"print(d)
Enter fullscreen mode Exit fullscreen mode

Previously, I had some slicing nonsense that required a computation to get right. This time around, I figured we could replicate the strings exactly. Then, replace duplicate words with a variable. And, somehow, it worked!

Now, were sitting pretty at 301 characters. Were getting dangerously close to 280, and Im starting to get excited.

From here, I started thinking: what would happen if we started removing some of the duplication in the strings? Well, it didnt work out:

from random import*a="Rock","Paper","Scissors"*g,='012'b=randint(0,2)c=a[b]h,i,j,k,n="chose "," beats ","You ","I ","
"l=k+h+c+n+j+hd=f"{l}nothing{n+j}lose by default"e=input(f'{", ".join(a)}{g}:')if e in g:d=f"{l}{(f:=a[int(e)])}
{['Tie',f'{f+i+c+n+j}win',f'{c+i+f+n+k}win'][(int(e)-b)%3]}"print(d)
Enter fullscreen mode Exit fullscreen mode

Not only is this ridiculously unreadable, but its also a larger program than before. So, I scrapped it and started from the previous version.

Reworking User Prompts

At this point, I felt sort of defeated, so I decided to remove language from the game strings. For example, instead of saying You, the program says U. In the end, it came down to having a little bit of fun with it:

from random import*a="Rock","Paper","Scissors"*g,='012'b=randint(0,2)c=a[b]h=" chose "d=f"I{h}{c}
U{h}death
I win"e=input(f'{", ".join(a)}!{g}:')if e in g:d=f"I{h}{c}
U{h}{(f:=a[int(e)])}
{['Tie',f'{f} beats {c}u win',f'{c} beats {f}I win'][(int(e)-b)%3]}"print(d)
Enter fullscreen mode Exit fullscreen mode

In other words, I basically let go of having exactly the same output. For example, I was able to swap words like You to U and Nothing to Death. In a sort of endearing way, I feel like these changes make the program better: it makes me feel like an edgy teen.

That said, the true magic of these changes is weve managed to squash the program from 301 characters to exactly 280. Thats it! Thats Rock Paper Scissors in a tweet.

Lessons Learned

Pretty much the moment I got this code down to 280 characters, I went ahead and posted it:

That said, as you can see, Im not sure all the effort was worth it. At best, two of my friends found it funny, but Im not sure its something Ill do again. Honestly, theres only so much meme value you can get out of something like this, and it just didnt hit like I expected.

Of course, if you thought this was funny and youd like to do something similar, let me know! Id love for a silly challenge like this to take over social media for a day or two. And if not, no worries! I had fun trying to do this.

Also, I know this article is kind of messy, so let me know if youd like me to distill some of what Ive learned into another article. It would enjoy doing that! But, only if its what you want to read. Its certainly not something people are going to search on Google.

At any rate, thanks again for sticking it out with me! In case you missed it, here are the articles that inspired this one:

Finally, if youd like to support the site directly, heres a list of ways you can do that. Otherwise, thanks for stopping by! Take care.


Original Link: https://dev.to/renegadecoder94/can-we-fit-rock-paper-scissors-in-python-in-a-tweet-2hhm

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To