Libove Blog

Personal Blog about anything - mostly programming, cooking and random thoughts

Walls in Tile Maps with Tile Strings

Example of a tile map.

I'm working on a small game with a procedural map. As the whole game is set in a labyrinth-like building I've decided to use tile maps. The map generation algorithm produces a 2D matrix with 0s and 1s. A 1 indicates that the tile is walkable while a 0 represents an impassable field.

In a simple 2D setting this could be represented by using a floor sprite for each 1 and a wall sprite for each 0. However, this isn't very appealing. Instead the visual representation of a tile should be depended on its neighborhood.

Each tile can have one of 256 (2^8) configurations. These configurations can be reduced to 41 by considering rotations as one configuration. As multiple configurations need the same wall layout we will only need 15 tiles to render the tile map.

Configurations of a tile with its neighbors.

My first approach to implement the rendering was to use shit ton of if statements. The approach tested for all configurations to find the needed piece and rotation. A few shortcuts prevented that I had to test for all 256 configurations but it was still too much. After implementing this approach for 4 tile pieces I gave up.

My next idea was to represent the knowledge of which piece and rotation is required for each configuration in a lookup map. This approach is similar to marching cubes where you need a lookup map to know how to construct the faces of a cube. After writing down 30 entries of the lookup map I came up with the following idea (which I will call "Tile Strings").

Calculating the Tile String.

Instead of checking for all 256 configurations, we only look at a 2x2 section of the neighborhood.

We start with the 2x2 patch in the upper left corner and determine the character it represents. This character represents whether the left side of the tile needs a wall, a corner or needs to be empty.

The 2x2 patch is than rotated around the center to determine the characters for the other sides. After the full rotation we have a 4 character string representing the configuration of the center tile, for example "WCCE". This is our tile string.

Example code for a single 2x2 check (written in GDScript):

if cell(p + Vector3(-1, 0, 0)):
    if cell(p + Vector3(0, 0, -1)) and not cell(p + Vector3(-1, 0, -1)):
        tile_str += "C"
    else:
        tile_str += "E"
else:
    tile_str += "W"

With the calculated tile string we can start to search for a suitable sprite or model in a library (our library should consist of 15 assets named by their tile string). We still have to manipulate the tile string and determine the rotation, as we most likely only want to create 15 assets.

As the tile string represents a unique configuration we only have to rotate it (at most 3 times) to find a fitting configuration in our library. We start by checking if the tile string already fits an element in our library. If this is the case we can just use it! If no element fits we rotate by 90 degrees, by moving the last character of the string to the front, "WCCE" is transformed to "EWCC". The number of rotations are counted. We do this at most 3 times. If you didn't find a fitting element after 3 rotations your library is not yet complete. Once the rotated element fits, it can be inserted into our scene with the determined rotation.

Example code for finding an element in the library (written in GDScript):

var rotation = 0
for i in range(4):
    if tile_str in library:
        add_part(position, rotation, tile_str)
        break
    rotation += 90
    cfg_str = cfg_str[3] + cfg_str[0] + cfg_str[1] + cfg_str[2]

Your library does not have to be limited to 15 elements. Maybe corridors running north-south should have a different design than east-west corridors. Just add a new element with the corresponding tile string to your library.

Rending of room using the tile string algorithm.

This approach only requires a few lines of code. There is probably a lot of potential for improvement but it works perfectly for my use case. The process of developing this once again showed me that it is worthwhile to take a step back and think about your problem. The first two approaches showed me properties of the problem and implementing them (partially) deepened my knowledge, which ultimately led to the "Tile String" algorithm.


Veganer Flammkuchen

Servings: 2 , Prep Time:

Ingredients

  • 175 g 550 Mehl
  • 100 ml lauwarmes Wasser
  • Prise Salz
  • 5 g Hefe
  • Vegane Crème fraîe
  • Rauchsalz
  • Knoblauchpulver
  • Pfeffer
  • 1 rote Zwiebel
  • 6 Champignons

Instructions

Veganer Flammkuchen.

  • Teigzutaten zu einem glatten Teig kneten
  • Teig im Kühlschrank mindestens 24 Stunden gehen lassen. Alle 24 Stunden Teig durch kurzes kneten entlüften.
  • 30-60 Minuten vor dem backen zwei Teigbälle a 100-150 g formen und warm gehen lassen und Backofen auf 300°C vorheizen
  • Teig sanft zu möglichste dünnen Fladen formen
  • Fladen mit Crème fraîche bestreichen und mit nach belieben würzen
  • Zwiebeln in dünne Ringe schneiden und Champignons würfeln und Fladen belegen
  • ca 5 Minuten bei 300°C backen

COVID-19 Tote in Relation setzen

