<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Maxime Wegesin on Medium]]></title>
        <description><![CDATA[Stories by Maxime Wegesin on Medium]]></description>
        <link>https://medium.com/@leshrimp?source=rss-bf86e7f10507------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*NGmyA6tdkBLriGz9jp7uxg.png</url>
            <title>Stories by Maxime Wegesin on Medium</title>
            <link>https://medium.com/@leshrimp?source=rss-bf86e7f10507------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Fri, 29 May 2026 17:51:43 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@leshrimp/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Writing Games — One choice at a time]]></title>
            <link>https://medium.com/@leshrimp/writing-games-one-choice-at-a-time-c45202cc725b?source=rss-bf86e7f10507------2</link>
            <guid isPermaLink="false">https://medium.com/p/c45202cc725b</guid>
            <category><![CDATA[game-development]]></category>
            <category><![CDATA[fiction]]></category>
            <category><![CDATA[writing]]></category>
            <category><![CDATA[creative-writing]]></category>
            <dc:creator><![CDATA[Maxime Wegesin]]></dc:creator>
            <pubDate>Sun, 26 Oct 2025 10:34:51 GMT</pubDate>
            <atom:updated>2025-10-26T11:21:27.001Z</atom:updated>
            <content:encoded><![CDATA[<p>When video games were first invented, computers couldn’t do much. Graphics were extremely limited, so the pioneers of game design had to find another way to convey complex emotions and sophisticated ideas: written words.</p><p>The 1980s brought a golden age of text adventures — titles like Zork, The Hobbit, and The Hitchhiker’s Guide to the Galaxy showed what could be achieved with imagination alone. These games offered everything a novel could, and more: they let players interact with the story. Interactive fiction combines the depth of literature with the immersion of video games, creating a uniquely personal storytelling experience.</p><p>Even today, interactive fiction (or IF) has a thriving community, with new stories published regularly.</p><p>The best part? You can start creating your own interactive story right now. All you really need is a pen and paper to make your own “choose your own adventure” tale. If you’re feeling more technical, tools like Twine let you build more complex games.</p><p>For anyone who wants to dive straight in, I created <a href="https://www.plant-a-story.com">Plant a Story</a> — a simple, web-based tool that lets you start writing immediately. No registration, no manuals. Just give your story a title, and it’s ready to grow. Bookmark your secret link, and you can return to your story anytime.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*l6ZDkdwCvjBSCxeYLSro2Q.png" /></figure><p>From there, it’s time to let your creativity branch out. Imagine writing a sci-fi story: your crew encounters a mysterious spaceship. Do they initiate contact—or switch to stealth mode and observe from a distance? You decide what paths your readers can explore.</p><p>Interactive fiction is a genre with immense potential — there’s still so much left to explore. With just a few words and a spark of curiosity, anyone can shape their own universe and perhaps uncover new stories within themselves along the way. And with tools like <a href="https://www.plant-a-story.com">Plant a Story</a>, bringing those worlds to life has never been easier. All it takes is an idea — and the courage to let it grow.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c45202cc725b" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How to make Contact Forms work]]></title>
            <link>https://medium.com/@leshrimp/how-to-make-contact-forms-work-3aafb4f149ad?source=rss-bf86e7f10507------2</link>
            <guid isPermaLink="false">https://medium.com/p/3aafb4f149ad</guid>
            <category><![CDATA[how-to]]></category>
            <category><![CDATA[fat-free-framework]]></category>
            <category><![CDATA[php]]></category>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[forms]]></category>
            <dc:creator><![CDATA[Maxime Wegesin]]></dc:creator>
            <pubDate>Wed, 23 Jul 2025 21:16:40 GMT</pubDate>
            <atom:updated>2025-07-23T21:16:40.596Z</atom:updated>
            <content:encoded><![CDATA[<p>A client of mine asked me to create a personal homepage for him. Knowing that he was a rather artistic type and would not be satisfied with a standard WordPress theme, I suggested using one of the marvelous website templates from <a href="https://html5up.net">html5up.net</a>, which I can highly recommend!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*JEeJ5ON6r9u8YSmgK2FnIw.png" /><figcaption>A beautiful form with no functionality…</figcaption></figure><p>The only problem: the contact form obviously does not work out of the box. I didn’t want to use an external service to solve this, so I wrote a small PHP script along with some JavaScript.</p><p><strong>The best part</strong>: to prevent spam, the script even includes simple bot protection that is invisible to the user, but should be enough to keep most bots away.</p><h3>The Form</h3><p>The template I chose basically consists of a single index.html, which contains the following code:</p><pre>&lt;form id=&quot;contact-form&quot;&gt;<br> &lt;div class=&quot;fields&quot;&gt;<br>  &lt;div class=&quot;field half&quot;&gt;<br>   &lt;label for=&quot;name&quot;&gt;Name&lt;/label&gt;<br>   &lt;input type=&quot;text&quot; name=&quot;name&quot; id=&quot;name&quot; required/&gt;<br>  &lt;/div&gt;<br>  &lt;div class=&quot;field half&quot;&gt;<br>   &lt;label for=&quot;email&quot;&gt;Email&lt;/label&gt;<br>   &lt;input type=&quot;email&quot; name=&quot;email&quot; id=&quot;email&quot; required/&gt;<br>  &lt;/div&gt;<br>  &lt;div class=&quot;field&quot;&gt;<br>   &lt;label for=&quot;message&quot;&gt;Message&lt;/label&gt;<br>   &lt;textarea name=&quot;message&quot; id=&quot;message&quot; rows=&quot;4&quot; required&gt;&lt;/textarea&gt;<br>  &lt;/div&gt;<br> &lt;/div&gt;<br> &lt;ul class=&quot;actions&quot;&gt;<br>  &lt;li&gt;&lt;input type=&quot;submit&quot; value=&quot;Send Message&quot; class=&quot;primary&quot; /&gt;&lt;/li&gt;<br>  &lt;li&gt;&lt;input type=&quot;reset&quot; value=&quot;Reset&quot; /&gt;&lt;/li&gt;<br> &lt;/ul&gt;<br>&lt;/form&gt;</pre><p>Note that we don’t need additional code for validating this form — basic validation is handled by the browser.</p><h3>The PHP Script</h3><p>To get started, I decided to use the excellent <a href="https://fatfreeframework.com/3.9/base"><strong>Fat-Free Framework</strong></a>, which provides all we need to send emails using SMTP and includes other useful tools that make the code shorter and more readable.</p><p>With a total size of less than 550 kB, the whole framework is smaller than PHPMailer and much smaller than Symfony’s mailer library — making it the perfect fit for this project.</p><p>To install it, just run:</p><pre>composer require bcosca/fatfree-core</pre><p>Now we’re ready to write the PHP script that will handle sending the email. I called it contact.php and saved it in my root folder.</p><pre>&lt;?php<br>// Load Fat-Free Framework<br>require_once(&#39;vendor/autoload.php&#39;);<br>$f3 = \Base::instance();<br><br>// Only accept AJAX calls with POST method (this will already prevent some bots)<br>if (!$f3-&gt;get(&#39;AJAX&#39;) || !$f3-&gt;get(&#39;VERB&#39;) === &#39;POST&#39;) {<br>  $f3-&gt;error(500);<br>}<br><br>// When calling this script we will POST a parameter called &#39;action&#39;<br>switch ($f3-&gt;get(&#39;POST.action&#39;)) {<br>    case &#39;INIT&#39;:<br>        // Store the current time in the session<br>        $f3-&gt;set(&#39;SESSION.start_editing&#39;, time());<br>        break;<br><br>    case &#39;SEND_MAIL&#39;:<br>        // If the session key is not set, or to little time elapsed, <br>        // it&#39;s likely a bot<br>        if (<br>          !$f3-&gt;exists(&#39;SESSION.start_editing&#39;)<br>          || time() - $f3-&gt;get(&#39;SESSION.start_editing&#39;) &lt; 15<br>        ) {<br>            $f3-&gt;error(500);<br>        }<br><br>        // Otherwise we proceed with sending the E-Mail<br><br>        // Read the values of the form fields from the POST request<br>        $name = $f3-&gt;get(&#39;POST.name&#39;);<br>        $email = $f3-&gt;get(&#39;POST.email&#39;);<br>        $message = $f3-&gt;get(&#39;POST.message&#39;);<br><br>        // Create an instance of Fat-Free&#39;s SMTP class<br>        $smtp = new SMTP(<br>            &#39;my.mailhost.com&#39;,   // host<br>            &#39;465&#39;,               // port<br>            &#39;ssl&#39;,               // scheme<br>            &#39;addr@mailhost.com&#39;, // user<br>            &#39;my_password&#39;        // password<br>        );<br><br>        // Send Mail to me<br>        $smtp-&gt;set(&#39;To&#39;, &#39;addr@mailhost.com&#39;);<br>        $smtp-&gt;set(&#39;From&#39;, $email);<br>        $smtp-&gt;set(&#39;Subject&#39;, &#39;A request from &#39; . $name);<br>        $smtp-&gt;send($message);<br><br>        // Send confirmation mail to sender<br>        $smtp-&gt;set(&#39;To&#39;, $email);<br>        $smtp-&gt;set(&#39;From&#39;, &#39;addr@mailhost.com&#39;);<br>        $smtp-&gt;set(&#39;Subject&#39;, &#39;Thanks for your message&#39;);<br>        $smtp-&gt;send(&quot;Hi $name,\n\nThanks for your message. I will reply ASAP.\n\nKind regards&quot;);<br><br>        break;<br><br>    default:<br>        // Invalid action<br>        $f3-&gt;error(500);<br>}<br><br></pre><blockquote><strong>Note:</strong> You’ll need to replace the SMTP credentials with your own when creating the SMTP object.</blockquote><h3>The JavaScript</h3><p>Next, we’ll write a short JavaScript file that performs the necessary AJAX requests. To make things easier, I’ll use jQuery.</p><pre>$(document).ready(function() {<br>    let $form = $(&#39;#contact-form&#39;);<br>    let $submitButton = $form.find(&#39;[type=&quot;submit&quot;]&#39;);<br>    let originalButtonText = $submitButton.text();<br>    let $resetButton = $form.find(&#39;[type=&quot;reset&quot;]&#39;);<br><br>    let resetForm = function() {<br>        $form[0].reset(); // Reset form fields<br><br>        // Call the INIT action of the PHP script<br>        $.ajax({<br>            type: &#39;POST&#39;,<br>            url: &#39;/contact.php&#39;,<br>            data: [{name: &#39;action&#39;, value: &#39;INIT&#39;}]<br>        });<br><br>        // Reset submit button<br>        $submitButton.prop(&#39;disabled&#39;, false);<br>        $submitButton.text(originalButtonText);<br>    }<br><br>    // Reset button functionality<br>    $resetButton.on(&#39;click&#39;, function(event) {<br>        event.preventDefault();<br>        resetForm();<br>    });<br><br>    // Handle form submit<br>    $form.on(&#39;submit&#39;, function(event) {<br>        event.preventDefault();<br><br>        // Disable the submit button to prevent multiple submissions<br>        $submitButton.prop(&#39;disabled&#39;, true);<br>        $submitButton.text(&#39;Sending...&#39;);<br><br>        // Serialize form data<br>        let formData = $form.serializeArray();<br>        formData.push({name: &#39;action&#39;, value: &#39;SEND_MAIL&#39;});<br><br>        // Send the form data using AJAX<br>        $.ajax({<br>            type: &#39;POST&#39;,<br>            url: &#39;/contact.php&#39;,<br>            data: formData<br>        }).done(function(data) {<br>            // Show success message, if desired<br>        }).fail(function(data) {<br>            // Show error message, if desired<br>        }).always(function() {<br>            resetForm(); // Reset form after submission<br>        });<br>    });<br>});</pre><p>Save this in assets/js/contact.js with your other JavaScript files.</p><h3>Wrap It Up</h3><p>That’s it! All you have to do now is include the JavaScript file at the end of your index.html:</p><pre>   ...<br>   &lt;script src=&quot;assets/js/jquery.min.js&quot;&gt;&lt;/script&gt;<br>   &lt;!-- More Scripts... --&gt;<br>   &lt;script src=&quot;assets/js/contact.js&quot;&gt;&lt;/script&gt;<br> &lt;/body&gt;<br>&lt;/html&gt;</pre><p>And that’s it. I hope this helps — or at least inspires you to solve the problem you’re facing. If it was useful, let me know!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=3aafb4f149ad" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How to write a simple interactive fiction engine in Lua]]></title>
            <link>https://medium.com/@leshrimp/a-simple-interactive-fiction-engine-written-in-lua-09406a17ff56?source=rss-bf86e7f10507------2</link>
            <guid isPermaLink="false">https://medium.com/p/09406a17ff56</guid>
            <category><![CDATA[interactive-narratives]]></category>
            <category><![CDATA[tutorial]]></category>
            <category><![CDATA[lua]]></category>
            <category><![CDATA[interactive-fiction]]></category>
            <dc:creator><![CDATA[Maxime Wegesin]]></dc:creator>
            <pubDate>Wed, 15 Nov 2023 17:36:34 GMT</pubDate>
            <atom:updated>2023-11-15T19:58:37.163Z</atom:updated>
            <content:encoded><![CDATA[<p>Interactive fiction is written fiction that let’s you choose the course of action. While this is nothing new, I often feel that the genre still has a lot of potential to explore. How comes there are numerous famous and celebrated novels, but no piece of interactive fiction (that I am aware of) that has reached mainstream popularity? Fortunately it is not difficult to start to write your own interactive fiction and creatively explore this genre on your own. One starting point could be the following simple interactive fiction engine written in Lua.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*6six6tTxxxHyJJFQA99CzQ.png" /></figure><p>We begin by defining the format of our story. We want to store it in a way, that is manageable for human to read and edit, but can also easily be interpreted in Lua. A Lua table is a perfect choice for this.</p><pre>local story =<br>{<br>    [1] = {<br>        -- As we want to be able to write even long text, this<br>        -- is a perfect use for Lua&#39;s multiline strings.<br>        text = [[<br>You wake up. You don&#39;t know where you are. Dust covers the floor. There are <br>walls to your left and to your right.]],<br>        options = {<br>            {<br>                text = &#39;You go straight ahead.&#39;,<br>                next = 2<br>            },<br>            {<br>                text = &#39;You go into the other direction.&#39;,<br>                next = 2<br>            }<br>        }<br>    },<br></pre><p>This is our first chapter. It offers two options for the player to choose. In this case both choices lead to the same result. They both lead to Chapter 2.</p><pre>    [2] = {<br>        text = [[<br>A weary looking man with a long beard appears: &quot;Ah. You seem to be new.&quot; <br>He offers a compassionate smile.]],<br>        options = {<br>            {<br>                text = &#39;&quot;Where am I?&quot;&#39;,<br>                next = 3<br>            },<br>            {<br>                text = &#39;&quot;Who are you?&quot;&#39;,<br>                next = 4<br>            }<br>        }<br>    }<br>    -- ...and so on<br>}</pre><p>Now that we have defined our story (or at least the beginning of it), we need to write code that runs it, i.e. that displays the text of the chapter and the options to the player and asks for his choice.</p><pre>function run(story)<br>    -- We start at chapter 1<br>    local currentChapter = 1<br>    <br>    -- The game ends when we reach a chapter that does not exist<br>    while story[currentChapter] ~= nil do<br>        local chapter = story[currentChapter]<br>        <br>        -- Clear the terminal before we display our text<br>        os.execute(&#39;clear&#39;)<br>        <br>        -- Show our chapter and the available options<br>        print(chapter.text)<br>        print()<br>        displayOptions(chapter.options)<br>        <br>        -- Ask for the players choice and set the next chapter accordingly<br>        local chosenOption = readOption(chapter.options)<br>        currentChapter = chapter.options[chosenOption].next<br>    end<br>    <br>    print()<br>    print(&#39;The End?!&#39;)<br>end</pre><p>This is the meat of the engine. However, we left out two little details: We still have to write the code for the <strong>displayOptions </strong>and<strong> readOption </strong>functions.</p><p>Displaying our options is pretty straightforward. We loop through every option, print its index number and the corresponding text.</p><pre>function displayOptions(options)<br>    for i = 1, #options do<br>        print(i .. &#39; - &#39; .. options[i].text)<br>    end<br>end</pre><p>The reading of the option is slightly more complex, because we want to make sure the player doesn’t mistype or provide us with an option that doesn’t exist.</p><pre>function readOption(options)<br>    local chosenOption<br><br>    repeat<br>        print()<br>        -- We do not want a newline after out prompt, so we have to use<br>        -- io.write() instead of print()<br>        io.write(&#39;&gt; &#39;)<br>        local l = io.read()<br>        -- We try to convert the input to a numer. The tonumber() function<br>        -- will return nil if l is not a number.<br>        chosenOption = tonumber(l)<br>    <br>        -- We ask the player for input until he enter a number that<br>        -- is integer and corresponds to an existing option<br>    until (<br>      chosenOption ~= nil<br>      and chosenOption == math.floor(chosenOption)<br>      and chosenOption &gt;= 1 <br>      and chosenOption &lt;= #options<br>    )<br>    <br>    return chosenOption<br>end</pre><p>Now all we have to do is run our story.</p><pre>run(story)</pre><p>That’s it. I hope this will inspire some of you to write a great piece of interactive fiction. If you do please let me know!</p><p>Here is the complete code for easier copying and pasting.</p><pre>local story =<br>{<br>    [1] = {<br>        -- As we want to be able to write even long text, this<br>        -- is a perfect use for Lua&#39;s multiline strings.<br>        text = [[<br>You wake up. You don&#39;t know where you are. Dust covers the floor. There are <br>walls to your left and to your right.]],<br>        options = {<br>            {<br>                text = &#39;You go straight ahead.&#39;,<br>                next = 2<br>            },<br>            {<br>                text = &#39;You go into the other direction.&#39;,<br>                next = 2<br>            }<br>        }<br>    },    [2] = {<br>        text = [[<br>A weary looking man with a long beard appears: &quot;Ah. You seem to be new.&quot; <br>He offers a compassionate smile.]],<br>        options = {<br>            {<br>                text = &#39;&quot;Where am I?&quot;&#39;,<br>                next = 3<br>            },<br>            {<br>                text = &#39;&quot;Who are you?&quot;&#39;,<br>                next = 4<br>            }<br>        }<br>    }<br>    -- ...and so on<br>}<br><br>function run(story)<br>    local currentChapter = 1<br><br>    while story[currentChapter] ~= nil do<br>        local chapter = story[currentChapter]<br><br>        os.execute(&#39;clear&#39;)<br><br>        print(chapter.text)<br>        print()<br>        displayOptions(chapter.options)<br><br>        local chosenOption = readOption(chapter.options)<br>        currentChapter = chapter.options[chosenOption].next<br>    end<br><br>    print()<br>    print(&#39;The End?!&#39;)<br>end<br><br>function displayOptions(options)<br>    for i = 1, #options do<br>        print(i .. &#39; - &#39; .. options[i].text)<br>    end<br>end<br><br>function readOption(options)<br>    local chosenOption<br><br>    repeat<br>        print()<br><br>        io.write(&#39;&gt; &#39;)<br>        local l = io.read()<br><br>        chosenOption = tonumber(l)<br>    until (<br>            chosenOption ~= nil<br>            and chosenOption == math.floor(chosenOption)<br>            and chosenOption &gt;= 1<br>            and chosenOption &lt;= #options<br>        )<br><br>    return chosenOption<br>end<br><br>run(story)</pre><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=09406a17ff56" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Using Typora to turn your Markdown into beautiful presentations]]></title>
            <link>https://medium.com/@leshrimp/using-typora-to-create-presentations-1f4dce83b656?source=rss-bf86e7f10507------2</link>
            <guid isPermaLink="false">https://medium.com/p/1f4dce83b656</guid>
            <category><![CDATA[presentations]]></category>
            <category><![CDATA[revealjs]]></category>
            <category><![CDATA[typora]]></category>
            <category><![CDATA[markdown]]></category>
            <dc:creator><![CDATA[Maxime Wegesin]]></dc:creator>
            <pubDate>Wed, 08 Nov 2023 16:50:56 GMT</pubDate>
            <atom:updated>2023-11-23T17:50:18.673Z</atom:updated>
            <content:encoded><![CDATA[<h3>What we will do</h3><p>Typora can easily be used to create good-looking presentations very quickly.</p><p>In this tutorial I will show how to setup Typora to allow exporting your Markdown as a presentation. Thanks to <a href="https://pandoc.org/">Pandoc</a> and <a href="https://revealjs.com/">RevealJs</a>, the presentation will be contained in a single html file and can thus be run on every device that has a Browser installed. No PowerPoint or other presentation software is needed!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*qUlHAl-FcFDHt_9LbwRRdw.png" /><figcaption>Turn this…</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*JUEigvPKh9TKHhOvTD5vGw.gif" /><figcaption>…into this.</figcaption></figure><h3>How to do it</h3><p>All you have to do is open Typora’s settings. There you go to the ‘Export’ section and add a new export option that we call ‘RevealJS’. Choose the ‘Pandoc’ template for the new export.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*uVkNnUbF6-xtBBfsNF0K-g.png" /></figure><p>Once this is done you set the target format to ‘revealjs’ and the target file extension to ‘.html’.</p><p>In the ‘Extra arguments’ section you put the following:</p><pre>-V revealjs-url=https://unpkg.com/reveal.js --slide-level 2 --embed-resources --standalone -i</pre><p>If you are interested in the details of these options you can go to <a href="https://pandoc.org/MANUAL.html#options">https://pandoc.org/MANUAL.html#options</a> to read about them. However, the above should provide a sensible default.</p><p>In the end it should look like this:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*xKyH5dp_y6dsyA8EkKXgLw.png" /></figure><p>Now you can write your Typora file. You should add a YAML frontmatter containing a ‘title’ key. This will be used in the presentation for a title slide and also as a name for the browser tab the presentation runs in.</p><p>Once done, just choose ‘Export &gt; RevealJS’ and voila!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=1f4dce83b656" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>