Code Adventures: fancypants, a Command-Line Text Conversion Utility

๐•‹๐•™๐•š๐•ค ๐•จ๐•–๐•š๐•ฃ๐•• ๐•ฅ๐•–๐•ฉ๐•ฅ ๐•จ๐•’๐•ค ๐•”๐• ๐•Ÿ๐•ค๐•ฅ๐•ฃ๐•ฆ๐•”๐•ฅ๐•–๐•• ๐•จ๐•š๐•ฅ๐•™ ๐•ฅ๐•™๐•– ๐•ฆ๐•ฅ๐•š๐•๐•š๐•ฅ๐•ช ๐•ž๐•–๐•Ÿ๐•ฅ๐•š๐• ๐•Ÿ๐•–๐•• ๐•š๐•Ÿ ๐•ฅ๐•™๐•– ๐•ฅ๐•š๐•ฅ๐•๐•–. ๐“ข๐“ธ ๐”€๐“ช๐“ผ ๐“ฝ๐“ฑ๐“ฒ๐“ผ ๐“ฝ๐“ฎ๐”๐“ฝ ๐“ฑ๐“ฎ๐“ป๐“ฎ. ๐—”๐—ป๐—ฑ ๐—ฎ๐—น๐˜€๐—ผ ๐˜๐—ต๐—ถ๐˜€. ๐•ฌ๐–‰๐–‰๐–Ž๐–™๐–Ž๐–”๐–“๐–†๐–‘๐–ž, ๐–™๐–๐–Ž๐–˜.

Can you read those? There’s a good chance you can! If you can’t (like if they all show up as hollow boxes) it’s because the font you’re reading this post in doesn’t support those kinds of characters, which are from the math symbols section of the Unicode character set.

It’s a command-line version of a web Unicode text converter, of the sort found at the other end of this link. It’s written in Python, and the source is at the end of this post. I saved it to a file named “fancypants” and put it in my home directory’s bin directory (which you’ll probably have to make first), where many Linux distributions are configured to look for things to execute if you type their names at the command prompt. (Yes, all of this assumes you’re running Linux. It’s not just for supergeeks anymore! If you’re running Windows you’ll have some adjustments to make, including figuring out how to add the script’s home to your path. It should work on Macs, although I don’t know if it’ll look in your home’s bin either.)

Oh, you will have to run a chmod +x fancypants on it. And the script as written assumes Python is at /usr/bin/python, where most distros will put it.

The script expects to be executed in the form:

fancypants [style] [text to convert]

The text should probably be in quotes if there’s any spaces in it, as should the style just in case. So to produce the first text mentioned at the start of the post, I entered:

fancypants "=" "This weird text was constructed with the utility mentioned in the title."

Usable style specifiers are “=” for double-stroke, “/” for script, “!” for a boldface kind of thing, “f” for the medieval script-looking fractur, and a few others that you can pretty easily see in the source code below. In fact each specifier has some synonyms if the single-character versions are too obscure for you to remember. And hey, if you don’t like the names I gave them you can use your own! The moment you paste it into a text file, this all becomes yours to do with as you please. Think of it as the blog version of a type-in program from an 80s computer magazine.

As a bonus, the names “r”, “rot” or “rot13” will perform a ROT13 code on the letters, useful for encoding spoiler text that readers can decode at ROT13.com. There are utilities that you can use to send the generated text directly to the clipboard, for pasting wherever you want, but since those differ if you’re using X.org or Wayland for your display manager (or, sure, Windows or Mac) I’ll leave those for you to figure out.

And if you can’t read the characters above, then I’m sorry that you’re missing out on the fun. It’s all pretty whimsical really, it’s not some huge thing that you’re missing. Come back tomorrow, I’m sure we’ll have a post about Mario or somesuch.

#!/usr/bin/python
import sys

base = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890!@#$%^&*()-_=+[]{};':\",./<>?~"
equals = "๐”ธ๐”นโ„‚๐”ป๐”ผ๐”ฝ๐”พโ„๐•€๐•๐•‚๐•ƒ๐•„โ„•๐•†โ„™โ„šโ„๐•Š๐•‹๐•Œ๐•๐•Ž๐•๐•โ„ค๐•’๐•“๐•”๐••๐•–๐•—๐•˜๐•™๐•š๐•›๐•œ๐•๐•ž๐•Ÿ๐• ๐•ก๐•ข๐•ฃ๐•ค๐•ฅ๐•ฆ๐•ง๐•จ๐•ฉ๐•ช๐•ซ๐Ÿ™๐Ÿš๐Ÿ›๐Ÿœ๐Ÿ๐Ÿž๐ŸŸ๐Ÿ ๐Ÿก๐Ÿ˜!@#$%^&*()-_=+[]{};':\",./<>?~"
script = "๐“๐“‘๐“’๐““๐“”๐“•๐“–๐“—๐“˜๐“™๐“š๐“›๐“œ๐“๐“ž๐“Ÿ๐“ ๐“ก๐“ข๐“ฃ๐“ค๐“ฅ๐“ฆ๐“ง๐“จ๐“ฉ๐“ช๐“ซ๐“ฌ๐“ญ๐“ฎ๐“ฏ๐“ฐ๐“ฑ๐“ฒ๐“ณ๐“ด๐“ต๐“ถ๐“ท๐“ธ๐“น๐“บ๐“ป๐“ผ๐“ฝ๐“พ๐“ฟ๐”€๐”๐”‚๐”ƒ1234567890!@#$%^&*()-_=+[]{};':\",./<>?~"
bold = "๐€๐๐‚๐ƒ๐„๐…๐†๐‡๐ˆ๐‰๐Š๐‹๐Œ๐๐Ž๐๐๐‘๐’๐“๐”๐•๐–๐—๐˜๐™๐š๐›๐œ๐๐ž๐Ÿ๐ ๐ก๐ข๐ฃ๐ค๐ฅ๐ฆ๐ง๐จ๐ฉ๐ช๐ซ๐ฌ๐ญ๐ฎ๐ฏ๐ฐ๐ฑ๐ฒ๐ณ๐Ÿ๐Ÿ๐Ÿ‘๐Ÿ’๐Ÿ“๐Ÿ”๐Ÿ•๐Ÿ–๐Ÿ—๐ŸŽ!@#$%^&*()-_=+[]{};':\",./<>?~"
bolditalic = "๐‘จ๐‘ฉ๐‘ช๐‘ซ๐‘ฌ๐‘ญ๐‘ฎ๐‘ฏ๐‘ฐ๐‘ฑ๐‘ฒ๐‘ณ๐‘ด๐‘ต๐‘ถ๐‘ท๐‘ธ๐‘น๐‘บ๐‘ป๐‘ผ๐‘ฝ๐‘พ๐‘ฟ๐’€๐’๐’‚๐’ƒ๐’„๐’…๐’†๐’‡๐’ˆ๐’‰๐’Š๐’‹๐’Œ๐’๐’Ž๐’๐’๐’‘๐’’๐’“๐’”๐’•๐’–๐’—๐’˜๐’™๐’š๐’›1234567890!@#$%^&*()-_=+[]{};':\",./<>?~"
monospace = "๐™ฐ๐™ฑ๐™ฒ๐™ณ๐™ด๐™ต๐™ถ๐™ท๐™ธ๐™น๐™บ๐™ป๐™ผ๐™ฝ๐™พ๐™ฟ๐š€๐š๐š‚๐šƒ๐š„๐š…๐š†๐š‡๐šˆ๐š‰๐šŠ๐š‹๐šŒ๐š๐šŽ๐š๐š๐š‘๐š’๐š“๐š”๐š•๐š–๐š—๐š˜๐š™๐šš๐š›๐šœ๐š๐šž๐šŸ๐š ๐šก๐šข๐šฃ๐Ÿท๐Ÿธ๐Ÿน๐Ÿบ๐Ÿป๐Ÿผ๐Ÿฝ๐Ÿพ๐Ÿฟ๐Ÿถ!@#$%^&*()-_=+[]{};':\",./<>?~"
block = "๐—”๐—•๐—–๐——๐—˜๐—™๐—š๐—›๐—œ๐—๐—ž๐—Ÿ๐— ๐—ก๐—ข๐—ฃ๐—ค๐—ฅ๐—ฆ๐—ง๐—จ๐—ฉ๐—ช๐—ซ๐—ฌ๐—ญ๐—ฎ๐—ฏ๐—ฐ๐—ฑ๐—ฒ๐—ณ๐—ด๐—ต๐—ถ๐—ท๐—ธ๐—น๐—บ๐—ป๐—ผ๐—ฝ๐—พ๐—ฟ๐˜€๐˜๐˜‚๐˜ƒ๐˜„๐˜…๐˜†๐˜‡๐Ÿญ๐Ÿฎ๐Ÿฏ๐Ÿฐ๐Ÿฑ๐Ÿฒ๐Ÿณ๐Ÿด๐Ÿต๐Ÿฌ!@#$%^&*()-_=+[]{};':\",./<>?~"
fraktur = "๐•ฌ๐•ญ๐•ฎ๐•ฏ๐•ฐ๐•ฑ๐•ฒ๐•ณ๐•ด๐•ต๐•ถ๐•ท๐•ธ๐•น๐•บ๐•ป๐•ผ๐•ฝ๐•พ๐•ฟ๐–€๐–๐–‚๐–ƒ๐–„๐–…๐–†๐–‡๐–ˆ๐–‰๐–Š๐–‹๐–Œ๐–๐–Ž๐–๐–๐–‘๐–’๐–“๐–”๐–•๐––๐–—๐–˜๐–™๐–š๐–›๐–œ๐–๐–ž๐–Ÿ1234567890!@#$%^&*()-_=+[]{};':\",./<>?~"
rot = "NOPQRSTUVWXYZABCDEFGHIJKLMnopqrstuvwxyzabcdefghijklm1234567890!@#$%^&*()-_=+[]{};':\",./<>?~"
tilde = len(equals)-1