Momentan sterben täglich etwa 800 Menschen an COVID-19. Eine Zahl unter der ich mir nichts vorstellen kann weil mir der Vergleich fehlt. Was bedeutet es wenn ~800 Menschen pro Tag sterben?

einwohner = 83_000_000
tote_pro_tag = 800
prozent_tote_pro_tag = tote_pro_tag / einwohner
print(f"{prozent_tote_pro_tag * 100:.4f}%\tder Bevölkerung stirbt pro Tag")
print(f"{prozent_tote_pro_tag * 100 * 7:.4f}%\tder Bevölkerung stirbt pro Woche")
print(f"{prozent_tote_pro_tag * 100 * 30:.4f}%\tder Bevölkerung stirbt pro Monat")
print(f"{prozent_tote_pro_tag * 100 * 365:.4f}%\tder Bevölkerung stirbt pro Jahr")
0.0010%	der Bevölkerung stirbt pro Tag
0.0067%	der Bevölkerung stirbt pro Woche
0.0289%	der Bevölkerung stirbt pro Monat
0.3518%	der Bevölkerung stirbt pro Jahr

Das ganze in Prozenten auszudrücken macht es nicht viel besser. Wie sieht es also aus wenn wir die COVID-19 Sterblichkeit auf andere Situationen übertragen?

Wie riskant wäre es zu fliegen?

fluggaeste_deutschland = 227_000_000 # Quelle: https://de.statista.com/themen/1060/flugpassagiere/
tote_fluggaeste_pro_tag = prozent_tote_pro_tag * fluggaeste_deutschland
print(f"{int(tote_fluggaeste_pro_tag)} Menschen würden täglich beim fliegen sterben")
print(f"{int(tote_fluggaeste_pro_tag * 7)} Menschen würden wöchentlich beim fliegen sterben")
print(f"{int(tote_fluggaeste_pro_tag * 30)} Menschen würden monatlich beim fliegen sterben")
2187 Menschen würden täglich beim fliegen sterben
15315 Menschen würden wöchentlich beim fliegen sterben
65638 Menschen würden monatlich beim fliegen sterben

Wie viele Flugzeuge müssten dafür abstürzen?

passagiere_pro_maschine = 101 # Quelle: https://de.statista.com/statistik/daten/studie/327019/umfrage/passagiere-je-flugzeug-deutsche-flughaefen/
fluege = fluggaeste_deutschland / passagiere_pro_maschine / 365
abstuerze = tote_fluggaeste_pro_tag / passagiere_pro_maschine
print(f"{abstuerze:.0f} von {fluege:.0f} Flugzeugen würden täglich abstürzen")
22 von 6158 Flugzeugen würden täglich abstürzen

Wie sieht es mit dem Schienenverkehr aus?

zuggaeste_deutschland = 2_600_000_000 # https://de.statista.com/statistik/daten/studie/13626/umfrage/reisende-im-schienenpersonenverkehr-der-db-ag/
tote_zuggaeste_pro_tag = prozent_tote_pro_tag * zuggaeste_deutschland
print(f"{int(tote_zuggaeste_pro_tag)} Menschen würden täglich bei Zugfahrten sterben")
print(f"{int(tote_zuggaeste_pro_tag * 7)} Menschen würden wöchentlich bei Zugfahrten sterben")
print(f"{int(tote_zuggaeste_pro_tag * 30)} Menschen würden monatlich bei Zugfahrten sterben")
25060 Menschen würden täglich bei Zugfahrten sterben
175421 Menschen würden wöchentlich bei Zugfahrten sterben
751807 Menschen würden monatlich bei Zugfahrten sterben

Wie viele Zugunglücke wären es pro Tag?

kapazitaet = 468 # Intercity 2 https://de.wikipedia.org/wiki/Intercity_2_(Deutsche_Bahn)
zuege = zuggaeste_deutschland / kapazitaet / 365
ungluecke = tote_zuggaeste_pro_tag / kapazitaet
print(f"{ungluecke:.0f} von {zuege:.0f} Zügen würden täglich verunglücken")
54 von 15221 Zügen würden täglich verunglücken

Fazit

Es ist schrecklich wie viele Menschen gerade an COVID-19 sterben. Wenn man diese Werte in Relation setzt wird einem das Ausmaß erst richtig bewusst. Niemand von uns würde in einen Zug oder Flugzeug steigen wenn es genau so gefährlich wäre wie es gerade in dieser Pandemie ist.


Django Migrate MySQL to Postgres

I've recently migrated my tools project from an old the server to a new one. In the process I've decided to change the used database from MySQL to PostgreSQL. To do so I've used this amazing guide from calazan.com.

