Tired of JavaScript - use browser-based Python

My experience of developing the game "Snake" in Brython



image



"Wait, what?" - I think most of the readers will react to the title that way.



Do you mean "just use Python in the browser"?



Everyone knows that only JavaScript works in browsers.



Well, above is a screenshot of my personal site source code. Take a look, maybe you will see something new for yourself.



Yes, it's Python!



Now, let's talk about how and how well it works, and also discuss a number of other JavaScript alternatives.



Introducing Brython



Brython is a JavaScript implementation of Python3 that lets you write Python code for the web.



Basically, it is a JavaScript library that converts your Python code to the equivalent JS and runs it at runtime.



Since writing browser code in Python sounds cool, I decided to give it a try.



Development of "Snake" in Brython



image



Here is a link to my site where you can try the JavaScript and Brython versions of Snake. And here is a link to GitHub with the source code .



In order to try out Brython, I decided to write the classic Snake.



Since I'm not an HTML Canvas expert or a game developer, I decided to use this JavaScript implementation as a starting point. Once I already created my "Snake" on the basis of Canvas, but this implementation is neater and more compact.



The author also wrote it in less than 5 minutes . I have to give credit to Chris DeLeon, it's very impressive.



So, I added scoring and saving the best score to Chris's implementation, and also slightly improved the interface (added a pause button and a button with instructions). Then I ported the game to Brython.



I also modified his code so that it works in a mode strict, since Chris's implementation uses things like implicit global variables, which, in my opinion, do not reflect what most of the JS code looks like (I do not criticize the author - he programmed for the time ). I wanted to get a good comparison between Brython and JS code.



JavaScript turned out to be like this , and I will not post this code here, so our goal is to focus on Brython.



Although most of the Brython code was literally translated from JS, some parts (such as the scoring functionality) were written directly in Brython and then implemented in JS to see the differences.



The final result looks like this:



<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Brython Snake</title>
    <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/brython@3.8.9/brython.min.js">
    </script>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">
    <style> /* Removed to keep the snippet short. Find the full file here: */ </style>
</head>

<body onload="brython()">

    <h1 class="text-center">Snake built with <a href="https://brython.info">Python!</a></h1>
    <canvas id="game-board" width="400" height="400"></canvas>
    <br>
    <h3 id="score" class="text-center">Score: 0</h3>
    <br>
    <h6 id="high-score" class="text-center">High Score: 0</h6>
    <br>
    <div class="text-center">
        <button id="instructions-btn" class="btn btn-info">Instructions</button>
    </div>

    <script type="text/python">
        
        from browser import document, html, window
        from javascript import Math
        
        score = 0
        high_score = 0

        px = py = 10
        gs = tc = 20
        ax = ay = 15
        xv = yv = 0
        trail = []
        tail = 5

        pre_pause = [0,0]
        paused = False
   
        def game():
            global px, py, tc, gs, ax, ay, trail, tail, score
            px += xv
            py += yv
            if px < 0:
                px = tc-1
            if px > tc-1:
                px = 0
            if py < 0:
                py = tc-1
            if py > tc-1:
                py = 0
            ctx.fillStyle = "black"
            ctx.fillRect(0, 0, canvas.width, canvas.height)
            ctx.fillStyle = "lime"
            for i in range(len(trail)):
                ctx.fillRect(trail[i][0]*gs, trail[i][1]*gs, gs-2, gs-2)
                if trail[i][0] == px and trail[i][1] == py:
                    score = score if paused else 0 
                    tail = 5
            trail.insert(0, [px, py])
            while len(trail) > tail:
                trail.pop()
        
            if ax == px and ay == py:
                tail += 1
                ax = Math.floor(Math.random()*tc)
                ay = Math.floor(Math.random()*tc)
                score += 1
            update_score(score)
            ctx.fillStyle = "red"
            ctx.fillRect(ax*gs, ay*gs, gs-2, gs-2)
        
        def update_score(new_score):
            global high_score
            document["score"].innerHTML = "Score: " + str(new_score)
            if new_score > high_score:
                document["high-score"].innerHTML = "High Score: " + str(new_score)
                high_score = new_score

        def key_push(evt):
            global xv, yv, pre_pause, paused
            key = evt.keyCode
            if key == 37 and not paused:
                xv = -1
                yv = 0
            elif key == 38 and not paused:
                xv = 0
                yv = -1
            elif key == 39 and not paused:
                xv = 1
                yv = 0
            elif key == 40 and not paused:
                xv = 0
                yv = 1
            elif key == 32:
                temp = [xv, yv]
                xv = pre_pause[0]
                yv = pre_pause[1]
                pre_pause = [*temp]
                paused = not paused
            
        def show_instructions(evt):
            window.alert("Use the arrow keys to move and press spacebar to pause the game.")
        
        canvas = document["game-board"]
        ctx = canvas.getContext("2d")
        document.addEventListener("keydown", key_push)
        game_loop = window.setInterval(game, 1000/15)
        instructions_btn = document["instructions-btn"]
        instructions_btn.addEventListener("click", show_instructions)
    
</script>

</body>

</html>


So, based on this snippet, let's understand some basic Brython concepts



Brython.js connection



No installation is required to use Brython. Just import the script inside head :