def convert(convertchar, intext):
outlist = []
match convertchar:
case "=" | "equal" | "equals":
clist = equals
case "/" | "slant" | "script":
clist = script
case "!" | "bold":
clist = bold
case "!/" | "bolditalic" | "boldital":
clist = bolditalic
case "m" | "mono" | "monospace":
clist = monospace
case "b" | "block" | "mathbold":
clist = block
case "f" | "fraktur":
clist = fraktur
case "r" | "rot" | "rot13":
clist = rot
case _:
raise ValueError("Unknown charset " + convertchar)
return intext
for char in intext:
try:
index = base.index(char)
except:
outlist.append(char)
continue
outchr = clist[index]
if outchr != "~":
outlist.append(outchr)
else:
outlist.append(base[index])
return "".join(outlist)

if __name__ == "__main__":
convertchar = sys.argv[1]
intext = sys.argv[2]
print(convert(convertchar, intext))

Code Adventures: Simulating One Handed Solitaire

My style in titling these things is to just present the subject on whatever it is I’m linking to in the title, so you might expect that this is about someone else doing that and me reporting on it. But no! This time it’s something I did myself!

First, you have to know of a card game called One Handed Solitaire, as reported by Metafilter member ChurchHatesTucker here. CHT’s been on a tear in presenting various card games lately, here’s the other recent posts they’ve made on solitaire card games, with a dungeon crawl flavor: Clear the Dungeon, Tomb of the Four Kings, Scoundrel.

One Handed Solitaire is very simple, and an example of a “zero player game.” There are no decisions to make; winning or losing is completely down to the initial state of the deck. Here are the rules in text:

You start with a shuffled deck of cards. Draw four to form your hand. Your hand is considered to be in sequence, you must keep them in the order drawn. Now:

  • If the first and fourth cards are the same suit, discard the second and third cards from your hand out of play. This of course moves the fourth card to be the second card.
  • If the first and fourth cards are the same rank, discard the first four cards from your hand.
  • If neither of these things are true, draw another card from the deck to the front of your hand. This makes a new first card, and changes which the fourth card is.
  • When the deck runs out and you can no longer remove cards, the game is over. If you clear your hand and there’s still cards in the deck you’re not done, draw four more.