As the guide was written for django 1.6 some minor changes were necessary when working with newer versions (I've used django 3.1.4).

Setup for migration

Steps 1 + 2 work as described in the guide above. A new database is created and added to the settings.py.

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'dbname',
        'USER': 'dbuser',
        'PASSWORD': 'dbpass',
        'HOST': 'mysql.example.com',
        'PORT': '',
    },
    'postgresql': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': 'dbname',
        'USER': 'dbuser',
        'PASSWORD': 'dbpass',
        'HOST': 'postgresql.example.com',
        'PORT': '',
    }
}

Apply migrations to new database

python manage.py migrate --database=postgresql

syncdb is no longer available in newer django versions and was replaced by migrate. The flag --no-initial-data is no longer available and I didn't find a substitution.

Clean Up the new database

Some migrations will create initial data in your database. This will probably create conflicts, as we are migrating existing data into this database. I did this step by hand, but the command python manage.py sqlflush --database=postgresql will give you the SQL query to do it automatically.

Copy data to new database

In order to copy the data we use dumpdata and loaddata.

When dumping the data it is important to use the flag --natural-foreign, otherwise the import process cannot import data with foreign keys.

python manage.py dumpdata --all --natural-foreign > dump.json

The dumped data can be loaded into the new database. This process can take a long time. Ensure that your machine executing this command is as close as possible to the database, ideally running on the same machine. First, I started this command on my local machine. After 30 minutes I decided that it took too long and uploaded the dump to the server running the database, which finished the command in about a minute. My dump file was only 16 MB big.

python manage.py loaddata dump.json --database=postgresql

Adjust config

Now all data is available in the new database. Just adjust the settings to use the postgresql as the default database.



C̸u̴r̶s̷e̷ ̶o̵f̵ ̴B̵l̷o̶o̸d̴

Nyarlathotep, ee ilyaa h'n'ghft llll shugg lloig throd

nafluh'e Chaugnar Faugn ch'.

H̸a̶s̶t̷u̷r̶o̸r̷ ̸i̸l̵y̴a̴a̴ ̸g̵o̷f̵'̴n̸n̶o̷g̸ ̵s̸h̵u̸g̷g̷ ̶p̴h̴'̴u̴h̸'̸e̵ ̵t̵h̴r̶o̶d̸ ̷a̶t̷h̶g̸

v̷͈̓u̸͍̓l̴̝͌g̶̮͠t̷̜͆m̴̗̿ ̷̪̈k̵̮͑ň̷̤'̶͍̊a̵̝̽ ̸̫̐ṇ̵̊a̸͈̅f̸̘̊l̶̺͠ó̴̢r̷̂ͅȓ̴̨'̸̡̓è̷̹,̸̘̈́

c̷̹͝ȋ̶̟l̴̨̈́y̸̨̽ạ̴̊â̴͇ ̴̙̂c̸̭͋h̵̲̓'̸͎͗ ̷͕́s̸̲̾ḧ̴̢́ů̶͈g̸̈́͜g̴̢̅o̴̧̔ṱ̴͆h̸̪̃ ̶̹̆h̴̲̊ủ̷̙p̸͇̒a̵̪͆d̵̲͗g̷̖̊h̸̳̋ ̷̖̊ń̸̥a̴̖͌f̷̦͝l̶̯̊'̵̞̃f̷̳̒h̸̢͋á̴͜l̵͕̐m̴̳͑a̶̢̾ ̸̘̈́e̵̯̎b̶͍͝u̵̮͘n̸̻̎m̵̺̃ả̴̝o̷̩͊r̸̨͝

b̷̼̏ṵ̷͋g̶̥̈ ̶̧͂'̸̭̉b̴̮͛ṱ̶͘h̵̬́n̵̝̊ḵ̸̄ǫ̶̛t̷̛̳h̶̹̿ ̸́͜H̴̜̿a̴̛̲s̴̡̓t̶̢̚u̶̢͆r̶̦̔ ̴̰̃f̸͍͘h̸̖̄t̵̛̹a̶̯̽g̸̮͠n̷͓͒ ̶͇̾s̴̖̄ḩ̴͆a̵͔̔g̷͗ͅg̵͕͠.̷̰̆

Blood Ring

Music: Remix of THE OLD ONES - Scott Buckly CC-BY 4.0


Kommunalwahlen Aachen 2020

Um die Ergebnisse der Kommunalwahlen in Aachen besser zu verstehen habe ich die Ergebnisse der einzelnen Stimmbezirke in einer Kartenansicht visualisiert. Hierbei habe ich für jede Partei eine Grafik erstellt, die zeigt wo die Partei besonders stark ist.

Quelle: https://wahlen.regioit.de/1/km2020/05334002/html5/Kreistagswahl_NRW_78_Uebersicht_stbz.html

