https://www.iceorfire.com/rssIceOrFire - All Feed2024-03-19T01:52:17.725453+00:00python-feedgenIce or Fire is your source for programming tutorials, Python tips, RenPy coding, and product reviews.https://www.iceorfire.com/post/uptime-robotUptime Robot2024-03-19T01:52:17.781049+00:00IoFAdmin<h4>I Needed A Monitor To Check Uptime</h4>
<p>I needed a website monitoring service for Ice or Fire that let me know if it went down. There are a many options but I only found one that met all of my needs:</p>
<ul>
<li><strong>Free -</strong> I'm running this site on a budget so I need a service that's reliable but doesn't cost me anything</li>
<li><strong>SMS and Email messaging -</strong> I need a service that emails and texts me if my site goes offline</li>
<li><strong>Easy to use -</strong> I need something simple that I can just set and forget</li>
<li><strong>Allows for multiple monitors -</strong> 1 just won't cut it</li>
</ul>
<h4>Verdict</h4>
<p><a href="https://uptimerobot.com/" target="_blank">Uptime Robot</a> met all of my needs and did I mention that they have a free option? If you need a bit more they have a cheap monthly plan too. Currently, the free plan is working for me.</p>
<p>Uptime Robot gets a Fire from me! </p>
<p><b class="icon solid fa-fire iof-fire"> </b></p><p>Need a tool to monitor your website's uptime? See our top recommendation!</p>2021-02-18T16:57:22-06:00https://www.iceorfire.com/post/mcdonalds-spicy-chicken-sandwich-reviewMcDonald's Spicy Chicken Sandwich Review2024-03-19T01:52:17.780989+00:00IoFAdmin<h4>Battle of the Poultry</h4>
<p>I love chicken sandwiches. I often choose them over burgers when I grab something from a drive thru. I was curious how the new Spicy Chicken Sandwich from the Golden Arches would stack up to the other ones on the market.</p>
<p>It seems like all of the fast food giants are doubling down on poultry lately: Popeyes, Wendy's, Chick-fil-A and KFC have all had new sandwiches in the last year. Obviously, McDonald's didn't want to be left out.</p>
<p>My favorite chicken sandwich is the Spicy Chicken Sandwich from Chick-fil-A so that's what I'll judge the new McDonald's one against.</p>
<h4>Spiciness</h4>
<p>I don't feel like the Chick-fil-A Spicy Chicken Sandwich is really that hot so I always add some Sriracha sauce to it. The McDonald's sandwich was spicy enough on it's own that I didn't need any additional hotness.</p>
<p><strong>Winner: McDonalds</strong></p>
<h4>Juiciness</h4>
<p>Chick-fil-A's is very juicy while the McDonald's sandwich felt a bit drier.</p>
<p><strong>Winner: Chick-fil-A</strong></p>
<h4>Pickles</h4>
<p>McDonald's pickles were limp while Chick-fil-A's pickles were crunchy when bitten. ("Crunchy when bitten" ... that's a phrase I never thought I'd say.)</p>
<p><strong>Winner: Chick-fil-A</strong></p>
<h4>Verdict</h4>
<p>The new Spicy Chicken Sandwich from McDonald's is ok but Chick-fil-A's blows it away. I'd eat the one from McDonald's only if I couldn't make it to Chick-fil-A or it was a Sunday.</p>
<p>I give it an Ice!</p>
<p><b class="icon solid fa-cubes iof-ice"> </b></p><p>McDonald's enters the Poultry Smackdown with their new Spicy Chicken Sandwich. See how it stacks up!</p>2021-02-28T16:57:55-06:00https://www.iceorfire.com/post/intro-to-renpyIntro To RenPy2024-03-19T01:52:17.780941+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-19T01:52:17.780875+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-19T01:52:17.780823+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/cheetos-macn-cheese-reviewCheetos Mac'n Cheese Review2024-03-19T01:52:17.780768+00:00IoFAdmin<p>I'm not a super Cheetos fan... I like them well enough and I probably only eat them a few times per year but when I saw Mac'n Cheese Cheetos on Amazon I knew I had to try them.</p>
<p>They come in a 3 pack to cover all of your cheesy gourmet needs: Bold & Cheesy, Cheesy Jalepeno, and Flamin' Hot. My daughter doesn't really like spicy foods so Bold & Cheesy won out.</p>
<iframe style="width:120px;height:240px;" marginwidth="0" marginheight="0" scrolling="no" frameborder="0" src="//ws-na.amazon-adsystem.com/widgets/q?ServiceVersion=20070822&OneJS=1&Operation=GetAdHtml&MarketPlace=US&source=ac&ref=tf_til&ad_type=product_link&tracking_id=iceorfire0e-20&marketplace=amazon&region=US&placement=B08HVTJ4JC&asins=B08HVTJ4JC&linkId=e263fa1eceb0708b1b53ff4ca87d576b&show_border=false&link_opens_in_new_window=false&price_color=333333&title_color=0066c0&bg_color=ffffff">
</iframe>
<h4>Cookin' Up Something Good!</h4>
<p>I diced, filleted, poached, and sautéed for hours... of course not. This is mac'n cheese. I boiled some water, threw the noodles in, drained it and added some milk and butter. Finally, I mixed in some very fragrant and bright orange "cheese" which may or may not have been created through dark magic. Since we're very classy noodle connoisseurs, we grilled some turkey burgers and steamed some locally sourced and sustainable corn (from the frozen section of Walmart).</p>
<img src="/static/images/mac_n_cheese.jpg" alt="Cheetos Mac'n Cheese" />
<h4>The Taste Test</h4>
<p><strong>Me:</strong> Not bad, not great. It kind of tastes like noodles mixed with a bag of Cheetos Puffs. It's also very fragrant and super bright orange. I'd probably eat it again if I had a box in the pantry but I wouldn't make a trip to the store just to buy it.</p><p><b class="icon solid fa-fire iof-fire"> </b></p>
<p><strong>Son:</strong> He loved it... finished his plate and wanted more. If you ask him, it's the best mac'n cheese he's ever tasted!</p><p><b class="icon solid fa-fire iof-fire"> </b></p>
<p><strong>Daughter:</strong> Yuck! It's the worst mac'n cheese she's ever tasted. She refused to finish it so my son was happy to eat it for her.</p><p><b class="icon solid fa-cubes iof-ice"> </b></p>
<h4>The Verdict</h4>
<p>2 Fires to 1 Ice. So we say it's Fire! <b class="icon solid fa-fire" style="font-size:38px;;color:#f00;"> </b></p><p>Cheetos makes mac'n cheese? Check out our review of Chester Cheetah's newest creation.</p>2021-03-12T02:55:07-06:00https://www.iceorfire.com/post/our-renpy-game-part-3-beginningOur RenPy Game Part 3 - Beginning2024-03-19T01:52:17.780713+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/kfcs-spicy-fried-chicken-sandwich-reviewKFC's Spicy Fried Chicken Sandwich Review2024-03-19T01:52:17.780649+00:00IoFAdmin<h4>Previous Poultry Punchout</h4>
<p>In the previous <strong>Spicy Chicken Smackdown</strong><sup>*</sup>, McDonald's and Chick-fil-A entered the ring and <a href="/post/mcdonalds-spicy-chicken-sandwich-review">Chick-fil-A won</a> with knock out. Now Chick-fil-A defends the title of <strong>Chicken King</strong> against KFC.</p>
<p>(* I totally copyrighted that just now. If you want to use it, be sure to contact me about licensing fees.)<p>
<h4>Let's Have A Fair Fight</h4>
<p>To keep this fair and avoid a fowl, we'll use the same criteria as last time: spiciness, juiciness, pickles. Ring the bell and let's get ready to rumble!</p>
<h4>Spiciness</h4>
<p>While delicious, the Chick-fil-A Spicy Sandwich isn't that hot so I always add Sriracha sauce to spice it up. KFC's new sandwich isn't very spicy either so we'll call this round a draw.</p>
<p><strong>Winner: Draw</strong></p>
<h4>Juiciness</h4>
<p>KFC's sandwich is juicy and tasty with just a little bit of crisp while Chick-fil-A's sandwich packs an uppercut of juiciness. The Col throws a mean punch but the Cow's uppercut can't be beat.</p>
<p><strong>Winner: Chick-fil-A</strong></p>
<h4>Pickles</h4>
<p>Peter picked a peck of perfect pickles and added them to our sandwiches. Both sandwiches feature yummy pickles that crunch when you bite into them.</p>
<p><strong>Winner: Draw</strong></p>
<h4>Verdict</h4>
<p>I have to hand it to KFC... they created a really good spicy sandwich with a little bit of spice, juicy chicken and crunchy pickles. It was <strong>very close</strong> but I have to declare <strong>Chick-fil-A the winner</strong>.</p>
<p>KFC didn't beat the defending champion, Chick-fil-A, but it's very good so I give it a Fire!</p><p><b class="icon solid fa-fire iof-fire"> </b></p><p>Can KFC's Spicy Chicken Sandwich defeat Chic-Fil-A's offering? We take the taste test!</p>2021-03-28T13:45:11-05:00https://www.iceorfire.com/post/bend-soaps-all-natural-skincare-reviewBend Soaps All Natural Skincare Review2024-03-19T01:52:17.780589+00:00IoFAdmin<h4>Carrot Cake Counts As Veggies, Right?</h4>
<p>My family and I have been trying to be a bit healthier by starting a garden and avoiding toxic chemicals. It's been a bit difficult because Diet Coke and Girl Scout cookies taste so good! I'm not sure about kicking Diet Coke to the curb but we have decided to try some natural products. Our search to avoid a witch's brew of unpronounceable ingredients led us to Bend Soap.</p>
<h4>So Why Bend Soap, Anyway?</h4>
<p>We read their story and it makes sense: your body's biggest organ is your skin so why not lather it up in healthy stuff? Instead of who knows what, their soaps are made with goat's milk and other natural ingredients. Ok enough with the introduction... is the product any good?
<h4>How We Judge Soap</h4>
<p>To determine if the soap is any good we had to set a baseline. Just what should soap do? Well, two obvious things: clean me and smell nice. Other than making me smell fresh and removing dirt, I expect soap to create a lather.</p>
<h4>Cleaning Power A.K.A Removing Your Funk</h4>
<p>Did Bend Soap clean well? Let's start by describing the soap. Goat milk soap isn't like your "normal" soaps such as Zest, Coast, or Dial because they are "soft" soaps that kind of dissolve in water. Harder soaps, including those made from goat milk, don't dissolve as easily in water and last longer. Bend Soap products cleaned just as well as the traditional soaps, left me feeling fresh, and without the "slime" that some other soaps leave on my skin after a shower.</p>
<p><b class="icon solid fa-fire iof-fire"> </b></p>
<h4>Does It Smell Nice?</h4>
<p>As you can tell from image above, we tested 6 different soaps: Lemon Lavender, Oatmeal & Honey, Citrus Mint, All Shield, Sweet Orange, and Honey Grapefruit. All 6 products have a pleasing smell but All Shield is my favorite. If I had to pick my least favorite, it'd have to be Honey Grapefruit which had a good fragrance but it was pretty muted.</p>
<p><b class="icon solid fa-fire iof-fire"> </b></p>
<h4>Scrub-a-dub-dub Are There Bubbles In my Tub?</h4>
<p>Bend Soap products do create a lather but not as much as the traditional soaps that you're probably used to. That didn't really matter to me because even with less bubbles the soap still left me clean and fresh. If you're a bubble freak then you might be let down with Bend Soap.</p>
<p><b class="icon solid fa-cubes iof-ice"> </b></p>
<p>Product Links</p>
<iframe style="width:120px;height:240px;" marginwidth="0" marginheight="0" scrolling="no" frameborder="0" src="//ws-na.amazon-adsystem.com/widgets/q?ServiceVersion=20070822&OneJS=1&Operation=GetAdHtml&MarketPlace=US&source=ac&ref=qf_sp_asin_til&ad_type=product_link&tracking_id=iceorfire0e-20&marketplace=amazon&region=US&placement=B0758JGS6M&asins=B0758JGS6M&linkId=7fc535683b0070aa2b0c661d3d718ec4&show_border=false&link_opens_in_new_window=false&price_color=333333&title_color=0066c0&bg_color=ffffff">
</iframe>
<iframe style="width:120px;height:240px;" marginwidth="0" marginheight="0" scrolling="no" frameborder="0" src="//ws-na.amazon-adsystem.com/widgets/q?ServiceVersion=20070822&OneJS=1&Operation=GetAdHtml&MarketPlace=US&source=ac&ref=qf_sp_asin_til&ad_type=product_link&tracking_id=iceorfire0e-20&marketplace=amazon&region=US&placement=B0758HXF8T&asins=B0758HXF8T&linkId=411433ca27feaba8567000f5ee1eb949&show_border=false&link_opens_in_new_window=false&price_color=333333&title_color=0066c0&bg_color=ffffff">
</iframe>
<p> </p>
<h4>Splish Splash, Is Bend Soap Worth the Cash?</h4>
<p>Yes! I recommend all 6 of the soaps that we tried and I believe that you won't be disappointed with your purchase. If you only want one fragrance, I'd pick All Shield. Bend Soaps earn a Fire!</p>
<p><b class="icon solid fa-fire iof-fire"> </b></p>
<p>Let me know what you think of Bend Soaps in the comments below.</p><p>We come clean about Bend Soap's all natural skincare soaps!</p>2021-03-30T13:38:18-05:00https://www.iceorfire.com/post/pythonanywhere-hosting-reviewPythonAnywhere Hosting Review2024-03-19T01:52:17.780521+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/bitbucket-git-repository-reviewBitbucket Git Repository Review2024-03-19T01:52:17.780461+00:00IoFAdmin<h4>So What Is Git Anyway?</h4>
<p>If you're familiar with Git, feel free to scroll down. </p>
<p>According to <a href="https://en.wikipedia.org/wiki/Git">Wikipedia</a>, Git is <q>software for tracking changes in any set of files, usually used for coordinating work among programmers collaboratively developing source code during software development. Its goals include speed, data integrity, and support for distributed, non-linear workflows (thousands of parallel branches running on different systems).</q></p>
<p>In other words, Git is a system to save files and their changes over time that allow multiple people to edit them.</p>
<h4>Bitbucket Vs. Github</h4>
<p>It might be an oversimplification but Bitbucket and Github are basically the same thing: hosting services for Git repositories. Github is more popular than Bitbucket in terms of numbers of users and hosted repos.</p>
<p>If Github is more popular than Bitbucket, why are you using Bitbucket? Excellent question random Internet Stranger! I've been using Bitbucket for years because Github didn't offer free private repos but Bitbucket did. That's not the case anymore but I haven't wanted to take the time to move my repos over to Github.</p>
<h4>So How Good Is Bitbucket?</h4>
<p>I use Git on a daily basis for my day job as a programmer and while I'm not an expert I am comfortable with it. For my job, we use Github and it works just fine.</p>
<p>Recently, I set up a Bitbucket git repo for my <a href="https://www.iceorfire.com/post/our-renpy-game-part-3-beginning">RenPy game that we covered in another post</a>. It didn't go well. I created the repo on the Bitbucket website and then cloned it down locally which worked correctly. When I tried to add files, commit them, and push them up to the remote repository, it all fell apart. Git kept telling me that everything was up to date even though it wasn't and my new files wouldn't show up on Bitbucket's file viewer on the website. I Googled the problem and apparently a lot of people have had the same problems.</p>
<h4>So I Can't Push To Bitbucket. Now What?</h4>
<p>After a lot of searching on the Internet, I finally found the solution: <a target="_blank" href="https://www.syntevo.com/smartgit/">Smartgit</a>. From the website, <q>SmartGit is a graphical Git client with support for GitHub, Bitbucket and GitLab. SmartGit runs on Windows, macOS and Linux.</q></p>
<h4>Verdict (Two For One)</h4>
<p>Since I needed two services to set up my Git repository, I'm going to have to separate verdicts.</p>
<p>I'm not happy with <strong>Bitbucket</strong> and I'll probably switch to Github when I can find the time and motivation. Bitbucket seems buggy for me and a lot of other users. I give Bitbucket an Ice because I can't recommend it. <b class="icon solid fa-cubes iof-ice"> </b></p>
<p><strong>Smartgit</strong> is easy to use and it allowed me to push to Bitbucket with no problems. I'm not sure that I would've got my repo working without it. I give Smartgit a Fire and recommend it. <b class="icon solid fa-fire iof-fire"> </b></p>
<h4>Give Me Your Feedback</h4>
<p>Let me know your experiences with Bitbucket and/or Smartgit by commenting below.</p><p>"Git" our review on Bitbucket for hosting your git repositories.<p>2021-04-07T09:23:05-05:00https://www.iceorfire.com/post/our-renpy-game-part-4-git-repoOur RenPy Game Part 4 - Git Repo 2024-03-19T01:52:17.780361+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/cheetos-jalapeno-macn-cheese-reviewCheetos Jalapeño Mac'N Cheese Review2024-03-19T01:52:17.780291+00:00IoFAdmin<h4>Spicy Cheesy Goodness or Blech?</h4>
<p>When I reviewed the last flavor of <a href="/post/cheetos-macn-cheese-review">Cheetos Mac'N Cheese</a>, there were mixed reviews. Two out of three of thought it was pretty good, while one of us was grossed out. How will this one rate?<p>
<p>This time there was only one reviewer... me. You can read my verdict three times if you really want three opinions. :)</p>
<h4>Which Flavor Do I Pick?</h4>
<p>I bought a three flavor variety pack which includes Bold & Cheesy, Cheesy Jalapeño, and Flamin' Hot. The Magic 8-ball picked <strong>Cheesy Jalapeño</strong> so that's the Chef's Choice.</p>
<iframe style="width:120px;height:240px;" marginwidth="0" marginheight="0" scrolling="no" frameborder="0" src="//ws-na.amazon-adsystem.com/widgets/q?ServiceVersion=20070822&OneJS=1&Operation=GetAdHtml&MarketPlace=US&source=ac&ref=tf_til&ad_type=product_link&tracking_id=iceorfire0e-20&marketplace=amazon&region=US&placement=B08HVTJ4JC&asins=B08HVTJ4JC&linkId=e263fa1eceb0708b1b53ff4ca87d576b&show_border=false&link_opens_in_new_window=false&price_color=333333&title_color=0066c0&bg_color=ffffff">
</iframe>
<h4>Fire Up The Pot!</h4>
<p>What can I say? It's mac'n cheese. Boil the water, throw in the noodles, drain them, add milk, butter and the incredibly fragrant flavor packet. Like the Bold & Cheesy variety that I tried last time, the "cheese" and "jalapeño" seasoning is three things: fragrant, colorful, and made from a ground up mystical substance.</p>
<img src="/static/images/jalepeno_mac_cheese.png" alt="Cheetos Mac'n Cheese Cheesy Jalapeño" />
<h4>The Taste Test</h4>
<p>I honestly didn't expect much from Chester Cheetah's cheesy concoction but I was pleasantly surprised. The Bold & Cheesy was ok but I enjoyed the Cheesy Jalapeño so much that I had a second bowl. It was spicier than I thought it would be and the flavor of the cheese came through without tasting artificial. I'll definitely buy some more of Cheetos Mac'N Cheese Cheesy Jalapeño!</p>
<h4>The Verdict</h4>
<p>I loved it! Chester Cheetah earns a Fire. <b class="icon solid fa-fire" style="font-size:38px;;color:#f00;"> </b></p><p>Chester Cheetah turns up the spicy with his Cheesy Jalapeño mac'n cheese!</p>2021-04-12T19:11:56-05:00https://www.iceorfire.com/post/greenstalk-vertical-garden-reviewGreenStalk Vertical Garden Review2024-03-19T01:52:17.780228+00:00IoFAdmin<h4>Eat Your Veggies!</h4>
<p>My family and I are trying to embrace a healthier lifestyle with using <a href="/post/bend-soaps-all-natural-skincare-review">all natural soaps</a> and eating more veggies. There's no better way to teach our kids about plants and get healthy than to plant a garden.</p>
<h4>So What Is GreenStalk?</h4>
<p>We wanted to grow a garden but we're new to it so we wanted something easy to maintain that didn't require a lot of setup. That's where GreenStalk comes in. The GreenStalk Vertical Garden system is a collection of specialized pots that stack on top of one another. It allows you to grow a tower of crops that that don't take up much space and are easy to water.</p>
<h4>Check Out My Green Thumb</h4>
<p>We started off slow by setting up 2 different towers of crops. In our first tower, we planted carrots and peas. Tower number two has radishes. You can see our radishes sprouting below.</p>
<img src="/static/images/greenstalk.png" alt="GreenStalk Vertical Garden">
<h4>Benefits of GreenStalk</h4>
<p>So what's the pros of the GreenStalk system?</p>
<ul>
<li><strong>It's easy.</strong> Setting up the towers are pretty simple even for a new "farmer" like me. Just add soil, plant the seeds, stack the towers, and water from the top level using the provided tray.</li>
<li><strong>The tower is on wheels.</strong> The whole tower can be rotated easily to make sure that all of your plants are getting lots of sun. That's a nice feature (if you buy the add-on).</li>
<li><strong>You can put it anywhere.</strong> Our towers are on the deck of our house. I don't have to kneel down in the dirt of my backyard to weed and water my plants.</li>
<li><strong>It's customizable.</strong> If you want a tower that's 5 levels high or 2 levels high you can do it. There's also an available add-on that clips on the outside of the pots for vines to grow around.</li>
</ul>
<iframe style="width:120px;height:240px;" marginwidth="0" marginheight="0" scrolling="no" frameborder="0" src="//ws-na.amazon-adsystem.com/widgets/q?ServiceVersion=20070822&OneJS=1&Operation=GetAdHtml&MarketPlace=US&source=ac&ref=qf_sp_asin_til&ad_type=product_link&tracking_id=iceorfire0e-20&marketplace=amazon&region=US&placement=B07QF29RB6&asins=B07QF29RB6&linkId=1e28df3eb44c4c9dadd9cf382c135df4&show_border=false&link_opens_in_new_window=false&price_color=333333&title_color=0066c0&bg_color=ffffff">
</iframe>
<iframe style="width:120px;height:240px;" marginwidth="0" marginheight="0" scrolling="no" frameborder="0" src="//ws-na.amazon-adsystem.com/widgets/q?ServiceVersion=20070822&OneJS=1&Operation=GetAdHtml&MarketPlace=US&source=ac&ref=tf_til&ad_type=product_link&tracking_id=iceorfire0e-20&marketplace=amazon&region=US&placement=B08C5LRYTK&asins=B08C5LRYTK&linkId=04e36774e9db216a6d8f3c76c35bd43d&show_border=false&link_opens_in_new_window=false&price_color=333333&title_color=0066c0&bg_color=ffffff">
</iframe>
<iframe style="width:120px;height:240px;" marginwidth="0" marginheight="0" scrolling="no" frameborder="0" src="//ws-na.amazon-adsystem.com/widgets/q?ServiceVersion=20070822&OneJS=1&Operation=GetAdHtml&MarketPlace=US&source=ac&ref=tf_til&ad_type=product_link&tracking_id=iceorfire0e-20&marketplace=amazon&region=US&placement=B07D6TWQWP&asins=B07D6TWQWP&linkId=11b8e21adec0755cf8d465fde0250164&show_border=false&link_opens_in_new_window=false&price_color=333333&title_color=0066c0&bg_color=ffffff">
</iframe>
<h4>Downsides of GreenStalk</h4>
<p>Nothing is all unicorns and rainbows... there are some cons.</p>
<ul>
<li><strong>GreenStalk is more expensive than a traditional garden.</strong> It's not super pricey but the cost of buying multiple towers, the wheels and the vine add-on do add up.</li>
<li><strong>It's heavy.</strong> Wheeling around and rotating a full tower can be a little difficult depending on what surface it's on. It's a little difficult to turn on my wooden deck but I'm guessing it'd be easier on a concrete patio.</li>
<li><strong>Weeding can be difficult.</strong> I haven't really had this problem (yet) but if weeds start to grow under or near the center of the tower layer they can be hard to reach.</li>
</ul>
<h4>Verdict AKA Green Thumbs Up or Down?</h4>
<p>I haven't gardened since I helped my parents with their plants many years ago. I remembered hating gardening as a kid because I had to kneel in the dirt and my back started to hurt after a while. The GreenStalk Vertical Garden system solves those problems as well as letting me grow plants anywhere. Overall, I love the GreenStalk system and I'd recommend it. </p>
<p>GreenStalk earns two green thumbs up and a Fire! <b class="icon solid fa-fire iof-fire"> </b></p> <p>See what sprouts when we review the GreenStalk Vertical Garden system.</p>2021-04-19T12:00:12-05:00https://www.iceorfire.com/post/old-mother-hubbard-p-nuttier-natural-dog-treats-reviewOld Mother Hubbard P-Nuttier Natural Dog Treats Review2024-03-19T01:52:17.780151+00:00IoFAdmin<h4>Dogs Can Be Healthy Too</h4>
<p>As I've mentioned before, my family and I are trying to improve our health through <a href="https://www.iceorfire.com/post/greenstalk-vertical-garden-review">growing nutritious veggies</a> and using <a href="https://www.iceorfire.com/post/bend-soaps-all-natural-skincare-review">all natural soaps</a>. Our dog, Lily, is part of our family so shouldn't she be healthy too?</p>
<h4>Treats For The Canine Companion</h4>
<p>There's lots of healthy options for dog treats but the one that <a target="_blank" href="https://www.petcarerx.com/old-mother-hubbard-bitz-crunchy-classic-assorted-flavor-chicken-liver-and-vegtable-natural-dog-treats/32761">caught my eye</a> at the local pet store was Old Mother Hubbard's P-Nuttier Oven-Baked Dog Biscuits. Like most puppers, my dog would push me off a cliff if she got a spoonful of peanut butter afterward. There's no way I could lose with peanut butter.</p>
<h4>Gratuitous Cute Dog Photo</h4>
<p>This is Lily the star of the show. Of course she's snuggled against me because that's her favorite spot.</p>
<span class="image"><img src="/static/images/lily2.png" alt="lily the spoiled dog"></span>
<h4>So Is It Peanut Buttery?</h4>
<p>Old Mother Hubbard's P-Nuttier biscuits are made with peanut butter, apples, carrots, oats and other all natural ingredients that I can actually pronounce. It's supposed to be peanut buttery, right? It's literally in the name. They smell pretty good (to a human) but I can't detect the fragrance of peanut butter. I did pick up the smell of apples though.</p>
<h4>Is It Yummy?</h4>
<p>I know what your thinking... how does he know that it tastes good? Well I don't but my dog loves Old Mother Hubbard's dog treats. It may not smell like peanut butter but apparently they're delicious because she gets very excited when the bag comes out.</p>
<iframe style="width:120px;height:240px;" marginwidth="0" marginheight="0" scrolling="no" frameborder="0" src="//ws-na.amazon-adsystem.com/widgets/q?ServiceVersion=20070822&OneJS=1&Operation=GetAdHtml&MarketPlace=US&source=ac&ref=qf_sp_asin_til&ad_type=product_link&tracking_id=iceorfire0e-20&marketplace=amazon&region=US&placement=B075F7ZSMF&asins=B075F7ZSMF&linkId=e8d5f428f6f76d30dfbe10aed0b69158&show_border=false&link_opens_in_new_window=false&price_color=333333&title_color=0066c0&bg_color=ffffff">
</iframe>
<h4>Verdict</h4>
<p>Lily, our spoiled dog, gives Old Mother Hubbard Baking Company's P-Nuttier Oven-Baked Dog Biscuits two paws up! Since Lily lacks opposable thumbs and a credit card, my opinion matters too. She loves them and I'm happy that the treats are made with natural products that are healthy for dogs. Lily and I both recommend the P-Nuttier dog biscuits! <b class="icon solid fa-fire iof-fire"> </b></p> <p>Old Mother Hubbard has some dog treats but are they doggone good?</p>2021-04-23T15:54:14-05:00https://www.iceorfire.com/post/our-renpy-game-part-5-variables-conditionals-and-screensOur Renpy Game Part 5 - Variables, Conditionals and Screens2024-03-19T01:52:17.780048+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-19T01:52:17.779989+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-19T01:52:17.779933+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-19T01:52:17.779866+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-19T01:52:17.779794+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/hbo-max-reviewHBO Max Review2024-03-19T01:52:17.779746+00:00IoFAdmin<h4>Another Streaming Service?</h4>
<p>My family and I have a lot of streaming services already but my kids kept bugging me to get HBO Max. Do I really need more than Disney +, Hulu, Prime, and Netflix? I told them that I'd try it for a month and then decide whether to keep it or dump it. HBO Max auto-renews each month so you can cancel at any time.</p>
<h4>HBO Max on Roku</h4>
<p>We're a Roku family and we watch almost all of our media on it. After signing up for HBO Max, I installed the Roku app and I was ready to go. Using HBO Max for the first time is similar to all of the other streaming services: sign in, set up a profile or 2.</p>
<h4>HBO Max Profiles</h4>
<p>One thing that HBO Max does that I wish other streaming services would adopt is setting viewing controls per profile. When you create your first profile, it's an <strong>adult</strong> account by default which gives access to everything on the service. You have the option of creating <strong>child</strong> accounts with viewing restrictions based on TV/Movie ratings. For example, if I create a child account for Bob, I can set it so that he can view TV shows up to TV-14 and movies up to PG-13. If I have a 7 year old daughter named Susie, I can limit her access to TV-Y7 and PG for TV and movies. Switching profiles requires entering a 4 digit passcode so Susie can't accidentally start watching Game of Thrones.</p>
<p><b class="icon solid fa-fire iof-fire"> </b> Profiles get a Fire!</p>
<h4>Quality of Service</h4>
<p>I'm not very impressed with the HBO Max app on Roku because it's somewhat buggy. It'll work for quite a while and then it'll just crash. Rebooting the Roku and/or restarting the app usually fixes the issue. I've seen multiple posts about the issue on Reddit so it's not just me. Hopefully, HBO is working on making the app more stable.</p>
<p><b class="icon solid fa-cubes iof-ice"> </b> Stability needs some work.</p>
<h4>Quality of Shows and Movies</h4>
<p>Content is where HBO shines. They have a large selection of TV and movies both original and not. Game of Thrones, Flight Attendant, The Nevers, Joker, Friends, Sesame Street, Westworld... the list goes on and on. There will is something for everyone here. I'm really liking Flight Attendant and my daughter loves the Powerpuff Girls.</p>
<p><b class="icon solid fa-fire iof-fire"> </b> Amazing content!</p>
<h4>Stream It or Skip It?</h4>
<p>I hate the occasional instabilities and crashes on the app but I'll put up with it for content. If you're a TV and movie fan, I say give HBO Max a try. There's not a lot to lose because you can cancel after a month if you don't like it. Fingers crossed that HBO will make the service more stable!</p>
<p><b class="icon solid fa-fire iof-fire"> </b> I give it a Fire!</p><p>HBO Max... stream or skip? Read my review.</p>2021-06-03T10:01:26-05:00https://www.iceorfire.com/post/nutro-crunchy-natural-biscuit-dog-treats-berryNutro Crunchy Natural Biscuit Dog Treats - Berry2024-03-19T01:52:17.779697+00:00IoFAdmin<h4>Healthy Dogs Are Happy Dogs</h4>
<p>As I've said before, my family and I are trying to eat healthier. The same goes for our dog, Lily. Last time she tried <a href="https://www.iceorfire.com/post/old-mother-hubbard-p-nuttier-natural-dog-treats-review">Old Mother Hubbard P-Nuttier Natural Dog Treats</a>. This week we try another healthy dog snack.</p>
<h4>Another Puppers Taste Adventure</h4>
<p>Like any dog I know, Lily loves treats almost as much as chasing the rabbits in our front yard. (By the way, there seem to be a lot more bunnies this year!) Every time I pick up her dog food at the pet store, I've been grabbing a new kind of healthy dog treat. This time, Nutro Crunchy Natural Biscuit Dog Treats caught my eye. Mmmm berry flavored!</p>
<h4>So Are They Healthy?</h4>
<p>According to the packaging, Nutro treats are "made with non-GMO ingredients" and contain dried blueberries, cranberries, cherries. Everything else in the ingredients list is easy to pronounce which is a good sign.</p>
<h4>Berry Good?</h4>
<p>With many dog treats the scents seem to be very weak... at least to this human. That's not the case with Nutro because I could smell berries through the packaging. When I opened the package, I felt like I was whisked away by John and Paul to a strawberry field... forever. (That's a reference to the Beatles for all you young-uns out there.) They did not skimp on the berry flavor.</p>
<h4>Taste Test AKA Four Paws Up or Down</h4>
<p>Lily loves these things and since they're so tiny she can look forward to getting a lot of them each day. (She's 55 pounds so 19 treats per day according to the package.) What's that Lily? She says that they're just the thing after a long walk or a rigorous session of chasing bunnies and squirrels that dare to show up in her yard.</p>
<iframe style="width:120px;height:240px;" marginwidth="0" marginheight="0" scrolling="no" frameborder="0" src="//ws-na.amazon-adsystem.com/widgets/q?ServiceVersion=20070822&OneJS=1&Operation=GetAdHtml&MarketPlace=US&source=ac&ref=qf_sp_asin_til&ad_type=product_link&tracking_id=iceorfire0e-20&marketplace=amazon&region=US&placement=B00BC470V0&asins=B00BC470V0&linkId=63c9a717a55e6f109c703a23c3bcd2eb&show_border=false&link_opens_in_new_window=false&price_color=333333&title_color=0066c0&bg_color=ffffff">
</iframe>
<h4>Verdict</h4>
<p>I give Nutro Crunchy Natural Biscuit Dog Treats two thumbs up and Lily barks her agreement! If your dog likes treats (and unless it's a hotdog your dog does) these treats will be a hit. <b class="icon solid fa-fire iof-fire"> </b></p><p>Are we barking up the wrong tree with Nutro Crunchy Natural Biscuit Dog Treats made with real berries?</p>2021-06-14T14:17:15-05:00https://www.iceorfire.com/post/mountain-dew-major-melon-reviewMountain Dew Major Melon Review2024-03-19T01:52:17.779638+00:00IoFAdmin<h4>When Did Mountain Dew Join the Military?</h4>
<p>When I see new flavors of Mountain Dew, two things go through my head: what were they thinking and I need to try that. The newest flavor at my local Walmart was Major Melon so I grabbed some. By the way, when did Mountain Dew join the army?</p>
<h4>Watermelon Mountain Dew? Really?</h4>
<p>My first impression was watermelon flavored Mountain Dew has to be nasty. Mountain Dew is my favorite soda and I do like watermelon on a hot summer day... but the two of them together? They go hand in hand like toothpaste and orange juice, right?</p>
<h4>What Unholy Color Is This?</h4>
<p>I poured a glass of our experimental drink and I was greeted with a very dark pink concoction. This color would've looked great on a 1980's sweater along with Electric Blue. (You young people should watch Stranger Things if you don't get the reference.)</p>
<h4>So Is It Any Good? AKA Should the Major Be Demoted?</h4>
<p>Surprisingly, I thought it was pretty good. It reminds me of Mountain Dew and Jolly Ranchers mixed together would taste like (I'm guessing, haven't tried it). You can definitely taste the watermelon but it's not over-powering. It won't replace "normal" Mountain Dew as my favorite soda but I recommend Mountain Dew Major Melon.</p>
<p><b class="icon solid fa-fire iof-fire"> </b></p><p>Mountain Dew Major Melon... yum or yuck?</p>2021-07-13T12:01:53-05:00https://www.iceorfire.com/post/text-to-speech-funText To Speech Fun2024-03-19T01:52:17.779582+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-19T01:52:17.779533+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/red-white-and-blue-bars-crunchy-dog-biscuitsRed, White and Blue Bars Crunchy Dog Biscuits2024-03-19T01:52:17.779470+00:00IoFAdmin<h4>Healthy Treats For Your Dog</h4>
<p>I'm always on the lookout for healthy treats for my spoiled dog Lily. She's tried <a href="post/nutro-crunchy-natural-biscuit-dog-treats-berry">Nutro Crunchy Natural Biscuit Dog Treats</a> and <a href="/post/old-mother-hubbard-p-nuttier-natural-dog-treats-review">Old Mother Hubbard P-Nuttier Natural Dog Treats</a>. This week, we'll review Red, White and Blue Crunchy Dog Biscuits baked with oats and apples from Blue Buffalo.</p>
<h4>Patriotic All Year</h4>
<p>I'm guessing that these Red, White and Blue biscuits were intended for the 4th of July (the United States' independence day for our non-American readers) but they're available all year long. Lily has loved fruit flavored treats in the past so I had high hopes for this apple and oats snack.</p>
<span class="image"><img src="/static/images/flag.png" alt="American flag"></span>
<h4>So Are They Healthy?</h4>
<p>I'm no dog food health expert but most of the ingredients in the ed, White and Blue Crunchy Dog Biscuits are things that I recognize: oatmeal, barley, apples, flaxseed, carrots, cinnamon, some other grains and chicken meal. There don't seem to be many filler ingredients here which is good.</p>
<h4>Apple of Lily's Eye?</h4>
<p>Lily didn't have much to say when I asked her if these treats were tasty. (She's cute and affectionate but not much of a conversationalist.) The scent of apple is noticeable upon opening the package which I take as a good sign.</p>
<iframe style="width:120px;height:240px;" marginwidth="0" marginheight="0" scrolling="no" frameborder="0" src="//ws-na.amazon-adsystem.com/widgets/q?ServiceVersion=20070822&OneJS=1&Operation=GetAdHtml&MarketPlace=US&source=ac&ref=qf_sp_asin_til&ad_type=product_link&tracking_id=iceorfire0e-20&marketplace=amazon&region=US&placement=B09449QH5G&asins=B09449QH5G&linkId=d32b401a53beb8e34062d9961f9b712d&show_border=false&link_opens_in_new_window=false&price_color=333333&title_color=0066c0&bg_color=ffffff">
</iframe>
<h4>Verdict</h4>
<p>My canine friend is excited to eat the Red, White and Blue Crunchy Dog Biscuits so it's safe to say she gives it two paws up! If you're looking for a healthy and natural treat for your dog, try Red, White and Blue Crunchy Dog Biscuits baked with oats and apples from Blue Buffalo. <b class="icon solid fa-fire iof-fire"> </b></p><p>Are Red, White and Blue Crunchy Dog Biscuits doggone tasty?</p>2021-11-02T16:25:06-05:00https://www.iceorfire.com/post/top-programmer-christmas-gifts-2021Top Programmer Christmas Gifts 20212024-03-19T01:52:17.779419+00:00IoFAdmin<h4>What Should I Buy the Coder In My Life?</h4>
<p>Programmers are mythical beasts that hang out in the dark, stare at screens for hours and live on coffee and Mountain Dew. Coders may be hard to shop for but these gifts are sure to bring a smile to their faces this Christmas!</p>
<h4>Bring On Santa's List</h4>
<p><a href="https://www.amazon.com/gp/product/B07PXGQC1Q/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=iceorfire0e-20&creative=9325&linkCode=as2&creativeASIN=B07PXGQC1Q&linkId=88f42d709218d717be0c4d54a2d1eeff">Apple Air Pods</a> - Program your little heart out while blasting your favorite music on these stylish headphones. They even work with Siri!</p>
<p><a href="https://www.amazon.com/gp/product/B07NQRM6ML/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=iceorfire0e-20&creative=9325&linkCode=as2&creativeASIN=B07NQRM6ML&linkId=9cb482bd1c7d847b4eb78a4cd908c4de">Ember Temperature Control Smart Mug</a> - Keep your favorite caffeinated drink hot all day with this awesome package. Control the temperature of the mug with the app and never have cold coffee again!</p>
<p><a href="https://www.amazon.com/gp/product/B082VM7Q4Y/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=iceorfire0e-20&creative=9325&linkCode=as2&creativeASIN=B082VM7Q4Y&linkId=092f0bae5cc2bf78499aaf5e11bcd9b6">Debugging Coffee Mug</a> - Every programmer knows the pain of debugging software. Drown the pain with delicious coffee from this hilarious mug!</p>
<p><a href="https://www.amazon.com/gp/product/B002MMY4WY/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=iceorfire0e-20&creative=9325&linkCode=as2&creativeASIN=B002MMY4WY&linkId=925c0acf5de0106b7054a8e7a4e60f3a">Logitech K350 Wireless Wave Ergonomic Keyboard</a> - Write the next Facebook (or cat website) and avoid wrist pain as you code! Your wrists will thank you.</p>
<p><a href="https://www.amazon.com/gp/product/B077B9W343/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=iceorfire0e-20&creative=9325&linkCode=as2&creativeASIN=B077B9W343&linkId=94f084b6868145233b59004ab51fdf40">Nulaxy Laptop Stand</a> - With this functional and elegant stand you'll always have perfect laptop placement. Works with a variety of laptops so you'll be covered.</p>
<p><a href="https://www.amazon.com/gp/product/B07VJ4P52V/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=iceorfire0e-20&creative=9325&linkCode=as2&creativeASIN=B07VJ4P52V&linkId=68c7d60e04a53f44477baec7ca94f0a0">Standing Desk Converter Computer Workstation</a> - Fight an aching back by standing up while you code your digital masterpiece! A curved spine is so 2017.</p>
<p><a href="https://www.amazon.com/gp/product/B07YMJ57MB/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=iceorfire0e-20&creative=9325&linkCode=as2&creativeASIN=B07YMJ57MB&linkId=fb84fda10f50e16bfc1187d9c49d7252">Google Nest Wifi Mesh WiFi System and Wifi Router</a> - Blanket your whole house in blazing fast wifi! You can stream movies and code in the shower if you want... kinda weird but do your thing man.</p>
<p><a href="https://www.amazon.com/gp/product/B0833FBNHV/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=iceorfire0e-20&creative=9325&linkCode=as2&creativeASIN=B0833FBNHV&linkId=585987d03763586ba9ecbf8b3b5b761b">The Pragmatic Programmer: 20th Anniversary Edition - Coders have been enjoying this book for 20 years! You'll up your coding game in no time.</a></p>
<p><a href="https://www.amazon.com/gp/product/B001GSTOAM/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=iceorfire0e-20&creative=9325&linkCode=as2&creativeASIN=B001GSTOAM&linkId=96ce0bffc41878bd33360da4de6bb585">Clean Code: A Handbook of Agile Software Craftsmanship</a> - I recommend this book for all programmers. We read this book at my job and we all loved it.</p>
<p><a href="https://www.amazon.com/gp/product/B00S8RN2NY/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=iceorfire0e-20&creative=9325&linkCode=as2&creativeASIN=B00S8RN2NY&linkId=39b91fc6a181a3f085954f088d785737">Timbuk2 Commute Messenger Bag</a> - Protect and organize your laptop, charger, headphones and peripherals in this functional and stylish bag. There's plenty of room for snacks too if that's your thing.</p>
<p><a href="https://www.amazon.com/gp/product/B00KNBHIQU/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=iceorfire0e-20&creative=9325&linkCode=as2&creativeASIN=B00KNBHIQU&linkId=c8dd344a1bd8e7aecfd10960b200ea37">Turn Coffee Into Code Programmer hoodie</a> - Coders can't leap buildings in a single bound, fly or see through walls but they do have super powers! Coffee + Code = Magic!</p>
<p><a href="https://www.amazon.com/gp/product/1942788290/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=iceorfire0e-20&creative=9325&linkCode=as2&creativeASIN=1942788290&linkId=d36c4aeddcc9ad9de0fc7d7c5629712d">The Phoenix Project (A Novel About IT, DevOps, and Helping Your Business Win)</a> - Succeeding in the IT industry can be tough. The Phoenix Project entertains and gives real-world solutions to help you in business.</p>
<p><a href="https://www.amazon.com/gp/product/B01N4GOSDF/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=iceorfire0e-20&creative=9325&linkCode=as2&creativeASIN=B01N4GOSDF&linkId=236f01324415f24467035f45f064036f">Floppy Disk Coasters</a> - Protect your desk with these awesome retro coasters! (For the young folks out there... we old fogies used to save data on floppy disks!)</p><p>Pick the perfect Christmas gift for the programmer in your life!</p>2021-11-18T09:08:49-06:00https://www.iceorfire.com/post/generative-art-with-pythonGenerative Art With Python2024-03-19T01:52:17.779370+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-19T01:52:17.779321+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/coke-starlight-reviewCoke Starlight Review2024-03-19T01:52:17.779271+00:00IoFAdmin<h4>So What Does Space Taste Like?</h4>
<p>Always on the prowl for new taste concoctions, Coca-Cola reached for the stars... literally. I grabbed a Coke Starlight Zero Sugar 20 ounce bottle to take for a taste drive. Coke describes Starlight as a "Space Flavored cola" whatever that means.</p>
<h4>My Best Guess On the Flavor</h4>
<p>There's something familiar about Coke Starlight's taste. I'm not really sure what flavor the Caffeine Gods of Atlanta were going for with Starlight but to me it's like someone in a white lab coat threw some cotton candy and Diet Coke together. Then they said, "Hmm... it's sweet and has a bit of an aftertaste but the kids'll love it!"</p>
<h4>What's With the Color?</h4>
<p>Space is black but for some reason Coke Starlight is some pale brownish/purple color. Not too "spacey" or appealing to me.</p>
<h4>Verdict... AKA Blast-Off or Stay on Earth?</h4>
<p>I'm not really a fan of Coke Starlight Zero Sugar. It started off alright but by the time I finished the bottle, I was sick of it. It's just too sweet and I don't like the aftertaste. I say skip it.</p>
<p><b class="icon solid fa-cubes iof-ice"> </b></p>
<p>Review of Coke Starlight</p>2022-03-11T13:09:12-06:00https://www.iceorfire.com/post/resume-generation-with-pythonResume Generation With Python2024-03-19T01:52:17.779209+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/omaha-mini-trip-reviewOmaha Mini Trip Review2024-03-19T01:52:17.779148+00:00IoFAdmin<h4>My First Time in Omaha</h4>
<p>My family and I wanted to go on a quick trip that was close to Kansas City and had something fun to do. We all like the <a target="_blank" href="https://www.broadway.org/tours/details/wicked,137">musical Wicked</a> and the <a target="_blank" href="https://www.omahazoo.com/">Omaha Zoo</a> has a great reputation so we scheduled our adventure for when the show was in town. This was also our first time staying in an AirBnB (which we made sure was pet-friendly).</p>
<h4>Our Temporary Home</h4>
<p>This tiny home was very cute and comfortable. It was much better than staying in a hotel and it allowed us to bring our spoiled puppy princess, Lily.</p>
<span class="image"><img src="https://res.cloudinary.com/diuqkp0ek/image/upload/v1652801868/airbnb.png"></span>
<p>We hung out on the front porch and showed the kids the wonders of being outside and not staring at a screen.<p>
<span class="image"><img src="https://res.cloudinary.com/diuqkp0ek/image/upload/v1652801868/lilly_omaha.png"></span>
<p>Lily loved/despised the squirrels that lived in the trees in front of the house.</p>
<h4>Wicked Review</h4>
<p>Wicked has been on stage for 20 years and millions of theater-goers have escaped to Oz for 3 hours. I can't say anything new that hasn't already been said about this awesome musical. It's a great time and I recommend that you go see it when it comes to your city (or make it a road-trip).</p>
<span class="image"><img src="https://res.cloudinary.com/diuqkp0ek/image/upload/v1652801868/wicked.png"></span>
<p>Five ruby red slippers! It'll blow you away... like a tornado... you know the one lol. <b class="icon solid fa-fire iof-fire"> </b></p>
<h4>Omaha Zoo Review</h4>
<p>The Omaha Zoo is known for being a good zoo so I went in with high expectations. While not a huge zoo, it offers a variety of locations such as Sea Lion Shores, Asian Highlands, African Grasslands, Desert Dome, Butterfly and Insect Pavilion, Aquarium and more.</p>
<h4>Desert Dome</h4>
<p>The Desert Dome, housed inside a geodesic dome, recreates 3 desert biomes: Namib Desert, The Red Center of Australia and Sonoran Desert. The dome was impressive and seemed very large when we were inside. My wife and daughter were creeped out by the venomous snakes in the Namib Desert but I enjoyed them. Australia features wallabies so be sure to look for them. The final desert, the Sonoran Desert, houses peccaries which look like warthogs but aren't. Check out the picture below.</p>
<span class="image"><img src="https://res.cloudinary.com/diuqkp0ek/image/upload/v1652800997/warthog.png"></span>
<h4>Asian Highlands</h4>
<p>The Asian Highlands was one of my favorite parts of the Omaha Zoo because of the architecture and the animals. They went all out to make this part of the park to feel "Asian" with enclosure designs and signs. I was very impressed by the tiger while my daughter squealed when she saw the adorable red pandas. The sloth bear and snow leopards are worth your time as well.</p>
<span class="image"><img src="https://res.cloudinary.com/diuqkp0ek/image/upload/v1652800997/tiger.png"></span>
<span class="image"><img src="https://res.cloudinary.com/diuqkp0ek/image/upload/v1652800997/red_panda.png"></span>
<h4>African Grasslands</h4>
<p>We donned our safari hats and toured the African Grasslands next. We thought the giraffe exhibit was fun because watching them eat leaves from the tall trees was entertaining. We watched the elephants for a bit but unfortunately we missed the baby elephants because they were inside. One of zookeepers said that the Omaha Zoo has the only baby elephants in the United States right now.</p>
<span class="image"><img src="https://res.cloudinary.com/diuqkp0ek/image/upload/v1652800996/giraffe.png"></span>
<span class="image"><img src="https://res.cloudinary.com/diuqkp0ek/image/upload/v1652800996/elephant.png"></span>
<h4>Suzanne and Walter Scott Aquarium</h4>
<p>Let me start off by saying that the Aquarium was my favorite part of the Omaha Zoo. I love watching the sea life swim around because it's relaxing and many of the creatures are beautiful. If you like multi-colored fish like I do, the Suzanne and Walter Scott Aquarium is a must-see. My top pick in the Aquarium is the jellyfish exhibit while the rest of my family vote for the penguins.</p>
<span class="image"><img src="https://res.cloudinary.com/diuqkp0ek/image/upload/v1652800997/jellyfish.png"></span>
<span class="image"><img src="https://res.cloudinary.com/diuqkp0ek/image/upload/v1652800997/penguins.png"></span>
<span class="image"><img src="https://res.cloudinary.com/diuqkp0ek/image/upload/v1652800996/shark.png"></span>
<h4>Hubbard Gorilla Valley</h4>
<p>Gorillas fascinate me. There's so much intelligence in their eyes that I can't help but wonder what they're thinking as they watch us as we inspect them. The gorillas are a popular exhibit at the zoo so you might need to wait a minute or two so that you can get up close to the glass and see them in all of their glory. The Gorilla Valley is a little small but definitely worth the visit on your zoological journey. </p>
<span class="image"><img src="https://res.cloudinary.com/diuqkp0ek/image/upload/v1652800996/gorilla_valley.png"></span>
<span class="image"><img src="https://res.cloudinary.com/diuqkp0ek/image/upload/v1652800996/gorilla.png"></span>
<h4>Berniece Grewcock Butterfly and Insect Pavilion</h4>
<p>The Butterfly Pavilion is the most tranquil section of the Omaha Zoo. Hundreds of multi-colored butterflies float past you and land on plant life right before your eyes. Be sure to have your camera at the ready in this part of the zoo. I could've stayed longer but we had to move on!</p>
<span class="image"><img src="https://res.cloudinary.com/diuqkp0ek/image/upload/v1652800996/butterfly.png"></span>
<h4>Omaha Zoo Verdict</h4>
<p>Overall, I loved the zoo and I would recommend it. My two biggest complaints are that some of the exhibits were closed (expansion? repairs?) and that only one place to eat was open in the whole zoo which is very inconvenient when it's lunchtime and you're on the other side of the zoo. If you're ever in the Omaha area, definitely check out the zoo. <b class="icon solid fa-fire iof-fire"> </b></p><p>Omaha.. worth the trip or you should skip?</p>2022-05-24T11:10:16-05:00https://www.iceorfire.com/post/chatgpt-vs-human-blog-postsChatGPT vs Human Blog Posts2024-03-19T01:52:17.779091+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-19T01:52:17.778972+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:00https://www.iceorfire.com/post/changing-your-mindset-can-change-your-dayChanging Your Mindset Can Change Your Day2024-03-19T01:52:17.773782+00:00IoFAdmin<h4>A Bad Day... We All Have Them</h4>
<p>The following paragraph sums up a bad day when everything seems to be going wrong. Read the whole thing and then see how to turn your day around with a simple trick.</p>
<pre>Today was the absolute worst day ever
And don't try to convince me that
There's something good in every day
Because, when you take a closer look,
This world is a pretty evil place.
Even if
Some goodness does shine through once in a while
Satisfaction and happiness don't last.
And it's not true that
It's all in the mind and heart
Because
True happiness can be obtained
Only if one's surroundings are good
It's not true that good exists
I'm sure you can agree that
The reality
Creates
My attitude
It's all beyond my control
And you'll never in a million years hear me say that
Today was a good day.</pre>
<h4>A Trick To Turn Your Bad Day Around</h4>
<p>Your day (and your life in general) is what you make of it. If you only focus on the negative, you're going to miss out on many beautiful things.</p>
<p>Ready for the trick? <strong>Read the paragraph from the bottom up, starting at the last line and finishing with the first line.</strong></p>
<h4>I Hope This Inspired You</h4>
<p>If this helped turn your day around, please share it with a friend!</p>
<p>A reminder that your mindset can bring happiness.</p>2023-09-03T12:09:52-05:00