Skip to main content

Exercise 6: Working with text

Please complete Exercise 5 first, since the steps below build upon your work from that project.

Text as tilesets

  1. Open your project called "BOUNCE BRIX" that you saved from Exercise 5.

  2. Go to the TILES screen, then click the "NEW TILEMAP" button:

    Create TILEMAP_1

  3. You should now have a tilemap called "TILEMAP_1" listed below your game map (which was "TILEMAP_0").

    With "TILEMAP_1" selected, click the "TILESET…" button:

    Opening the tileset chooser

  4. The "CHOOSE A TILESET" window will open. Click the "FONT" tab, then click on the "STARTER" tileset.

    Then click the "CHOOSE" button:

    Choosing the font tileset

    Your map will change to show the gray "x" boxes that indicate control codes (tile #0 from our font):

    Mapp filled with tile #0

  5. Let's clear our text "screen" by filling it with spaces. In the "FONT TILES" tray on the right, click the box numbered 32 (the space character). Then click the "FILL" button. Then click anywhere in the blue tilemap rectangle to fill it:

    Clearing the tilemap

  6. Now click the "DRAW" button. In the "FONT TILES" tray on the right, find the letter "A" and click on it. (You may need to scroll down a bit.) Then click on the tilemap to draw a letter "A".

    Try drawing letters "B" and "C" as well:

    Drawing ABC

  7. Draw some more letters and symbols from the tray. Draw whatever you like! See what kind of pictures you can make using the "STARTER" font.

    Here are some ideas:

    More drawing experiments

    What did we learn?

    Hybrix displays text using the same tilemap system that we used to make our game maze. The text font (letter appearance) is a normal tileset, just with smaller sized tiles. Each letter is a single tile. This is called a bitmap font, meaning that the letter pictures are made from pixels instead of lines and curves. It is also called a fixed-width font, meaning that each letter and punctuation mark has the exact same size.

    You can show text in your program by using the Hybrix designer to make tilemaps, but it is not very convenient. The CONSOLE is a better way: It can print messages, change colors, and scroll the screen, and even let you type using a blinking cursor. Let's try the console!

Text from keyboard input

  1. Go to the CODE screen, and choose your "MAIN" file. It should still have the starter file code:

    The code screen

    We'll add a function called TYPE_ON_SCREEN() that reads a key from the keyboard. If a key was typed (KEY <> 0), we call CONSOLE::PRINT_CHAR() to print it on the screen:

    FUNC TYPE_ON_SCREEN()
    TRUE -> CONSOLE::CURSOR_VISIBLE
    VAR KEY: BYTE
    CONSOLE::READ_KEY() -> KEY
    IF KEY <> 0 THEN
    CONSOLE::PRINT_CHAR(KEY)
    END IF
    END FUNC

    Add a call to the MAIN::TYPE_ON_SCREEN() function at the end of the main LOOP, as shown below:

    (Click to see the complete "MAIN" file with these changes)

    The "MAIN" file:

    MODULE MAIN
    FUNC START()
    ENGINE::INIT()
    SOUND::INIT()
    CONSOLE::INIT()

    IF SYMBOLS::SCENES <> NULL AND SYMBOLS::SCENES.SIZE > 0 THEN
    SYMBOLS::SCENES[0].LOAD()
    END IF

    LOOP
    ENGINE::RENDER()
    ENGINE::WAIT_FOR_PAINT()
    ENGINE::THINK()
    SOUND::THINK()
    CONSOLE::THINK()

    MAIN::TYPE_ON_SCREEN()
    END LOOP
    END FUNC

    FUNC TYPE_ON_SCREEN()
    TRUE -> CONSOLE::CURSOR_VISIBLE
    VAR KEY: BYTE
    CONSOLE::READ_KEY() -> KEY
    IF KEY <> 0 THEN
    CONSOLE::PRINT_CHAR(KEY)
    END IF
    END FUNC
    END MODULE
  2. Click the "RUN" button to run your code. You should now see a blinking cursor on the screen. If you type words using the keyboard, they will appear on top of your game maze!

    Typing on top of the game maze

    Two tilemaps at once?

    The Hybrix's virtual machine's video hardware is called "Palix". Palix can show as many as 64 sprites and 3 tilemaps at the same time. (See Palix: tiles for technical details.)

    The three Palix tilemaps are called Tilemap A, Tilemap B, and Tilemap C. The Hybrix framework usually connects the CONSOLE to Tilemap A, and it uses Tilemap B to display your scene from the board. According to the display priority rules, Tilemap A will always appear in front of Tilemap B.

    What if you wanted the CONSOLE text to appear behind your game map? You could change some of the setup from CONSOLE::INIT() to move the CONSOLE to Tilemap C instead of Tilemap A.

  3. You may notice that when you press the arrow keys on your keyboard, the ball moves around the maze. This is a bit distracting! We can fix that by temporarily disabling the virtual gamepad.

    Click this button to open the "CONFIGURE CONTROLLERS" window:

    The configure controllers button

    Then unmark the checkboxes for "DEVICE 0" and "DEVICE 1":

    Disabling the keyboard controls

    Then click the "DONE" button to close this window. Now when you run your program, you can use the keyboard arrows without moving the red ball.

  4. Each tile number corresponds to a keyboard key. For example, A is number 65, B is number 66, and so forth. This mapping from numbers to tiles is called HASCII. (All the details are explained on the HASCII Table reference sheet.)

    When our loop calls CONSOLE::PRINT_CHAR() with the number 65, it will print A on the screen and move the cursor. There are also some special control codes with interesting effects.

    Try typing these keys while the program is running:

    HASCIIEscapeType this......to make PRINT_CHAR() do this:
    18{2}CTRL + 2change the cursor color to RED
    19{3}CTRL + 3change the cursor color to ORANGE
    21{5}CTRL + 5change the cursor color to GREEN
    3{SUP}CTRL + Cscroll the screen up by one line
    4{SDOWN}CTRL + Dscroll the screen down by one line
    5{CLS}CTRL + Eerase the whole screen
    6{CLL}CTRL + Ferase the current line only
    9{TAB}TABmove the cursor rightwards to the next tab column
    11{HOME}HOMEmove the cursor to the upper left corner of the screen
    Apple keyboards: FN + LEFT
    12{END}ENDmove the cursor to the lower left corner of the screen
    Apple keyboards: FN + RIGHT
    14{REV}CTRL + ,enable reversed theme mode
    (the effect used to blink the cursor)
    15{MAT}CTRL + .enable matte coloring mode, where color #0 (clear) is replaced by IO::MATTE_COLOR
    1{RESET}SHIFT + ESCdisable the {REV} and {MAT} modes

    (Even more control codes can be found in the HASCII Table.)

    👉 Make sure to give "matte coloring" a try; we'll use this effect in Step 17.

    👉 The "reversed theme" can also be used to make complementary shapes, for example transforming character {$B1} from into , or transforming character {$B9} from into .

Text with PRINT

  1. Now let's try printing more than just one letter at a time. We'll add a function like this:

    FUNC SHOW_SCORE()
    CONSOLE::PRINT("HELLO {CAT}")
    END FUNC

    ...and then update our TYPE_ON_SCREEN() function to call this function whenever you press the ESC key on your keyboard:

    IF KEY = 27 THEN # (27 IS THE ESCAPE KEY)
    MAIN::SHOW_SCORE()
    ELSIF KEY <> 0 THEN
    CONSOLE::PRINT_CHAR(KEY)
    END IF

    To save time, you can copy+paste the entire file from the box below:

    (Click to see the complete "MAIN" file with these changes)

    The "MAIN" file:

    MODULE MAIN
    FUNC START()
    ENGINE::INIT()
    SOUND::INIT()
    CONSOLE::INIT()

    IF SYMBOLS::SCENES <> NULL AND SYMBOLS::SCENES.SIZE > 0 THEN
    SYMBOLS::SCENES[0].LOAD()
    END IF

    LOOP
    ENGINE::RENDER()
    ENGINE::WAIT_FOR_PAINT()
    ENGINE::THINK()
    SOUND::THINK()
    CONSOLE::THINK()

    MAIN::TYPE_ON_SCREEN()
    END LOOP
    END FUNC

    FUNC TYPE_ON_SCREEN()
    TRUE -> CONSOLE::CURSOR_VISIBLE
    VAR KEY: BYTE
    CONSOLE::READ_KEY() -> KEY
    IF KEY = 27 THEN # (27 IS THE ESCAPE KEY)
    MAIN::SHOW_SCORE()
    ELSIF KEY <> 0 THEN
    CONSOLE::PRINT_CHAR(KEY)
    END IF
    END FUNC

    FUNC SHOW_SCORE()
    CONSOLE::PRINT("HELLO {CAT}")
    END FUNC
    END MODULE

    Click the "RUN" button. Now whenever you press ESC, the word "HELLO" gets printed with a cat!

    Hello with a cat character

    Try holding down the ESC key so it repeats. It will print very fast!

  2. The special {CAT} pattern is called an escape sequence. It "escapes" from the regular meaning of the characters, allowing you to specify special character values.

    Try putting these other escape codes in your CONSOLE::PRINT("HELLO {CAT}") statement:

    Escape...to print this:
    {CAT}🐱
    {COIN}🪙
    {NOTE}
    {-->}🠞
    {<--}🠜
    {#:#}
    {:::}
    {: :}
    {$9A}

    {D} makes the cursor move down. What will this do?

    CONSOLE::PRINT("{D}{-->} HELLO {<--}")

Keeping score

  1. Let's "keep score" in our game by counting the number of diamonds that you collect. To test our logic more easily, for now we'll simply increase the counter whenever you press ESC. (Later in Step 20, we'll make it happen when you really collect a diamond.)

    Add a variable under MODULE MAIN:

    VAR DIAMOND_COUNT

    Next change your FUNC SHOW_SCORE() block to add 1 each time. We can also use CONSOLE::PRINT_INT() to print the number before our message "DIAMONDS !":

    FUNC SHOW_SCORE()
    MAIN::DIAMOND_COUNT + 1 -> MAIN::DIAMOND_COUNT

    CONSOLE::PRINT_INT(MAIN::DIAMOND_COUNT)
    CONSOLE::PRINT(" DIAMONDS!")
    END FUNC

    Here's the MAIN file with all these changes:

    (Click to see the complete "MAIN" file with these changes)

    The "MAIN" file:

    MODULE MAIN
    VAR DIAMOND_COUNT

    FUNC START()
    ENGINE::INIT()
    SOUND::INIT()
    CONSOLE::INIT()

    IF SYMBOLS::SCENES <> NULL AND SYMBOLS::SCENES.SIZE > 0 THEN
    SYMBOLS::SCENES[0].LOAD()
    END IF

    LOOP
    ENGINE::RENDER()
    ENGINE::WAIT_FOR_PAINT()
    ENGINE::THINK()
    SOUND::THINK()
    CONSOLE::THINK()

    MAIN::TYPE_ON_SCREEN()
    END LOOP
    END FUNC

    FUNC TYPE_ON_SCREEN()
    TRUE -> CONSOLE::CURSOR_VISIBLE
    VAR KEY: BYTE
    CONSOLE::READ_KEY() -> KEY
    IF KEY = 27 THEN # (27 IS THE ESCAPE KEY)
    MAIN::SHOW_SCORE()
    ELSIF KEY <> 0 THEN
    CONSOLE::PRINT_CHAR(KEY)
    END IF
    END FUNC

    FUNC SHOW_SCORE()
    MAIN::DIAMOND_COUNT + 1 -> MAIN::DIAMOND_COUNT

    CONSOLE::PRINT_INT(MAIN::DIAMOND_COUNT)
    CONSOLE::PRINT(" DIAMONDS!")
    END FUNC
    END MODULE
  2. Try running the program. Then press the ESC key a few times. You should see an increasing number:

    &quot;1 DIAMONDS!&quot; etc

  3. Can you use {END} to move the score to the bottom of the screen? And {9} to change the color to violet? (You will need to add another CONSOLE::PRINT before CONSOLE::PRINT_INT.)

    (Click to see the answer)
    FUNC SHOW_SCORE()
    MAIN::DIAMOND_COUNT + 1 -> MAIN::DIAMOND_COUNT

    CONSOLE::PRINT("{END}{9}")
    CONSOLE::PRINT_INT(MAIN::DIAMOND_COUNT)
    CONSOLE::PRINT(" DIAMONDS!")
    END FUNC

    When you run your program, now the counter should appear like this:

    &quot;1 DIAMONDS!&quot; in violet

  4. Here's a trick: if you add {MAT} after {9}, you can add a "matte" color backdrop to make your text more readable. The clear pixels will be replaced by whatever color number you store in IO::MATTE_COLOR. 7 will produce a white backdrop.

    (Click to see the answer)
    FUNC SHOW_SCORE()
    MAIN::DIAMOND_COUNT + 1 -> MAIN::DIAMOND_COUNT

    7 -> IO::MATTE_COLOR
    CONSOLE::PRINT("{END}{9}{MAT}")
    CONSOLE::PRINT_INT(MAIN::DIAMOND_COUNT)
    CONSOLE::PRINT(" DIAMONDS!")
    END FUNC

    &quot;2 DIAMONDS!&quot; with white matte color

  5. After {END}, let's move the cursor one step up ({U}) and then right ({R}), to make a nice margin around our score box.

    (Click to see the answer)
    FUNC SHOW_SCORE()
    MAIN::DIAMOND_COUNT + 1 -> MAIN::DIAMOND_COUNT

    7 -> IO::MATTE_COLOR
    CONSOLE::PRINT("{END}{U}{R}{9}{MAT}")
    CONSOLE::PRINT_INT(MAIN::DIAMOND_COUNT)
    CONSOLE::PRINT(" DIAMONDS!")
    END FUNC

    &quot;3 DIAMONDS!&quot; with a margin

  6. Lastly, we can make the message more compact by using the diamond ♦ graphical glyph instead of the word DIAMONDS!. This glyph doesn't have a friendly escape sequence, but we can make an escape sequence {$99} from its HASCII hex number. Let's also add some space characters before and after to fill out the matte color backdrop:

    FUNC SHOW_SCORE()
    MAIN::DIAMOND_COUNT + 1 -> MAIN::DIAMOND_COUNT

    7 -> IO::MATTE_COLOR
    CONSOLE::PRINT("{END}{U}{R}{9}{MAT} {$99} ")
    CONSOLE::PRINT_INT(MAIN::DIAMOND_COUNT)
    CONSOLE::PRINT(" ")
    END FUNC

    This final result should look like this:

    &quot;♦ 14&quot; as a score

If you use your keyboard to write other text on the same line as our score, you may notice that it stays there when a new score is printed. This situation could also happen if your program shows a big score like 9876, then later resets the score to 0. The leftover 76 "junk" characters might remain on the screen.

To clear this junk, you can add the {CLL} escape code after {U}. {CLL} erases the whole line, so we can print cleanly. (Why doesn't it work correctly if you put {CLL} after {END}, or after {R}?)

Putting it all together

  1. Now that we figured out how to show a score on the screen, let's use it in our game! It's just a few final code changes:

    In the "MAIN" file:

    • In the main LOOP, replace the MAIN::TYPE_ON_SCREEN() statement with MAIN::SHOW_SCORE()
    • We can delete the whole FUNC TYPE_ON_SCREEN() block, since it was just for experimentation.
    • Remove MAIN::DIAMOND_COUNT + 1 -> MAIN::DIAMOND_COUNT from the FUNC SHOW_SCORE() block

    In the "PLAYER" file:

    • Add MAIN::DIAMOND_COUNT + 1 -> MAIN::DIAMOND_COUNT after the call to SOUND::PLAY_TRACK()

    (Click to see the "MAIN" file for completed Exercise 6)

    The "MAIN" file:

    MODULE MAIN
    VAR DIAMOND_COUNT

    FUNC START()
    ENGINE::INIT()
    SOUND::INIT()
    CONSOLE::INIT()

    IF SYMBOLS::SCENES <> NULL AND SYMBOLS::SCENES.SIZE > 0 THEN
    SYMBOLS::SCENES[0].LOAD()
    END IF

    LOOP
    ENGINE::RENDER()
    ENGINE::WAIT_FOR_PAINT()
    ENGINE::THINK()
    SOUND::THINK()
    CONSOLE::THINK()

    MAIN::SHOW_SCORE()
    END LOOP
    END FUNC

    FUNC SHOW_SCORE()
    7 -> IO::MATTE_COLOR
    CONSOLE::PRINT("{END}{U}{CLL}{R}{9}{MAT} {$99} ")
    CONSOLE::PRINT_INT(MAIN::DIAMOND_COUNT)
    CONSOLE::PRINT(" ")
    END FUNC
    END MODULE
    (Click to see the "PLAYER" file for completed Exercise 6)

    The "PLAYER" file:

    CLASS PLAYER EXTENDS ACTOR
    # REMEMBER THE BALL'S VELOCITY
    VAR DX
    VAR DY

    HOOK THINK()
    IF GAMEPAD::LEFT AND .DX >= -2 THEN
    .DX - 1 -> .DX
    END IF
    IF GAMEPAD::RIGHT AND .DX <= 2 THEN
    .DX + 1 -> .DX
    END IF
    IF GAMEPAD::UP AND .DY >= -2 THEN
    .DY - 1 -> .DY
    END IF
    IF GAMEPAD::DOWN AND .DY <= 2 THEN
    .DY + 1 -> .DY
    END IF

    # ADD THE BALL'S VELOCITY, MOVING THE BALL BY ONE STEP
    .X + .DX -> .X
    .Y + .DY -> .Y

    # MAKE THE CAMERA FOLLOW THE BALL
    ENGINE::SET_CAMERA_CX(.X)
    ENGINE::SET_CAMERA_CY(.Y)

    # READ THE TILE NUMBER AT THE BALL'S CENTER
    # (FIND THE CENTER BY ADDING 8, SINCE OUR SPRITE IS 16 X 16)
    VAR TILE
    ENGINE::TILE_LAYER_B.GET_TILE_AT_XY(.X + 8, .Y + 8) -> TILE

    IF TILE = 2 THEN # DIAMOND
    # ERASE THE DIAMOND WITH USING TILE #0
    ENGINE::TILE_LAYER_B.SET_TILE_AT_XY(.X + 8, .Y + 8, 0)
    SOUND::PLAY_TRACK(ID_SOUND::SOUND_0, 0)
    MAIN::DIAMOND_COUNT + 1 -> MAIN::DIAMOND_COUNT
    END IF

    END HOOK
    END CLASS
  2. In Step 10, we had used the "CONFIGURE CONTROLLERS" window to disable the virtual gamepad. Let's reenable it, so you can play your game again:

    &quot;♦ 14&quot; as a score

    Click "DONE" to close this window.

    Then click "RUN" to play your game. Use the keyboard arrow keys to move your ball around. Each time you collect a diamond, now the counter at the bottom of the screen should increase by one!

  3. Don't forget to click the "SAVE" button to save your work. We'll need it for the next exercise.

Great job! That's all for Exercise 6. In the next exercise, we'll make the ball bounce off the walls, so that it stays inside the maze.