Blutsauger, Lachflash und abwesende Spielentscheider

Blood on the Clocktower kann kompliziert sein? Ja, auf jeden Fall. Gibt es deshalb Einsteigerszenarien? Genau deshalb! Wie lange sollte man diese spielen, bevor man sich an die Fortgeschrittenenszenarien wagt? So etwa 10x. Hat das letzten Donnerstag irgendeinen Einsteiger interessiert? Nicht die Bohne. Gab das Chaos? Aber sowas von!

Aber von vorne: wir trafen uns im Nauwieser 19 für eine Runde Trouble Brewing. Da wir nur 6 Spieler + Erzähler waren, gab es stattdessen das Winzigweiler-Spiel (Teensyville) „Keine größere Freude“ (No Greater Joy). Bei Winzigweiler-Spielen erfahren Dämon und Schergen nicht von einander, was ich in der ersten Nacht trotzdem gemacht habe, so dass alles wieder von vorne gemacht werden musste. Im zweiten Anlauf klappte dann alles und das Dorf wollte mich nicht alleine dumm da stehen lassen: so kam es dazu, dass jemand vom Guten Team für sich selbst stimmte und damit dem Bösen Team zum Sieg verhalf. Ich sehe hier eine große Karriere als Politiker:

Falls du der Spieler warst, der am meisten dafür verantwortlich war, dass sein Team verliert, änderst du deine Gesinnung & gewinnst, selbst wenn du tot bist.

Ok, das erste Spiel war etwas zu kompliziert, was macht man da? Selbstverständlich gleich ein fortgeschrittenes Szenario spielen, dann man wächst bekanntlich mit seinen Aufgaben. Also wurde auf „Blutsauger des Misstrauens“ (Lleech of Distrust) gewechselt und los ging der wilde Ritt. Die Lleech vergiftete die Fischerin, die prompt zu mir kam und direkt zu Beginn des 1. Tages wissen wollte, wie ihr Team gewinnt. Da kommt man als Erzähler mächtig ins Schwitzen, denn:

Einmal pro Spiel, während des Tages, gehe zum Erzähler und frage nach 1 hilfreichen Ratschlag, wie dein Team gewinnt.

Das ist nach 10 Sekunden Spielzeit schon sehr schwierig, wenn man noch gar nicht weiß, wie das Böse Team sich darstellt. Aber dann noch eine falsche Information geben? Es wurde dann „Vertraue nicht Anke“, die sich als Elfe ebenfalls als Fischerin ausgab:

Du weißt zu Beginn, dass 1 bestimmter Dorfbewohner im Spiel ist. Falls du besessen davon warst, diese Rolle zu sein, erhältst du dessen Fähigkeit, sobald dieser stirbt.

Zu diesem Mix kam dann noch die Marionette

Du denkst, dass du eine gute Rolle bist, aber du bist es nicht. Der Dämon weiß, wer du bist. [Du sitzt neben dem Dämon]

die von ihrer Dämonin vorsorglich nicht eingeweiht wurde. Und damit wurde das Skript seinem Namen voll gerecht: keiner vertraute irgendjemand, nicht mal sich selbst.

Das hielt die Dämonung und die Reisende nicht davon ab, einen dermaßen heftigen Lachflash zu bekommen, dass ich dachte, die beiden laufen gleich blau an, weil sie keine Luft mehr bekommen.

Wie ging das Spiel aus? Das Chaos wäre nicht perfekt, wenn der spielentscheidende Zug nicht von jemanden ausgeführt worden wäre, der gar nicht mehr da war: dadurch, dass die Knochensammlerin

Einmal im Spiel, in der Nacht, wähle einen toten Spieler: er erhält seine Fähigkeit bis zur Abenddämmerung zurück.

ankündigte ihre Fähigkeit zu nutzen, es aber nicht tat, wurde dem guten Team die entscheidende Information verwehrt, so dass das Böse Team den Sieg nach Hause schaukeln konnte. Glückwunsch Kristiane, du warst zwar nicht mehr da, aber hast das Spiel entschieden!

Schulen öffnen mit irreführenden Statistiken

Die Corona-Statistik des Saarlandes zählt zu den irreführendsten Statistiken, die ich bisher gesehen haben. Konkret geht es um diese Tabelle:

Relative Anteile von Altersgruppen an den Coronavirus-Infektionszahlen im Saarland

Hier die gleichen Daten als Balkendiagramm:

Anteil Infizierte nach Altersgruppen

Augenscheinlich sind Schulkinder – insbesondere Kita und Grundschüler – kaum betroffen, sondern stattdessen die Erwachsenen. Aber stimmt das wirklich? Schaut man sie die vom Autor gewählten Altersgruppen an, so fällt auf, dass sie stark unterschiedlich sind:Anzahl der Jahrgänge pro Altersbereich

Das ist zumindest sehr verdächtig: warum wählt man in dem Bereich der Grundschülern nur 4 Jahrgänge aus, während bei den Erwachsenen die Bereiche mehr als 6x so groß sind? Die Antwort darauf bekommt man, wenn man zuerst die Zahl der Menschen in den entsprechenden Altersgruppen bestimmt:

Anzahl der Menschen nach Altersgruppen

Aha! Das hat doch eine frappierende Ähnlichkeit mit den Werten aus der Statistik. Kann es sein dass hier jemand eine Vorgabe dafür hatte, wie die Werteverteilung aussehen sollten und entsprechend die Altersgruppierung gewählt hat? Machen wir es doch einmal rückgängig und berechnen, wie viele innerhalb einer Altersgruppe infiziert sind:

Inzidenz nach Altersgruppen

Und was sieht man: am stärksten infiziert sind Schüler, dicht gefolgt von ihren Eltern und den Kita-Kindern. Erst danach kommt mit einer deutlich geringeren Infektionszahl der Rest der Bevölkerung.

Warum wird immer noch behauptet, dass die Schulen kein Treiber der Pandemie sind? Warum werden im Saarland die Schulen wieder geöffnet? Und warum werden irreführende Statistiken veröffentlich, die den falschen Eindruck suggerieren sollen, dass Schüler kaum betroffen sind?

Zoom, WhatsApp, Steam, Minecraft, AnyDesk auf Raspberry Pi

Mit Raspbian werden bereits sehr viele Anwendungen für den Raspberry Pi mitgeliefert. Leider fehlen aus unterschiedlichen Gründen einige, wie beispielsweise

  • Zoom
  • WhatsApp
  • Steam
  • Minecraft
  • Anydesk

Glücklicherweise macht das Pi Apps Projekt diese und andere Apps einfach installierbar. Um es nutzen zu können, sind diese Schritte notwendig:

Zuerst muss man ein LXTerminal öffnen. Dieses findet man unter

Zubehör – LXTerminal

In diesem Terminal gibt man einzeln und nacheinander diese Befehle ein:

sudo apt install git
git clone https://github.com/Botspot/pi-apps
~/pi-apps/install

Hierbei muss man die Frage

„YAD is required but not installed. Install now?“

mit „Y“ beantworten. Danach hat man einen neuen Menueinträg

Zubehör – Pi Apps

Startet man diesen, kann man viele Apps bequem auswählen und installieren.

Homeschooling PC für 200€

Update: Mittlerweile gibt es auch ein schickes Komplettsystem. Wobei ich eine separate Tastatur bevorzuge.

Hier mein Vorschlag:

Homeschooling steht wieder an und für vieles benötigen die Kindern einen Rechner. Gut wenn man noch einen Laptop hat oder den PC jeden Tag für ein paar Stunden entbehren kann…oder mehrere, wenn man mehr als ein Kind hat. Wenn dies nicht der Fall ist, dann sorgt ein weiterer PC für deutliche Entspannung in der Wohnung. Leider muss man für einen einfachen Laptop schon 500€ anlegen und bei PCs + Monitor + Tastatur + Maus sieht es auch nicht besser aus.

Deshalb hier ein Vorschlag, wie man günstig an einen ausreichend schnellen PC kommt, der zudem

  • keine Viren bekommen kann
  • für Homebanking geeignet ist
  • immer aktuell bleibt
  • kaum Strom braucht
  • auf dem keine Shooter-Spiele laufen, so dass sich entsprechende Diskussionen erübrigen

Bestellung

Für den Homeschooling PC benötigt man:

macht zusammen 208,39€. Ein Fernseher hat den Vorteil, dass er günstiger als ein Monitor ist und das Bild trotzdem gleich aussieht – die Technologie ist die gleiche und HDMI ist digital. Bei diesem ist der Fuß recht klein, aber man kann einfach einen Kasten drunterstellen, in dem die Kinder auch die Tastatur und andere Dinge aufbewahren können.

Der andere Vorteil ist, dass es eben ein Fernseher ist und die Kinder auch schulisches Fernsehen schauen können – wenn es denn mal angeboten wird.

 

Zusammenbau

Das Raspberry Pi 4 Kit muss man noch zusammenschrauben. Geht in wenigen Minuten und ohne besondere Vorkenntnisse:

Zuerst klebt man die Kühlkörper auf die zugehörigen Chips. Manche Leute sind der Meinung, dass sie überflüssig sind, aber sie schaden auf jeden Fall nicht. Der Lüfter kommt in den Deckel und wird mit den 4 spitzen Schrauben festgeschraubt. Auch hier scheiden sich die Geister. Ohne Lüfter ist der PC vollkommen lautlos. Am besten einfach montieren und später dann ggf. den Strom abklemmen.

Die Platine wird mit den stumpfen Schrauben ins Gehäuse geschraubt. Hierbei sollte die SD-Karte noch nicht drin stecken, sonst bekommt man die Platine nicht so gut hinein. Vom Lüfter kommt das rote Kabel in der unteren Reihe ganz links rein und das schwarze in der oberen Reihe auf den 3. Pin von links. Ist in der beiliegenden Anleitung sehr gut und anschaulich mit Bildern erklärt.

Danach den Deckel zuklappen und schon ist die Montage erledigt. An der Seite die SD-Karte einstecken und zwar mit den PINs zur Platine hin – man kann es nicht falsch einstecken. Das HDMI-Monitor-Kabel am Raspberry in die linke Micro-HDMI Buchse stecken, den kleinen USB-Stecker der Tastatur in einen der schwarzen USB 2.0 Buchen und das USB-C Netzteil in die Power-Buchse (damit kann man übrigens auch neuere Handies laden). Bei Maus & Tastatur die Laschen an den Batteriefächern rausziehen, damit sie Strom haben. Monitor mit Strom versorgen und dann es schon losgehen!

Auf der Fernbedienung die Taste „SOURCE“ drücken und HDMI auswählen:

Wenn alles richtig verkabelt ist, meldet der Fernseher den Raspberry:

Für die richtige Darstellung ist es wichtig, das Seitenverhältnis des Fernsehers auf „PointToPoint“ zu stellen. Hierzu an der Fernbedienung die „ASPECT“ Taste so lange drücken, bis dies oben links erscheint:

Sollte man nach der Installation schwarze Ränder sehen, kann man es jederzeit in der „Raspberry-Pi-Konfiguration über die Option „Übertastung“ anpassen (wird erst durch einen Neustart aktiv):

Beim Starten sieht man, dass der PC ganze 4 CPU-Kerne hat – also nicht gerade wenige.

Dem entsprechend schnell erscheint der Desktop:

Standardmäßig ist leider alles auf Englisch, was sich aber gleich ändert. Es ist hierfür erforderlich, dass der PC mit dem Internet verbunden ist. Entweder über ein LAN-Kabel oder WLAN.

Zuerst das Land, Sprache und Tastaturbelegung auswählen:

Danach wird die gewählte Sprache automatisch heruntergeladen und ebenso die Software aktualisiert:

Es wird einige Minuten dauern, bis alle Updates installiert sind. Nicht wundern, wenn sich dabei der Balken nicht bewegt – das ist normal, da sehr große Updates dabei sind. Man braucht aber auch nicht nebendran zu sitzen. Sobald alles fertig ist, sieht man diesen Schirm:

und man sollte den „Restart“ auswählen, damit man endlich alles in seiner Wunschsprache hat:

Nach dem Neustart oben links auf den Browser gehen und Homeschooling genießen:

Online Schule Saar auf Raspberry Pi 4

Extras

Desktop Hintergrund

Damit ist die Installation eigentlich schon abgeschlossen. Wem das Hintergrundbild nicht gefällt, kann es so ändern: Rechtsklick auf den Desktop und zu den Desktop Einstellungen:

Dort dann ein anderes Bild oder „No Image“ (die Übersetzung ist leider nicht überall vollständig) für einen einfarbigen Hintergrund:

Meiner ist orange.

Drucker

Drucker unter Raspberry Pi

WhatsApp Web

WhatsApp Web auf dem Raspberry Pi

Zoom, WhatsApp & Co

Zoom, WhatsApp, Steam, Minecraft, AnyDesk auf Raspberry Pi

Handy Git tricks

Hier eine Sammlung von Tricks, die ich verwende, um mein Leben mit Git zu vereinfachen und eine saubere Histories zu haben, die es erleichtert, den Code zu verstehen. Was der Hauptgrund für eine Versionkontrolle ist, oder?

Zweig verschieben

Um einen Git Zweig zu einem anderen Zweig zu verschieben, benutzt man

git rebase --onto

Beispiel

git rebase --onto beta develop fix

Aktualisieren ohne Checkout

Um einen Zweig zu aktualisieren, ohne ihn vorher auszuchecken – beispielsweise um ein Rebase auf den Elternzweig zu machen – benutzt man

git fetch origin <Zweig>:<Zweig>

Man muss den Ziel-Zweig wirklich 2x angeben!

Beispiel

git fetch origin develop:develop

Geschichte umschreiben

Bevor ich meine Änderungen in merge, schreibe ich meine Historie um, so dass jeder Commit sauber ist und es keine „Änderungen nach einem Review“-Commits mehr gibt, denn sie helfen nicht beim Verstehen, warum der Code so ist, wie er ist..

git add .
git commit --fixup
git rebase -i --autosquash develop

Read more about this in the Git Book

Kenne die Version

Wir verwenden annotated tags, um jedes Release zu markieren. Außerdem mergen wir niemals zurück, sondern nur ältere Branches in neuere Branches. Dies erlaubt es uns auf einfache Weise die Versionsnummer basierend auf Tags zu bestimmen.

git describe --first-parent [Zweig]

Wenn man keinen Zweig angibt (oder Git hash o.ä.), bekommt man die Versionsnummer des aktuellen Zweiges.

Beispiel

git describe --first-parent beta

Pas de fichiers Word svp!

Et au lieu de fichiers Word, veuillez envoyer des fichiers PDF afin que tout le monde puisse les ouvrir facilement:

Avant Windows 10: https://www.linternaute.fr/hightech/guide-high-tech/1412973-comment-convertir-un-document-word-en-fichier-pdf/

Avec Windows 10: https://pdf.wondershare.com/fr/pdf-knowledge/print-to-pdf-on-windows-10.html

Avec MacOS: https://www.futura-sciences.com/tech/questions-reponses/mac-mac-creer-pdf-depuis-nimporte-document-482/

Avec Linux: http://thorpora.fr/imprimer-en-pdf-sous-linux/

Atomare Updates mit Android LiveData

Android’s LiveData is a valuable asset in wrinting easy to maintain software. It introduces a life-cycle for data updates that help to reduce boilerplate code and make user interfaces more flexible. Unfortunately LiveData only covers one data point. If you need more data in your UI, you’ll face the problem that you see each update separately and therefore have unnecessary updates of your UI. Here I’ll show you how to get rid of those by updating the UI with atomic updates.

Introduction: LiveData basics

LiveData follows the observer pattern which was described over 25 years ago: you have a mutable object and you get informed when this object changes. What makes this flexible is that there can be zero or many observers on the object. This allows loose coupling of the components of an application.

When it comes to data updates, concurrency is always something to pay attention to. On Android there is a special thread called the main or UI thread. All UI updates happen on it as well as the handling of all UI events like button presses. It is therefore imperative that you don’t block this thread for too long and it is prohibited to do network IO on it. LiveData notifies its observers on the main thread as well. It therefore offers two methods that are important for multi-threading:

  • setValue(x)
  • postValue(x)

The difference is that setValue may only be called on the main thread but changes the value immediately while postValue may be called on any thread and puts the update to the value into the queue on the main thread. This means that any changes via postValue do not take effect immediately but delayed. If you run this code:

liveData.setValue(1);
liveData.getValue();  // returns 1
liveData.postValue(2);
liveData.getValue();  // still returns 1!

then both calls to getValue will return 1 as the change to 2 has not happened yet on the second call.

If you run this code