![Karte mit Wahlergebnissen für Städteregion Aachen: CDU](media/Städteregion: CDU.png) ![Karte mit Wahlergebnissen für Städteregion Aachen: GRÜNE](media/Städteregion: GRÜNE.png) ![Karte mit Wahlergebnissen für Städteregion Aachen: SPD](media/Städteregion: SPD.png) ![Karte mit Wahlergebnissen für Städteregion Aachen: LINKE](media/Städteregion: LINKE.png) ![Karte mit Wahlergebnissen für Städteregion Aachen: FDP](media/Städteregion: FDP.png) ![Karte mit Wahlergebnissen für Städteregion Aachen: PIRATEN](media/Städteregion: PIRATEN.png) ![Karte mit Wahlergebnissen für Städteregion Aachen: UWG](media/Städteregion: UWG.png) ![Karte mit Wahlergebnissen für Städteregion Aachen: AfD](media/Städteregion: AfD.png) ![Karte mit Wahlergebnissen für Städteregion Aachen: PARTEI](media/Städteregion: PARTEI.png)


Home Office Pizza

Vegan Pizza with bell peppers, onions and basil

This is my pizza recipe for an easy to prepare lunch. It takes 15 minutes to prepare the dough. I typically prepare enough dough for 4 servings. The dough can be stored for several days and improves each day ;).

Preparing the pizza takes around 10 minutes.

Ingredients

Dough (per serving):

Toppings

  • Passata
  • Olive Oil
  • Salt and pepper
  • (Chili, optional)
  • Oregano
  • vegan cheese (I use Simply V)
  • whatever you like on a pizza!

Preparation

  • Mix your flour and salt
  • Dissolve your yeast in the water
  • Add water and oil to the dry ingredients and mix everything
  • Once all liquid is absorbed start kneading the dough until you reach an even consistency
  • Put your dough in the fridge overnight

Cooking

In the morning take 275g of your dough out of the fridge. Fold or knead it shortly to create a round ball. Put the ball into a bowl and cover it.

After 1-3 hours fold the dough once. Be careful to keep the air building up!

Preheat your oven to 250°C (20-30 minutes before your lunch).

Put the dough ball on a floured surface and carefully push and pull it into shape. Be gentle and keep as much air as possible inside the dough! Put the pizza dough on your baking sheet.

Coat the pizza lightly with olive oil. Add 3-5 tablespoon passata and spread it evenly. You don't need a lot of sauce, only cover the dough lightly.

Season the sauce with salt, pepper and oregano (I also add chili). Add your ingredients and cheese.

Bake the pizza for 10 minutes.


Wealth Tax

Paul Graham wrote an article about wealth tax. In the article he 'models' the effect of a wealth tax on the fortune of a person. His assumption for the modeling is a person who earned a lot of money with a startup in her 20s. Paul Graham shows how much of the earned stocks/money would be taken by the government by a wealth tax.

Paul Graham comes to the conclusion that wealth taxes have "dramatic effects" and that "even a .5% wealth tax would start to keep founders away from a state or country that imposed it."

I think this conclusion is wrong. His model assumes that the earned money just lies on a big pile and is not used for 60 years. A more reasonable model would include interest being earn with the money. For this I will use an extended model:

(growth * (1-wealth_tax)^years

If we assume a reasonable return of investment of 2% per year the number shifts dramatically. Instead of "losing" 25% at 0.5% wealth tax you end up with 242% of your original value after 60 years. Even a wealth tax of 3% would only half your wealth.

Wealth Tax Graham Model With 2% Interest
0.10% 94.17% 308.99%
0.50% 74.03% 242.88%
1.00% 54.72% 179.52%
2.00% 29.76% 97.63%
3.00% 16.08% 52.76%
4.00% 8.64% 28.33%
5.00% 4.61% 15.12%

Paul Graham's article also misses to mention that a wealth tax only affect people with ALOT of money. Elizabeth Warren's "Ultra-Millionaire Tax" would only tax assets over $50 million. Once you are affected by a wealth tax you have enough money to live a luxurious life (and your children will probably never have to work to live). Most startup founders never reach this amount of wealth.

As this tax only targets the super rich let's look at the effect this tax would have on Jeff Bezos (data source: Wikipedia):

Effect of Wealth Tax on Jeff Bezos

Even a high wealth tax of 5% leaves Bezos the majority of his wealth. If Bezos stopped earning new wealth in 2018 he could bequeath $30 billion to his children in 2080 with a 2% wealth tax. With a 5% wealth tax his children would still inherit over $4 billion!

The questions I'm asking myself when thinking about a wealth tax are the following:

  • Should a person have virtually unlimited resources for the rest of her life, just because she had a clever idea in her 20s?
  • Should children have virtually unlimited resources for the rest of their lives, just because one of their parents had a clever idea in her 20s?
  • Should grandchildren have virtually unlimited resources for the rest of their lives, just because one of their grandparents had a clever idea in her 20s?