Recent Blogposts of Programming | Page 2

Arduino 1602 Snake Game - Snake Game on Alphanumeric LCD!

I'm thinking about making an AVR (non-Arduino) portable game console. I'm evaluating the type of display to be used (including multiple 8x8 LED matrix, graphical LCD, TFT/OLED screen, alphanumeric LCD and combination of them). Then I came up with a weird idea. What if I use a alphanumeric LCD as a graphical LCD? That'd cut me quite a bit of the cost compared with using graphical LCD of the same physical dimension.

I'm a bit bored today. I feel like tinkering around with Arduino and 1602 LCD that has been around in my home. Here's what I've got as a result of hours of boredom. An Arduino-based 1602 Snake Game:

Source Code

Check out the source code in this github repository!

Gameplay

  • Just like other snake games.
  • Controlled with two push buttons. One for turning clockwise, another for turning counterclockwise
  • The length of the snake starts at 4
  • Game field: 16x4 (yes, I split each character into two rows so that you get 16x4 instead of 16x2)
  • The game gets approximately 1.1x faster whenever an apple is eaten.
  • There's no wraping. You die if you hit the wall.
  • If the snake had grown enough to fill the entire game field, you win

Hardware List

The hardware is roughly based on this official Arduino LCD Hello World tutorial, with an addition of two push buttons.

  • Arduino Uno
  • Current-limiting resistor for LCD backlight
  • Potentiometer for LCD contrast adjustment
  • HD44780-compatible 1602 LCD
  • Two push buttons
  • Two pull-down resistors for the push buttons
  • Breadboard or PCB or whatever similar to connect everything above properly

The left push button is connected to D8, while the right push button is connected to D9. Both push buttons are pulled-down. For other connections, please refer to the schematics in the tutorial.

Turning an Alphanumeric LCD into a Graphical LCD

HD44780-compatible LCD driver supports up to 8 custom characters. By carefully defining those 8 characters, it's possible to subdivide each character into multiple "pixels". That can effectively turn the alphanumeric display into a graphical LCD display.

My design subdivides each character two rows. Each row on the character can be either empty, snake, or apple as shown below:

Custom characters defined in HD44780

There're two "pixels" in each character. Each pixel can have three possible values. Therefore, the total combination is 3^2 = 9. Since one of these combination is visually empty, a space character were used to represent that. At the end only 8 custom characters are needed. So the 8 available characters in CGRAM of HD44780 are just enough for our purpose.

To reduce RAM usage, each pixel is represented by 2 bits. So a byte can store 4 pixels. Everything is cramped into a uint8_t graphicRam[GRAPHIC_WIDTH*2/8][GRAPHIC_HEIGHT]. At width of 16 and height of 4, only 16 bytes of RAM are taken for the graphic! Had I used a uint8_t for each pixel, 64 bytes of RAM would be required.

After the completion of this project, I've found other designs like spliting each character into three rows, or try making use of all pixels by generating the CGRAM on-the-fly. I'll consider using these techniques for my future projects.

Randomization of the Apple Position

To make the position looks random, we need to somehow seed the random number generator. For computer programs, we usually seed it with the current time of the machine. However, this couldn't be done on Arduino because it doesn't have a real time clock.

My solution is to make a menu screen of the game. When the user start the game, the time of the moment that the user pressed the button is used to seed the random number generator. The micros() method of Arduino Time library were used. This has the equivalent effect of using system time.

Funny Failure: Bug Went Unnoticed for Hours

I was having fun playing with this game. I thought that it was reasonably bug-free because I had played it for a while. I've also asked one of my family members to try it out. I swear. We haven't spotted any bug.

Until I tried to record a video of the game play, something funny happened. I realized that I haven't implemented self-collision detection of the snake. I was like "Wow. How come no one had notice that earlier?". Hah. What a terrible failure!

Upon the discovery of the bug, it was fixed in no time.

Future Development

Seems that using alphanumeric LCD as graphical LCD is promising. I'll consider going for this solution for the portable game console project. Of course, I won't be using Arduino for that. Arduino is good for prototyping. But it isn't as efficient as lower-level C/C++ programming.

I've played this game for many times. While it's technically possible to win, I haven't managed to do so. And I haven't tested the code of winning the game. I doubt that anyone could beat it anyway. I guess I'd just leave the code there as it is. :P

Game Over photo of Arduino 1602 Snake showing the text "Boo! You lose!" and "Length: 14" in the other line

Alright. That's enough fun for today. Gotta sleep.


Removing Unused Glyphs of a Font

Dec. 7, 2015, 11:38 a.m. Programming Simple Tools

As you might have know, CJK fonts are huge in size. If the characters to be used are known, the file size of the font file can be greatly reduced by removing the unused glyphs.

I'll probably encounter this issue when I use HaxeFlixel to display CJK characters in my next project. Then I Googled for a solution. Unfortunately, there is no such thing searchable in Google.

That's why I've written the script below with the use of FontForge:

#!/usr/bin/python2
import sys
import fontforge

if len(sys.argv) == 4:
    font = fontforge.open(sys.argv[1])

    f = open(sys.argv[2], "r")

    for i in f.read().decode("UTF-8"):
        font.selection[ord(i)] = True
    f.close()

    font.selection.invert()

    for i in font.selection.byGlyphs:
        font.removeGlyph(i)

    font.generate(sys.argv[3])
