Welcome! :-)

This is both a wiki (a website editable by all) and a blog (an online diary about the stuff Alex Schroeder reads and does). If you’re a friend or relative, you might be interested in reading Life instead of this page. If you’ve come here from an RPG blog, you might want to head over to RPG. There are other similar categories to be found on the SiteMap.

Für Rollenspieler gibt es ebenfalls eine eigene RSP Kategorie.

2016-07-27 OSR and DIY D&D

Ben Milton recently asked about the difference between the OSR and DIY D&D on Google+.

OSR is about going back to the old games and exploring avenues not taken at the time. In terms of products, this meant republishing rules compatible with the old games and adventures looking like the old modules. As time went by, the OSR developed new settings, new ways of presenting setting materials, rules that where still compatible but included many house rules, or rules that were incompatible but still recognizably derived from the old rules. This latest development is what I call DIY D&D. So for me, DIY D&D is a subset of the OSR.

The market being so small, all of this was driven by very small teams of people and facilitated by POD. I’m not convinced that words such as independent and anti-establishment mean so much in this context. If a writer, two or three artists, an editor, a layout person and a publisher make a book, is it all that different from how Paizo and WotC work? Are their teams so much different? It would seem to me that their product is simply more opinionated, less designed to reach the widest audience possible. As such, I also see DIY D&D as an aesthetic movement. In way, pushing the hardest down “avenues not taken at the time”.

Zak also left a comment: “DIY D&D is a term I invented because I hate a lot of old stuff but I liked the bloggers who talked about it and their garage-rock house rules approach.”


Add Comment

2016-07-25 IP number range to regular expression

I can’t believe how long it took me to develop a little script to do the following: given an IP number, look up the IP number network range this particular number falls in and create a regular expression that matches all of them.


  1. the IP number of a spam wiki edit is
  2. whois says the range is -
  3. the regular expression we want is ^45\.58\.42\.

Harder example:

  1. the IP number of a spam wiki edit is
  2. whois says the range is -
  3. the regular expression we want is ^46\.101\.([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-7])$

It took me way too long to get at the following solution written in Perl 5. Let me know, if you have a more elegant solution. It doesn’t have to be written in Perl.

The script I wrote is called with an IP number or the string “test” to run its tests.


#!/usr/bin/env perl

use Modern::Perl;
use Net::Whois::IP qw(whoisip_query);
use Test::More;

die "Usage: banned-host-regexp (IP-NUMBER|test)\n" unless $ARGV[0];

