Code Contribution Principles - Toma400/The_Isle_of_Ansur GitHub Wiki

Welcome on page which serves as guiding point for anyone who would like to contribute to The Isle of Ansur game through code contributions.

As this project is (and will be) maintained by only one person, I request all code contributions to follow them - this is requested so it's easy for me to read, and so the code form is coherent.


1. Try to avoid list comprehensions and long one-line code*

List comprehensions are powerful tool, but as much as they can be helpful, their syntax is quite confusing. I prefer using typical syntax for loops or maps. That said, it doesn't mean you can't use comprehensions at all, but be extremely picky, as it will be first thing I may ask you to change.
Long one-line code being ommited is quite obvious requirement, although it has caveats, explained in next two principles:

2. Use ';' and make one-line code if next action is closely related

I use non-pythonic semicolon to fit code which is closely related and happens immediately after itself. This way I can mix importing modules with some actual work on them, making further code cleaner:

import pygame; pygame.init(); pygame.mixer.init()
from core.graphics.gui_types import gui_handler

or just do following work on the same object, when it is extremely related to it:

image        = image.resize(size); image.save(f"{self.path}{self.imgname}")

Cases of this solution are not always clear and it's up to your intuition to implement those. Those should not be a major concern in code review, but I may change the code later to fit more my vision.

3. Also do one-liners (again)

Another point of one-line code should be to simplify some short conditionals and loops. Instead of putting it in pythonic way, see how beautifully you can one-line those examples:

dict = {"1": "one", 
        "2": "two", 
        "3": "three", 
        "4": "fifty-five"}
for i in dict: print (dict[i])

if x == 1:  return "one"
if x == 2:  return "two"
if x == 3:  return "three"
if x == 55: return "fifty-five"

match x:
    case 1:  return "one"
    case 2:  return "two"
    case 3:  return "three"
    case 55: return "fifty-five"

And yes, I actually suggest not doing dictionary in one line. This is exactly why one-liners can have purpose: they should be made when they serve the purpose of cleaner code. You may have spotted though one more thing, which I will show you explicitly in last one-liner example:

if len(dest_tp)   == 4: i = returnCells(dest_tp[0], dest_tp[1]), returnCells(dest_tp[2], dest_tp[3]); return list(i)
elif len(dest_tp) == 2: i = returnCells(dest_tp[0], dest_tp[1]);                                      return list(i)

Not only those are heavy one-liners (conditional + two actions in one line), but also...

4. Spaces. A LOT.

When creating variables or using functions, you may stumble across repetitive actions. Or, let's be honest: you will stumble across them.
In such case, it's cleaner for you to use spaces to make the code cleaner. So instead of doing this:

some_value: int = 5
other_value: str = "banana"
yet_different_value: float = 5.5

Write like this:

some_value:          int   = 5
other_value:         str   = "banana"
yet_different_value: float = 5.5

This makes it easier to distinguish purpose of each element of the code. This also works for functions:

gt1 = Text(text="menu__button_exit",     fonts="menu", size=30, pos=(50,64)).lstr().colour(tcol="#4E3510").put(screen)
gt2 = Text(text="menu__button_settings", fonts="menu", size=30, pos=(50,70)).lstr().colour(tcol="#4E3510").put(screen)
gt3 = Text(text="menu__button_packs",    fonts="menu", size=30, pos=(50,76)).lstr().colour(tcol="#4E3510").put(screen)

5. Fluent programming

You may have also spotted big amount of dots in example above. This is because most objects will have methods with @Callable decorator. Let's try to deconstruct this to be pythonic:

gt1 = Text(text="menu__button_exit", fonts="menu", size=30, pos=(50,64))
gt1.lstr()
gt1.colour(tcol="#4E3510")
gt1.put(screen)
gt2 = Text(text="menu__button_settings", fonts="menu", size=30, pos=(50,70))
gt2.lstr()
gt2.colour(tcol="#4E3510")
gt2.put(screen)
gt3 = Text(text="menu__button_packs", fonts="menu", size=30, pos=(50,76))
gt3.lstr()
gt3.colour(tcol="#4E3510")
gt3.put(screen)

You can see how inefficient it is.
Maybe you can also use some loop to auto-put each of repeating functions (such as lstr() and put(screen)) to each object, but this does take away either control over the object, or cleaniness of the code.
My solution for this is decorator returning self for each method - this way you can append methods like you do in Java, without hurt to how readable your code will be.

6. Try to use "Pyramid importing"

It is the least important request, but imports being neatly distributed in reversed pyramid shape are just great to look at. I tend to organise them by purpose, so all long-named imports go on top, as well as imports which are immediately used (as in line 6 of main.py file - they usually use semicolons a lot; look at section 2 of this document).