Close

Log 2: But Wait, There's More!

A project log for Tetris Display

When the world gives you a random 8x32 WS2812 LED display, you make ... Tetris!

Jorj BauerJorj Bauer 06/23/2019 at 19:540 Comments

The basic design of the software to this point was to do everything via the web server embedded in the ESP-01 (in the Arduino libraries for the ESP8266). Unfortunately, this has a lot of overhead: every connection takes time, and all of the latency adds up to a terrible playing experience. ("Right-right-I PUSHED RIGHT WHY IS IT NOT MOVING RIGHT right right OH TOO FAR LEFT LEFT ... crap".)

The second version listens on a TCP socket for a connection. I wrote a quick Perl script to connect to it and send commands:

#!/usr/bin/perl

use strict;
use Curses;
use IO::Socket::INET;

use Data::Dumper;

$| = 1;
$SIG{'INT'} = 'finishup';


my $destination = shift || die "No destination IP:port given";

unless ($destination =~ /\:/) {
    $destination .= ':8267';
}

my ($socket,$data);
$socket = IO::Socket::INET->new ( PeerHost => '10.0.0.231',
				  PeerPort => 8267,
				  Proto => 'tcp',
				  Blocking => 0 )
    || die "Error creating socket: $!";

initscr();
raw();
noecho();

while (1) {
    my $c = getch();
    if ($c) {
	if (($c == 3) or ($c eq '=')) {
	    endwin();
	    exit(0);
	}

	$socket->send($c);
    }

    my $data;
    $socket->recv($data, 1024);
    if (length($data)) {
	print Dumper ($data);
    }
}

exit 0;

sub finishup {
    endwin();
    exit(0);
}

This works *most* of the time. Until the ESP (or laptop) can't quite reach the WiFi access point, and a packet needs to be retransmitted. So, in the third version, we do away with the overhead of the TCP connection itself - resorting on the default user behavior of "it doesn't look like it moved, so I'm just going to press the button again" by dropping down to UDP.

The perl script looks almost identical:

#!/usr/bin/perl

use strict;
use Curses;
use IO::Socket::INET;

use Data::Dumper;

$| = 1;
$SIG{'INT'} = 'finishup';


my $destination = shift || die "No destination IP:port given";

unless ($destination =~ /\:/) {
    $destination .= ':8267';
}

my ($socket,$data);
$socket = IO::Socket::INET->new ( PeerHost => '10.0.0.231',
				  PeerPort => 8267,
				  Proto => 'tcp',
				  Blocking => 0 )
    || die "Error creating socket: $!";

initscr();
raw();
noecho();

while (1) {
    my $c = getch();
    if ($c) {
	if (($c == 3) or ($c eq '=')) {
	    endwin();
	    exit(0);
	}

	$socket->send($c);
    }

    my $data;
    $socket->recv($data, 1024);
    if (length($data)) {
	print Dumper ($data);
    }
}

exit 0;

sub finishup {
    endwin();
    exit(0);
}
candor:display jorj$ cat input-udp.pl 
candor:display jorj$ cat input-udp.pl 
#!/usr/bin/perl

use strict;
use Curses;
use IO::Socket::INET;

$| = 1;
$SIG{'INT'} = 'finishup';


my $destination = shift || die "No destination IP:port given";

unless ($destination =~ /\:/) {
    $destination .= ':8267';
}

my ($socket,$data);
$socket = IO::Socket::INET->new ( PeerAddr => $destination,
				  Proto => 'udp' )
    || die "Error creating socket: $!";

initscr();
raw();
noecho();

while (1) {
    my $c = getch();
    if ($c) {
	if (($c == 3) or ($c eq '=')) {
	    endwin();
	    exit(0);
	}

	$socket->send($c);
    }
}

exit 0;

sub finishup {
    endwin();
    exit(0);
}

And, with this finally sorting out the network issues, I built a quick remote:

Four buttons left over from other projects (two sent by a friend while prototyping Aiie, thanks Jennifer!); one $5 lithium-ion battery charger with +5v output; a 3.3v linear regulator; an on/off switch; one ESP-01; 3 resistors; a proto-board lying around; and a 1000mAh 3.7v lithium-ion battery I'd also bought for another project years ago.

 The remote connects to WiFi and uses multicast DNS to find the IP address of the Tetris Display. When you press a button, it opens a TCP connection; as long as that connection is open, the Display keeps "playing" tetris (the pieces move down by themselves). When you press buttons from here on, each button press is one UDP packet sent to the display.

This is the point where I started hearing "the pieces are too random" and "but I can't do new-fangled super-rotation so it's broken". I suppose this is why we keep the customer in the loop early and get feedback often ... sigh. A little coding to butcher what used to be a straightforward and simple game, and then everyone's happy!

Well, mostly.

The side-scrolling-text display is still in there, but it's not doing much. You can play Tetris, which I suppose is nice, but it's not something we do all the time. The truth is that, after a weekend of fun, this thing wound up plugged in sucking up power for two months doing  ... nothing at all. Which seems like a waste. But sometimes you need to let things percolate for a while, and eventually inspiration strikes.

And it finally did, in the form of this Hackaday blog article about a Tetris Clock.

When I saw the pieces falling there, I realized that the biggest mental obstacle with this project is its aspect ratio. The vertical portrait mode makes the display ... awkward. I can't figure out how to elegantly display anything. But with pieces dropping, I can see how this would work - as long as we can make everything out of Tetris tetrominoes, then Tetris itself becomes the vehicle for display. Which just means that we have to design a font out of Tetris pieces.

This was the first pass at making a font:

Which looks something like this:

(The left and right columns are debugging data, telling me what it's doing old-school 1-bit style.) The color of the numbers turned out to be distracting, and on the advice of my eldest (who is actually a professional UX designer and knows a little something) I made them all one color. Then he designed a second font that was only 3 pixels wide, with variable height (where mine was either 4 or 5 pixels wide, but always 5 pixels tall).

At which point, I realized they're narrow enough to show two across:

Nice.

All of that brings us to some action of what it's doing today:

Discussions