Your score (lower is better) is how many cards are in your hand when you run out of deck and can no longer discard cards. The average score is about 13.32 cards left. If you get a score of zero, that is you discard all of the cards from your hand and the deck is empty, you win.

Here are the rules demonstrated by Gather Together Games in a Youtube video (1ยพ minutes):

ChurchHatesTucker ran a simulation of 200,000 runs and found the win rate of the game is close to 0.7%. I ran my own simulation, in a Python script, and found that out as well. I’ll put my code at the end of this post. No AI was used in its writing, and permission is not given to use it to train AIs. In fact, that’s true of all the text in this blog.

My first attempt found a win rate of 0.94%, but that turns out to be because I left out the aces from the deck! I tried a run with only 20 cards in the deck the 2-6 of each suit, and the win rate became about 7.5%.

If you want to try yourself, here’s the Python code I used. If you have Python installed, just paste it into a text file, give it the extension .py, and run it. It assumes you’re running it in a Linux or other Unix-like system; if you’re on Windows, you might have to change the “shebang” line at the front to point to where your Python is.

#!/usr/bin/python
import random

def draw(deck):
if len(deck) > 0:
return deck.pop(0)
else:
return None

def gameend(deck, hand, verbose):
score = len(hand)
if verbose >= 1:
print("Deck exhaused. Final score:",score)
if score == 0:
print("Win!")
if verbose >= 2:
print("Deck state:",deck)
print("Hand state:",hand)
return score

def play(verbose = 0):
deck = ["2H","3H","4H","5H","6H","7H","8H","9H","TH","JH","QH","KH","AH",
"2D","3D","4D","5D","6D","7D","8D","9D","TD","JD","QD","KD","AD",
"2C","3C","4C","5C","6C","7C","8C","9C","TC","JC","QC","KC","AC",
"2S","3S","4S","5S","6S","7S","8S","9S","TS","JS","QS","KS","AS"]
hand = []
random.shuffle(deck)
for a in range(4):
hand.append(draw(deck))
if verbose >= 3:
print("Game starting--")
while True:
if len(hand) < 4:
drawcard = draw(deck)
if drawcard == None:
return gameend(deck, hand, verbose)
if verbose >= 2:
print("Drew a",drawcard)
hand.insert(0, drawcard)
continue
cardtop = hand[0]
cardfourth = hand[3]
if verbose >= 3:
print("********: Deck length:",len(deck), "Hand length:",len(hand))
if verbose >= 2:
print("CARDS:", cardtop, cardfourth)
# case 1: if the 1st and 4th cards match suit, discard the second and third cards
if cardtop[1] == cardfourth[1]:
d1 = hand.pop(1)
d2 = hand.pop(1)
if verbose >= 2:
print("Discarded",d1,"and",d2)
continue
# case 2: if the 1st and 4th cards match rank, discard the top four
if cardtop[0] == cardfourth[0]:
d1 = hand.pop(0)
d2 = hand.pop(0)
d3 = hand.pop(0)
d4 = hand.pop(0)
if verbose >= 2:
print("Discarded:",d1,d2,d3,d4)
continue
# case 3: if neither is true, draw a card
drawcard = draw(deck)
if drawcard == None:
return gameend(deck, hand, verbose)
else:
if verbose >= 2:
print("Drew a card")
hand.insert(0, drawcard)
# end of loop

if __name__ == "__main__":
numgames = 10000000
wins = 0
scores = []
scoresum = 0
for count in range(numgames):
score = play(verbose = 0)
scores.append(score)
scoresum += score
if score == 0:
wins += 1
#print("A win on game #",count+1)
if count % 500000 == 0:
print("Played",count,"games...")
print("Finished playing",numgames,"games")
print("Wins:",wins)
print("Win rate:",wins/numgames)
print("Total score:",scoresum)
print("Average score:",scoresum/numgames)
print("Run compete.")