liveData.postValue(2);
liveData.setValue(1);

then every observer will first get the value 1 and then the value 2.

What happens if you update it several times? This code (which can only run on the main thread)

liveData.setValue(1);
liveData.setValue(2);

will call each observer twice (immediately), first with 1 and then with 2, while this code (which can run on any thread)

liveData.postValue(1);
liveData.postValue(2);

will call the observers only once with the value 2 after everything is done on this call from the main thread.

The problem

Imagine you have a UI that shows two different items. Let’s say it is a map that has a location and a zoom level. Both of these values can change independently. They are also unrelated in the sense, that each can live without the other. You could model them into one object LocationAndZoom, but this would violate the single responsibility principle. So you would model them with two independent LiveData:

LiveData<Location> liveLocation;
LiveData<Zoom> liveZoom;

You want your UI to update whenever one of them changes, but you need both values to paint the map.

liveLocation.observe(this, t -> updateMap());
liveZoom.observe(this, s -> updateMap());

Now picture that after a search you want you to update both location and zoom level. So you would do

liveLocation.postValue(searchResult.getLocation());
liveZoom.postValue(ZOOM_CITY);

This results in two calls to updateMap(), one with stale and one with current data. As map redraws are expensive, we should optimize it!

The solution

So you want to get only one update no matter how many of your UI variables change at the same time. For this you need another LiveData with an object that consolidates all data that you need:

private static class LocationAndZoom {
    private final Location location;
    private final Zoom zoom;

    private LocationAndZoom(Location location, Zoom zoom) {
        this.location = location;
        this.zoom = zoom;
    }
}

This is not a violation of the single responsibility principle as a change to it is driven by the same stake holder („and now we need humidity as well in the UI“). Now we can define a LiveData that holds this LocationAndZoom. But how can we achieve that it only notifies its observers once even when there are multiple updates? We will use the fact that postValue postpones updates to the next run of the main thread. This is the idea:

1st run of the main thread

Both LiveData will be updated:

liveLocation.postValue(searchResult.getLocation());
liveZoom.postValue(ZOOM_CITY);

2nd run of the main thread

This results in two calls to the observeable which will update the consolidated LiveData:

// from liveLocation observer:
liveLocationAndZoom.postValue(new LocationAndZoom(...)); // <= here the zoom is stale

// from liveZoom observer
liveLocationAndZoom.postValue(new LocationAndZoom(...)); // <= here both are recent

Certainly not like this in the code, but you should get the point that there are two updates.

3rd run of the main thread

As both calls are postValue, only the last one wins and resulting in just one call to the observables of liveLocationAndZoomd with the recent value.

Implementation

The MediatorLiveData allows to change LiveData objects on the fly. This is used to change from the source changes to the consolidated change. To simplify the code use this class:

import androidx.annotation.NonNull;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MediatorLiveData;
import androidx.lifecycle.Observer;

public abstract class Merger<T> {
    private MediatorLiveData<T> liveMerged;

    private T currentMerged;

    protected abstract boolean isValid(final T merged);

    public synchronized LiveData<T> getMerged() {
        if (null == liveMerged) {
             liveMerged = new MediatorLiveData<>();
             init();
        }
        return liveMerged;
    }

    protected abstract void init();

    protected <S> void addSource(
            @NonNull LiveData<S> source, @NonNull Observer<? super S> observer) {
        liveMerged.addSource(source, observer);
    }

    protected T getCurrentMerged() {
        return currentMerged;
    }

    protected void updateCurrent(T newMerged) {
        currentMerged = newMerged;
        if (isValid(currentMerged)) {
            liveMerged.postValue(currentMerged);
        }
    }
}

now you can implement the consolidates LiveData like this:

private static class LocationAndZoomMerger extends Merger<LocationAndZoom> {
    private final LiveData<Location> liveLocation;
    private final LiveData<Zoom> liveZoom;

    private LocationAndZoomMerger(final LiveData<Location> liveLocation,
                                  final LiveData<Zoom> liveZoom) {
        this.liveLocation = liveLocation;
        this.liveZoom = liveZoom;
    }

    @Override
    protected void init() {
        updateCurrent(new LocationAndZoom(null, null));
        addSource(
            liveLocation,
            l -> updateCurrent(new LocationAndZoom(l, getCurrentMerged().zoom)));
        addSource(
            liveZoom,
            z -> updateCurrent(new LocationAndZoom(getCurrentMerged().location, z)));
    }