<script type=”text/javascript” src=”https://cdn.jsdelivr.net/npm/brython@3.8.9/brython.min.js">


Running Brython



In order for Brython to translate and execute Python code as if it were JS code, we need to call Brythonjust when the document body is loaded. For example, like this:



<body onload=”brython()”>


This tag will search for tags of scripttype "text/python"and run their code.



API for working with the web



JavaScript by default gives access to objects like documentand windowneeded in any JS project. Accordingly, Brython should be able to work with them too.



To solve this problem, the creators of Brython could simply give developers the ability to access these objects from Python code, but this would lead to debugger cries undefined variableand degraded performance.



Thus, to use these APIs, we must import them just like we import any other Python module:



from browser import document, html, window


And you don't need to execute the command pip install. After all, you embed it all in HTML! Just add the required imports and Brython will handle the rest.



To see how well it works, I tried to use several different methods of the API the Web: alert, setInterval, addEventListeneretc. They all worked as they should.



Built-in JavaScript Objects and Methods



In "Snake", as soon as the snake eats the apple, we need to generate a new apple at a random location.



However, I cannot use the random module from the Python * library. So how can I generate a random number (without writing my own library)?



It turns out that Brython has broader JavaScript support than I thought. See:



from javascript import Math
random_num = Math.floor(Math.random()*10)


Thanks to the module javascript, if there is an object that I can access using JS, then I can access it using Brython.



If I import a JavaScript library (jQuery, Bootstrap) and want to use its methods, I can do it with from javascript import <>. And of course I can also use built-in JS objects like Dateor String.

* Brython appears to come with a number of standard Python libraries implemented directly in JavaScript, and if a module does not have a JS version, you can still import it. Brython will get a pure Python version and the imported module code will work alongside the Brython code. However, the random module did not work for me - but I can understand why.

Specific constructions



In Python, if I want to unpack a list, I can write list2 = [*list1]. Also, if I want to assign values ​​to a variable based on some condition, I can write foo = 10 if condition else 20.



These constructs have JavaScript equivalents: the spread ( [...arr]) operator and the ternary ( let foo = condition ? 10 : 20) operator .



But does Brython support them?



I tried them and they worked great. You can see that list unboxing from Python and conditional assignment are used in my code.



Debugging



To be honest, I thought debugging in Brython would be awful.



Actually, it's not that bad.



Of course, I wrote a very small and not very complex project, but the errors thrown by Brython were mostly accurate and quite understandable.



This is true at least for syntax errors. Importing modules from the Python library is a completely different story.



Performance



image



JavaScript Snake



image



Brython Snake



As expected, Brython code is slower than JavaScript. In my case, it was about 1.7 times slower.



I suspect that in more complex projects Brython will be several times slower than pure JS.



However, you can transpile your Brython code ahead of time and only use JavaScript on the page, which should perform better.



I actually tried to use the Brython Editor to convert my Brython code to JS and run the resulting code on a web page, but due to a huge number of errors, I have given up on this for now. However, I have not put too much effort into this.



Final thoughts on Brython



To be honest, I was quite impressed with Brython. Here are a few pros and cons from my own experience with the language:



Pros



  • I managed to write "Snake" without unnecessary hassle, and the debugging experience was surprisingly positive.
  • In my simple project, Brython interacted seamlessly with the native JavaScript objects available on the page
  • I appreciate the fact that my code looks cleaner in Python, and I also love that I can use useful Python constructs to write browser code.
  • In the case of my game, although Brython loads slower than JavaScript, the user doesn't notice this difference.
  • I am pleased to see Python in the source code of my site.


Minuses



  • Brighton is significantly slower than pure JS.
  • Brython JavaScript.
  • Brython
  • Brython .


In general, having finished my first project in Brython, I can confidently say that I will try again someday.



However, I believe Brython is now more suited for JavaScript developers familiar with Python and tired of JS, rather than Python developers who want to do web development without learning JavaScript.



I think an understanding of JavaScript is essential in order to work well with Brython. And if you decide to take the time to learn JavaScript to make it easier for you to write in Brython, then you can just use JavaScript.



Other browser JS alternatives



image



The reason I chose Brython was because of most of the Python to JS migration options I first learned about, it was the only one actively developing on GitHub. Most of the Python to JavaScript transpilers I've looked at haven't had commits for several years.



However, there are other alternatives.



Pyodide , for example, seems like an interesting option. It compiles Python (along with its scientific libraries) to WebAssembly, which allows it to run in a browser.



WebAssembly, as the name suggests, is an assembler for the web. Just as assembler on our computers can act as an intermediary between high-level languages ​​and machine code, WebAssembly does the same on the web.



Thus, it is possible to write a compiler that will translate Python (or any other language) into WebAssembly, allowing it to run in the browser.



This is an ambitious and promising project that will most likely lead to the fact that we will see more and more web development without JavaScript.



However, it is still in its infancy (~ 3 years), so it will probably take some time before we see JavaScript being regularly replaced by other languages.



And while we wait, you'll have to use tools like Brython if you really can't deal with JavaScript.



But honestly, this is a good start!



image


Learn more about how to get a high-profile profession from scratch or Level Up in skills and salary by taking SkillFactory's paid online courses:











All Articles