else:
    print "WARNING: Check the license of the source font\nbefore distributing the output font generated by this script.\nI'm not responsible for any legal issue caused by\ninappropriate use of this script!\n"
    print "Usage: {} [source font] [file with glyphs NOT to be removed] [output]".format(sys.argv[0])
    print "Example: {} /path/to/ukai.ttc chineseTranslation.txt ukaiStripped.ttf".format(sys.argv[0])

Mind you, ensure to check the license of the source font. It may not be legal to use this script under certain condition with certain source fonts. I'm not responsible for any legal issue cause by fonts generated with this script.

I've also posted this code to stackoverflow. Hopefully the internet would find it useful!


Java String Split Gotcha

Oct. 6, 2015, 2:12 p.m. Programming

I was fixing a networking-related bug in Koloniigo. I did something like this:

String[] fields = data.split(";"); //data contains something like "a;b;;"
foo(fields[3]);

Then the program gave IndexOutOfBoundsException

I wonder what's wrong. After a few hours of debugging, I found this thingie in the documentation of split()

Trailing empty strings are therefore not included in the resulting array.

Oh well, wtf? A few hours wasted.

You may think that I'm an idiot that I didn't notice such an obvious mistake. Actually, the real code is way more complicated and there was very many possible causes of the issue. That's why it took me so long to figure out that it's the issue of the split(). :(


Gotcha of Rendering libgdx NinePatch

Sept. 15, 2015, 1:22 p.m. Gamedev Programming libgdx

Today I ran into a gotcha of libgdx. I was trying to render a NinePatch packed with texture packer by constructing a NinePatch using this:

new NinePatch(assetManaget.get("pack.atlas", TextureAtlas.class).findRegion("path/to/ninepatch"));

Then I tried to render the object using its draw() method. Didn't work. Instead, the NinePatch image was just stretched.

Then I read the documentation about the constructor of new NinePatch(TextureRegion)

Construct a degenerate "nine" patch with only a center component.

What? This is obviously not what I was expecting.

Turned out that the proper way to do this is:

assetManaget.get("pack.atlas", TextureAtlas.class).createPatch("path/to/ninepatch");

See also: TextureAtlas.createPatch(java.lang.String)


libgdx pinch to zoom

Aug. 15, 2015, 12:40 p.m. Programming libgdx

I was looking for the code for pinch to zoom on libgdx that zooms with the origin at the center of two fingers. Then I found this. Unfortunately, it seems that the solution there is far from being elegant. Then I figure out a new solution using the camera matrix myself. Here is the code:

this.gestureDetector = new GestureDetector(new GestureAdapter(){
        private Vector2 oldInitialFirstPointer=null, oldInitialSecondPointer=null;
        private float oldScale;
        @Override
        public boolean pan(float x, float y, float deltaX, float deltaY) {
            game.getCamera().update();
            game.getCamera().position.add(
                game.getCamera().unproject(new Vector3(0, 0, 0))
                .add(game.getCamera().unproject(new Vector3(deltaX, deltaY, 0)).scl(-1f))
            );
            return true;
        }
        @Override
        public boolean pinch (Vector2 initialFirstPointer, Vector2 initialSecondPointer, Vector2 firstPointer, Vector2 secondPointer){
            if(!(initialFirstPointer.equals(oldInitialFirstPointer)&&initialSecondPointer.equals(oldInitialSecondPointer))){
                oldInitialFirstPointer = initialFirstPointer.cpy();
                oldInitialSecondPointer = initialSecondPointer.cpy();
                oldScale = game.getCamera().zoom;
            }
            Vector3 center = new Vector3(
                    (firstPointer.x+initialSecondPointer.x)/2,
                    (firstPointer.y+initialSecondPointer.y)/2,
                    0
            );
            zoomCamera(center, oldScale*initialFirstPointer.dst(initialSecondPointer)/firstPointer.dst(secondPointer));
            return true;
        }
        private void zoomCamera(Vector3 origin, float scale){
            game.getCamera().update();
            Vector3 oldUnprojection = game.getCamera().unproject(origin.cpy()).cpy();
            game.getCamera().zoom = scale; //Larger value of zoom = small images, border view
            game.getCamera().zoom = Math.min(2.0f, Math.max(game.getCamera().zoom, 0.5f));
            game.getCamera().update();
            Vector3 newUnprojection = game.getCamera().unproject(origin.cpy()).cpy();
            game.getCamera().position.add(oldUnprojection.cpy().add(newUnprojection.cpy().scl(-1f)));
        }
});

The method Camera.unproject() is used to convert the coordinate of the points you touch(let's say it's coordinate A) to the coordinate that you use for displaying objects(let's say it's coordinate B). For example, if you draw a object on a SpriteBatch, and the batch has set to use a camera, then the coordinate used there is probably in coordinate B. However, another coordinate system, coordinate A is used for touch input. That's why we need to do an unprojection for the conversion between two coordinate systems. Remember to update the camera before using the unproject method. Otherwise, it may do that unprojection based on an outdated transformation matrix of the camera, which make the calculation inaccurate