sub get_range {
  my $ip = shift;
  my $response = whoisip_query($ip);
  my ($start, $end);
  my $ip_regexp = '(?:[0-9]{1,3}\.){3}[0-9]{1,3}';
  for (sort keys(%{$response})) {
    if (($start, $end)
	= $response->{$_} =~ /($ip_regexp) *- *($ip_regexp)/) {
  die "Did not find an IP range in the response:\n"
      . join("\n", map { "$_: $response->{$_}" } sort keys(%{$response}))
      . "\n" unless $start and $end;
  return $start, $end;

sub get_groups {
  my ($from, $to) = @_;
  my @groups;
  if ($from < 10) {
    my $to = $to >= 10 ? 9 : $to;
    push(@groups, [$from, $to]);
    $from = $to + 1;
  while ($from < $to) {
    my $to = int($from/100) < int($to/100) ? $from + 99 - $from % 100 : $to;
    if ($from % 10) {
      push(@groups, [$from, $from + 9 - $from % 10]);
      $from += 10 - $from % 10;
    if (int($from/10) < int($to/10)) {
      if ($to % 10 == 9) {
	push(@groups, [$from, $to]);
	$from = 1 + $to;
      } else {
	push(@groups, [$from, $to - 1 - $to % 10]);
	$from = $to - $to % 10;
    } else {
      push(@groups, [$from - $from % 10, $to]);
    if ($to % 10 != 9) {
      push(@groups, [$from, $to]);
      $from = 1 + $to; # jump from 99 to 100
  return \@groups;

if ($ARGV[0] eq 'test') {
  is_deeply(get_groups('2', '5'), [[2, 5]], "2-5");
  is_deeply(get_groups('9', '15'), [[9, 9], [10, 15]], "9-15");
  # diag explain get_groups('80', '90');
  is_deeply(get_groups('80', '90'), [[80, 89], [90, 90]], "80-90");
  is_deeply(get_groups('85', '99'), [[85, 89], [90, 99]], "85-99");
  # diag explain get_groups('80', '110');
  is_deeply(get_groups('80', '110'), [[80, 99], [100, 109], [110, 110]], "80-110");
  # diag explain get_groups('0', '127');
  is_deeply(get_groups('0', '127'), [[0, 9], [10, 99], [100, 119], [120, 127]], "0-127");
  # diag explain get_groups('0', '255');
  is_deeply(get_groups('0', '255'), [[0, 9], [10, 99], [100, 199], [200, 249], [250, 255]], "0-255");

sub get_regexp_range {
  my @chars;
  for my $group (@{get_groups(@_)}) {
    my ($from, $to) = @$group;
    my $char;
    for (my $i = length($from); $i >= 1; $i--) {
      if (substr($from, - $i, 1) eq substr($to, - $i, 1)) {
	$char .= substr($from, - $i, 1);
      } else {
	$char .= '[' . substr($from, - $i, 1) . '-' . substr($to, - $i, 1). ']';
    push(@chars, $char);
  return join('|', @chars);

if ($ARGV[0] eq 'test') {
  is(get_regexp_range('2', '2'), '2', "2-2");
  is(get_regexp_range('2', '5'), '[2-5]', "2-5");
  is(get_regexp_range('2', '15'), '[2-9]|1[0-5]', "2-15");
  is(get_regexp_range('9', '15'), '9|1[0-5]', "9-15");
  is(get_regexp_range('2', '20'), '[2-9]|1[0-9]|20', "2-20");
  is(get_regexp_range('2', '25'), '[2-9]|1[0-9]|2[0-5]', "2-25");
  is(get_regexp_range('2', '35'), '[2-9]|[1-2][0-9]|3[0-5]', "2-35");
  is(get_regexp_range('80', '99'), '[8-9][0-9]', "80-99");
  is(get_regexp_range('85', '99'), '8[5-9]|9[0-9]', "85-99");
  is(get_regexp_range('80', '110'), '[8-9][0-9]|10[0-9]|110', "80-110");
  is(get_regexp_range('0', '127'), '[0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-7]', "0-127");

sub get_regexp_ip {
  my ($from, $to) = @_;
  my @start = split(/\./, $from);
  my @end = split(/\./, $to);
  my $regexp = "^";
  for my $i (0 .. 3) {
    if ($start[$i] eq $end[$i]) {
      $regexp .= $start[$i];
    } elsif ($start[$i] eq '0' and $end[$i] eq '255') {
    } elsif ($start[$i + 1] > 0) {
      $regexp .= '(' . $start[$i] . '\.('
	  . get_regexp_range($start[$i + 1], '255') . ')|'
	  . get_regexp_range($start[$i] + 1, $end[$i + 1]) . ')';
      $regexp .= '\.';
    } else {
      $regexp .= '(' . get_regexp_range($start[$i], $end[$i]) . ')';
      $regexp .= $i < 3 ? '\.' : '$';
    $regexp .= '\.' if $i < 3;
  return $regexp;

if ($ARGV[0] eq 'test') {
  is(get_regexp_ip('', ''),
     ' -');
  is(get_regexp_ip('', ''),
     ' -');

if ($ARGV[0] eq 'test') {
  exit 0;

print get_regexp_ip(get_range($ARGV[0])) . "\n";


Comments on 2016-07-25 IP number range to regular expression

Hi Alex, love your blog.

Why not turn it to an integer and do the math as if it were a subnet? I’m pretty sure the output from whois will always be a subnet range.

If you don’t want to roll your own, http://search.cpan.org/~luismunoz/NetAddr-IP-4.007/IP.pm. The trick is to turn the range form into a CIDR form.

Ben Bennett 2016-07-26 11:12 UTC

Thanks! Yeah, I thought about that and truly, it would have been so much easier. I guess I was lured along by two things:

  1. the current solution to banning IP numbers by Oddmuse wiki involves regular expressions because in the old days, it used to resolve IP numbers and get hostnames – and regular expressions worked well enough for host names but in recent years we’re more concerned with dedicated spammers instead of lazy vandals and as it turns out, regular expressions are a lousy way to match IP numbers using decimal dot notation
  2. when I realized how tricky it turned out to be, I started thinking of it as personal challenge – which is stupid but there you go :)

What I should do is change how BannedHosts is parsed and if the regular expression is in fact a CIDR, then I should use math instead of regular expression to check IP numbers.

– Alex Schroeder 2016-07-26 13:07 UTC

+1 for no regular expressions in BannedHosts, but don’t forget about ipv6… :)

– AlexDaniel 2016-07-26 21:55 UTC

Add Comment

2016-07-22 New Server

Welcome to the new server! A year later and I’ve finally made the move to Debian Jessie.

My notes: Sibirocobombus.


Add Comment

2016-07-18 w3m and Emacs Wiki

w3m is a text browser. I use it a lot if I need to test stuff. Recently, I got an email saying that Emacs Wiki had an invalid certificate. Everything seemed to be fine, however. But a few minutes ago, I tried to run w3m https://www.emacswiki.org/ from my Terminal (OSX El Capitan) and got an SSL error.

alex@Megabombus:~$ w3m -dump_extra https://www.emacswiki.org/
SSL error: error:00000000:lib(0):func(0):reason(0)
w3m: Can't load https://www.emacswiki.org/.

Thanks to twb on #emacs, I found the problem. He said: “What’s ldd called on OS X? Find out if w3m is using gnutls or openssl.” The equivalent to ldd on OSX is otool -L.

As it turns out, a simple reinstall switched from libssl 0.9.8 to 1.0.0 and now it works.

alex@Megabombus:~$ otool -L `which w3m`
	/usr/local/lib/libgc.1.dylib (compatibility version 2.0.0, current version 2.3.0)
	/usr/lib/libssl.0.9.8.dylib (compatibility version 0.9.8, current version 50.0.0)
	/usr/lib/libcrypto.0.9.8.dylib (compatibility version 0.9.8, current version 50.0.0)
	/usr/lib/libncurses.5.4.dylib (compatibility version 5.4.0, current version 5.4.0)
	/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1197.1.1)
alex@Megabombus:~$ brew reinstall w3m
==> Reinstalling w3m
==> Downloading https://homebrew.bintray.com/bottles/w3m-0.5.3.el_capitan.bottle.tar.gz
######################################################################## 100,0%
==> Pouring w3m-0.5.3.el_capitan.bottle.tar.gz
🍺  /usr/local/Cellar/w3m/0.5.3: 23 files, 1.7M
alex@Megabombus:~$ otool -L `which w3m`
	/usr/local/opt/bdw-gc/lib/libgc.1.dylib (compatibility version 2.0.0, current version 2.3.0)
	/usr/local/opt/openssl/lib/libssl.1.0.0.dylib (compatibility version 1.0.0, current version 1.0.0)
	/usr/local/opt/openssl/lib/libcrypto.1.0.0.dylib (compatibility version 1.0.0, current version 1.0.0)
	/usr/lib/libncurses.5.4.dylib (compatibility version 5.4.0, current version 5.4.0)
	/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1225.1.1)


Add Comment

2016-07-18 Bretagne

We spent a week in Roscoff, Bretagne. It was the week of Bastille day, of the arrival of the Tour de France à la voile, with the main season just about to start. The weather was beautiful, the water was cold, the food was delicious and Claudia’s insomnia was gone. That surprised both of us. The very first day we were back in Switzerland, it raised it’s ugly head again: one and a half hours of waiting for sleep from Saturday to Sunday, then no sleep until five in the morning from Sunday to Monday. We’re about ready to call for dowsing rods and dowsing pendulums. Grrr!

Here are some pictures of our trip:


Add Comment

2016-07-08 Saas Fee

A few days ago we were in the Wallis. We visited Saas Fee:

In the local museum, I saw this old map: Vallesia superior, ac inferior, Wallis, le Valais, by Gabriel Walser from 1768. You can download a 6208×5151 pixel image.


Add Comment

2016-07-08 Rheinfall

A while ago we visited the Rhine Falls.


Comments on 2016-07-08 Rheinfall

Wow, Schwarzweiss, gut! Ich versuche es immer wieder, hatte bisher aber keinen Erfolg.

Chris 2016-07-09 12:52 UTC

Hah, ist leider etwas billig: das ist ein Filter der Kamera, inklusive dramatischem Kontrast und Rahmen. :)

– AlexSchroeder 2016-07-09 15:36 UTC

Das ist doch super; mir ist alles lieb, was mir hilft bessere Bilder zu machen. Ich brauche Lightroom, das hat auch Schwarzweissfilter, aber irgendwie…

Chris 2016-07-10 10:53 UTC

Du hast recht. Auf dem iPhone hatte ich eine Weile ein Photo App namens Vint B&W MII, die Bilder nach einem eigenen Algorithmus entfärbte und bessere Resultate lieferte als ein einfaches Entfärben im Gimp oder iPhoto. Offensichtlich gibt es da eine Kunst zum Thema. Aber worauf man schauen muss, weiss ich leider auch nicht. Früher hatte schwarz-weiss Film ja eine Affinität für rot.

Jetzt habe ich mir gerade die Tutorial-Seite für schwarze-weiss Bilder mit Gimp angeschaut. Da gibt es wirklich viel zum Ausprobieren.

– AlexSchroeder 2016-07-10 18:37 UTC

Add Comment

2016-07-08 Perlbrew With Threads

I want to run fgallery on my laptop running OSX instead of uploading the images and running fgallery on the server in order to generate the album. Perhaps that’s stupid. Anyway, the first order of business is to compile a version of Perl that has threads.

perlbrew install-multiple --both thread perl-5.22.0

Let’s see…

alex@Megabombus:~/src/fgallery-1.8.2$ perlbrew exec --with perl-5.22.0-thread-multi ./fgallery
Can't locate Image/ExifTool.pm in @INC (you may need to install the Image::ExifTool module) (@INC contains: /Users/alex/perl5/perlbrew/perls/perl-5.22.0-thread-multi/lib/site_perl/5.22.0/darwin-thread-multi-2level /Users/alex/perl5/perlbrew/perls/perl-5.22.0-thread-multi/lib/site_perl/5.22.0 /Users/alex/perl5/perlbrew/perls/perl-5.22.0-thread-multi/lib/5.22.0/darwin-thread-multi-2level /Users/alex/perl5/perlbrew/perls/perl-5.22.0-thread-multi/lib/5.22.0 .) at ./fgallery line 27.

OK, so now for dependencies, which have to be reinstalled.

alex@Megabombus:~/src/fgallery-1.8.2$ perlbrew use perl-5.22.0-thread-multi
alex@Megabombus:~/src/fgallery-1.8.2$ cpanm Image::ExifTool
--> Working on Image::ExifTool
Fetching http://www.cpan.org/authors/id/E/EX/EXIFTOOL/Image-ExifTool-10.20.tar.gz ... OK
Configuring Image-ExifTool-10.20 ... OK
Building and testing Image-ExifTool-10.20 ... OK
Successfully installed Image-ExifTool-10.20
1 distribution installed

I also got myself a copy of the facedetect. When I tried it:

alex@Megabombus:~/src/facedetect-master$ ./facedetect 
Traceback (most recent call last):
  File "./facedetect", line 8, in <module>
    import numpy as np
ImportError: No module named numpy

OK, the page mentions opencv and apparently Homebrew offers something like it.

alex@Megabombus:~/src/fgallery-1.8.2$ brew install homebrew/science/opencv
alex@Megabombus:~/src/fgallery-1.8.2$ ../facedetect-master/facedetect 
usage: facedetect [-h] [--biggest] [--best] [-c] [--data-dir DIRECTORY] [-q]
                  [-s FILE] [--search-threshold PERCENT] [-o OUTPUT] [-d]
facedetect: error: too few arguments

I guess it works!

OK, so back to my “default” Perl and let’s check that command line.

alex@Megabombus:/Users/Shared/Fotos 2016$ perlbrew exec --with perl-5.22.0-thread-multi ~/src/fgallery-1.8.2/fgallery -f Saas\ Fee/Album/ 2016-saas-fee
error: cannot execute exiftran or exifautotran for lossless JPEG autorotation

OK, so more problems. Let’s install fbida.

alex@Megabombus:/Users/Shared/Fotos 2016$ brew install fbida
alex@Megabombus:/Users/Shared/Fotos 2016$ perlbrew exec --with perl-5.22.0-thread-multi ~/src/fgallery-1.8.2/fgallery -f Saas\ Fee/Album/ 2016-saas-fee
reading completed        
facedetect: error: cannot load HAAR_FRONTALFACE_ALT2 from /usr/share/opencv/haarcascades/haarcascade_frontalface_alt2.xml
error: command "facedetect --best --center 2016-saas-fee/imgs/P7020100.jpg" failed

OK, some data is missing. I downloaded a recent copy of the version 2 zip file and extracted just the data:

alex@Megabombus:~/src$ unzip /Users/alex/Downloads/opencv-2.4.13.zip "opencv-2.4.13/data*"

And then I tried again:

alex@Megabombus:/Users/Shared/Fotos 2016$ facedetect --data-dir /Users/alex/src/opencv-2.4.13/data/ --best --center 2016-saas-fee/imgs/P7020100.jpg

And it worked! (No output because it contains no face, I presume.)

So how can we pass this option from fgallery to facedetect? I think the simplest solution is to just change the DATA_DIR setting in facedetect:

alex@Megabombus:~$ diff ~/src/facedetect-master/facedetect bin/facedetect 
< DATA_DIR = '/usr/share/opencv/'
> DATA_DIR = '/Users/alex/src/opencv-2.4.13/data/'

Oh, and I dedided to install the optional stuff as well.

alex@Megabombus:/Users/Shared/Fotos 2016$ brew install jpegoptim
alex@Megabombus:/Users/Shared/Fotos 2016$ brew install pngcrush
alex@Megabombus:/Users/Shared/Fotos 2016$ brew install p7zip
alex@Megabombus:/Users/Shared/Fotos 2016$ perlbrew exec --with perl-5.22.0-thread-multi cpanm Cpanel::JSON::XS

And now, finally, the test:

alex@Megabombus:/Users/Shared/Fotos 2016$ perlbrew exec --with perl-5.22.0-thread-multi ~/src/fgallery-1.8.2/fgallery -f Saas\ Fee/Album/ 2016-saas-fee
reading completed        
processing completed        
generating archive...

Yay! Hours wasted but mission accomplished! :)

Copy it over:

alex@Megabombus:/Users/Shared/Fotos 2016$ scp -r 2016-saas-fee/ alexschroeder.ch:alexschroeder.ch/gallery/

As it turns out, I dislike the stupid exif comments my camera leaves and sometimes I create little text files with alternate captions. Thus, I need the -c txt option. And then to copy it all over to the gallery.

alex@Megabombus:/Users/Shared/Fotos 2016$ perlbrew exec --with perl-5.22.0-thread-multi ~/src/fgallery-1.8.2/fgallery -c txt -f Saas\ Fee/Album/ 2016-saas-fee
alex@Megabombus:/Users/Shared/Fotos 2016$ rsync --progress --recursive 2016-saas-fee alexschroeder.ch:alexschroeder.ch/gallery/


Add Comment

2016-07-07 Unprofessional

An excellent article by Zak, Stay Unprofessional. I laughed, when I came to the following paragraph on alienation.

In short: it’s possible for even indie RPG authors to be so professional they aren’t making anything they want to (or have to) live with. In a curious quirk of early 21st century post-hobby production, they have alienated themselves from their labor, with almost no help from the larger capitalist system.

How true.

It’s why I’m only doing the stuff I want for my own games. The amount of effort I put into the Caverns of Slime for Fight On! #15 which never got published showed me that every step of the process has to be enjoyable. If it is not, if it is premised on some later reward and recognition, what happens when the reward does not manifest? Time wasted, that’s what.


Add Comment

2016-07-07 Preparing a Sandbox

Recently, Rob Monroe asked about tools to use when preparing a sandbox.

I listed the following:

  1. A map. I started my last one with a random Text Mapper map using the Hex Based Campaign Design blog posts by the Welsh Piper.[1][2]
  2. I also enjoy +Kevin Crawford’s system of assigning two tags to each hex as described in his book Red Tide, and of establishing a cabinet of evil movers and shakers right at the beginning as described in his book An Echo Resounding.

Alternatively, I could also imagine running my next game as a point crawl based on the structured input of my players as suggested by +Jason Lutes in The Perilous Wilds.

Jason Lutes then linked to two examples of how he did it in 2013, which are also based on blog posts by the Welsh Piper, random generators by Chaotic Shiny, and all that, in order to create the world and a village.

It looks like a lot of work but it looks super beautiful!

I still think that starting out with a random Text Mapper Map and adding notes and settlements to the text describing the area and using the tool to render a picture of the area still is the best value for my time. See How To Get Started With Text Mapper for more.

Tags: s

Add Comment



Please make sure you contribute only your own work, or work licensed under the GNU Free Documentation License. See Info for text formatting rules. You can edit this page if you need to fix typos. You can subscribe to updates by email without leaving a comment.

To save this page you must answer this question:

Please say HELLO.

Referrers: Gothridge Manor Planet Emacsen The Insidious Big Brother DataBase / Mailing Lists The City of Iron: Dolmenwood: Thinking About Random Encounter Tables