    @Override
    protected boolean isValid(final LocationAndZoom locationAndZoom) {
        return null != locationAndZoom.location && null != locationAndZoom.zoom;
    }
}

Finally observe the merged LiveData and enjoy atomic updates:

new LocationAndZoomMerger(liveLocation, liveZoom)
        .getMerged()
        .observe(this, m -> Log.e("Atomic", "update"));

 

Zuschuss-Karte bei Ölmagnat

Zuschuss-Karte von Öl-MagnatAls Kind haben wir immer Ölmagnat gespielt. Schon damals hat uns geärgert, dass in der Anleitung die „Zuschuss $ 500“-Karte nicht erklärt war. In der Tat findet man in der Anleitung lediglich den Halbsatz, dass die Karte bedeutet, dass „eine Öl-Ausschöpfungsgenehmigung erteilt wird“. Jedoch wird nirgendwo erklärt, was dies bedeutet.

Glücklicherweise gibt es die Original-Anleitung von KingOil von Hasbro. Hier heißt es: „The oil depletion allowance card of $500 is a payment of $500 without regard to the number of derricks a player has erected.“ auf Deutsch: „Die Öl-Ausschöpfungsgenehmigungskarte über $500 ist eine Auszahlung von $500, umabhängig davon, wie viele Bohrtürme ein Spieler errichtet hat.“

Dies bedeutet, dass der Spieler der diese Karte zieht, immer $500 erhält, egal wie viele Bohrtürme er hat.

Damit ist auch klar, dass wir das damals immer falsch gespielt hatten, denn bei uns musste der Spieler die $500 bezahlen.

Bonus:

Bohrturm von Öl-Magnat Wisst ihr, warum der Bohrturm unterschiedliche Farben hat? In der US-Version wurden die Kosten einer erfolgreichen Bohrung nicht erwürfelt, sondern hingen davon ab, wie tief der Bohrturm in das Spielfeld eindrang:

  • war er ganz drin, so war das Bohrloch trocken, also genau umgekehrt, wie bei der deutschen Variante, wo dies bedeutet, dass man Öl gefunden hat
  • ist er eine Stufe draußen (rot), so muss der Spieler $6.000 bezahlen
  • sind zwei eine Stufe draußen (gelb), so muss der Spieler $4.000 bezahlen
  • sind alle 3 Stufe draußen (blau), so muss der Spieler $2.000 bezahlen

 

Runtastic mit Android Wear OS 1.0

Ich besitze noch die beiden ersten Uhren, die es mit Android Wear OS gab, eine Samsung Gear Live und eine LG G Watch. Seit Jahren verwende ich beim Laufen Runtastic, damit ich Puls, Geschwindigkeit und Strecke automatisch aufzeichnen kann. Mit den Uhren läuft es sich einfacher, da man den Puls einfach am Handgelenk ablesen kann, statt umständlich auf ein Handy zu schauen.

Leider unterstützt Runtastic mittlerweile nur noch Wear OS ab Version 2.0, was es für die Samsung Gear Live gar und für die LG G Watch immerhin bei XDA gibt. Eine Anfrage beim Runtastic Support ergab leider auch nur ein „kauf dir eine neue Uhr“. Aber zum Glück gibt es das Internet, in dem man alte Versionen von Android Apps finden kann. Wenn man nach Runtastic Running App Run Mileage Tracker_v8.11.2_apkpure.com.apk oder Runtastic PRO-v8.11.2_build_201812173.apk sucht, dann findet man die richtige alte Version. Hierbau auf jeden Fall die MD5-Prüfsumme vergleichen, damit man sich keinen Virus einfängt:

e752f2325264be4e965970e98e12b258 Runtastic-v8.11.2.apk
986e5012292bab985c5748a24ca8293c Runtastic PRO-v8.11.2.apk

Runtastic Update deaktivierenDamit die alte Versionauch dauerhaft bestehen bleibt, muss man in der Play Store App die automatischen Updates deaktiveren. Hierzu einfach auf die Runtastic App auswählen, dann oben rechts auf die drei Punkte tippen und den Haken bei „Autom. Update an“ entfernen.