https://www.iceorfire.com/rss/pythonIceOrFire - python Feed2024-03-29T14:51:09.169777+00:00python-feedgenIce or Fire is your source for python posts.https://www.iceorfire.com/post/intro-to-renpyIntro To RenPy2024-03-29T14:51:09.200654+00:00IoFAdmin<p>Welcome to the first programming and Python related post here on Ice or Fire. Today we'll discuss RenPy. What is RenPy? Funny you should ask Random Internet Stranger!</p>
<h4>What Is RenPy?</h4>
<p>To quote from <a target="_blank" href="https://renpy.org/">the RenPy website</a>:</p>
<p><q>Ren'Py is a visual novel engine – used by thousands of creators from around the world – that helps you use words, images, and sounds to tell interactive stories that run on computers and mobile devices. These can be both visual novels and life simulation games. The easy to learn script language allows anyone to efficiently write large visual novels, while its Python scripting is enough for complex simulation games.</q></p>
<h4>Say what now?</h4>
<p>Basically, RenPy is a game framework that easily allows you to create a "choose your own adventure" type book with images. There's not a lot of programming involved to do simple things but it does allow you to script complex tasks in Python. Sounds pretty cool, right?</p>
<h4>Ok... why are you telling me this?</h4>
<p>I'm learning RenPy and creating a family friendly pirate game using it. In the coming weeks, I'll post more about my progress. Stay tuned!</p>
<p>Check out <a href="/post/our-renpy-game-part-1-planning">part 1 of our game</a>!</p><p>RenPy is a visual novel engine that allows us to make our own "Choose Your Own Adventure" type games.</p>2021-03-04T16:49:52-06:00https://www.iceorfire.com/post/our-renpy-game-part-1-planningOur RenPy Game Part 1 - Planning2024-03-29T14:51:09.200565+00:00IoFAdmin<h4>Starting Our RenPy Game</h4>
<p>I'm learning RenPy as I go so I'm trying to document my steps as a learning process and hopefully help you readers along the way.</p>
<h4>Quick Overview</h4>
<p>RenPy games are what the cool kids call visual novels (VN). These visual novels are very similar to the "<a target="_blank" href="https://en.wikipedia.org/wiki/Choose_Your_Own_Adventure">Choose Your Own Adventure</a>" books from my youth but they have pictures/animations. (Yes, I know that I'm old. Ask your elderly neighbors about CYOA books). Much like CYOA books, VNs present the player with some text and then some choices.</p>
<p>Choices in VNs, much like in life, drive everything forward. RenPy focuses on choices and they make up the backbone of your game.</p>
<h4>Premise Of Our Game</h4>
<p>Our untitled pirate game, which we'll call UPG from now on, will be a family friendly game packed with puns and bad jokes. I love Dad Jokes so be prepared for some bad ones. Our unnamed hero will be an ordinary teenager who accidentally joins a pirate crew to save his cat.</p>
<h4>Planning</h4>
<p>While we are figuring this out as we learn RenPy, we need some basic goals so that we don't get totally lost. Here are the basic things we need to keep in mind:</p>
<ol>
<li><strong>Graphic Style:</strong> I want this game to have a retro feel so I'll be trying my hand at 16-bit pixel art. I'm VERY new to pixel art so don't expect too much. To get started, we'll just use some placeholder images.</li>
<li><strong>Game Play:</strong> We will be focusing on choices that drive the story forward. There will be some text choices such as "Talk to the man" or "Leave him alone" and some visual choices such as requiring the player to click a certain part of the screen.</li>
<li><strong>Storyline:</strong> I know that this is a big project so I'm trying to keep it as small as possible to start. I'm planning on making this game in "chapters" with each chapter having a main goal. Chapter 1 will involve joining a pirate crew to get your cat back who wound up on their ship. When we complete the first chapter, we can worry about continuing the story from there.</li>
</ol>
<h4>Wrapping Up</h4>
<p>We have a basic plan in place so we can take a break for now. Next time we'll start outlining the story for chapter 1. What do you say? Arrrhg! you with me? Don't say I didn't warn you about the bad puns and Dad Jokes!</p>
<p><a href="/post/our-renpy-game-part-2-storyline">Bring on part 2</a>!</p><p>We begin our RenPy journey with planning our visual novel pirate game.</p>2021-03-09T23:43:47-06:00https://www.iceorfire.com/post/our-renpy-game-part-2-storylineOur RenPy Game Part 2 - Storyline2024-03-29T14:51:09.200470+00:00IoFAdmin<h4>Recap of Last Time</h4>
<p>We decided that we're going to write a family-friendly pirate visual novel with a pixelated 16-bit style that uses text-based choices as well as visual buttons that react when clicked.</p>
<p><a href="https://www.iceorfire.com/post/our-renpy-game-part-1-planning">Catch up with part 1</a>.</p>
<h4>Main Storyline of Chapter 1</h4>
<p>Let's start with a broad idea and flesh out the ideas of Chapter 1.<p>
<ol>
<li>Our cat, Miss Fluffybottom, runs out of the house and down to the beach.</li>
<li>A giant squid swallows Miss Fluffybottom but then spits her out. (I guess cats aren't very tasty?)</li>
<li>The cat lands on the deck of a nearby pirate ship.</li>
<li>We have to somehow get onboard the ship and get Fluffybottom back.</li>
</ol>
<h4>Fleshing Out the Main Points</h4>
<p>We know we lost our cat and that we need to get her back from a bunch of pirates but that's it. Now we'll list the tasks that we need to accomplish to save our precious kitty.</p>
<ol>
<li><strong>Find a pot:</strong> We need a pot to join the crew as the cook. We'll find in in our house but we'd better not let Mom know!</li>
<li><strong>Get a cookbook:</strong> After buying pirate clothes, we'll give our eye-patch to tavern barkeeper and she'll give us her cookbook which is needed to join crew.</li>
<li><strong>Buy pirate clothes:</strong> Everyone knows you can't board a pirate ship if you don't look like a salty dog. We can buy clothes from ex-pirate with a gold coin.</li>
<li><strong>Obtain a banana:</strong> We'll trade the monkey in the tree our banana for his gold coin. Apparently monkeys don't grasp the monetary value of bananas.</li>
<li><strong>Find a gold coin:</strong> We'll use it to buy pirate clothes.</li>
<li><strong>Learn the banana bread recipe:</strong> We can't be pirate cook if can't make a tasty treat.</li>
<li><strong>Earn 100 points:</strong> Who doesn't like points?</li>
</ol>
<h4>Wrapping Up</h4>
<p>That completes our main story points for Chapter 1. Next time we'll start <a href="/post/our-renpy-game-part-3-beginning">setting up our RenPy project</a>. See you soon!</p><p>In Part 2 of our RenPy series we outline the first chapter of our story.</p>2021-03-11T19:43:13-06:00https://www.iceorfire.com/post/our-renpy-game-part-3-beginningOur RenPy Game Part 3 - Beginning2024-03-29T14:51:09.200376+00:00IoFAdmin<h4>Last Post</h4>
<p>Last time we created a <a href="/post/our-renpy-game-part-2-storyline">rough outline</a> for our game's story.</p>
<h4>Let's Install RenPy</h4>
<p>I'm not going to cover installing and setting up RenPy in detail because the <a href="https://www.renpy.org/doc/html/quickstart.html">Quickstart official documentation</a> does that very well. Follow the instructions and then come back here. Go on! I'll wait.</p>
<h4>Getting Started</h4>
<p>Create a new project as detailed in the Quickstart and name it "Pirates" with a resolution of 1280 x 720. Then just take the defaults. Select "Launch Project" and <strong>Pirates</strong> should start. Close the game and then we can start with our game.</p>
<h4>Time For Some Code</h4>
<p>RenPy is built on the Python programming language but simple tasks don't really require programming in the traditional sense. Since we're just starting out, we don't need anything complex so we aren't going to write any custom Python code... the "normal" RenPy scripting tags will cover us for now.</p>
<p>From the main "Projects" screen of RenPy, make sure "Pirates" is selected and click <strong>script.rpy</strong> under "Edit File". Assuming you've set up your text editor correctly, <strong>script.rpy</strong> will open for editing. I recommend Atom for writing RenPy scripts but you can use whatever you want.</p>
<h4>Copy/Paste Time (or Ctrl + C / Ctrl + V for the cool kids)</h4>
<p>Copy and paste the code below into your <strong>script.rpy</strong> and save it. I'll explain what it all does in minute.</p>
<pre>
define p = Character("Player")
define s = Character("Squid")
define f = Character("Miss Fluffybottom")
label start:
scene bg insidehouse
show pirate happy
p "No Miss Fluffybottom! Wait!"
"Your cat, the adventurous (and naughty) Miss Fluffybottom runs out the front door of your house. That bad kitty's chasing a bird again! You should probably go after her right meow."
menu:
"Go after her.":
jump house_outside
"She'll be fine.":
jump house_inside
return
label house_inside:
scene bg insidehouse
show pirate happy
p "Just your normal run-of-the-mill house. It's not much but it's home. Mom doesn't seem to be home right now."
p "Maybe I should go check on Miss Fluffybottom."
menu:
"Check on Ms Fluffybottom.":
jump house_outside
label house_outside:
scene bg outsidehouse
show pirate happy
p "There she is down on the beach chasing the seagulls again. Crazy cat! I should probably bring her back in the house before she gets lost or drowns."
"The door slams shut and you're locked out of the house!"
menu:
"Knock on the door.":
jump door_knock
"Go after Ms Fluffybottom.":
jump beach_squid
label door_knock:
scene bg outsidehouse
show pirate happy
"Knock, knock, knock!"
p "I guess Mom's not home."
return
label beach_squid:
scene bg beach
show squid
"As you reach for your cat, a sea creature rises out of the sea."
s "Boo! Sorry... I mean Roar!"
"The squid, who definitely doesn't speak English, roars and grabs poor Miss Fluffybottom! Today would've been a good day for a catnap but too late for that."
show pirate happy at right
p "Let my cat go you under-cooked calamari!"
f "Meow!!!"
"The squid, holding your terrified feline, wipes tears from her eyes. (That calamari insult hurt.) She dives under the waves."
hide squid
p "Wow! Mom's gonna be mad."
show squid
"The giant squid pops her head above the water and wiggles her tentacles in disgust."
s "Roar! Gurgle. (Yuck! I guess cousin Johnny is right... cats really are nasty.)"
"The squid spits out the water-logged Miss Fluffybottom who flies through the air and lands on..."
"a docked pirate ship!"
p "Ok... correction. Mom's gonna be SUPER mad. Somehow I have to get on that pirate ship and rescue the cat."
p "I can start at the pier or those palm trees further down the beach."
</pre>
<h4>Code Explanation</h4>
<pre>
define p = Character("Player")
define s = Character("Squid")
define f = Character("Miss Fluffybottom")
</pre>
<p>We define three Character objects with names and assign them to short variable names for ease of use. Why type <strong>player</strong> when we can type <strong>p</strong>?</p>
<pre>
label start:
scene bg insidehouse
show pirate happy
p "No Miss Fluffybottom! Wait!"
"Your cat, the adventurous (and naughty) Miss Fluffybottom runs out the front door of your house. That bad kitty's chasing a bird again! You should probably go after her right meow."
menu:
"Go after her.":
jump house_outside
"She'll be fine.":
jump house_inside
return
</pre>
<p><strong>label start</strong> is a special label where RenPy always starts your game. <strong>scene bg insidehouse</strong> loads a file named "bg insidehouse" as the background image and <strong>show pirate happy</strong> loads a file named "show pirate happy" as a Character image.</p>
<p><strong>p "No Miss Fluffybottom! Wait!"</strong> has Player (referenced by p) say the text in the quotes. <strong>"Your cat, the adventurous (and naughty) Miss Fluffybottom runs out the front door of your house. That bad kitty's chasing a bird again! You should probably go after her right meow."</strong> has the Narrator say the text in the quotes.</p>
<p><strong>menu:</strong> creates two options: <strong>Go after her.</strong> and <strong>She'll be fine</strong>. The <strong>jump</strong> statements link the menu options to other labels. If someone playing your game selects "Go after her", the game will jump to the code section labeled "house_outside".</p>
<pre>
label beach_squid:
scene bg beach
show squid
"As you reach for your cat, a sea creature rises out of the sea."
s "Boo! Sorry... I mean Roar!"
"The squid, who definitely doesn't speak English, roars and grabs poor Miss Fluffybottom! Today would've been a good day for a catnap but too late for that."
show pirate happy at right
p "Let my cat go you under-cooked calamari!"
f "Meow!!!"
"The squid, holding your terrified feline, wipes tears from her eyes. (That calamari insult hurt.) She dives under the waves."
hide squid
p "Wow! Mom's gonna be mad."
show squid
"The giant squid pops her head above the water and wiggles her tentacles in disgust."
s "Roar! Gurgle. (Yuck! I guess cousin Johnny is right... cats really are nasty.)"
"The squid spits out the water-logged Miss Fluffybottom who flies through the air and lands on..."
"a docked pirate ship!"
p "Ok... correction. Mom's gonna be SUPER mad. Somehow I have to get on that pirate ship and rescue the cat."
p "I can start at the pier or those palm trees further down the beach."
</pre>
<p><strong>show pirate happy at right</strong> loads the file named "pirate happy" and displays it a the right hand side of the screen. <strong>hide squid</strong> unsurprisingly hides the image from view.</p>
<p>The labels <strong>start</strong> and <strong>door_knock</strong> both end with the <strong>return</strong> statement which ends the program.</p>
<h4>Game Images</h4>
<p>The beginning version of our Untitled Pirate Game (UPG) is almost complete but we're missing something... Arrrhg piratey images. Feel free to use any images you want as long as your name them correctly or you can use mine. My images are just placeholders right now so that we can get the game functioning.</p>
<p><strong>Image Downloads:</strong></p>
<ul>
<li><a target="_blank" href="https://res.cloudinary.com/diuqkp0ek/image/upload/v1615700188/pirate_happy.png">pirate happy.png</a></li>
<li><a target="_blank" href="https://res.cloudinary.com/diuqkp0ek/image/upload/v1615700188/bg_beach.jpg">bg beach.jpg</a></li>
<li><a target="_blank" href="https://res.cloudinary.com/diuqkp0ek/image/upload/v1615700188/squid.png">squid.png</a></li>
<li><a target="_blank" href="https://res.cloudinary.com/diuqkp0ek/image/upload/v1615700188/bg_insidehouse.jpg">bg insidehouse.jpg</a></li>
<li><a target="_blank" href="https://res.cloudinary.com/diuqkp0ek/image/upload/v1615700188/bg_outsidehouse.jpg">bg outsidehouse.jpg</a></li>
</ul>
<p><strong>Note:</strong> if you use my images, you will have to rename them by replacing the underscores with spaces in the filenames.</p>
<h4>Where Do I Put My Images?</h4>
<p>Assuming you named your project "Pirates" like I did, RenPy loads images from "Pirates/game/images/" so you'll need to place your images there.</p>
<h4>Update (Apr 25, 2021) - Git Repo Link</h4>
<p>If you want to download the files, <a target="_blank" href="https://bitbucket.org/iceorfire-renpy/pirates/src/main/part3/">get them here</a>.</p>
<h4>Screenshots</h4>
<p>I captured these from RenPy running on Ubuntu. If you followed the tutorial correctly, you should have something similar.</p>
<span class="image"><img src="https://res.cloudinary.com/diuqkp0ek/image/upload/v1617740752/screenshot_3_1.png" alt="kitchen"></span>
<span class="image"><img src="https://res.cloudinary.com/diuqkp0ek/image/upload/v1617740752/screenshot_3_2.png" alt="front door"></span>
<span class="image"><img src="https://res.cloudinary.com/diuqkp0ek/image/upload/v1617740752/screenshot_3_3.png" alt="octopus on the beach"></span>
<span class="image"><img src="https://res.cloudinary.com/diuqkp0ek/image/upload/v1617740752/screenshot_3_4.png" alt="confronting the octopus"></span>
<h4>Wrapping Up</h4>
<p>If everything went well, you should have a very simple working RenPy game that allows you to reach a "good" and "bad" ending. The bad conclusion ends with you locked out of the house and the good conclusion ends with you on the beach.</p>
<p>We've learned a lot this post: how to set up a RenPy project, how to create Character objects, load image assets, create scenes, create menus and label jumps, and edit the script.rpy file. Pour yourself a piratey drink and take a break because you've earned it!</p>
<p>In our next post, we'll <a href="https://www.iceorfire.com/post/our-renpy-game-part-4-git-repo">set up our Git repository</a>.</p><p>In this post we set up RenPy and start our game!</p>2021-03-13T21:42:56-06:00https://www.iceorfire.com/post/pythonanywhere-hosting-reviewPythonAnywhere Hosting Review2024-03-29T14:51:09.200276+00:00IoFAdmin<h4>Looking For Python Webhosting?</h4>
<p>I'm a programmer by day and aspiring blogger/product reviewer by night so I needed somewhere to host IceOrFire. I write Python in my spare time so I wanted somewhere to host a Python based website. <strong>Spoiler:</strong> I chose PythonAnywhere.</p>
<h4>So What Is PythonAnywhere?</h4>
<p>PythonAnywhere is a full hosting solution for Python-based websites. Basically, it offers an online file editor, full linux terminal access, MySQL/Postgres databases, and web hosting options like Let's Encrypt, and DNS setup.</p>
<h4>Full Disclosure</h4>
<p>This post contains affiliate links.</p>
<h2>What Are The Perks Of PythonAnywhere?</h2>
<p>Here's what I found in no particular order...</p>
<h4>No System Administration Required</h4>
<p>I'm not a system administrator or devops expert and honestly it doesn't interest me that much. I just want to write some code and not worry about the infrastructure. That's where <a target="_blank" href="https://www.pythonanywhere.com/?affiliate_id=002e0d0f">PythonAnywhere</a> shines for me: they handle everything except for the code. All you have to do is install packages in pip, create your virtual environment, and write your code.</p>
<h4>It's Cheap</h4>
<p>I pay $5 per month for <a target="_blank" href="https://www.pythonanywhere.com/?affiliate_id=002e0d0f">PythonAnywhere</a> hosting. You just need a domain name such as (Google Domains which I use) and you have a website. $5 x 12 months + $10 for hosting = $70 per year in webhosting costs.</p>
<h4>Reliability</h4>
<p><a target="_blank" href="https://www.pythonanywhere.com/?affiliate_id=002e0d0f">PythonAnywhere</a> has great uptime. Other than the occasional planned downtime for maintenance, it's never been down for me.</p>
<h4>Great Support</h4>
<p>Any time you have a question, you can message the admins and they'll get back to you by email. They only focus on Python so they're going to know the answer to your problem.</p>
<h2>What Are The Cons Of PythonAnywhere?</h2>
<p>No honest review would only focus on the positives. Here's my gripes with the service...</p>
<h4>Postgres Costs Extra</h4>
<p>If you want to use Postgres instead of MySQL, you'll have to pay more than $5 per month. MySQL works fine for IceOrFire so it's not really a problem for me. It might be a problem for you depending on your use case.</p>
<h4>PythonAnywhere Is In London And I'm American</h4>
<p>This isn't a problem for me unless I'm running into a problem <strong>Now</strong> and it's night time in London. They are very good at answering questions but depending on the time of day that I ask, I may have to wait until the next morning to get a response. I don't blame <a target="_blank" href="https://www.pythonanywhere.com/?affiliate_id=002e0d0f">PythonAnywhere</a> because they're responsive and I can't expect them to answer me in the middle of the night (London time).</p>
<h4>The Service Is For Small/Medium Sites</h4>
<p>Yet again, this may not apply to you. IceOrFire isn't Reddit: I don't need a super powered server that handles millions of hits per hour. For a small to medium site, <a target="_blank" href="https://www.pythonanywhere.com/?affiliate_id=002e0d0f">PythonAnywhere</a> is responsive and works great. If (fingers crossed) this site ever gets HUGE, I will need a more powerful service. Honestly, you can't expect the world for $5 per month.</p>
<h2>Verdict: Do You Recommend PythonAnywhere?</h2>
<p>Obviously, I do recommend using PythonAnywhere because I'm hosting IceOrFire on it. It's a great deal at $5 per month for a small to medium site with the ease of use and great customer support.</p>
<p><a target="_blank" href="https://www.pythonanywhere.com/?affiliate_id=002e0d0f">Sign me up!</a></p><p>We review PythonAnywhere's webhosting service in our latest product review.</p>2021-04-02T12:50:48-05:00https://www.iceorfire.com/post/our-renpy-game-part-4-git-repoOur RenPy Game Part 4 - Git Repo 2024-03-29T14:51:09.200171+00:00IoFAdmin<h4>Last Time</h4>
<p>In the <a href="https://www.iceorfire.com/post/our-renpy-game-part-3-beginning">last post</a>, we started coding and created a very simple Renpy game.</p>
<h4>I Finally Git It Done</h4>
<p>I had a bunch of problems setting up my Git repository under Bitbucket which I describe in <a href="https://www.iceorfire.com/post/bitbucket-git-repository-review">another post</a>. Once I finally got it working, I created a folder for Part 3 of our tutorials. With each new Renpy tutorial, I'll post the files that are needed to follow along.</p>
<h4>Access The Git Repo</h4>
<p>The files for Part 3 can be <a target="_blank" href="https://bitbucket.org/iceorfire-renpy/pirates/src/main/part3/">accessed here</a>.</p>
<h4>That's All Folks!</h4>
<p>That wraps up our short post. Check out <a href="/post/our-renpy-game-part-5-variables-conditionals-and-screens">Part 5</a> when we dive back into the code for our Pirate Renpy game!</p><p>In part 4 of our Renpy adventure, we set up our Git repository.</p>2021-04-11T18:19:24-05:00https://www.iceorfire.com/post/our-renpy-game-part-5-variables-conditionals-and-screensOur Renpy Game Part 5 - Variables, Conditionals and Screens2024-03-29T14:51:09.200080+00:00IoFAdmin<h4>Previously On Our Pirate Adventure...</h4>
<p>In <a href="/post/our-renpy-game-part-3-beginning">post #3</a> we installed Renpy and started our game code and then we set up our git repository for the project in <a href="/post/our-renpy-game-part-4-git-repo">post #4</a>.</p>
<h4>Continuing Our Game</h4>
<p>In this post we're continuing our game by exploring three new aspects of Renpy:</p>
<ol>
<li>Variables</li>
<li>Conditionals</li>
<li>Screens</li>
</ol>
<h4>Some Programming Info</h4>
<p>These Renpy tutorials are for a wide range of people. Some of you already know how to program and are interested in learning Renpy while others of you don't know much about programming and will need some more guidance and explanation.</p>
<p>If you're familiar with programming, you can skip the <strong>Variables</strong> and <strong>Conditionals</strong> sections of this post and go straight to the <strong>Screens</strong> section. If you're new to writing code, you should read them. Alright enough chit chat. Let's get to work.</p>
<h4>So What Are Variables?</h4>
<p>Variables in Renpy (and for programming in general) are parts of the code to save values so you can do calculations or display data. For our pirate game, we'll use variables to keep track of the player's name, items he/she has found, and whatever else we need along the way. Once we get to the code below, we'll show how to save the current player's name.</p>
<h4>Conditionals Overview</h4>
<p>Putting it as simply as possible, conditionals are <strong>ifs</strong> and <strong>elses</strong>. If I get up on time, I'll start my work day well. Else, I'll be late and my boss will be angry with me. Conditionals in programming work the same way: if the player has a key, open the door, else have him knock. We'll explore conditionals in our code below.</p>
<h4>Renpy Screens</h4>
<p>In Renpy, screens are used to group user interface elements like buttons, text, and images. Since the elements are grouped into a unit, they can be hidden or shown together instead of individually. Users interact with a screen's elements instead of the screen itself. The <a href="https://www.renpy.org/doc/html/screens.html">Renpy documentation explains screens</a> in detail. In our code, we'll define a screen that's displayed on top of a background and allow the user to click an image. While Renpy allows you to show many screens at once, we'll start simple with one screen.</p>
<h4>Yo Ho Ho! Where's the Code?</h4>
<p>Like we did in <a href="/post/our-renpy-game-part-3-beginning">post #3</a>, I'll show you all of the code before breaking it down into sections that I'll explain. We'll start with script.rpy from post #3. Open that file and then copy/paste the code below into it (replacing everything there).</p>
<pre>
default player_name = ""
define p = Character("[player_name]")
define s = Character("Squid")
define f = Character("Miss Fluffybottom")
define hasHouseKey = False
label start:
scene bg insidehouse
show pirate happy
$ player_name = renpy.input("What is your name?", length=10)
$ player_name = player_name.strip()
if player_name == "":
$ player_name = "Sheldon"
p "No Miss Fluffybottom! Wait!"
"Your cat, the adventurous (and naughty) Miss Fluffybottom runs out the front door of your house. That bad kitty's chasing a bird again! You should probably go after her right meow."
menu:
"Go after her.":
jump house_outside
"She'll be fine.":
jump house_inside
return
label house_inside:
scene bg insidehouse
show pirate happy
p "Just your normal run-of-the-mill house. It's not much but it's home. Mom doesn't seem to be home right now."
p "Maybe I should go check on Miss Fluffybottom."
menu:
"Check on Ms Fluffybottom.":
jump house_outside
label flower_pot:
scene bg outsidehouse
show pirate happy
if hasHouseKey:
p "Mom's favorite plant could use some watering but I'll LEAVE it alone right now."
else:
$ hasHouseKey = True
p "Hey what's that? Looks like a house key! Now I have the key to my front door."
jump front_porch
label house_outside:
scene bg outsidehouse
show screen frontPorchScreen
show pirate happy
p "There she is down on the beach chasing the seagulls again. Crazy cat! I should probably bring her back in the house before she gets lost or drowns."
"The door slams shut and you're locked out of the house!"
menu:
"Use my key." if hasHouseKey:
hide screen frontPorchScreen
jump house_inside
"Knock on the door." if not hasHouseKey:
jump door_knock
"Go after Ms Fluffybottom.":
hide screen frontPorchScreen
jump beach_squid
label front_porch:
scene bg outsidehouse
show screen frontPorchScreen
show pirate happy
p "My front porch."
menu:
"Use my key." if hasHouseKey:
hide screen frontPorchScreen
jump house_inside
"Knock on the door." if not hasHouseKey:
hide screen frontPorchScreen
jump door_knock
"Go after Ms Fluffybottom.":
hide screen frontPorchScreen
jump beach_squid
label door_knock:
scene bg outsidehouse
show pirate happy
"Knock, knock, knock!"
p "I guess Mom's not home."
jump front_porch
label beach_squid:
scene bg beach
show squid
"As you reach for your cat, a sea creature rises out of the sea."
s "Boo! Sorry... I mean Roar!"
"The squid, who definitely doesn't speak English, roars and grabs poor Miss Fluffybottom! Today would've been a good day for a catnap but too late for that."
show pirate happy at right
p "Let my cat go you under-cooked calamari!"
f "Meow!!!"
"The squid, holding your terrified feline, wipes tears from her eyes. (That calamari insult hurt.) She dives under the waves."
hide squid
p "Wow! Mom's gonna be mad."
show squid
"The giant squid pops her head above the water and wiggles her tentacles in disgust."
s "Roar! Gurgle. (Yuck! I guess cousin Johnny is right... cats really are nasty.)"
"The squid spits out the water-logged Miss Fluffybottom who flies through the air and lands on..."
"a docked pirate ship!"
p "Ok... correction. Mom's gonna be SUPER mad. Somehow I have to get on that pirate ship and rescue the cat."
p "I can start at the pier or those palm trees further down the beach."
</pre>
<h4>Even More Code</h4>
<p>Next, we'll create a new file. In the same directory as <strong>script.rpy</strong>, create a new file named <strong>myscreens.rpy</strong> and copy/paste the following code into it.</p>
<pre>
screen frontPorchScreen():
imagebutton:
xalign 0.75
yalign 1.0
idle "flower"
action Jump("flower_pot")
</pre>
<h4>Last One I Promise</h4>
<p>Save the image flower.png in the <strong>images</strong> directory that we used in post #3. That's it with the changes... time for some explanations.</p>
<h4>What Did We Just Do?</h4>
<pre>
default player_name = ""
define p = Character("[player_name]")
define s = Character("Squid")
define f = Character("Miss Fluffybottom")
define hasHouseKey = False
</pre>
<p>We changed the <strong>p</strong> character to be <strong>player_name</strong> and we defined <strong>hasHouseKey</strong> as False. We define variables and constants outside of any label which is ran before any other code (as our game starts). To understand what's going on with <strong>p</strong>, let's move on to <strong>label start</strong>.</p>
<pre>
label start:
scene bg insidehouse
show pirate happy
$ player_name = renpy.input("What is your name?", length=10)
$ player_name = player_name.strip()
if player_name == "":
$ player_name = "Sheldon"
p "No Miss Fluffybottom! Wait!"
"Your cat, the adventurous (and naughty) Miss Fluffybottom runs out the front door of your house. That bad kitty's chasing a bird again! You should probably go after her right meow."
menu:
"Go after her.":
jump house_outside
"She'll be fine.":
jump house_inside
return
</pre>
<p>The <strong>$ player_name</strong> lines are running Python code. The <strong>renpy.input</strong> line asks the player to enter a name that is 10 characters or less in length and the following line is removing blank spaces at the beginning and end of the name. Finally, if the player name is blank ("") set it to Sheldon. In case you were wondering, <strong>player_name</strong> is a Renpy variable.</p>
<pre>
label flower_pot:
scene bg outsidehouse
show pirate happy
if hasHouseKey:
p "Mom's favorite plant could use some watering but I'll LEAVE it alone right now."
else:
$ hasHouseKey = True
p "Hey what's that? Looks like a house key! Now I have the key to my front door."
jump front_porch
</pre>
<p>Here we see a Renpy variable again: <strong>hasHouseKey</strong>. If you remember, we defined hasHouseKey and set it to False at the very top of our script. Here we're using a conditional to see if hasHouseKey is equal to True. (In Renpy and Python, if <variable> is a shortcut for saying "if <variable> is True".) If hasHouseKey is True, we're displaying some text. Else (hasHouseKey is equal to False), we're setting our hasHouseKey variable to True and displaying some other text.</p>
<p>Why are we doing this? Well, inquisitive reader, we are making our code check to see if we have a house key (hasHouseKey is equal to True) and if not, now we do (set hasHouseKey to True). Another way to think about this is to only find the house key once and to display the text about watering the plant if we go to this label more than once (we already have the key).</p>
<pre>
label house_outside:
scene bg outsidehouse
show screen frontPorchScreen
show pirate happy
p "There she is down on the beach chasing the seagulls again. Crazy cat! I should probably bring her back in the house before she gets lost or drowns."
"The door slams shut and you're locked out of the house!"
menu:
"Use my key." if hasHouseKey:
hide screen frontPorchScreen
jump house_inside
"Knock on the door." if not hasHouseKey:
jump door_knock
"Go after Ms Fluffybottom.":
hide screen frontPorchScreen
jump beach_squid
</pre>
<p>Here's something that we haven't seen before: <strong>show screen frontPorchScreen</strong>. show <screen_name> tells Renpy to display a screen on top of the current display. We talked about screens in an earlier section but we need some more explanation here. Renpy loads up the screen named <strong>frontPorchScreen</strong> located in myscreens.rpy</p>
<pre>
screen frontPorchScreen():
imagebutton:
xalign 0.75
yalign 1.0
idle "flower"
action Jump("flower_pot")
</pre>
<p>Ok so here's our first screen! The <strong>imagebutton</strong> part is defining a part of the screen. (See <a href="https://www.renpy.org/doc/html/screens.html#imagebutton">Imagebuttons documentation</a> for more info.) An imagebutton is just like what it sounds: an image that we display to the user that acts as a button. <strong>xalign 0.75</strong> tells Renpy to display the image at 75% of the way to the right and <strong>yalign 1.0</strong> means display the image 100% of the way to the bottom. The idle statement means to display the <strong>flower</strong> image from our <strong>images</strong> directory. When the user clicks on the imagebutton, <strong>jump</strong> to the "flower_pot" label in our script.rpy file. <strong>frontPorchScreen</strong> is like a variable, since we can use it to refer to the screen.</p>
<p>There's a lot going on here but nothing terribly difficult.</p>
<p>Now we come to the "menu" part of the code.</p>
<pre>
"Use my key." if hasHouseKey:
hide screen frontPorchScreen
jump house_inside
</pre>
<p>This may look a little weird but it's very simple. If the <strong>hasHouseKey</strong> is equal to True, display <strong>Use my key.</strong></p>
<pre>
"Knock on the door." if not hasHouseKey:
jump door_knock
</pre>
<p>This is the exact opposite of the previous statement: if hasHouseKey is equal to False, display <strong>Knock on the door.</strong> and jump to door_knock.</p>
<pre>
"Go after Ms Fluffybottom.":
hide screen frontPorchScreen
jump beach_squid
</pre>
<p>For the last part of this menu, hide the <strong>frontPorchScreen</strong> screen (the imagebutton of the flower pot will not be shown) and jump to the <strong>beach_squid</strong> label.</p>
<p>Everything else in the code is something that you've seen before.</p>
<h4>Screenshots</h4>
<span class="image"><img src="https://res.cloudinary.com/diuqkp0ek/image/upload/v1619408164/screenshot_5_1.png"></span>
<span class="image"><img src="https://res.cloudinary.com/diuqkp0ek/image/upload/v1619408164/screenshot_5_2.png"></span>
<span class="image"><img src="https://res.cloudinary.com/diuqkp0ek/image/upload/v1619408164/screenshot_5_3.png"></span>
<span class="image"><img src="https://res.cloudinary.com/diuqkp0ek/image/upload/v1619408164/screenshot_5_4.png"></span>
<span class="image"><img src="https://res.cloudinary.com/diuqkp0ek/image/upload/v1619408164/screenshot_5_5.png"></span>
<span class="image"><img src="https://res.cloudinary.com/diuqkp0ek/image/upload/v1619408164/screenshot_5_6.png"></span>
<h4>Wrapping Up</h4>
<p>If you followed along and everything worked correctly, your Renpy game should look very similar to the images above.</p>
<h4>Git Repo Link</h4>
<p>If you'd like to download the code and images in this post, <a target="_blank" href="https://bitbucket.org/iceorfire-renpy/pirates/src/main/part5/">get them here</a>.</p>
<p>Continue my <a href="/post/renpy-pirate-game-part-6-styles-scoring-inventory-system">Renpy journey</a>.</p><p>In part 5 of our Renpy journey, we explore variables, screens and conditionals.</p>2021-04-25T18:08:16-05:00https://www.iceorfire.com/post/four-useful-python-libraries-you-dont-know-aboutFour Useful Python Libraries You Don't Know About2024-03-29T14:51:09.199976+00:00IoFAdmin<h4>4 Obscure Python Libraries</h4>
<p>In this post, I thought I'd try something different. I picked 4 Python libraries that you probably haven't heard of that I find useful. I'll show you how to use them and then we'll make a fun little program with them.</p>
<h4>Jump Ahead Links</h4>
<ul>
<li><a href="#delorean">Delorean</a></li>
<li><a href="#fuzzywuzzy">FuzzyWuzzy</a></li>
<li><a href="#emoji">Emoji</a></li>
<li><a href="#inflect">Inflect</a></li>
<li><a href="#miniproject">Mini Project Code</a></li>
<li><a href="#output">Mini Project Output</a></li>
</ul>
<h4 id="delorean">Great Scott Marty! Introducing Delorean</h4>
<p>You mean Delorean is more than a time-traveling car? Yes, random Internet Stranger, it is. Datetime objects in Python can be a little difficult to work with. Delorean is a library that makes dealing with Datetime objects easier.</p>
<p>According to the <a target="_blank" href="https://delorean.readthedocs.io/en/latest/">documentation</a>, <q>Delorean is a library for clearing up the inconvenient truths that arise dealing with datetimes in Python. Understanding that timing is a delicate enough of a problem delorean hopes to provide a cleaner less troublesome solution to shifting, manipulating, generating datetimes.</q></p>
<h4>Fire Up the Flux Capacitor</h4>
<p>Since the docs for Delorean are pretty good, so I won't go into a lot of detail here. Below are a few examples of using it. (You can run these yourself in your Python interpreter or IDE.) <a target="_blank" href="https://www.oreillyauto.com/flux-500.html">Buy your Flux Capacitor</a> before you start.</p>
<pre>
from delorean import Delorean
# create delorean obj
d = Delorean()
d
# set obj to central timezone
d = d.shift("US/Central")
d
# print datetime
d.datetime
# print date
d.date
</pre>
<p>In the example above, we create a Delorean object, set it to <strong>Central timezone</strong> and then print it's datetime and date values.</p>
<pre>
# create obj
d = Delorean()
d
# print next tuesday's date
d.next_tuesday()
# print datetime two tuesdays ago at midnight
d.last_tuesday(2).midnight
</pre>
<p>In our second example, we print the datetime of next Tuesday based on the current date set by Delorean. Finally we count back to 2 previous Tuesdays and get the datetime at midnight. That would've been painful to do manually.</p>
<h4 id="fuzzywuzzy">Bears and Fuzzy Pattern Matching. Introducing FuzzyWuzzy</h4>
<p>Our next library, FuzzyWuzzy, gives us a way to easily fuzzy match strings. Fuzzy matching involves comparing an input to a variety of values and computing their amount of "sameness". The <a target="_blank" href="https://github.com/seatgeek/fuzzywuzzy">FuzzyWuzzy documentation</a> gives more information.</p>
<pre>
from fuzzywuzzy import fuzz
from fuzzywuzzy import process
fuzz.ratio("this is a test", "this is a test!")
fuzz.partial_ratio("this is a test", "this is a test!")
fuzz.token_sort_ratio("fuzzy was a bear", "fuzzy fuzzy was a bear")
choices = ["Atlanta Falcons", "New York Jets", "New York Giants", "Dallas Cowboys"]
process.extract("new york jets", choices, limit=2)
process.extractOne("cowboys", choices)
</pre>
<p>With <strong>fuzz.ratio</strong>, we compare "this is a test" to "this is a test!" which gives us a score of 97 (97% the same). <strong>fuzz.partial_ratio</strong>, scoring on the first string existing in the second, gives us 100 because 100% of the first string exists in the second. <strong>fuzz.token_sort_ratio</strong>, using "tokens" to compare the first set of words compared to the second, gives us a score of 84.</p>
<p>The second section of the code compares the first string to matches in a list of strings. <strong>process.extract</strong> returns a list of tuples containing the matched string from the list and the score. (limit sets the number of matches returned.) <strong>process.extractOne</strong> does the same thing but only returns one match which is always the <strong>best match</strong> (highest score).</p>
<h4 id="emoji">Wink, Smile, Thumbs Up. Introducing Emoji</h4>
<p>Emoji is a Python library that unsurprisingly allows us to display emojis. <a target="_blank" href="https://github.com/carpedm20/emoji/">Emoji's documentation</a> is easy to follow but it doesn't give much detail.</p>
<pre>
import emoji
print(emoji.emojize('Python is :thumbs_up:'))
print(emoji.emojize('Python is :thumbsup:', use_aliases=True))
print(emoji.emojize("Python is fun :red_heart:"))
print(emoji.emojize("Python is fun :red_heart:",variant="emoji_type"))
</pre>
<p>There's not much to Emoji but it does its job well. <strong>emoji.emojize</strong> replaces any emoji code with the actual emoji symbol. <strong>use_aliases=True</strong> allows you to use alternate names for the emoji (:thumbs_up: vs :thumbsup:). <strong>variant</strong> seems to force your computer to load an actual emoji in the terminal instead of an ascii code character. (That's just a guess based on my experimenting with it but it doesn't say anything in the docs.)</p>
<h4 id="inflect">The Last Stop On Our Magical Mystery Tour. Introducing Inflect</h4>
<p>Inflect is a bit harder to explain because it does a lot. It's a toolbox that helps us build messages/sentences to display to the user that are "smart". If you give Inflect a verbs, nouns or adjectives, it's smart enough to return the correct version of the word. For example the plural form of "dog" is "dogs". Inflect also allows you to switch numbers (1, 100, 1000) to words (one, one hundred, one thousand).
<pre>
import inflect
inflector = inflect.engine()
num_words = inflector.number_to_words(1632976)
num_words
num_turkeys = 1
print("I saw", num_turkeys, inflector.plural(word, num_turkeys))
num_turkeys = 17
print("I saw", num_turkeys, inflector.plural(word, num_turkeys))
n1 = 1
n2 = 2
print(
inflector.plural_noun("I", n1),
inflector.plural_verb("saw", n1),
inflector.plural_adj("my", n2),
inflector.plural_noun("saw", n2)
)
</pre>
<h4 id="miniproject">Let's Build A Project!</h4>
<p>Now that we've seen how to use these libraries, let's put them all together to build a mini project. Our project is going to be a very small version of MadLibs. I'll show you the code and then give a tiny bit of explanation.</p>
<pre>
import random
from datetime import timedelta
import delorean
import emoji
import inflect
from delorean import Delorean
from fuzzywuzzy import process
class Story:
verbs = ['runs', 'swims', 'walks', 'flies', 'paints', 'sleeps', 'eats', 'drives', 'spells', 'bakes', 'slices', 'waters']
nouns = ['taco', 'elephant', 'cheeseburger', 'shoe', 'ogre', 'broccoli', 'lightbulb', 'cloud', 'coffee', 'mailbox', 'slime', 'hat']
descriptors = ['green', 'tired', 'huge', 'sleepy', 'delicious', 'stinky', 'funky', 'hairy', 'moldy', 'expensive', 'wet', 'droopy']
places = ['house', 'stadium', 'theater', 'store', 'bakery', 'amusement park', 'canyon', 'moon', 'valley', 'circus tent', 'forest', 'mountain']
subjects = {
0: ['boy', 'girl', 'rabbit', 'platypus', 'astronaut', 'horse', 'alligator', 'clown', 'dentist', 'beekeeper', 'mushroom', 'monster'],
1: ['candle', 'toe', 'headache', 'spoon', 'cat', 'toaster', 'racecar', 'baseball', 'toothbrush', 'towel', 'sandbox', 'grampa']
}
adjectives = ['this', 'that', 'a', 'my', 'your', 'his', 'her', 'the']
prepositions = ['at', 'in', 'under', 'beside', 'behind', 'above', 'from']
intervals = ['minutes', 'hours', 'days', 'weeks']
feelings = {'sad': ':frowning:', 'happy': ':smile:', 'tired': ':tired_face:', 'sleepy': ':sleepy:', 'angry': ':angry:', 'worried': ':worried:', 'confused': ':confused:', 'disappointed': ':disappointed:', 'amazed': ':open_mouth:'}
lunch_stuff = ['Chicken Tacos', 'Cheeseburgers', 'Steamed Broccoli', 'Black Coffee']
num_stories = 1
def __init__(self, num_stories = 1):
self.inflector = inflect.engine()
self.num_stories = num_stories
def about_lunch(self, noun):
if process.extractOne(noun, self.lunch_stuff)[1] >= 75:
return 'is'
return 'isn\'t'
def random_emotion(self):
emotion = random.choice(
list(self.feelings)
)
return (
emotion,
self.emoji_by_emotion(emotion)
)
def emoji_by_emotion(self, emotion):
selected_emoji = self.feelings.get(emotion)
return emoji.emojize(selected_emoji, use_aliases=True)
def single_or_plural(self):
num = random.randint(1, 10000)
if num % 2 == 0:
return 1
return 2
def random_number(self):
num = random.randint(1, 10000)
if num % 3 == 0:
return 1
return num
def number_to_words(self,num):
return self.inflector.number_to_words(num)
def random_preposition(self):
return random.choice(self.prepositions)
def random_descriptor(self):
return random.choice(self.descriptors)
def random_place(self):
return random.choice(self.places)
def random_subject(self, idx, num):
return self.inflector.plural_noun(
random.choice(self.subjects.get(idx)),
num
)
def random_adjective(self, num):
adjective = random.choice(self.adjectives)
if num == 1:
return adjective
return self.inflector.plural_adj(adjective)
def random_noun(self, num):
noun = random.choice(self.nouns)
if num == 1:
return noun
return self.inflector.plural(noun)
def random_verb(self, num):
return self.inflector.plural_verb(
random.choice(self.verbs), num
)
def adjective_subject_verb(self, subject_idx):
num = self.single_or_plural()
return [
self.random_adjective(num),
self.random_subject(subject_idx, num),
self.random_verb(num)
]
def adjective_subject_place(self, subject_idx):
num = self.single_or_plural()
subject = self.random_subject(subject_idx, num)
if num == 2:
subject += '\''
else:
subject += '\'s'
return [
self.random_adjective(num),
subject,
self.random_place()
]
def adjective_num_descriptor_noun(self):
num = self.random_number()
return [
self.random_adjective(num),
self.number_to_words(num),
self.random_descriptor(),
self.random_noun(num)
]
def timeframe(self):
self.delorean = Delorean()
self.delorean = self.delorean.shift("US/Central")
interval = random.choice(self.intervals)
num = random.randint(1, 10)
self.delorean += timedelta(
**{interval: num}
)
out = f'In {num} {interval}, it will be {self.delorean.datetime.strftime("%a %b %d, %Y %I:%M %p")}'
return out
def capitalize_sentences(self, orig):
sentences = orig.split(". ")
sentences2 = [sentence[0].capitalize() + sentence[1:] for sentence in sentences]
return '. '.join(sentences2)
def create_story(self):
part1 = self.adjective_subject_verb(0)
part2 = self.adjective_num_descriptor_noun()
part3 = self.adjective_subject_place(1)
preposition = self.random_preposition()
time = self.timeframe()
(emotion, feeling) = self.random_emotion()
lunch = f'The first sentence {self.about_lunch(part2[3])} about lunch.'
out = self.capitalize_sentences(f"{' '.join(part1)} {' '.join(part2)} {preposition} {' '.join(part3)}.\n{time}.\nI\'m {feeling} {emotion}.\n{lunch}\n\n")
print(out)
if __name__ == '__main__':
story_maker = Story(
int(input('How many stories: '))
)
for i in range(story_maker.num_stories):
story_maker.create_story()
</pre>
<h4>Explanation</h4>
<p>This post has gotten pretty long so I'm not going to explain the code in a lot of detail. If you have specific questions, please ask them in the comment section below.</p>
<h4>Basic Overview</h4>
<ul>
<li><strong>Input:</strong> Ask the user for how many "stories" he/she wants to create.</li>
<li><strong>Random adjective, subject, verb:</strong> Select whether noun is singular or plural and randomly get an adjective, subject and verb with the correct choice of singular or plural.</li>
<li><strong>Get more random words:</strong> Randomly select an adjective, number, descriptor and noun.</li>
<li><strong>Pick another random adjective, subject and place:</strong> You know the drill by now.</li>
<li><strong>Preposition:</strong> Randomly choose a preposition from our list.</strong>
<li><strong>Use our Delorean object:</strong> Set the timezone and randomly set a time in the future.</li>
<li><strong>Express yourself:</strong> Pick a random feeling and an associated emoji.</li>
<li><strong>Does the first sentence contain a food word?</strong> Fuzzy match the first sentence against our list of food words.</li>
<li><strong>Capitalization:</strong> Concatenate all of the sentences together and capitalize the first word of each one.</li>
<li><strong>Display:</strong> Print out the result.</li>
<li><strong>Repeat:</strong> Continue the process for each other story we need to create.</li>
</ul>
<h4>Support This Site By Buying Me A Coffee!</h4>
<p>If you find this tutorial helpful, please consider <a target="_blank" href="https://www.buymeacoffee.com/z4F8QVS6w">buying me a coffee</a>. Thanks!</p>
<h4 id="output">Sample Output</h4>
<span class="image"><img src="https://res.cloudinary.com/diuqkp0ek/image/upload/v1619982556/miniproject1.png"></span><p>We introduce Python libraries you've probably missed and make a fun mini project.</p>2021-05-02T10:47:45-05:00https://www.iceorfire.com/post/renpy-pirate-game-part-6-styles-scoring-inventory-systemRenpy Pirate Game Part 6 - Styles, Scoring, Inventory System2024-03-29T14:51:09.199661+00:00IoFAdmin<h4>Our Previous Trip On the High Seas...</h4>
<p>In <a href="/post/our-renpy-game-part-5-variables-conditionals-and-screens">our last post</a>, we learned all about Renpy variables, conditionals and screens. We'll build on those concepts in this post.</p>
<h4>Programming Notice!</h4>
<p>For brevity's sake, I'll only be showing code that has changed since last post instead of all of the code. (Your noticing the notice has been noticed.)</p>
<h4>Sailing Ever Onward</h4>
<p>We'll be exploring three concepts in this tutorial:</p>
<ul>
<li>Styles</li>
<li>A Scoring System</li>
<li>An Inventory System</li>
</ul>
<h4>So What Are Renpy Styles?</h4>
<p>Styles in Renpy allow us to change the appearance and layout of components in our game. If you've ever learned CSS for websites, it's similar in concept. While Renpy Styles can become quite complicated, we're keeping it pretty simple here. (See the <a target="_blank" href="https://www.renpy.org/doc/html/style.html">Renpy documentation</a> for more information on styles.)</p>
<h4>Getting the High Score</h4>
<p>To help the player and add a little extra fun, we're going to add scoring to our game. (By the way, how and what does a pirate score? A date? Some treasure? A bottle of rum?) When our player completes certain tasks in the game, he/she will receive points. To complete the current level, our player will have to complete all of the required tasks (and score all of the points). </p>
<h4>An Inventory System... AKA Our Pirate Treasure</h4>
<p>If you remember from our last post, our pirate hero found a house key when he investigate the potted plant in front of his house. We set a variable, hasHouseKey, to keep track of whether he collected the key or not. Since we're going to have many more items in the future, setting a variable for each one will get to be impractical. An inventory system will solve our problem. We'll be visualizing our inventory with a backpack.</p>
<h4>X Marks the Spot... For Our Code</h4>
<p>Keeping with the format of the previous posts, I'll show you the code and then explain it. We'll start with <strong>script.rpy</strong> from post #5. Like I mentioned earlier, we'll only be looking at code that has changed so your code will be longer than what's shown here.</p>
<pre>
default inventory = []
label start:
scene bg insidehouse
show pirate happy
show screen hudScreen
$ points = 0
$ player_name = renpy.input("What is your name?", length=10)
$ player_name = player_name.strip()
if player_name == "":
$ player_name = "Sheldon"
p "No Miss Fluffybottom! Wait!"
"Your cat, the adventurous (and naughty) Miss Fluffybottom runs out the front door of your house. That bad kitty's chasing a bird again! You should probably go after her right meow."
menu:
"Go after her.":
jump house_outside
"She'll be fine.":
jump house_inside
return
label flower_pot:
scene bg outsidehouse
show pirate happy
if 'house_key' in inventory:
p "Mom's favorite plant could use some watering but I'll LEAVE it alone right now."
else:
$ inventory.append('house_key')
$ points += 10
p "Hey what's that? Looks like a house key! Now I have the key to my front door."
jump front_porch
label house_outside:
scene bg outsidehouse
show screen frontPorchScreen
show pirate happy
p "There she is down on the beach chasing the seagulls again. Crazy cat! I should probably bring her back in the house before she gets lost or drowns."
"The door slams shut and you're locked out of the house!"
menu:
"Use my key." if 'house_key' in inventory:
hide screen frontPorchScreen
jump house_inside
"Knock on the door." if 'house_key' not in inventory:
jump door_knock
"Go after Ms Fluffybottom.":
hide screen frontPorchScreen
jump beach_squid
label front_porch:
scene bg outsidehouse
show screen frontPorchScreen
show pirate happy
p "My front porch."
menu:
"Use my key." if 'house_key' in inventory:
hide screen frontPorchScreen
jump house_inside
"Knock on the door." if 'house_key' not in inventory:
hide screen frontPorchScreen
jump door_knock
"Go after Ms Fluffybottom.":
hide screen frontPorchScreen
jump beach_squid
</pre>
<h4>Another One</h4>
<p>Open up <strong>myscreens.rpy</strong> and replace everything with the code below.</p>
<pre>
screen frontPorchScreen():
imagebutton:
xalign 0.75
yalign 1.0
idle "flower"
action Jump("flower_pot")
screen hudScreen():
frame style style["hud_frame"]:
hbox:
if player_name == "":
label "Player"
else:
label player_name
label " Points: "
text "%d " % points
imagebutton:
idle "backpack"
action Show("inventoryScreen")
screen inventoryScreen():
modal True
frame style style["inventory_frame"]:
imagebutton style style["close_btn"]:
idle "close"
action Hide("inventoryScreen")
vbox:
text "Inventory"
grid 7 4:
for i in range(28):
frame:
maximum(155,155)
if i < len(inventory):
background Image(inventory[i]+".png")
$ inv_item_name = inventory[i].replace('_', ' ')
text [inv_item_name] style style["inv_item"]
else:
background Image("placeholder.png")
</pre>
<h4>Arrrgh! This Be The Last One</h4>
<p>Create a new file in the same directory as script.rpy, name it <strong>mystyles.rpy</strong>, and paste all of the following code into it.</p>
<pre>
style inv_item is text:
size 16
bold True
color Color((0, 255, 0, 255))
pos (5,125)
style inventory_frame is frame:
xpadding 10
ypadding 10
xalign 0.5
yalign 0.5
xsize 1152
ysize 660
background Color((193, 66, 66, 192))
style close_btn:
xpos 1100
ypos -4
style hud_frame is frame:
xpadding 10
ypadding 10
xalign 0.5
yalign 0.0
</pre>
<h4>So What Was All of That?</h4>
<p>We'll start with <strong>script.rpy</strong> mateys.</p>
<pre>
default inventory = []
</pre>
<p>We replaced <strong>define hasHouseKey = False</strong> with <strong>default inventory = []</strong> which is required to work with our screen code below.</p>
<pre>
label start:
scene bg insidehouse
show pirate happy
show screen hudScreen
$ points = 0
</pre>
<p>Here we're showing a new screen <strong>hudScreen</strong> (which we'll create later) and we have a new variable <strong>points</strong> that we'll use in our scoring system.</p>
<pre>
label flower_pot:
scene bg outsidehouse
show pirate happy
if 'house_key' in inventory:
p "Mom's favorite plant could use some watering but I'll LEAVE it alone right now."
else:
$ inventory.append('house_key')
$ points += 10
p "Hey what's that? Looks like a house key! Now I have the key to my front door."
jump front_porch
</pre>
<p>In our <strong>flower_pot</strong> label, we're checking to see if <strong>house_key</strong> exists in our <strong>inventory</strong> variable. If it does, make a bad pun, else append it to our inventory list variable and give our player 10 points.</p>
<pre>
label house_outside:
scene bg outsidehouse
show screen frontPorchScreen
show pirate happy
p "There she is down on the beach chasing the seagulls again. Crazy cat! I should probably bring her back in the house before she gets lost or drowns."
"The door slams shut and you're locked out of the house!"
menu:
"Use my key." if 'house_key' in inventory:
hide screen frontPorchScreen
jump house_inside
"Knock on the door." if 'house_key' not in inventory:
jump door_knock
"Go after Ms Fluffybottom.":
hide screen frontPorchScreen
jump beach_squid
</pre>
<p>Same thing here... see if <strong>house_key</strong> is in our inventory variable.</p>
<pre>
label front_porch:
scene bg outsidehouse
show screen frontPorchScreen
show pirate happy
p "My front porch."
menu:
"Use my key." if 'house_key' in inventory:
hide screen frontPorchScreen
jump house_inside
"Knock on the door." if 'house_key' not in inventory:
hide screen frontPorchScreen
jump door_knock
"Go after Ms Fluffybottom.":
hide screen frontPorchScreen
jump beach_squid
</pre>
<p>Third time's a charm.</p>
<pre>
screen hudScreen():
frame style style["hud_frame"]:
hbox:
if player_name == "":
label "Player"
else:
label player_name
label " Points: "
text "%d " % points
imagebutton:
idle "backpack"
action Show("inventoryScreen")
</pre>
<p>In <strong>myscreens.rpy</strong>, we define a new screen <strong>hudScreen</strong> which will sit at the top of our game and display the player's name, current points, and an imagebutton of a backpack that will load our inventory screen. We're using a style here: <strong>frame style style["hud_frame"]</strong>. That tells Renpy that we have a style called <strong>hud_frame</strong> in our new mystyles.rpy file which we want to apply to our frame object. Inside our frame, we have a horizontal box (hbox) with a player label, a points label, a points text, and an imagebutton. The <strong>text "%d " % points</strong> code tells Renpy that we are wanting to display points as an integer.</p>
<pre>
screen inventoryScreen():
modal True
frame style style["inventory_frame"]:
imagebutton style style["close_btn"]:
idle "close"
action Hide("inventoryScreen")
vbox:
text "Inventory"
grid 7 4:
for i in range(28):
frame:
maximum(155,155)
if i < len(inventory):
background Image(inventory[i]+".png")
$ inv_item_name = inventory[i].replace('_', ' ')
text [inv_item_name] style style["inv_item"]
else:
background Image("placeholder.png")
</pre>
<p>We define our screen as <strong>modal</strong> which means nothing below it can be interacted with. We have a frame using the <strong>inventory_frame</strong> style that contains an imagebutton and a vbox. The imagebutton closes the current screen when clicked. Inside the vbox we have a <strong>grid</strong> which has 7 columns and 4 rows. We loop over each cell in the grid and add a frame with a maximum width and height of 155 pixels. If the current index of <strong>i</strong> is less than the number of elements in the inventory variable, add a background Image and the name of the item. Else, display a placeholder image.</p>
<pre>
style inv_item is text:
size 16
bold True
color Color((0, 255, 0, 255))
pos (5,125)
</pre>
<p>Here we define our first style. We're creating styles in the <strong>mystyles.rpy</strong> file to keep the other files less cluttered. Renpy styles are pretty self-explanatory which makes them easy to understand. The <strong>inv_item</strong> part gives us a name that we can use the access the style in other files. The <strong>is text</strong> section tells Renpy that our style is modifying the default text style included with Renpy. All of the other parts do just what you'd expect: set the text size to 16 pixels, bold the text, set the text color to green, and display the text at 5 pixels to the left and 125 pixels down from the top left corner of the Renpy component that the style is applied to.</p>
<pre>
style inventory_frame is frame:
xpadding 10
ypadding 10
xalign 0.5
yalign 0.5
xsize 1152
ysize 660
background Color((193, 66, 66, 192))
</pre>
<p>Define a style named <strong>inventory_frame</strong> that modifies the default Renpy frame styles. Give it 10 pixels padding on the all sides (both x and y), center it horizontally and vertically (xalign 0.5 -> horizontal, yalign 0.5 -> vertical), set the size as 1152 x 660 pixels, and set the background color to reddish with a partial opacity (a little bit transparent).</p>
<pre>
style close_btn:
xpos 1100
ypos -4
</pre>
<p>Another style... This time it doesn't modify any default style. Set the position as 1110 pixels to the left and 4 pixels above the top left corner of the container that the component is inside of.</p>
<pre>
style hud_frame is frame:
xpadding 10
ypadding 10
xalign 0.5
yalign 0.0
</pre>
<p>Last style... Modify the default frame style, set padding to 10 pixels in all directions, center horizontally, display at the top of the container.</p>
<h4>Screenshots</h4>
<span class="image"><img src="https://res.cloudinary.com/diuqkp0ek/image/upload/v1620767922/screenshot_6_1.png"></span>
<span class="image"><img src="https://res.cloudinary.com/diuqkp0ek/image/upload/v1620767921/screenshot_6_2.png"></span>
<span class="image"><img src="https://res.cloudinary.com/diuqkp0ek/image/upload/v1620767921/screenshot_6_3.png"></span>
<span class="image"><img src="https://res.cloudinary.com/diuqkp0ek/image/upload/v1620767921/screenshot_6_4.png"></span>
<h4>Wrapping Up</h4>
<p>If you followed along and didn't walk the plank, your Renpy game should look very similar to the images above. As always, if you have feedback please let me know your opinions in the comments below.</p>
<h4>Git Repo Link</h4>
<p>If you'd like to download the code and images in this post, <a target="_blank" href="https://bitbucket.org/iceorfire-renpy/pirates/src/main/part6/">get them here</a>.</p>
<h4>Support Me</h4>
<p>If you find these tutorials useful (or enjoy my bad puns), please consider <a href="https://www.buymeacoffee.com/z4F8QVS6w">buying me a coffee</a>. Thanks!</p>
<p>In part 6 of our Renpy tutorial series, we explore styles, and inventory system and scoring.</p>2021-05-11T16:06:05-05:00https://www.iceorfire.com/post/python-madlibs-tutorialPython Madlibs Tutorial2024-03-29T14:51:09.199569+00:00IoFAdmin<h4>We're Going To Create Our Own Madlibs!</h4>
<p>In this tutorial, we're going to make our own version of Madlibs using the <a href="/post/four-useful-python-libraries-you-dont-know-about#inflect">Inflect library</a> that we covered in a previous post and our own code.</p>
<h4>Copyright Notice</h4>
<p>I pulled Madlib templates from the <a target="_blank" href="https://github.com/HermanFassett/madlibz">Madlibz API</a> so I'm guessing that they're handling any copyright issues or that they created the templates for public use.</p>
<h4>You Don't Know What Madlibs Are?</h4>
<p>In case you don't know what Madlibs are, they're very short fill in the blank stories that tell funny and crazy tales. The user doesn't know what their story will be when they provide a list of words. They're in for a silly surprise when the whole Madlib is revealed.</p>
<h4>Enough Jibber Jabber! Let's Get To It</h4>
<p>We'll focus on 3 parts of our project:</p>
<ul>
<li><strong>Madlibz Api:</strong> this is where we'll get our templates. This is <strong>optional</strong> if you want to create your own Madlibs templates from scratch.</li>
<li><strong>Inflect library:</strong> this awesome library will help us handle verb and noun cases easily.</li>
<li><strong>Our Madlibs Python class:</strong> this is where the magic happens.</li>
</ul>
<h4>Fetch My Template Please!</h4>
<p>It's out of the scope of this tutorial for me to go into using an API in Python. If you want to request a few templates by hand, you can just go to the <a href="http://madlibz.herokuapp.com/api/random">random generation page</a> and save the responses in a text file.</p>
<p>Once you have your template(s) saved, you'll have to modify save them to a CSV file so that our Python code can use it. If you go to the random generation page, you'll get something back like this:</p>
<pre>
{
"title": "Learning About History",
"blanks": [
"adjective",
"noun",
"nouns",
"adjective",
"nouns",
"nouns",
"animals",
"nouns",
"nouns",
"number",
"number",
"nouns",
"adjective",
"nouns"
],
"value": [
"History is ",
" because we learn about ",
" and ",
" that happened long ago. I can't believe people used to dress in ",
" clothing and kids played with ",
" and ",
" instead of video games. Also, before cars were invented, people actually rode ",
"! People read ",
" instead of computers and tablets, and sent messages via ",
" that took ",
" days to arrive. I wonder how kids will view my life in ",
" year(s); maybe they will ride flying cars to school and play with ",
" and ",
" ",
"!",
0
]
}
</pre>
<p>That's the data we need to build our data file. Create a new text file and name it <strong>templates.csv</strong>. </p>
<h4>Set Up Our Templates File</h4>
<p>If you use the data above, you can modify it to look like the the data below (or copy/paste). If you have something else, you'll have to modify it yourself. This data will end up in templates.csv</p>
<pre>
adjectives,nouns,numbers,colors,places,verbs,person,food,clothing,celebrity,occupation,text
3,9,3,0,0,0,0,0,0,0,0,"History is |ADJ| because we learn about |NOUN| and |PLURAL_NOUN| that happened long ago. I can't believe people used to dress in |ADJ| clothing and kids played with |PLURAL_NOUN| and |PLURAL_NOUN| instead of video games. Also, before cars were invented, people actually rode |PLURAL_NOUN|! People read |PLURAL_NOUN| instead of computers and tablets, and sent messages via |PLURAL_NOUN| that took |NUM| days to arrive. I wonder how kids will view my life in |NUM| year(s); maybe they will ride flying cars to school and play with |PLURAL_NOUN| and |ADJ| |PLURAL_NOUN|!"
</pre>
<p>That's our CSV (comma separated values) file. The first line of the file describes what the data is and all of the other lines (you can and should add more later) make up the actual data that our program will use. But what are <strong>|PLURAL_NOUN|</strong> and <strong>|ADJ|</strong>? I'm glad you asked, Random Internet Stranger. Anything between the two pipe characters are our placeholders that will be filled in with user-provider words. In other words, they're the blanks in our Madlibs. The numbers correspond to how many of the placeholder types our template has. In our example, we have 3 adjectives and 0 occupations.</p>
<h4>Placeholders?</h4>
<p>Our Madlibs clone allows for 11 types of placeholders: adjectives, nouns, numbers, colors, places, verbs, person, food, clothing, celebrity, and occupation. Their corresponding placeholders are: |ADJ|, |NOUN|, |PLURAL_NOUN|, |VERB|, |NUM|, |COLOR|, |PLACE|, |PERSON|, |FOOD|, |CLOTHING|, |CELEBRITY|, |OCCUPATION|. You may have noticed that we have two placeholders for nouns... we'll get to that later.</p>
<h4>Madlibs class</h4>
<p>This is the main part of our code. I'll post the code and then break it down.</p>
<pre>
import inflect
import csv
import random
class Madlibs:
# madlib templates from http://madlibz.herokuapp.com/api/random
listNames = ['adjective', 'noun', 'verb', 'number', 'color', 'place', 'person', 'food', 'clothing', 'celebrity', 'occupation']
templatePlaceholders = {
'adjective': '|ADJ|',
'noun': '|NOUN|',
'plural_noun': '|PLURAL_NOUN|',
'verb': '|VERB|',
'number': '|NUM|',
'color': '|COLOR|',
'place': '|PLACE|',
'person': '|PERSON|',
'food': '|FOOD|',
'clothing': '|CLOTHING|',
'celebrity': '|CELEBRITY|',
'occupation': '|OCCUPATION|'
}
def __init__(self):
self.inflector = inflect.engine()
def setClassListByName(self, listName, data):
try:
if listName not in self.listNames:
raise Exception(f'{listName} not in list of names')
setattr(self, listName, data)
except:
print('Error!')
def getClassListByName(self, listName):
try:
if listName not in self.listNames:
raise Exception(f'{listName} not in list of names')
return getattr(self, listName)
except:
pass
def setSlug(self, slugType):
return self.templatePlaceholders.get(slugType)
def replaceCurrentSlug(self, replaceValue, slugType):
self.template = self.template.replace(slugType, replaceValue.strip(), 1)
def setTemplate(self, template):
self.template = template
def parseTemplates(self):
self.templates = []
with open('templates.csv') as csv_file:
csv_reader = csv.reader(csv_file, delimiter=',')
line_count = 0
for row in csv_reader:
if line_count == 0:
line_count += 1
continue
self.templates.append({
'num_adjs' : row[0],
'num_nouns' : row[1],
'num_numbers' : row[2],
'num_colors' : row[3],
'num_places' : row[4],
'num_verbs' : row[5],
'num_persons' : row[6],
'num_foods' : row[7],
'num_clothes' : row[8],
'num_celebs' : row[9],
'num_occupations' : row[10],
'text' : row[11]
})
self.getInput(
self.templates[ random.randint(0, len(self.templates) - 1) ]
)
def getTemplate(self):
return self.template
def fillNouns(self, nouns):
for noun in nouns:
next_noun_pos = self.template.find(self.setSlug('noun'))
next_pluralnoun_pos = self.template.find(self.setSlug('plural_noun'))
if next_noun_pos == -1 and next_pluralnoun_pos == -1:
continue
if next_noun_pos == -1:
self.replaceCurrentSlug(self.inflector.plural(noun), self.setSlug('plural_noun'))
else:
self.replaceCurrentSlug(noun, self.setSlug('noun'))
def fillTemplate(self):
for listName in self.listNames:
if self.getClassListByName(listName) is None:
continue
if listName == 'noun':
self.fillNouns(self.getClassListByName('noun'))
continue
for ln in self.getClassListByName(listName):
self.replaceCurrentSlug(ln, self.setSlug(listName))
def getInput(self, templateDict):
if int(templateDict.get('num_adjs')) > 0:
self.setClassListByName('adjective', input(f"Enter {templateDict.get('num_adjs')} adjectives separated by commas: ").split(','))
if int(templateDict.get('num_nouns')) > 0:
self.setClassListByName('noun', input(f"Enter {templateDict.get('num_nouns')} nouns separated by commas: ").split(','))
if int(templateDict.get('num_numbers')) > 0:
self.setClassListByName('number', input(f"Enter {templateDict.get('num_numbers')} numbers separated by commas: ").split(','))
if int(templateDict.get('num_colors')) > 0:
self.setClassListByName('color', input(f"Enter {templateDict.get('num_colors')} colors separated by commas: ").split(','))
if int(templateDict.get('num_places')) > 0:
self.setClassListByName('place', input(f"Enter {templateDict.get('num_places')} places separated by commas: ").split(','))
if int(templateDict.get('num_verbs')) > 0:
self.setClassListByName('verb', input(f"Enter {templateDict.get('num_verbs')} verbs separated by commas: ").split(','))
if int(templateDict.get('num_persons')) > 0:
self.setClassListByName('person', input(f"Enter {templateDict.get('num_persons')} people separated by commas: ").split(','))
if int(templateDict.get('num_foods')) > 0:
self.setClassListByName('food', input(f"Enter {templateDict.get('num_foods')} foods separated by commas: ").split(','))
if int(templateDict.get('num_clothes')) > 0:
self.setClassListByName('clothing', input(f"Enter {templateDict.get('num_clothes')} articles of clothing separated by commas: ").split(','))
if int(templateDict.get('num_celebs')) > 0:
self.setClassListByName('celebrity', input(f"Enter {templateDict.get('num_celebs')} celebrities separated by commas: ").split(','))
if int(templateDict.get('num_occupations')) > 0:
self.setClassListByName('occupation', input(f"Enter {templateDict.get('num_occupations')} jobs separated by commas: ").split(','))
self.setTemplate(templateDict.get('text'))
if __name__ == '__main__':
madlib = Madlibs()
madlib.parseTemplates()
madlib.fillTemplate()
print(madlib.getTemplate())
</pre>
<h4>Explanation</h4>
<pre>
def __init__(self):
self.inflector = inflect.engine()
</pre>
<p>In the class constructor, we get an instance of the Inflector engine and save it to a class variable <strong>self.inflector</strong>.</p>
<pre>
def parseTemplates(self):
self.templates = []
with open('templates.csv') as csv_file:
csv_reader = csv.reader(csv_file, delimiter=',')
line_count = 0
for row in csv_reader:
if line_count == 0:
line_count += 1
continue
self.templates.append({
'num_adjs' : row[0],
'num_nouns' : row[1],
'num_numbers' : row[2],
'num_colors' : row[3],
'num_places' : row[4],
'num_verbs' : row[5],
'num_persons' : row[6],
'num_foods' : row[7],
'num_clothes' : row[8],
'num_celebs' : row[9],
'num_occupations' : row[10],
'text' : row[11]
})
self.getInput(
self.templates[ random.randint(0, len(self.templates) - 1) ]
)
</pre>
<p>First we open <strong>templates.csv</strong> and loop over each line. If we're on the first line, just do nothing and go to the next iteration of the loop. We use <strong>csv.reader</strong> to parse the current line of the CSV into a list named <strong>row</strong>. Then we append a dictionary containing the data of our current line to our <strong>self.templates</strong> list variable. After reading all of our data in the CSV, randomly select one of our list elements and send it to the <strong>self.getInput</strong> method.</p>
<pre>
def getInput(self, templateDict):
if int(templateDict.get('num_adjs')) > 0:
self.setClassListByName('adjective', input(f"Enter {templateDict.get('num_adjs')} adjectives separated by commas: ").split(','))
if int(templateDict.get('num_nouns')) > 0:
self.setClassListByName('noun', input(f"Enter {templateDict.get('num_nouns')} nouns separated by commas: ").split(','))
if int(templateDict.get('num_numbers')) > 0:
self.setClassListByName('number', input(f"Enter {templateDict.get('num_numbers')} numbers separated by commas: ").split(','))
if int(templateDict.get('num_colors')) > 0:
self.setClassListByName('color', input(f"Enter {templateDict.get('num_colors')} colors separated by commas: ").split(','))
if int(templateDict.get('num_places')) > 0:
self.setClassListByName('place', input(f"Enter {templateDict.get('num_places')} places separated by commas: ").split(','))
if int(templateDict.get('num_verbs')) > 0:
self.setClassListByName('verb', input(f"Enter {templateDict.get('num_verbs')} verbs separated by commas: ").split(','))
if int(templateDict.get('num_persons')) > 0:
self.setClassListByName('person', input(f"Enter {templateDict.get('num_persons')} people separated by commas: ").split(','))
if int(templateDict.get('num_foods')) > 0:
self.setClassListByName('food', input(f"Enter {templateDict.get('num_foods')} foods separated by commas: ").split(','))
if int(templateDict.get('num_clothes')) > 0:
self.setClassListByName('clothing', input(f"Enter {templateDict.get('num_clothes')} articles of clothing separated by commas: ").split(','))
if int(templateDict.get('num_celebs')) > 0:
self.setClassListByName('celebrity', input(f"Enter {templateDict.get('num_celebs')} celebrities separated by commas: ").split(','))
if int(templateDict.get('num_occupations')) > 0:
self.setClassListByName('occupation', input(f"Enter {templateDict.get('num_occupations')} jobs separated by commas: ").split(','))
self.setTemplate(templateDict.get('text'))
</pre>
<p>For each type of placeholder, we see if our dictionary variable has any corresponding data. For example, </p>
<pre>
if int(templateDict.get('num_adjs')) > 0:
</pre>
<p>checks if the number of adjectives in our template is greater than 0. </p>
<pre>
self.setClassListByName('adjective', input(f"Enter {templateDict.get('num_adjs')} adjectives separated by commas: ").split(','))
</pre>
<p>has a lot going on so let's break it down. </p>
<pre>
input(f"Enter {templateDict.get('num_adjs')} adjectives separated by commas: ")
</pre>
<p>This would display <strong>Enter 5 adjectives separated by commas:</strong> (assuming that our template has 5 adjectives) and <strong>input</strong> reads the user's entered data from the screen.</p>
<pre>
.split(','))
</pre>
<p>takes the data read in from <strong>input</strong> and splits values by comma and turns them into a list.</p>
<pre>
self.setClassListByName('adjective', VARIABLE)
</pre>
<p>calls the <strong>self.setClassListByName</strong> method with the string <strong>adjective</strong> and the list that we built from <strong>split</strong>.</p>
<pre>
self.setTemplate(templateDict.get('text'))
</pre>
<p>From our template dictionary variable, get the text of the Madlib which also contains the placeholders. Finally, send that data to <strong>self.setTemplate</strong> method.</p>
<p>So to sum up this whole method... for each type of placeholder we ask the user for input if the Madlib template has that type of placeholder. Then we take that user-provided data (formatted as a list) along with the placeholder type and send it to the <strong>self.setClassListByName</strong> method. Finally, we get the Madlib text containing the placeholders that we'll fill with user data and send it to <strong>self.setTemplate</strong> method.</p>
<pre>
def setClassListByName(self, listName, data):
try:
if listName not in self.listNames:
raise Exception(f'{listName} not in list of names')
setattr(self, listName, data)
except:
print('Error!')
</pre>
<p>If <strong>listName</strong> (adjective in our example) is in our <strong>self.listNames</strong> list, use <strong>setattr</strong> to save our data to the corresponding list. Else we're trying to use an invalid placeholder type so we raise an exception and print "Error!".</p>
<pre>
def fillTemplate(self):
for listName in self.listNames:
if self.getClassListByName(listName) is None:
continue
if listName == 'noun':
self.fillNouns(self.getClassListByName('noun'))
continue
for ln in self.getClassListByName(listName):
self.replaceCurrentSlug(ln, self.setSlug(listName))
</pre>
<p>We loop over each element in <strong>self.listNames</strong> and check to see if <strong>self.getClassListByName(listName) </strong> returns <strong>None</strong>. If it is <strong>None</strong>, it means we have no data for that type of placeholder so we skip to the next iteration of the loop. If the list is <strong>noun</strong>s, get the noun data from <strong>self.getClassListByName</strong> and pass it along to the <strong>self.fillNouns</strong> method. Otherwise... get the data for the current listName and loop over each data element. For each data element, call <strong>self.replaceCurrentSlug</strong> with the current listName and the current slug from <strong>self.setSlug</strong>.</p>
<p>When this method is complete, our Madlib content will have placeholders replaced with the user's provided words.</p>
<pre>
def fillNouns(self, nouns):
for noun in nouns:
next_noun_pos = self.template.find(self.setSlug('noun'))
next_pluralnoun_pos = self.template.find(self.setSlug('plural_noun'))
if next_noun_pos == -1 and next_pluralnoun_pos == -1:
continue
if next_noun_pos == -1:
self.replaceCurrentSlug(self.inflector.plural(noun), self.setSlug('plural_noun'))
else:
self.replaceCurrentSlug(noun, self.setSlug('noun'))
</pre>
<p>Since nouns can be singular or plural, we're handling them separately from all of the other placeholder types. We loop over all of our nouns.</p>
<pre>
next_noun_pos = self.template.find(self.setSlug('noun'))
next_pluralnoun_pos = self.template.find(self.setSlug('plural_noun'))
</pre>
<p>We use Python's <strong>find</strong> function to get the next position of our <strong>noun</strong> placeholder and then do the same thing for the next <strong>plural noun</strong> placeholder.</p>
<pre>
if next_noun_pos == -1 and next_pluralnoun_pos == -1:
continue
</pre>
<p>If the position of the next noun and plural noun is -1, skip this time through the loop. This shouldn't ever happen.</p>
<pre>
if next_noun_pos == -1:
self.replaceCurrentSlug(self.inflector.plural(noun), self.setSlug('plural_noun'))
</pre>
<p>If there are no more singular nouns (-1 for position means not found), use <strong>inflect</strong> to get the plural form of the current noun and send it to <strong>self.replaceCurrentSlug</strong>.</p>
<pre>
else:
self.replaceCurrentSlug(noun, self.setSlug('noun'))
</pre>
<p>The next noun to replace is singular so send it to <strong>self.replaceCurrentSlug</strong>.</p>
<pre>
def getTemplate(self):
return self.template
</pre>
<p>Alright! Finally a simple method. We just return our <strong>self.template</strong> variable which contains the final Madlib content with the user-provided words replacing the placeholders.</p>
<h4>Sample Output</h4>
<span class="image"><img src="https://res.cloudinary.com/diuqkp0ek/image/upload/v1621023256/Screen_Shot_2021-05-14_at_3.13.17_PM.png"></span>
<h4>Leave Me A Message... I Get Lonely</h4>
<p>If you have feedback or make your own Madlib templates, please share them in the comments below.</p>
<h4>Support This Site By Buying Me A Coffee!</h4>
<p>If you find this tutorial helpful, please consider <a target="_blank" href="https://www.buymeacoffee.com/z4F8QVS6w">buying me a coffee</a>. Thanks!</p><p>We explore creating a Madlibs clone in Python.</p>2021-05-14T16:32:32-05:00https://www.iceorfire.com/post/python-one-liners-book-reviewPython One Liners Book Review2024-03-29T14:51:09.199473+00:00IoFAdmin<h4>Make Your Code More Concise With One Liners</h4>
<p>The Python language allows for writing easy to follow code but that doesn't mean it's efficient or concise. Concise code is usually easier to understand than verbose code. One liners are code that do multiple things efficiently in one line instead of multiple lines.</p>
<h4>Python One-Liners by Christian Mayer</h4>
<p>According to Christian Mayer in his book <a target="_blank" href="https://amzn.to/3hOE5vj">Python One-Liners: Write Concise, Eloquent Python Like a Professional</a>, <q>focusing on one-liners will help you read and write code faster and more concisely, and will improve your understanding of the language</q>. I agree with Christian that shorter code is better than longer code as long as it's understandable.</p>
<h4>So What Will I Learn?</h4>
<p>There are 6 chapters in Python One-Liners with multiple code examples in each one.</p>
<ul>
<li><strong>Chapter 1 - Python Refresher:</strong> basic data structures (integers, floats, booleans, strings), container data structures (lists, sets, dictionaries, comprehensions), control flow (conditionals, loops, functions, lambdas)</li>
<li><strong>Chapter 2 - Python Tricks:</strong> list comprehensions, reading files, lambda/map, slicing, list concatenation</li>
<li><strong>Chapter 3 - Data Science:</strong> in depth one-liners with NumPy arrays</li>
<li><strong>Chapter 4 - Machine Learning:</strong> linear regression, logistic regression, k-means clustering, neural network analysis, decision-tree learning, classification</li>
<li><strong>Chapter 5 - Regular Expressions:</strong> string matching, web scraping, hyperlink analysis, string extraction, time format validation, duplicate string detection, string modification</li>
<li><strong>Chapter 6 - Algorithms:</strong> put everything from the previous chapters to use</li>
</ul>
<h4>Who Is This Book For?</h4>
<p>This book is not for beginners. You should at least know the basics of Python and have some programming experience because while this book does a good job of explaining the code, it doesn't handhold.</p>
<h4>Summary</h4>
<p><a target="_blank" href="https://amzn.to/3hOE5vj">Python One-Liners: Write Concise, Eloquent Python Like a Professional</a> is a pretty short book (just shy of 200 pages) but there's a lot in it. There's a little bit of something for every Pythonista so you can pick and choose what you need. I focused on chapters 2, 5, and 6 because those apply to me the most but everyone has different programming specialties.</p>
<h4>So Buy It or Skip It?</h4>
<p>I liked many parts of Christian Mayer's <a target="_blank" href="https://amzn.to/3hOE5vj">Python One-Liners: Write Concise, Eloquent Python Like a Professional</a> and I found it useful. </p>
<p>I give it a Fire! Buy it today! <b class="icon solid fa-fire" style="font-size:38px;;color:#f00;"> </b></p><p>We review Python One-Liners: Write Concise, Eloquent Python Like a Professional by Christian Mayer.</p>2021-05-25T10:37:16-05:00https://www.iceorfire.com/post/text-to-speech-funText To Speech Fun2024-03-29T14:51:09.199354+00:00IoFAdmin<h4>We're Going To Create A Program That Reads A Book To Us!</h4>
<p>In this tutorial, we'll use PIL (Python Imaging Library), gtts (Google's text to speech library), and TkInter to create a suite of programs that will read a simple book to us. Let's get started!</p>
<h4>The Jabberwocky AKA I Didn't Want to Write A Story Myself</h4>
<p>Since it's in the public domain and pretty well-known, we'll be using the poem "The Jabberwocky" by Lewis Carroll as our source material. The whole text can be found on <a target="_blank" href="https://en.wikipedia.org/wiki/Jabberwocky">Wikipedia</a>.</p>
<h4>Not Exactly Van Gogh But It'll Do</h4>
<p>The first step in our self-reading book is to create some images. I couldn't find many images of the Jabberwocky online so I decided to create the images using text. Our pictures will be sort of like the "Word Of the Day" calendars that many people have on their desks except ours will feature a word from each stanza of the poem.</p>
<h4>No We Don't Actually Have The Ghost of Stephen Hawking</h4>
<p>Step number two will be to send the text of our poem to gtts and have it create a series of mp3 files that Stephen Hawking... I mean our computer can read to us.</p>
<h4>Finally, We'll Have Storytime With Our Computer</h4>
<p>After we generate our images and mp3s, we'll create a TkInter GUI program that will play the mp3s along with displaying the corresponding images.</p>
<h4>One Thing To Note</h4>
<p>You should run the Python scripts in the order that they're listed here. If you don't, the book reading script won't have the files that it needs to actually work.</p>
<h4>Shut Up Already And Bring On The Code!</h4>
<p>Like all of our tutorials, we'll see the code and then explain how it works. </p>
<h4>Image Creation Code</h4>
<p>Create a file named <strong>text2img.py</strong> and paste the following code into it:</p>
<pre>from tkinter import font
from PIL import Image, ImageDraw, ImageFont
import os
def generateTextImage(word, idx):
posX = 0
posY = 10
pageNumX = 385
pageNumY = 250
offset = 25
fontSize = 20
textColor = (255, 255, 255)
backgroundColor = (0, 0, 0)
img = Image.new('RGB', (500, 300), color = backgroundColor)
curFont = ImageFont.truetype('/Library/Fonts/Arial.ttf', fontSize)
d = ImageDraw.Draw(img)
lines = word.split('|')
for line in lines:
posX += offset
posY += offset
d.text((posX,posY), line, font=curFont, fill=textColor)
d.text((pageNumX, pageNumY), f'page {idx + 1}', font=curFont, fill=textColor)
img.save(f'./img/word_{idx}.png')
if __name__ == '__main__':
words = [
'brillig: four o\'clock in the afternoon, the time|when you begin broiling things for dinner',
'bandersnatch: a swift moving creature with|snapping jaws, capable of extending its neck',
'manxome: fearsome; a portmanteau|of manly and buxom',
'burbled: a mixture of the three verbs|bleat, murmur, and warble',
'galumphing: to move with a clumsy and heavy tread',
'chortled: combination of chuckle and snort',
'wabe: the grass plot around a sundial'
]
os.mkdir('./img')
for idx, val in enumerate(words, start=1):
generateTextImage(val, idx)
generateTextImage('Jabberwocky|by Lewis Carroll', 0)</pre>
<p>There might be a lot going on here but it's relatively simple. First, we create a list of strings containing our words and definitions:</p>
<pre>words = [
'brillig: four o\'clock in the afternoon, the time|when you begin broiling things for dinner',
'bandersnatch: a swift moving creature with|snapping jaws, capable of extending its neck',
'manxome: fearsome; a portmanteau|of manly and buxom',
'burbled: a mixture of the three verbs|bleat, murmur, and warble',
'galumphing: to move with a clumsy and heavy tread',
'chortled: combination of chuckle and snort',
'wabe: the grass plot around a sundial'
]</pre>
<p>Then we create a directory named <strong>img</strong> where we'll save our images.</p>
<pre>os.mkdir('./img')</pre>
<p>Next, we loop over our list and send the data to our <strong>generateTextImage</strong> function.</p>
<pre>for idx, val in enumerate(words, start=1):
generateTextImage(val, idx)</pre>
<p>Finally, we call our function one more time (to account for an image with no corresponding mp3).</p>
<pre>generateTextImage('Jabberwocky|by Lewis Carroll', 0)</pre>
<p>Let's actually create the image using the <strong> generateTextImage</strong> function. Here we define the variables we need to generate our image. <strong>posX</strong> and <strong>posY</strong> set the starting position of the main text in the image. <strong>pageNumX</strong> and <strong>pageNumY</strong> set the starting position of the page number text. The other variables are self-explanatory.</p>
<pre>posX = 0
posY = 10
pageNumX = 385
pageNumY = 250
offset = 25
fontSize = 20
textColor = (255, 255, 255)
backgroundColor = (0, 0, 0)</pre>
<p>We create an image 500 by 300 pixels and use the provided font size and font face. (The path to the font used here is for Mac so you'll have to adjust if you're on a different operating system.) Finally, we create an image variable using our values.</p>
<pre>img = Image.new('RGB', (500, 300), color = backgroundColor)
curFont = ImageFont.truetype('/Library/Fonts/Arial.ttf', fontSize)
d = ImageDraw.Draw(img)</pre>
<p>We split our <strong>word variable</strong> into a list of lines on the pipe character. For each line, we increase the offset so that we have a hanging indent effect. Then we add the text to our image.</p>
<pre>lines = word.split('|')
for line in lines:
posX += offset
posY += offset
d.text((posX,posY), line, font=curFont, fill=textColor)</pre>
<p>We add our current page number text to the image.</p>
<pre>d.text((pageNumX, pageNumY), f'page {idx + 1}', font=curFont, fill=textColor)</pre>
<p>Finally, write the image to a file and save it.</p>
<pre>img.save(f'./img/word_{idx}.png')</pre>
<h4>Mp3 Creation Code</h4>
<p>Now we'll create the mp3 files. Paste the following code into a new file named <strong>text2mp3s.py</strong></p>
<pre>from gtts import gTTS
import os
def generateMp3(words, idx):
myobj = gTTS(text=words, lang='en', slow=False)
myobj.save(f'./mp3/jabberwocky_{idx}.mp3')
if __name__ == '__main__':
jabberwocky = [
'Jabberwocky by Lewis Carroll',
'Twas brillig, and the slithy toves Did gyre and gimble in the wabe: All mimsy were the borogoves, And the mome raths outgrabe.',
'Beware the Jabberwock, my son! The jaws that bite, the claws that catch! Beware the Jubjub bird, and shun The frumious Bandersnatch!',
'He took his vorpal sword in hand; Long time the manxome foe he sought— So rested he by the Tumtum tree And stood awhile in thought.',
'And, as in uffish thought he stood, The Jabberwock, with eyes of flame, Came whiffling through the tulgey wood, And burbled as it came!',
'One, two! One, two! And through and through The vorpal blade went snicker-snack! He left it dead, and with its head He went galumphing back.',
'And hast thou slain the Jabberwock? Come to my arms, my beamish boy! O frabjous day! Callooh! Callay! He chortled in his joy.',
'Twas brillig, and the slithy toves Did gyre and gimble in the wabe: All mimsy were the borogoves, And the mome raths outgrabe.'
]
os.mkdir('./mp3')
for idx, val in enumerate(jabberwocky):
generateMp3(val, idx)</pre>
<p>Through the power of Google and maybe Stephen Hawking... Who knows? Maybe Google's channeling him from the Great Beyond?</p>
<p>We create a list of poetry lines.</p>
<pre>jabberwocky = [
'Jabberwocky by Lewis Carroll',
'Twas brillig, and the slithy toves Did gyre and gimble in the wabe: All mimsy were the borogoves, And the mome raths outgrabe.',
'Beware the Jabberwock, my son! The jaws that bite, the claws that catch! Beware the Jubjub bird, and shun The frumious Bandersnatch!',
'He took his vorpal sword in hand; Long time the manxome foe he sought— So rested he by the Tumtum tree And stood awhile in thought.',
'And, as in uffish thought he stood, The Jabberwock, with eyes of flame, Came whiffling through the tulgey wood, And burbled as it came!',
'One, two! One, two! And through and through The vorpal blade went snicker-snack! He left it dead, and with its head He went galumphing back.',
'And hast thou slain the Jabberwock? Come to my arms, my beamish boy! O frabjous day! Callooh! Callay! He chortled in his joy.',
'Twas brillig, and the slithy toves Did gyre and gimble in the wabe: All mimsy were the borogoves, And the mome raths outgrabe.'
]</pre>
<p>Create a directory to save our mp3 files in...</p>
<pre>os.mkdir('./mp3')</pre>
<p>Loop over our list and call the <strong>generateMp3</strong> function for each one.</p>
<pre>for idx, val in enumerate(jabberwocky):
generateMp3(val, idx)</pre>
<p>Send our text string to our text to speech library and save the generated mp3 files.</p>
<pre>def generateMp3(words, idx):
myobj = gTTS(text=words, lang='en', slow=False)
myobj.save(f'./mp3/jabberwocky_{idx}.mp3')</pre>
<h4>Book Reading Creation Code</h4>
<p>Last one, I promise... Paste the following code into <strong>jabberwocky.py</strong></p>
<pre>from tkinter import *
from PIL import Image, ImageTk
import os
import threading
def playMp3(idx):
# only works for mac
os.system(f'afplay ./mp3/jabberwocky_{idx}.mp3')
# for linux
#os.system(f'mpg321 ./mp3/jabberwocky_{idx}.mp3')
root = Tk()
root.title("Jabberwocky")
root.resizable(0, 0)
frame=Frame(root, width=600, height=500, bg='white', relief=GROOVE, bd=2)
frame.pack(padx=10, pady=10)
images = []
startImg = Image.open('./img/word_0.png')
startImg.thumbnail((500, 300))
start = ImageTk.PhotoImage(startImg)
th = threading.Thread(target=playMp3, args=(0,))
th.start()
for idx in range(0, 8):
img = Image.open(f'./img/word_{idx}.png')
img.thumbnail((500, 300))
images.append(
ImageTk.PhotoImage(img)
)
i = 0
image_label = Label(frame, image=start)
image_label.pack()
def previous():
global i
i = i - 1
if i < 0:
i = 7
image_label.config(image=images[i])
th = threading.Thread(target=playMp3, args=(i,))
th.start()
def next():
global i
i = i + 1
if i > 7:
i = 0
image_label.config(image=images[i])
th = threading.Thread(target=playMp3, args=(i,))
th.start()
btn1 = Button(root, text="< Back", highlightbackground='black', fg='gold', font=('ariel 15 bold'), relief=GROOVE, command=previous)
btn1.pack(side=LEFT, padx=60, pady=5)
btn2 = Button(root, text="Next >", width=8, highlightbackground='black', fg='gold', font=('ariel 15 bold'), relief=GROOVE, command=next)
btn2.pack(side=LEFT, padx=60, pady=5)
btn3 = Button(root, text="Exit", width=8, highlightbackground='black', fg='gold', font=('ariel 15 bold'), relief=GROOVE, command=root.destroy)
btn3.pack(side=LEFT, padx=60, pady=5)
root.mainloop()</pre>
<p>Now we'll create our program that actually "reads" our book. <strong>DISCLAIMER:</strong> this is the first time that I've ever used TkInter so I'm by no means an expert. I urge you to find some tutorials on it so that you can learn more.</p>
<p>Create the root level of our GUI layout, set the title, and add a frame to contain all of our GUI elements.</p>
<pre>root = Tk()
root.title("Jabberwocky")
root.resizable(0, 0)
frame=Frame(root, width=600, height=500, bg='white', relief=GROOVE, bd=2)
frame.pack(padx=10, pady=10)</pre>
<p>We open our "title card" image, set it's size, and save it to a variable named <strong> start</strong>.
<pre>startImg = Image.open('./img/word_0.png')
startImg.thumbnail((500, 300))
start = ImageTk.PhotoImage(startImg)</pre>
<p>Next, we create a new Python thread, assign the <strong> playMp3</strong> function to it, and include "0" as a parameter to it. Then we start the thread. If you've never used Python threads, you should look them up on the official documentation. Basically, a thread is a separate Python process that allows you to run some code that doesn't slow down the main process. In this example, we're playing our mp3 file in a thread while our main process handles updating the GUI. If we don't use a thread, playing the mp3 will make the GUI hang until it finishes.</p>
<pre>th = threading.Thread(target=playMp3, args=(0,))
th.start()</pre>
<p>For each of our images, we create a thumbnail and add it to our images list.</p>
<pre>for idx in range(0, 8):
img = Image.open(f'./img/word_{idx}.png')
img.thumbnail((500, 300))
images.append(
ImageTk.PhotoImage(img)
)</pre>
<p>We take our "start" image and display it.</p>
<pre>image_label = Label(frame, image=start)
image_label.pack()</pre>
<p>Now we define our buttons and display them. The <strong>command</strong> sets a callback to another function when the button is pressed. For example, when <strong>btn1</strong> is pressed, the <strong>previous</strong> function is called. The last line is required to keep TkInter continuously waiting on user input. </p>
<pre>btn1 = Button(root, text="< Back", highlightbackground='black', fg='gold', font=('ariel 15 bold'), relief=GROOVE, command=previous)
btn1.pack(side=LEFT, padx=60, pady=5)
btn2 = Button(root, text="Next >", width=8, highlightbackground='black', fg='gold', font=('ariel 15 bold'), relief=GROOVE, command=next)
btn2.pack(side=LEFT, padx=60, pady=5)
btn3 = Button(root, text="Exit", width=8, highlightbackground='black', fg='gold', font=('ariel 15 bold'), relief=GROOVE, command=root.destroy)
btn3.pack(side=LEFT, padx=60, pady=5)
root.mainloop()</pre>
<p>Here we define our <strong>previous</strong> function. I try never to use global functions in my code but this simplifies the code for tutorial purposes so I'm allowing it here. We decrement the <strong>i variable</strong> by 1 and if we get to a negative number we set it to 7. Since 7 is the last image/mp3 in our book, we are effectively going to the last page. We set the <strong>image_label variable</strong> to use the current image (based on "i" as the index) and display it. Finally, we create a thread to play the current mp3 based on the "i" index.</p>
<pre>def previous():
global i
i = i - 1
if i < 0:
i = 7
image_label.config(image=images[i])
th = threading.Thread(target=playMp3, args=(i,))
th.start()</pre>
<p>Our <strong>next</strong> function is similar to <strong>previous</strong> except that we are incrementing the <strong>i variable</strong> and setting <strong>i</strong> to 0 if it goes beyond the value of 7.</p>
<pre>def next():
global i
i = i + 1
if i > 7:
i = 0
image_label.config(image=images[i])
th = threading.Thread(target=playMp3, args=(i,))
th.start()</pre>
<p>Time to play the actual mp3 file. Depending on your operating system, there are built in programs to play sound files. For <strong>Mac</strong> you can use <strong>afplay</strong> and for <strong>Linux</strong> you can use <strong>mpg321</strong>. I don't have access to <strong>Windows</strong> so you'll have to research that one yourself. (If you run Windows and figure it out, please post in the comments below.) We use <strong>os.system()</strong> to call out to a program on our computer and send the file path of our mp3 file. Pretty cool, huh?</p>
<pre>def playMp3(idx):
# only works for mac
os.system(f'afplay ./mp3/jabberwocky_{idx}.mp3')
# for linux
#os.system(f'mpg321 ./mp3/jabberwocky_{idx}.mp3')</pre>
<h4>So What Did We Learn?</h4>
<ol>
<li>Creating text-based images with the PIL</li>
<li>Generating text to speech mp3 files with gtts</li>
<li>Basic GUI app creation with TkInter</li>
</ol>
<h4>Give Me Some Feedback AKA I Need Constant Validation</h4>
<p>Please let me know what you think about this tutorial. I love comments!</p>
<h4>Support This Site By Buying Me A Coffee!</h4>
<p>If you find this tutorial helpful, please consider <a target="_blank" href="https://www.buymeacoffee.com/z4F8QVS6w">buying me a coffee</a>. Thanks!</p><p>We're building a mini project using text to speech technology!</p>2021-08-03T07:35:17-05:00https://www.iceorfire.com/post/win-the-lottery-with-pythonWin the Lottery With Python2024-03-29T14:51:09.199260+00:00IoFAdmin<h4>We're Going To Be Rich!</h4>
<p>Rich with Python knowledge that is... If you do win millions of dollars with this mini project, please send me some cash too! As Mr. T would say when I was a kid, that's enough jibba jabba so let's get started.</p>
<span class="image"><img src="https://res.cloudinary.com/diuqkp0ek/image/upload/v1633897471/mr_t_small.jpg"></span>
<h4>How Powerball Works</h4>
<p>In case you're not familar with the rules of Powerball, you can <a target="_blank" href="https://www.molottery.com/powerball/gameRules.shtm">read them here</a>.</p>
<h4>Random Numbers</h4>
<p>To simulate Powerball picks, we're going to need some random numbers. If you're new to programming and/or Python then you may not know much about generating random numbers. Our code will pick a number between <strong>MIN_NUMBER</strong> and <strong>MAX_NUMBER</strong>, once for each ball in our simulation. Computers can't actually generate totally random numbers but we're not launching rockets so our <a target="_blank" href="https://en.wikipedia.org/wiki/Pseudorandom_number_generator">pseudo random numbers</a> are good enough.</p>
<h4>Let's Get "Classy"</h4>
<p>We're going to represent our Professional Powerball Picker (is that really a job?) with a Python class that we'll call <strong>Powerball</strong>. Not very imaginative naming but I digress. We'll instantiate this class and then simulate <strong>NUM_OF_DRAWS</strong> draws.</p>
<h4>Our Dependencies</h4>
<p>Our Powerball class is mostly self-contained but we'll need to use Python's built-in <strong>random</strong> module. Check out the <a target="_blank" href="https://docs.python.org/3/library/random.html">official docs</a> for more on random.</p>
<h4>Bring On the Code</h4>
<p>I'll show you all of the code and then we'll break it down in easy to understand chunks. Kinda like knowledge nuggets... without the dipping sauce.</p>
<pre>
from random import shuffle, randint
class Powerball:
NUM_BALLS = 5
MAX_BALL = 69
MAX_POWERBALL = 26
NUM_OF_DRAWS = 5
def __init__(self) -> None:
self.balls = list()
self.balls_selected = list()
self.powerball = None
def setup(self) -> None:
self.balls = [i for i in range(self.MAX_BALL)]
shuffle(self.balls)
self.selectBalls()
self.selectPowerball()
def selectBalls(self) -> None:
self.balls_selected = [self.balls.pop() for i in range(self.NUM_BALLS)]
def selectPowerball(self) -> None:
self.powerball = randint(1, self.MAX_POWERBALL)
def showResults(self) -> None:
print(f'white balls: {self.balls_selected}')
print(f'power ball: {self.powerball}')
print('*'*20)
if __name__ == '__main__':
powerball = Powerball()
for x in range(powerball.NUM_OF_DRAWS):
powerball.setup()
powerball.showResults()</pre>
<p>Let's start at the beginning... because the end would be silly.</p>
<pre>
if __name__ == '__main__':
powerball = Powerball()
for x in range(powerball.NUM_OF_DRAWS):
powerball.setup()
powerball.showResults()</pre>
<p>The <strong>if __name__=='__main__':</strong> part protects users from accidentally running the code if they import it in. If you want more information on that <a target="_blank" href="https://stackoverflow.com/questions/419163/what-does-if-name-main-do">StackOverflow has a great post</a> but you can probably ignore it for now.</p>
<p>Then we create a new instance of the <strong>Powerball class</strong> and save it to the <strong>powerball</strong> variable. Finally, we loop <strong>NUM_OF_DRAWS</strong> times and make our picks.</p>
<pre>
class Powerball:
NUM_BALLS = 5
MAX_BALL = 69
MAX_POWERBALL = 26
NUM_OF_DRAWS = 5</pre>
</pre>
<p>We define our <strong>Powerball</strong> class and then define four variables which are known as member variables in object oriented lingo. We're going to use them as constants even though Python doesn't really support constants like some other programming languages do.</p>
<pre>
def __init__(self) -> None:
self.balls = list()
self.balls_selected = list()
self.powerball = None</pre>
<p>With our init method, we're telling our users that we're not going to return anything (-> None) and we define two lists and a None type variable. We'll set the value of these variables shortly.</p>
<pre>
def setup(self) -> None:
self.balls = [i for i in range(self.MAX_BALL)]
shuffle(self.balls)
self.selectBalls()
self.selectPowerball()</pre>
<p>Here's our <strong>setup</strong> method which also returns nothing. The first line of our method is using list comprehension to create integers from <strong>1</strong> to <strong>MAX_BALL</strong> and saving them into our self.balls variable. Then we're using <strong>shuffle</strong> to randomly shuffle the order of the integers of our list. Finally we call two methods to actually select our numbers.</p>
<pre>
def selectBalls(self) -> None:
self.balls_selected = [self.balls.pop() for i in range(self.NUM_BALLS)]</pre>
<p>Our aptly named <strong>selectBalls</strong> method lives up to its namesake: it selects our balls. The <strong>for i in range(self.NUM_BALLS)</strong> code is taking the first <strong>NUM_BALLS</strong> balls and <strong>self.balls.pop()</strong> is removing them from the list. Then finally we're saving those selected balls to the <strong>self.balls_selected</strong> variable. So to sum it up, at the end of our method we'll have 5 integers in our list. Note that we're not randomizing anything in this method because we already shuffled the list in the <strong>setup</strong> method.</p>
<pre>
def selectPowerball(self) -> None:
self.powerball = randint(1, self.MAX_POWERBALL)</pre>
<p>Our <strong>selectPowerball</strong> method also returns nothing. We use the <strong>randint</strong> function to randomly select a number between <strong>1</strong> and <strong>MAX_POWERBALL</strong> and then we save it to the <strong>self.powerball</strong> variable.</p>
<pre>
def showResults(self) -> None:
print(f'white balls: {self.balls_selected}')
print(f'power ball: {self.powerball}')
print('*'*20)</pre>
</pre>
<p>The <strong>showResults</strong> method is very simple: just show results. First we print the white balls and then we print our powerball. Finally, we print a divider made of asterisks.</p>
<span class="image"><img src="https://res.cloudinary.com/diuqkp0ek/image/upload/v1633896890/powerball.png"></span>
<h4>Give Me Some Feedback AKA I Need Constant Validation</h4>
<p>Please let me know what you think about this tutorial. I love comments!</p>
<h4>Support This Site By Buying Me A Coffee!</h4>
<p>If you find this tutorial helpful, please consider <a target="_blank" href="https://www.buymeacoffee.com/z4F8QVS6w">buying me a coffee</a>. Thanks!</p><p>We simulate Powerball lottery draws in this mini project.</p>2021-10-10T11:06:26-05:00https://www.iceorfire.com/post/generative-art-with-pythonGenerative Art With Python2024-03-29T14:51:09.199161+00:00IoFAdmin<h4>You Can Be the Next Jackson Pollock!</h4>
<p>Well probably not at least you can create some cool looking art with Python code.</p>
<h4>Standing On the Shoulders Of Giants</h4>
<p>I'm still learning Generative Art and I needed a place to start so I took <a target="blank" href="https://github.com/Absolute-Tinkerer/Generative-Art">Absolute-Tinkerer's</a> code and simplified it for learning purposes. Check it out on <a target="blank" href="https://github.com/Absolute-Tinkerer/Generative-Art">Github</a>.</p>
<h4> Digital Paintbrush?</h4>
<p>So what is Generative Art anyway? Basically, it's writing code that follows some rules and uses random inputs to create unique art each time it's ran. There are many types of Generative Art but we're just going to focus on <a target="_blank" href="https://tylerxhobbs.com/essays/2020/flow-fields">Flow Fields</a>.</p>
<h4>So What Do We Need?</h4>
<p>We're going to be using four pieces of code:</p>
<ul>
<li><a target="_blank" href="https://github.com/Absolute-Tinkerer/Generative-Art/blob/main/painter.py">painter</a> (from Absolute-Tinkerer)</li>
<li><a target="_blank" href="https://github.com/Absolute-Tinkerer/Generative-Art/blob/main/utils.py">utils</a> (from Absolute-Tinkerer)</li>
<li>numpy (pip install numpy)</li>
<li>our code below</li>
</ul>
<h4>Perlin Noise? What's That?</h4>
<p>TL;DR In the 1980's this really smart guy working on the movie Tron came up with a way to make computer imagery look more organic and less artificial. You can read more about it on <a target="_blank" href="https://en.wikipedia.org/wiki/Perlin_noise">Wikipedia</a>. We're using Perlin Noise to make our images look better.</p>
<h4>Show Me The Code Already!</h4>
<p>I'll show you the code and then we'll break down what it's doing. I'm still learning Generative Art so if I'm describing something incorrectly, please let me know in the comments below.<p>
<pre>
import math
import random
import numpy as np
from PyQt5.QtGui import QColor, QPen
from PyQt5.QtCore import QPointF
import painter
from utils import QColor_HSV, save, Perlin2D
def draw(width, height, color=200, backgroundColor=(0,0,0), perlinFactorW=2, perlinFactorH=2, step=0.001):
seed = random.randint(0, 100000000)
# Set the random seed for repeatability
np.random.seed(seed)
p = painter.Painter(width, height)
# Allow smooth drawing
p.setRenderHint(p.Antialiasing)
# Draw the background color
p.fillRect(0, 0, width, height, QColor( *backgroundColor ))
# Set the pen color
p.setPen(QPen(QColor(150, 150, 225, 5), 2))
print('Creating Noise...')
p_noise = Perlin2D(width, height, perlinFactorW, perlinFactorH)
print('Noise Generated!')
MAX_LENGTH = 2 * width
STEP_SIZE = step * max(width, height)
NUM = int(width * height / 1000)
POINTS = [(random.randint(0, width - 1), random.randint(0, height - 1)) for i in range(NUM)]
for k, (x_s, y_s) in enumerate(POINTS):
print(f'{100 * (k + 1) / len(POINTS):.1f}'.rjust(5) + '% Complete', end='\r')
# The current line length tracking variable
c_len = 0
# Actually draw the flow field
while c_len < MAX_LENGTH:
# Set the pen color for this segment
sat = 200 * (MAX_LENGTH - c_len) / MAX_LENGTH
hue = (color + 130 * (height - y_s) / height) % 360
p.setPen(QPen(QColor_HSV(hue, sat, 255, 20), 2))
# angle between -pi and pi
angle = p_noise[int(x_s), int(y_s)] * math.pi
# Compute the new point
x_f = x_s + STEP_SIZE * math.cos(angle)
y_f = y_s + STEP_SIZE * math.sin(angle)
# Draw the line
p.drawLine(QPointF(x_s, y_s), QPointF(x_f, y_f))
# Update the line length
c_len += math.sqrt((x_f - x_s) ** 2 + (y_f - y_s) ** 2)
# Break from the loop if the new point is outside our image bounds
# or if we've exceeded the line length; otherwise update the point
if x_f < 0 or x_f >= width or y_f < 0 or y_f >= height or c_len > MAX_LENGTH:
break
else:
x_s, y_s = x_f, y_f
save(p, fname=f'image_{seed}', folder='.', overwrite=True)
draw(3000, 2000, color=63, perlinFactorW=4, perlinFactorH=5, step=0.35)</pre>
<p>We import our Python modules:</p>
<pre>
import math
import random
import numpy as np
from PyQt5.QtGui import QColor, QPen
from PyQt5.QtCore import QPointF</pre>
<p>Then we import in code from Absolute-Tinkerer's project (see above):</p>
<pre>
import painter
from utils import QColor_HSV, save, Perlin2D</pre>
<p>Now we come to the <strong>draw</strong> function which has 2 positional arguments and 5 keyword arguments.:</p>
<pre>
def draw(width, height, color=200, backgroundColor=(0,0,0), perlinFactorW=2, perlinFactorH=2, step=0.001):</pre>
<p>We'll describe the arguments when we get to them in the code.</p>
<pre>
seed = random.randint(0, 100000000)
# Set the random seed for repeatability
np.random.seed(seed)</pre>
<p>We randomly select an integer between 0 and 100000000 and then use that number set a random seed in numpy</p>
<pre>
p = painter.Painter(width, height)
# Allow smooth drawing
p.setRenderHint(p.Antialiasing)
# Draw the background color
p.fillRect(0, 0, width, height, QColor( *backgroundColor ))
# Set the pen color
p.setPen(QPen(QColor(150, 150, 225, 5), 2))</pre>
<p>In this part of the code, we define our <strong>painter</strong> which is an instance of <a target="_blank" href="https://doc.qt.io/qtforpython-5/PySide2/QtGui/QPainter.html">QPainter</a> (a class allowing us to "paint" pixels). Using our painter, we turn on antialiasing to smooth the angles, fill a rectangle with our provided background color and then finally set our pen color to draw.</p>
<pre>
print('Creating Noise...')
p_noise = Perlin2D(width, height, perlinFactorW, perlinFactorH)
print('Noise Generated!')</pre>
<p>Here we're using Absolute-Tinkerer's Perlin Noise generator to make our noise. This will allow angle calculations further on in the code.</p>
<pre>
MAX_LENGTH = 2 * width
STEP_SIZE = step * max(width, height)
NUM = int(width * height / 1000)
POINTS = [(random.randint(0, width - 1), random.randint(0, height - 1)) for i in range(NUM)]</pre>
<p><strong>MAX_LENGTH</strong> acts as a guard so that we don't draw/calculate values outside of our image edges and it's also used in some color calculations. <strong>STEP_SIZE</strong> is used to calculate drawing points with smaller values leading to organic curves and larger values generating "chaotic" features. <strong>NUM</strong> dictates how many points we create. Finally, <strong>POINTS</strong> calculates points (X, Y coordinates) using list comprehension.</p>
<pre>
for k, (x_s, y_s) in enumerate(POINTS):
print(f'{100 * (k + 1) / len(POINTS):.1f}'.rjust(5) + '% Complete', end='\r')</pre>
<p>For each one of our points, get its (X, Y) coordinates and display our percentage completed for drawing our image.</p>
<pre>
# The current line length tracking variable
c_len = 0</pre>
<p>Set our tracking variable to 0.</p>
<pre>
# Actually draw the flow field
while c_len < MAX_LENGTH:</pre>
<p>Keep looping until our tracking variable is less than our image limit.</p>
<pre>
# Set the pen color for this segment
sat = 200 * (MAX_LENGTH - c_len) / MAX_LENGTH
hue = (color + 130 * (height - y_s) / height) % 360
p.setPen(QPen(QColor_HSV(hue, sat, 255, 20), 2))</pre>
<p>Calculate our <strong>sat</strong> variable for our saturation value and <strong>hue</strong> for our hue value. (Read a nice explanation of <a target="_blank" href="http://learn.leighcotnoir.com/artspeak/elements-color/hue-value-saturation/">hue and saturation</a>.) Next we translate our values into a color and apply it to our painter's drawing pen.</p>
<pre>
# angle between -pi and pi
angle = p_noise[int(x_s), int(y_s)] * math.pi
# Compute the new point
x_f = x_s + STEP_SIZE * math.cos(angle)
y_f = y_s + STEP_SIZE * math.sin(angle)</pre>
<p>Using our current (X, Y) coordinates in <strong>x_s</strong> and <strong>y_s</strong> we calculate our <strong>angle</strong> variable. Next, we find the cos of the angle, multiply it by <strong>STEP_SIZE</strong>, add it to our current X value. Follow a similar process with the current Y value. Now we have the coordinates of our new point!</p>
<pre>
# Draw the line
p.drawLine(QPointF(x_s, y_s), QPointF(x_f, y_f))</pre>
<p>Create <strong>QPointF</strong>s for our current and new points and then draw a line between them.<p>
<pre>
# Update the line length
c_len += math.sqrt((x_f - x_s) ** 2 + (y_f - y_s) ** 2)</pre>
<p>Square the difference between the old and new X values and add it to the squared difference between the old and new Y values. Take the square root of that value and add it to the current value of <strong>c_len</strong>.<p>
<pre>
# Break from the loop if the new point is outside our image bounds
# or if we've exceeded the line length; otherwise update the point
if x_f < 0 or x_f >= width or y_f < 0 or y_f >= height or c_len > MAX_LENGTH:
break
else:
x_s, y_s = x_f, y_f</pre>
<p>If we're outside of the image bounds, do nothing. Otherwise save the new coordinates to the old coordinates variables. That way, they'll be available for the next iteration of the loop.</p>
<pre>
save(p, fname=f'image_{seed}', folder='.', overwrite=True)</pre>
<p>Finally some simple code! We take our painter instance and save its pixels to our jpg image.</p>
<pre>
draw(3000, 2000, color=63, perlinFactorW=4, perlinFactorH=5, step=0.35)</pre>
<p>Call the code and generate our masterpiece!</p>
<h4>What Did We Learn?</h4>
<p>By using some somewhat complicated math we can generate unique images with very limited inputs.</p>
<h4>Bonus Section A.K.A Tweak Some Params</h4>
<p>If we call the <strong>draw</strong> function using the parameters listed above we'll get a chaotic image that reminds us of <a target="_blank" href="https://www.thesprucecrafts.com/string-art-1791403">string art</a>. Why is that? Great question random Internet friend! With large step values (0.35 vs the default 0.001) we move farther apart between points and generate less points. That leads to lines instead of organic soft curves.</p>
<p>Try calling the function with different values for the step and Perlin factors to see what images you can make!</p>
<span class="image"><img src="https://res.cloudinary.com/diuqkp0ek/image/upload/c_scale,w_700/v1640727749/image_35244542.jpg"></span>
<span class="image"><img src="https://res.cloudinary.com/diuqkp0ek/image/upload/c_scale,w_700/v1640727749/image_41190500.jpg"></span>
<span class="image"><img src="https://res.cloudinary.com/diuqkp0ek/image/upload/c_scale,w_700/v1640727748/image_16424452.jpg"></span><p>We create Generative Art with Python. No paintbrush required!</p>2021-12-28T16:45:27-06:00https://www.iceorfire.com/post/sending-html-emails-with-pythonSending HTML Emails With Python2024-03-29T14:51:09.199053+00:00IoFAdmin<h4>Send HTML Emails And Amaze Your Friends!</h4>
<p>I guess that depends on how easy your friends are to impress...</p>
<h4>Overview and a Disclaimer</h4>
<p>We're going to use Google's Gmail to act as our email provider because it's free and easy to set up.<p>
<p>What this project is:</p>
<ul>
<li>a simple example of how to send multipart emails</li>
<li>a proof of concept</li>
</ul>
<p>What this project is not:</p>
<ul>
<li>a production-ready email sending system</li>
<li>an in-depth tutorial of email design</li>
</ul>
<h4>Don't Use This For An Email Server!</h4>
<p>Like I said above, this is a proof of concept. If you try to send a ton of emails in a short period of time, Google will probably ban your account. This tutorial will work fine if you send to your friends and family but not to a contact list of a 1000 emails.</p>
<h4>First Steps</h4>
<ul>
<li>Create a new email address at <a href="https://gmail.com">Gmail</a>.</li>
<li>Set up your Gmail account to allow sending emails from your Python script. <a target="_blank" href="https://kb.synology.com/en-global/SRM/tutorial/How_to_use_Gmail_SMTP_server_to_send_emails_for_SRM">Follow these steps</a>. This will make you Gmail account "less secure" (as Google puts it) so that's why you should use your newly created account instead of your main one.</li>
</ul>
<h4>Super Simple Discussion of Multipart Emails</h4>
<p>So what are multipart emails you ask? I got you covered Random Internet Stranger! When you send HTML emails you are actually sending two version of the email: text and HTML. It can get rather complicated so I won't go into here but your email client will display either the text or HTML version depending on its settings. The two versions of the email can be completely different, but the main content should be the same.</p>
<h4>Compose Your Email!</h4>
<p>Create a file called <strong>email.txt</strong> and put this in it:</p>
<pre>
Email Test
----------
See More at {QUICKLINK_URL}
{FROM_ROLE} {FROM_FULLNAME}
Internet Expert
Hi {TO_FIRSTNAME},
We're so glad that you're interested in becoming an Internet Guru! Just go to our website and we'll tell you how you can become the envy of your friends, loved ones and co-workers. It's very exciting!
Millions of other people have already followed the program and changed their mundane lives!
Thank you!
- {FROM_ROLE} {FROM_LASTNAME}
Start Now at {QUICKLINK_URL}
This is an auto-generated message that a very smart computer created just for you!
----------
Remind me Later at {QUICKLINK_URL}
Unsubscribe at {UNSUBSCRIBE}</pre>
<p>Create a file called <strong>email.html</strong> and put this in it:</p>
<pre><!DOCTYPE html>
<html lang="en">
<head>
<title>Test Email</title>
<meta name="color-scheme" content="light dark">
<meta name="supported-color-schemes" content="light dark">
<style>
:root {
color-scheme: light dark;
supported-color-schemes: light dark;
}
@media (prefers-color-scheme: dark) {
.lightimage{display: none !important;}
.darkimageWrapper,.darkimage{display: block !important;}
h1,h2,p{color:#ffffff;}
<!--[if !mso]>.mainwrapper{background-color:#000;}<![endif]-->
}
@media (prefers-color-scheme: light) {
h1,h2,p{color:#000000;}
}
.highlight{color:#a1a1a5 !important;}
</style>
</head>
<body style="margin:0; padding:0;">
<table class="mainwrapper" width="600" style="margin:0 auto;">
<tr>
<td>
<div lang="en" style="width:600px; font-family:Arial; margin-bottom:25px; padding:20px 0; margin:0 auto;">
<h1 style="text-align:center; font-size:30px;">Test Email</h1>
<p style="text-align:center;"><img src="SOMETHING/header2.png" alt="header" width="550" style="width:92%;"></p>
<div style="width:100%; text-align:center; font-size:24px; margin:35px 0;">
<a href="{QUICKLINK_URL}" style="display:block; margin:0px 85px; padding:15px 10px; border:2px solid #e5d6ff; color:#7a3fe1; border-radius:30px; text-decoration:none;">See More</a>
</div>
<div style="margin:25px 0 0 0 0; color:#000000;">
<table style="width:100%;" role="presentation" border="0" cellpadding="0" cellspacing="0">
<tr>
<td style="width:100px;">
<img src="SOMETHING/profile.png" width="100" height="auto" class="lightimage" style="display:block; height:auto; padding:0; margin:0; width:100px; height:100px; border-radius:50%;">
<div class="darkimageWrapper" style="mso-hide:all; display:none;">
<img src="SOMETHING/profile.png" width="100" class="darkimage" style="display:none; padding:0; margin:0; width:100px; height:100px; border-radius:50%;">
</div>
</td>
<td style="width:500px; padding-left:30px;">
<h2 style="padding:0; margin:0; font-size:20px; line-height:24px;">{FROM_TITLE} {FROM_FULLNAME}</h2>
<div class="highlight" style="width:font-size:18px; line-height:24px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;">Internet Expert</div>
</td>
</tr>
</table>
<hr style="margin:25px -5px 10px -5px; border-top:1px solid #a1a1a5;">
<div style="font-size:23px; padding:0 10px; color:#000000;">
<p>Hi {TO_FIRSTNAME},</p>
<p>We're so glad that you're interested in becoming an Internet Guru! Just go to our website and we'll tell you how you can become the envy of your friends, loved ones and co-workers. It's very exciting!</p>
<p>Millions of other people have already followed the program and changed their mundane lives!</p>
<p>Thank you!</p>
<p>- {FROM_TITLE} {FROM_LASTNAME}</p>
</div>
<div style="width:100%; text-align:center; font-size:24px; margin:35px 0;">
<a href="{QUICKLINK_URL}" style="display:block; margin:0px 85px; padding:15px 10px; background-color:#7a3fe1; color:#f5f7f7; border-radius:30px; text-decoration:none;">Start Now!</a>
</div>
</div>
<div style="width:438px; margin:0 49px 0px 49px; padding:0 30px;">
<p style="font-size:20px; text-align:center; color:#a1a1a5;">This is an auto-generated message that a very smart computer created just for you!</p>
</div>
<div style="text-align:center; font-size:24px; margin-top:35px;">
<a href="{QUICKLINK_URL}" style="display:block; margin:0px 100px; padding:15px 10px; border:2px solid #e5d6ff; color:#7a3fe1; border-radius:30px; text-decoration:none;">Remind me Later</a>
</div>
<div style="text-align:center; font-size:20px; margin-top:35px;">
<a href="{UNSUBSCRIBE}" class="highlight" style="display:block; margin:0px 100px; padding:10px;">Unsubscribe</a>
</div>
</div>
</td>
</tr>
</table>
</body>
</html></pre>
<p><strong>Note on images:</strong> for the images in the above HTML they have "SOMETHING" as the domain. You'll have to save the images to a website so that your email client can load them.</p>
<h4>Let's Create Our Python Script</h4>
<p>Create a file called <strong>send_email.py</strong> and put this in it:</p>
<pre>
import smtplib, ssl
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
import os
# How to allow Google Email Sending
# https://kb.synology.com/en-global/SRM/tutorial/How_to_use_Gmail_SMTP_server_to_send_emails_for_SRM
tags = {
'{FROM_TITLE}': 'Dr.',
'{FROM_FULLNAME}': 'Jane Jones',
'{TO_FIRSTNAME}': 'Steven',
'{FROM_LASTNAME}': 'Jones',
'{UNSUBSCRIBE}': 'http://www.google.com',
'{QUICKLINK_URL}': 'http://www.google.com'
}
def replaceTags(taggedContent):
for key, value in tags.items():
taggedContent = taggedContent.replace(key, value)
return taggedContent
sender_email = "SENDER_EMAIL"
password = "SENDER_EMAIL_PASSWORD"
receiver_email = "TO_EMAIL"
message = MIMEMultipart("alternative")
message["Subject"] = "multipart test"
message["From"] = sender_email
message["To"] = receiver_email
here = os.path.dirname(os.path.abspath(__file__))
final_html = os.path.join(here, 'email.html')
final_txt = os.path.join(here, 'email.txt')
with open(final_html, 'r') as htmlFile:
html = htmlFile.read()
with open(final_txt, 'r') as txtFile:
text = txtFile.read()
html = replaceTags(html)
text = replaceTags(text)
# Turn these into plain/html MIMEText objects
part1 = MIMEText(text, "plain")
part2 = MIMEText(html, "html")
# Add HTML/plain-text parts to MIMEMultipart message
# The email client will try to render the last part first
message.attach(part1)
message.attach(part2)
# Create secure connection with server and send email
context = ssl.create_default_context()
with smtplib.SMTP_SSL("smtp.gmail.com", 465, context=context) as server:
server.login(sender_email, password)
server.sendmail(
sender_email, receiver_email, message.as_string()
)</pre>
<h4>Modify the Code With Your Info</h4>
<p>For <strong>sender_email</strong> change it to be your newly created Gmail email. <strong>password</strong> should be your Gmail password. <strong>receiver_email</strong> will be the email address that you're sending to.</p>
<h4>What's Going On Here?</h4>
<p>For both the HTML and text version of the email, we're opening the file (email.html and email.txt) and then calling <strong>replaceTags()</strong> on it. The replaceTags() function looks for tagged items (content between braces such as {FROM_TITLE}) and replaces it with the corresponding value defined in <strong>tags</strong> dictionary.</p>
<p>Then we define the two <strong>MIMEText</strong> objects with one being the HTML content and the other being the text. Next, we attach the two parts to the <strong>message</strong> object that is a <strong>MIMEMultipart</strong> object.</p>
<p>Finally, we connect to Gmail and send an email with the predefined info. </p>
<h4>So What Does Our Email Look Like?</h4>
<p>Our HTML email will look something like this:<p>
<p><span class="image"><img src="https://res.cloudinary.com/diuqkp0ek/image/upload/v1643925323/email_html.png"></span>
</p>
<p>The text version will look like this:</p>
<p><span class="image"><img src="https://res.cloudinary.com/diuqkp0ek/image/upload/v1643925323/email_txt.png"></span>
</p><p>Send HTML emails with the power of Python!</p>2022-02-03T14:34:53-06:00https://www.iceorfire.com/post/resume-generation-with-pythonResume Generation With Python2024-03-29T14:51:09.198923+00:00IoFAdmin<h4>Let's Make A Resume That Stands Out</h4>
<p>With the Covid pandemic, everyone seems to be changing jobs and updating their resumes. Many work from home jobs receive hundreds of applicants so you need something to differentiate your resume from all of the others. Follow along Random Internet Stranger as we create an eye-pleasing resume that will get you a first interview... you're on your own after that!</p>
<h4>First Steps</h4>
<p>To complete this tutorial (and make it rain with that new high paying job), you'll need <strong>PDFKit</strong> which you can install with:</p>
<pre>pip install pdfkit (or pip3 for python3)</pre>
<h4>Project Overview</h4>
<ul>
<li>Create a HTML layout of our resume</li>
<li>Write a Python script to generate a PDF of the resume</li>
<li>Live a life of luxury with a new job*</li>
</ul>
<p><small>*results not guaranteed</small></p>
<h4>Step 0 - Planning</h4>
<p>This kind of goes without saying but I'm saying it anyway.... plan ahead and gather your information. What jobs do you want to list? What were the dates you worked there? What skills do you want to highlight? Do you have any special projects that you'd like interviewers to know about?</p>
<h4>Step 1 - HTML Content and CSS Layout</h4>
<p>For this step, we're going to create a webpage version of your resume which is totally self-contained without loading any external CSS files. Feel free to use my example as a starting point and modify it to suit your needs. Enough talk! Show me the markup!</p>
<h4>Resume Markup</h4>
<p>Create a file named <strong>resume_sample.html</strong> and enter the following markup into it:</p>
<pre>
<!DOCTYPE HTML>
<html lang="en">
<head>
<title>Test Guy's Resume</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" />
<style>
body {
margin: 0;
padding: 0;
font-family: 'Calibri';
font-size:11px;
}
a {
text-decoration: none;
color: inherit;
}
.box1 {
width: 28%;
height:90%;
float: left;
background-color: #370D32;
color: white;
padding-left: 7em;
padding-top:1.5em;
}
.box2 {
width: 60%;
float: left;
height:90%;
padding-left: 1.25em;
padding-right: 0.75em;
padding-top:0.75em;
}
.cols {
padding-bottom: 100%;
margin-bottom: -100%;
}
.box1 h1{
font-weight: bold;
font-size: 2.75em;
margin:0;
padding:0;
}
.box1 h2{
font-weight: bold;
font-size: 1.75em;
margin: 1.25em 0 0 0;
}
.box1 .jobtitle {
margin:0.25em 0 0 0;
padding:0;
}
.box1 ul {
list-style: none;
margin: 0 0 0 0;
padding-left: 0;
font-size: 1.5em;
}
.box2 ul {
margin: 0 0 1.5em 0;
}
.box1 li {
line-height: 1.5em;
}
.box2 h2, .box2 h3, .box2 p {
margin: 0 0 0.2em 0;
}
.box2 p {
margin:0.25em 0 1em 0;
}
.box2 h3 {
color: #696969;
}
progress {
border: 0;
height: 0.5em;
border-radius: 9px;
margin-top: 0.2em;
margin-bottom: 0.6em;
}
/* background: */
progress::-webkit-progress-bar {background-color: #696969; width: 100%;}
progress {background-color: #696969;}
/* value: */
progress::-webkit-progress-value {background-color: #fff !important;}
progress::-moz-progress-bar {background-color: #fff !important;}
progress {color: green;}
hr {
margin:0 0 0 5.25em;
width:20%;
}
</style>
</head>
<body>
<div class="box1 cols">
<h1>Test Guy</h1>
<hr>
<h2 class="jobtitle">Widget Maker</h2>
<h2>Contact Details</h2>
<ul>
<li>Anytown, USA</li>
<li>123-555-4321</li>
<li>test.guy@gmail.com</li>
</ul>
<h2>Skills</h2>
<ul>
<li>Widget Making<br><progress value="100" max="100"></progress></li>
<li>Widget Reports<br><progress value="100" max="100"></progress></li>
<li>Coffee Breaks<br><progress value="80" max="100"></progress></li>
<li>Widget Testing<br><progress value="70" max="100"></progress></li>
<li>Excel<br><progress value="100" max="100"></progress></li>
<li>Widget Inventory<br><progress value="80" max="100"></progress></li>
<li>Widget Development<br><progress value="80" max="100"></progress></li>
<li>Singing<br><progress value="70" max="100"></progress></li>
<li>Piano<br><progress value="60" max="100"></progress></li>
<li>Blogging<br><progress value="100" max="100"></progress></li>
<li>Widgetifcation<br><progress value="70" max="100"></progress></li>
</ul>
</div>
<div class="box2 cols">
<h1>Profile</h1>
<p>Widget Maker with more than 10 years of industry-standard widget experience.</p>
<h1>Employment History</h1>
<h2>Widget Maker, Widgets Inc, Anytown, USA</h2>
<h3>2018 to Present</h3>
<ul>
<li>Oversee widget production in the factory</li>
<li>Sed ut perspiciatis unde omnis iste natus error</li>
<li>Nemo enim ipsam voluptatem quia voluptas sit</li>
<li>Lorem ipsum dolor sit amet, consectetur adipiscing eli</li>
<li>Ut enim ad minim veniam, quis nostrud exercitation</li>
</ul>
<h2>Widget Master, Widgets For You, Anytown, USA</h2>
<h3>2016 to January 2018</h3>
<ul>
<li>Widget production and manage widget tests</li>
<li>Sed ut perspiciatis unde omnis iste natus error</li>
<li>Nemo enim ipsam voluptatem quia voluptas sit</li>
<li>Lorem ipsum dolor sit amet, consectetur adipiscing eli</li>
<li>Ut enim ad minim veniam, quis nostrud exercitation</li>
</ul>
<h2>Widget Guy, Another Company, Anytown, USA</h2>
<h3>2015 to December 2016</h3>
<ul>
<li>Increase widget production by 37%</li>
<li>Nemo enim ipsam voluptatem quia voluptas sit</li>
<li>Lorem ipsum dolor sit amet, consectetur adipiscing eli</li>
</ul>
<h2>Widget Pro, That Widget Company, Anytown, USA</h2>
<h3>2010 to 2015</h3>
<ul>
<li>Do something with widgets</li>
<li>Sed ut perspiciatis unde omnis iste natus error</li>
<li>Nemo enim ipsam voluptatem quia voluptas sit</li>
<li>Lorem ipsum dolor sit amet, consectetur adipiscing eli</li>
<li>Ut enim ad minim veniam, quis nostrud exercitation</li>
</ul>
<h1>Projects</h1>
<h2>My Widgets, <a target="_blank" href="https://www.google.com/">https://www.somefakeurl.com/</a></h2>
<h3>2019 to Present</h3>
<ul>
<li>Widget making tutorial site</li>
<li>Sell widgets</li>
</ul>
<h2>Volunteering</h2>
<h3>2021 to Present</h3>
<ul>
<li>Volunteer with kids who have no widgets</li>
<li>Neque porro quisquam est, qui dolorem ipsum quia</li>
<li>Quis autem vel eum iure reprehenderit qui</li>
</ul>
<h1>Education</h1>
<h2>Bachelor of Science in Widgets</h2>
<p>Widget University - Anytown, USA - October 2010</p>
<h2>Widget Cerification</h2>
<p>Widget Training Center - Anytown, USA - December 2009</p>
</div>
</body>
</html></pre>
<h4>Step 2 - Create Our Python Script</h4>
<p>Create Python script named <strong>generate.py</strong> and enter the following code into it:</p>
<pre>import pdfkit
options = {
'page-size': 'Letter',
'margin-top': '0in',
'margin-right': '0in',
'margin-bottom': '0in',
'margin-left': '0in',
'encoding': "UTF-8",
'custom-header': [
('Accept-Encoding', 'gzip')
],
'no-outline': None
}
pdfkit.from_file('resume_sample.html', 'resume_sample.pdf', options=options)</pre>
<h4>Step 3 - Generate the PDF</h4>
<p>Run the <strong>generate.py</strong> script with the HTML file in the same directory. If everything worked, you should a fancy PDF version of your resume.</p>
<h4>Disclaimer</h4>
<p>I have seen the error of a second blank page being added at the end of the PDF. This is easily solvable by printing the PDF to a file and only including the first page. If you know how to stop the second page from being generated, I'd love to hear your solution.</p>
<h4>Let's See What We Made</h4>
<span class="image"><img src="https://res.cloudinary.com/diuqkp0ek/image/upload/v1647621877/resume.png"></span>
<h4>Step 4 - Make It Your Own</h4>
<p>Feel free to modify the layout, colors, fonts, etc. however you want. Obviously, you'll need to replace our test content with the real content that you gathered in Step 0. Good luck with your job hunt!</p><p>Today we'll make a resume with Python!</p>2022-03-18T10:43:32-05:00https://www.iceorfire.com/post/chatgpt-vs-human-blog-postsChatGPT vs Human Blog Posts2024-03-29T14:51:09.198775+00:00IoFAdmin<h4>Human Or Computer?</h4>
<p>We're going to have some fun this post: I'm going to be a criminal and you're going to be an investigator. What's the crime? Great question random Internet Stranger! I've had ChatGTP (everyone's favorite AI content generator) create 2 content snippets and I wrote one myself. I'm breaking the law (of content creation?) as passing all three off as my own creation. Can you identify mine?</p>
<h4>Your Task</h4>
<p>Read all three snippets about Python best practices and see if you can determine which one was created by a human and which two are crafted by Skynet.</p>
<h4>Article #1</h4>
<p>Python is a powerful and versatile programming language that is widely used in a variety of applications. As with any programming language, there are certain best practices that can help you write more efficient and maintainable code. In this blog post, we'll discuss some of the most important best practices for writing Python code.</p>
<p>Use meaningful variable and function names: One of the most important best practices in any programming language is to use meaningful variable and function names. When your code is easy to read and understand, it is much easier to maintain and debug.</p>
<p>Use comments: Comments are a great way to document your code and make it more readable. Use comments to explain what your code is doing, and to provide any information that might be useful to someone else reading your code.</p>
<p>Use the right data types: Python has a number of built-in data types, including integers, floats, and strings. It's important to use the right data type for the task at hand. For example, using a float to represent an integer can lead to unexpected results.</p>
<p>Avoid using global variables: Global variables can make your code difficult to understand and maintain. Instead, use local variables and pass them between functions as needed.</p>
<p>Use list comprehensions: List comprehensions are a powerful feature in Python that allow you to create lists in a concise and readable way. They are often faster than using a for loop, and can make your code more readable.</p>
<p>Avoid using unnecessary imports: Importing unnecessary modules can slow down your code and make it harder to understand. Only import the modules that you actually need.</p>
<p>Use the 'with' statement: The 'with' statement is a useful tool for working with resources that need to be cleaned up after they are used. It is especially useful for working with files, as it ensures that the file is closed correctly, even if an error occurs.</p>
<p>Use exception handling: Exception handling is an important part of any programming language. It allows you to handle errors and unexpected conditions gracefully, and can help to make your code more robust.</p>
<p>By following these best practices, you can write more efficient and maintainable Python code that is easy to understand and debug. Remember that it's always a good idea to keep learning and experimenting with new features and techniques to improve your Python skills.</p>
<h3>Content #2</h3>
<p>Python is a high-level, general-purpose programming language known for its ease of use. By following best practices, your Python code will be easy to change and have minimal errors. In this blog post, we'll cover some best practices to keep in mind when writing your next Python project.</p>
<p>Be sure to use the correct data types for the data you want to represent. For example, numeric data should not be stored as a string because you won't be able to perform mathematical functions on it.</p>
<p>Using descriptive names for your variables, functions, and classes keeps your code easy to understand and is also self-documenting. While short names are nice to type, they are much harder to grasp when coming back to your code in the future.</p>
<p>List comprehensions are a powerful and simple tool to generate lists and they are often more performant than corresponding for loops. They make your code easier to read than loops and they allow conditionals as well.</p>
<p>Try to avoid global variables because they are hard to maintain and tightly couple your code. Instead of global variables, pass data to functions and return values as needed.</p>
<p>Only import packages that your code is actively using. This will speed up processing and keep your code clean, easy to follow and maintain. Most IDEs will mark unused imports in a different color so that you can quickly identify them.</p>
<p>Comment your code so that you and other programmers will understand what the code is doing. A simple explanation of parameters passed into a function and its return types will quickly give programmers an overview of functionality.</p>
<p>Following the guidelines laid out in PEP 8 will ensure that your code is consistent and easy to read. PEP 8 covers style guidelines such as indentation, tabs/spaces, naming conventions, and programming recommendations.</p>
<p>Test and debug your code to minimize logic and runtime errors. Pytest is a popular Python testing tool that allows you to test your code through a series of assertions that compare expected versus actual values.</p>
<p>By following the best practices we've discussed, you can write clean and testable code that is easy to understand and maintain. Keep learning and experimenting with Python's features so that you can improve your coding skills.</p>
<h3>Content #3</h3>
<p>Python is a popular programming language known for its simplicity and versatility. Whether you're a beginner or an experienced developer, it's important to follow best practices when writing Python code. In this blog post, we'll discuss some of the most important best practices to keep in mind when writing Python code.</p>
<p>Use PEP 8: PEP 8 is a set of guidelines for writing Python code. It's a good idea to follow these guidelines as they help to make your code more readable and consistent. This includes using lowercase letters, using spaces around operators and commas, and naming conventions for variables, functions and methods.</p>
<p>Keep it Simple: Simple code is easier to understand, maintain and extend. Avoid using complex solutions when a simple one will do. When in doubt, keep it simple.</p>
<p>Use meaningful variable and function names: Use descriptive and meaningful names for your variables and functions. This makes it easier for others to understand your code and for you to remember what the code does.</p>
<p>Use comments: Comments are a great way to document your code and make it more readable. Use comments to explain what your code is doing, and to provide any information that might be useful to someone else reading your code.</p>
<p>Use the right data types: Python has a number of built-in data types, including integers, floats, and strings. It's important to use the right data type for the task at hand. For example, using a float to represent an integer can lead to unexpected results.</p>
<p>Avoid using global variables: Global variables can make your code difficult to understand and maintain. Instead, use local variables and pass them between functions as needed.</p>
<p>Use list comprehensions: List comprehensions are a powerful feature in Python that allow you to create lists in a concise and readable way. They are often faster than using a for loop, and can make your code more readable.</p>
<p>Use the 'with' statement: The 'with' statement is a useful tool for working with resources that need to be cleaned up after they are used. It is especially useful for working with files, as it ensures that the file is closed correctly, even if an error occurs.</p>
<p>Use exception handling: Exception handling is an important part of any programming language. It allows you to handle errors and unexpected conditions gracefully, and can help to make your code more robust.</p>
<p>Keep testing and debugging: The key to writing clean and efficient code is testing and debugging. Make sure to test your code thoroughly and debug any issues that you encounter.</p>
<p>By following these best practices, you can write more efficient and maintainable Python code that is easy to understand and debug. Remember that it's always a good idea to keep learning and experimenting with new features and techniques to improve your Python skills.</p>
<h4>What's Your Choice Sherlock?</h4>
<p>In the next blog post I'll reveal which one is mine and which two are computer generated. See you next time!</p><p>In this post, let's see if you can spot my writing among ChatGPT content.</p>2023-01-28T20:35:53-06:00https://www.iceorfire.com/post/create-a-microsoft-teams-botCreate A Microsoft Teams Bot2024-03-29T14:51:09.184175+00:00IoFAdmin<h4>Let's Create A Bot To Post Notifications To Our MS Teams Channel</h4>
<p>We use MS Teams at my day job so I thought it would be fun to create a bot that could post notifications to a channel. Now that I have an army of bots at my command, I'll show you how to do the same.</p>
<h4>What Will Our Bot Do?</h4>
<p>I'm sure that you've seen all of the motivational posters that tell you to "Soar with the eagles!" and "Hang in there!" while walking around your office. Today, we're going to take the opposite approach by posting demotivational quotes in Teams.</p>
<h4>Tools of the Trade AKA PIP Is Your Friend</h4>
<p>To help us truly demotivate our co-workers, we're going to need a few things:</p>
<ul>
<li><a href="https://chat.openai.com/auth/login">ChatGPT</a>: our AI buddy that'll generate some quotes</li>
<li><a href="https://ponyorm.org/">Pony ORM</a>: our database object relational mapping tool</li>
<li>MS Teams: we'll need a channel for our posts</li>
</ul>
<h4>With the Power of AI...</h4>
<p>I'm not going to explain how to use ChatGPT because there are many good tutorials out there to get you started. Once you've created an account and get logged in, enter the following prompt:</p>
<pre>generate a list of demotivational quotes</pre>
<p>That prompt generated 20 quotes so I ran it again for a total of 40 quotes. Save those quotes somewhere because we'll need them soon.</p>
<h4>Create A Teams Webhook</h4>
<p>Setting up Teams to accept our posts is pretty easy if you follow the steps below.<p>
<ul>
<li>Decide what channel you'd like to post in and create it if needed</li>
<li>Click on the Settings three dots of the channel and then select Connections</li>
<li>Find the "webhooks" in the search bar</li>
<li>Select Add or modify an "Incoming Webhook"</li>
<li>Add or save it</li>
<li>Re-open the connectors for the channel under Settings (if it closed)</li>
<li>Click "Configure" for the webhook</li>
<li>In the configuration box, fill in the name of your bot and add a profile picture (optional)</li>
<li>Click "Create"</li>
<li>Copy the newly created URL for your bot</li>
<li>Save the URL (we'll need it soon)</li>
<li>Click "Done"</li>
<li>Open the channel in Teams and you should see a message showing that you configured an incoming Webhook</li>
</ul>
<h4>Python To the Rescue</h4>
<p>Now comes the fun part: our Python code! Open up your favorite editor and let's get started.</p>
<h4>Install Pony ORM</h4>
<p>Like most Python packages, you can easily install Pony with PIP by running the following command in your terminal:</p>
<pre>pip install pony</pre>
<p><strong>Note:</strong> you might run in to problems if you're running Python 3.11 because currently Pony supports 3.10 and below. (Hopefully 3.11 support is coming soon.)</p>
<h4>Create Our Quotes</h4>
<p>Create a new Python file named <strong>create_db.py</strong> and put the following code in it:</p>
<pre>
from pony.orm import *
from Quote import Quote
db = Database()
@db_session
def doInserts():
Quote(content="Everything happens for a reason. Sometimes the reason is you're stupid and make bad decisions.", person='Marion G. Harmon')
Quote(content="Every dead body on Mt. Everest was once a highly motivated person, so… maybe calm down.", person='')
Quote(content="Light travels faster than sound. This is why some people appear bright until you hear them speak.", person='Alan Dundes')
Quote(content="Just because we accept you as you are doesn't mean we've abandoned hope you'll improve.", person='')
Quote(content="Idiocy - never underestimate the power of stupid people in large groups.", person='')
Quote(content="If life doesn't break you today, don't worry. It will try again tomorrow.", person='')
Quote(content="People who say they'll give 110% don't understand how percentages work.", person='')
Quote(content="A thousand-mile journey starts with one step. Then again, so does falling in a ditch and breaking your neck.", person='')
Quote(content="Two things are infinite: the universe and human stupidity; and I'm not sure about the universe.", person='Albert Einstein')
Quote(content="If you never try anything new, you'll miss out on many of life's great disappointments.", person='')
Quote(content="If at first, you don't succeed, try, try again. Then quit. No use being a damn fool about it.", person='W.C. Fields')
Quote(content="It could be that your purpose in life is to serve as a warning to others.", person='Ashleigh Brilliant')
Quote(content="Today is the first day of the rest of your life. But so was yesterday, and look how that turned out.", person='')
Quote(content="Just because you are unique doesn't mean you are useful.", person='')
Quote(content="Oh, you hate your job? Why didn't you say so? There's a support group for that. It's called EVERYBODY, and they meet at the bar.", person='Drew Carey')
Quote(content="I am free of all prejudice. I hate everyone equally.", person='W.C. Fields')
Quote(content="Always remember that you are absolutely unique. Just like everyone else.", person='Margaret Mead')
Quote(content="Multitasking - the art of doing twice as much as you should half as well as you could.", person='')
Quote(content="The story so far: In the beginning, the Universe was created. This has made a lot of people very angry and been widely regarded as a bad move.", person='Douglas Adams')
Quote(content="Life is pain. Anyone who says otherwise is selling something.", person='William Goldman')
Quote(content="If you want to know what God thinks of money, just look at the people he gave it to.", person='Dorothy Parker')
Quote(content="When life knocks you down, stay there and take a nap.", person='')
Quote(content="Nothing says \"you're a loser\" more than owning a motivational poster about being a winner.", person='')
Quote(content="Not everything is a lesson. Sometimes you just fail.", person='Dwight Schrute')
Quote(content="Fate is like a strange, unpopular restaurant filled with odd little waiters who bring you things you never asked for and don't always like.", person='Lemony Snicket')
Quote(content="The worst part of success is trying to find someone who is happy for you.", person='Bette Midler')
Quote(content="Your life can't fall apart if you never had it together.", person='')
Quote(content="Doing nothing is very hard to do… you never know when you're finished.", person='')
Quote(content="The road to success is always under construction.", person='Lily Tomlin')
Quote(content="The reward for good work is more work.", person='Francesca Elisia')
Quote(content="There are no stupid questions, but there are a LOT of inquisitive idiots.", person='')
Quote(content="Go ahead and take risks - it gives the rest of us something to laugh at.", person='')
Quote(content="Eagles may soar, but weasels don't get sucked into jet engines.", person='John Benfield')
Quote(content="Raise your hand if you have had quite enough unsolicited advice about what should be done with any lemons that life may or may not give you.", person='')
Quote(content="There's always someone on Youtube that can do it better than you.", person='')
Quote(content="It's only when you look at an ant through a magnifying glass on a sunny day that you realize how often they burst into flames.", person='Harry Hill')
Quote(content="Every day is Friday when you're unemployed.", person='')
Quote(content="You're naturally funny because your life is a joke.", person='')
Quote(content="The meaning of life is to find your gift. So good luck with that.", person='')
Quote(content="Challenging yourself... is a good way to fail.", person='Dom Mazzetti')
Quote(content="Why aim for the stars when you can comfortably settle for the couch?", person='')
Quote(content="Dream big, then promptly forget about it and return to your mediocre existence.", person='')
Quote(content="Procrastination: because there's always tomorrow to not get things done.", person='')
Quote(content="Why try when failure is so much easier to achieve?", person='')
Quote(content="The only thing standing between you and success is your complete lack of talent.", person='')
Quote(content="Why be motivated when you can just blame everyone else for your problems?", person='')
Quote(content="If at first you don't succeed, quit. It's easier that way.", person='')
Quote(content="Don't worry about setting goals; they'll only remind you of how far you haven't come.", person='')
Quote(content="You can achieve anything you don't put your mind to.", person='')
Quote(content="Success is overrated. Embrace the comfort of mediocrity.", person='')
Quote(content="Remember, the path to failure is paved with good intentions.", person='')
Quote(content="Don't bother taking risks; just stay in your comfort zone and stagnate.", person='')
Quote(content="Opportunity knocks once, but it usually just walks right past you.", person='')
Quote(content="Hard work pays off, but laziness is so much more appealing.", person='')
Quote(content="Don't aim for perfection; settle for 'good enough.", person='')
Quote(content="Why strive for greatness when mediocrity is so much more comfortable?", person='')
Quote(content="Motivation is for the ambitious. Embrace your lack of ambition.", person='')
Quote(content="Failure is not just an option; it's practically inevitable.", person='')
Quote(content="Success is fleeting, but failure? It's a lifelong companion.", person='')
Quote(content="Don't bother trying to stand out; conformity is so much easier.", person='')
doInserts()
</pre>
<p>So what's this code doing? I'm glad you asked!</p>
<p>First we import <strong>Pony ORM</strong> and our <strong>Quote</strong> class (that we'll write in a moment). Next, we instantiate a <strong>Database</strong> object that Pony will use to save our data.</p>
<p>Now we create our <strong>doInserts</strong> function using the <strong>@db_session</strong> decorator. Your can read more about that in the <a href="https://docs.ponyorm.org/firststeps.html#db-session">Pony ORM documentation</a>. Here we're creating our <strong>Quote</strong> objects and inserting them into our database.</p>
<p>Observant readers will be saying "what database?" Don't worry because we'll handle that soon.</p>
<h4>Our Quote Class</h4>
<p>Create another Python file named <strong>Quote.py</strong> with the following code:</p>
<pre>
from pony.orm import *
db = Database()
class Quote(db.Entity):
content = Required(str)
person = Optional(str)
@db_session
def showAll():
data = select(q for q in Quote)
for q in data:
print(f'{q.content} - {q.person if q.person else "unknown"}')
@db_session
def randomQuote():
data = select(q for q in Quote).random(1)[0]
return f'{data.content} - {data.person if data.person else "unknown"}'
@db_session
def findById(id):
data = Quote[id]
return f'"{data.content}" - {data.person if data.person else "unknown"}'
db.bind(provider='sqlite', filename='quotes.sqlite', create_db=True)
db.generate_mapping(create_tables=True)
</pre>
<p>I won't go too in depth with this because SQL is out of scope for this tutorial and Pony has pretty good documentation.</p>
<p>After importing <strong>Pony ORM</strong>, we define our <strong>Quote</strong> database table. We have two columns: "content", which is a required string datafield, and "person", which is an optional string datafield.</p>
<p>Now we create our three methods that will interact with our database: <strong>showAll</strong>, <strong>randomQuote</strong>, and <strong>findById</strong>. </p>
<p>Our first method, <strong>showAll</strong>, queries our database for all Quote records and prints them out to the terminal. We'll just be using this for debugging purposes.</p>
<p>In <strong>randomQuote</strong>, we're randomly selecting a Quote record and returning it as an <strong>f-string</strong>.</p>
<p>Our final method, <strong>findById</strong>, allows us to query by our primary key and get back an <strong>f-string</strong> representation of that Quote record.</p>
<p>Lastly, we create our sqlite database named <strong>quotes.sqlite</strong> to save our Quote records.</p>
<h4>Posting To Teams</h4>
<p>Create <strong>TeamsPost.py</strong> and add this code:</p>
<pre>
import requests
class TeamsPost:
url = 'YOUR_WEBHOOK_HERE'
def sendTeams(self, content:str, title:str, color:str="000000") -> int:
"""
- Send a teams notification to the desired webhook_url
- Returns the status code of the HTTP request
- webhook_url : the url you got from the teams webhook configuration
- content : your formatted notification content
- title : the message that'll be displayed as title, and on phone notifications
- color (optional) : hexadecimal code of the notification's top line color, default corresponds to black
"""
response = requests.post(
url=self.url,
headers={"Content-Type": "application/json"},
json={
"themeColor": color,
"summary": title,
"sections": [{
"activityTitle": title,
"activitySubtitle": content
}],
},
)
return response.status_code
</pre>
<p>The class uses Python's <strong>request</strong> package to POST a JSON payload to our webhook URL that we saved from Teams. (Don't forget to use your webhook URL in the "url" variable.) As long as our webhook URL is correct and we set up Teams, we'll be POSTing.</p>
<h4>Last One I Promise</h4>
<p>Create our last file named <strong>post_quote.py</strong> and add this:</p>
<pre>
from pony.orm import *
from Quote import Quote
from TeamsPost import TeamsPost
def main(quoteId):
quote = Quote.findById(quoteId)
post = TeamsPost()
post.sendTeams(quote, 'Daily Demotivational Quote', '00FF00')
main(1)
</pre>
<p>This very simple function is sending an id (the primary key) to Quote's findById method. After querying the database for that id, we get back a Quote object. Finally, we pass that object to our sendTeams method which POSTs it to our channel. <strong>Note:</strong> you will have to manually change the id that's sent to the <strong>main</strong> function.</p>
<h4>Running Our Code</h4>
<p>To actually post our demotivational quote to MS Teams, we need to set the quote id and the run <strong>post_quote.py</strong>.<p>Let's create a MS Teams Bot with the magic of Python!</p>2023-07-07T14:14:50-05:00