Author Topic: emulating a terminal, and how to create a custom font?  (Read 13147 times)

joeclark77

  • Rogueliker
  • ***
  • Posts: 90
  • Karma: +0/-0
    • View Profile
emulating a terminal, and how to create a custom font?
« on: February 25, 2013, 08:07:31 PM »
I've decided that rather than deal with curses, I'm going to create a fake/emulated terminal.  For those who have done this, I have two questions:

(1) Do you generally display your map as a grid of "letters" or as a grid of "images" that look like letters?  Which would you expect to work better/faster in a roguelike?

(2) In python, how can I create a font from an image file (i.e. a graphic containing all the ASCII symbols in a grid)?  I know libtcod can do this, but it's written in C and the code is way too complicated for me to be able to figure out.  I want to use custom ascii characters to control my game's look, and perhaps enable modding.

Nymphaea

  • Rogueliker
  • ***
  • Posts: 74
  • Karma: +0/-0
  • Maria Fox
    • View Profile
    • Nymphaea.ca
Re: emulating a terminal, and how to create a custom font?
« Reply #1 on: February 25, 2013, 08:40:09 PM »
A grid of letters is a grid of images, usually. The easiest way to have a font is to make a bitmap font, which is usually either 16 or 32 characters wide(I prefer 16) and contains everything in it's proper place. Most roguelikes use Code Page  437. Then you just draw characters one at a time into the grid.

As for why I like 16 wide, the coordinates line up with the hex value of the character. 0x20, which is a space, would be at [0,2] on the grid, for example.
« Last Edit: February 25, 2013, 09:21:16 PM by Nymphaea »

sokol815

  • Rogueliker
  • ***
  • Posts: 85
  • Karma: +0/-0
  • Web Developer by Day, still Web Developer by night
    • View Profile
    • Email
Re: emulating a terminal, and how to create a custom font?
« Reply #2 on: February 25, 2013, 08:56:42 PM »
I experimented with this using XNA a while back to emulate a terminal. I chose the font Lucinda sans because it is a monotype font. The engine was actually pretty easy to write. it used a spritebatch to draw characters to the screen (through spritebatch's draw font functionality) This also made it easier to add special effects to the screen, change colors of backgrounds/foregrounds and even make it easy to toggle between spritemode and ASCII mode. XNA was able to get 60 fps easily. of course this does limit you to Windows/Mono

XNA also lets you generate sprite fonts from truetype fonts you have installed on your computer, but I am unsure as to how viable it is to use such a font outside of XNA or what you would have to do to interpret the file.

It sounds like you are trying to use python, though. A language which I know nearly nothing about.

You might also consider looking around for existing character maps (like the ones available for dwarf fortress) that may give you a good starting point for how to work with the images. And there are many player-created bitmap fonts available to look at.
« Last Edit: February 25, 2013, 08:59:56 PM by sokol815 »

joeclark77

  • Rogueliker
  • ***
  • Posts: 90
  • Karma: +0/-0
    • View Profile
Re: emulating a terminal, and how to create a custom font?
« Reply #3 on: February 25, 2013, 09:12:31 PM »
You might also consider looking around for existing character maps (like the ones available for dwarf fortress) that may give you a good starting point for how to work with the images. And there are many player-created bitmap fonts available to look at.

Yes! The maps that Dwarf Fortress uses are exactly the kind that I'd like to use in my game.  I can see how you would use pyglet (for example) to import them as a grid of sprites/images.  What I cannot find out is how you would transform them into a bitmap font. 

Drawing the game map and sprites is one thing, we can use images there.  But what about printing status messages to the screen?  What about getting text input from the character?  It seems that some internal representation as a font is necessary.

joeclark77

  • Rogueliker
  • ***
  • Posts: 90
  • Karma: +0/-0
    • View Profile
Re: emulating a terminal, and how to create a custom font?
« Reply #4 on: February 25, 2013, 09:17:46 PM »
A grid of letters is a grid of images, usually. The easiest way to have a font is to make a bitmap font, which is usually either 16 or 32 characters wide(I prefer 16) and contains everything in it's proper place. Most roguelikes use Code Page  437. Then you just draw characters one at a time into the grid.

As for why I like 16 wide, the coordinates line up with the hex value of the character. 0x20, which is a space, would be at [0,2] on the grid, for example.
16 or 32 characters wide... do you mean the entire font is represented as a bitmap graphic?  BMP, or PNG or what?  I have tried downloading bitmap fonts and what I found was a .FON file, which I couldn't find any way to open and look at without installing it to my operating system.

This is exactly what I want to do, is take an editable bitmap image and import it as a font.

sokol815

  • Rogueliker
  • ***
  • Posts: 85
  • Karma: +0/-0
  • Web Developer by Day, still Web Developer by night
    • View Profile
    • Email
Re: emulating a terminal, and how to create a custom font?
« Reply #5 on: February 25, 2013, 09:41:32 PM »
In my implementation, everything was represented as characters... this is how you would be able to use the bitmaps like what dwarf fortress uses.

All languages I have heard of have the ability to cast a character as an int or byte... or what have you. This is because they all use (or used) the standard ascii format. e.g. an 'A' can be cast to a byte which would then be 65... 'B' casts to 66... etc.

That looks something like this in C#:
byte val = (byte)'A'; //val is now set to 65 (the parenthesis are doing the casting)
byte val2 = (byte)'B'; //val2 is now set to 66

utilizing this, you could create an array of points that represent the top left corner of each character. if your characters were 16 x 24 pixels then you could then just create the array based off of the bitmap character size.
(0,0), (16,0), (32,0)... etc.
it is easy to create a forloop that will generate all these for you with good use of modulus.

Then because the point for 'A' is at index 65 in the array just draw out the image that has a top left coordinate of the element at index 65.

Then because you are storing the characters instead of the bytes, it is a lot easier to adapt this to other strings being displayed... you would just need a function that can either convert a string of characters into an array of bytes (using casting) and store it, or one that does it every frame(although the latter is a much less efficient use of processing power)
« Last Edit: February 25, 2013, 09:43:19 PM by sokol815 »

Nymphaea

  • Rogueliker
  • ***
  • Posts: 74
  • Karma: +0/-0
  • Maria Fox
    • View Profile
    • Nymphaea.ca
Re: emulating a terminal, and how to create a custom font?
« Reply #6 on: February 25, 2013, 10:00:22 PM »
Yes, an image with the font in it. A bitmap font is a font stored in an image file. DF's font is a good example of it, and uses the Code Page I said earlier :P (I fixed the link after your post :P so check it out, first image is example of 32 wide)

They aren't too hard to make, and drawing them in a game usually isn't hard either. Most drawing functions have a way to draw a piece of an image. If using an 8x8 font, for example, you would get the coordinates(x = character_value & 0x0f; y = (character_value>>4) & 0x0f;) and then multiply it by 8, for the actual coordinates, then just use 8 for the width and height.

joeclark77

  • Rogueliker
  • ***
  • Posts: 90
  • Karma: +0/-0
    • View Profile
Re: emulating a terminal, and how to create a custom font?
« Reply #7 on: February 25, 2013, 10:25:05 PM »
All right, I can see how to use a bitmap grid of characters as a set of sprites.  I can even see how I could make a new "print" function that uses for loops to arrange an array of sprites as a "word" on the screen.  But how about getting multi-character text input from the player? For example, how would you ask the player to enter the name of their character?  I mean, without reinventing the wheel.

Nymphaea

  • Rogueliker
  • ***
  • Posts: 74
  • Karma: +0/-0
  • Maria Fox
    • View Profile
    • Nymphaea.ca
Re: emulating a terminal, and how to create a custom font?
« Reply #8 on: February 25, 2013, 10:53:00 PM »
Same way every console does basically, take key input, and display that character at the cursor location, and increase the cursor location.

NON

  • Rogueliker
  • ***
  • Posts: 349
  • Karma: +0/-0
    • View Profile
    • Infra Arcana
    • Email
Re: emulating a terminal, and how to create a custom font?
« Reply #9 on: February 26, 2013, 07:45:02 AM »
But how about getting multi-character text input from the player? For example, how would you ask the player to enter the name of their character?

I run this function in a loop (C++), very simple! readKeysUntilFound is the same function used everywhere in the game for retrieving a new key press:
Code: [Select]
void PlayerEnterName::readKeys(string& currentString, bool& done, const int RENDER_Y_POS) {
  const KeyboardReadReturnData& d = eng->input->readKeysUntilFound();

  if(d.sfmlKey_ == sf::Keyboard::Return) {
    done = true;
    return;
  }

  if(currentString.size() < PLAYER_NAME_MAX_LENGTH) {
    if(
      d.sfmlKey_ == sf::Keyboard::Space ||
      (d.key_ >= int('a') && d.key_ <= int('z')) ||
      (d.key_ >= int('A') && d.key_ <= int('Z')) ||
      (d.key_ >= int('0') && d.key_ <= int('9'))) {
      if(d.sfmlKey_ == sf::Keyboard::Space) {
        currentString.push_back(' ');
      } else {
        currentString.push_back(char(d.key_));
      }
      draw(currentString, RENDER_Y_POS);
      return;
    }
  }

  if(currentString.size() > 0) {
    if(d.sfmlKey_ == sf::Keyboard::Back) {
      currentString.erase(currentString.end() - 1);
      draw(currentString, RENDER_Y_POS);
    }
  }
}
« Last Edit: February 26, 2013, 07:59:12 AM by NON »
Happy is the tomb where no wizard hath lain and happy the town at night whose wizards are all ashes.

joeclark77

  • Rogueliker
  • ***
  • Posts: 90
  • Karma: +0/-0
    • View Profile
Re: emulating a terminal, and how to create a custom font?
« Reply #10 on: February 26, 2013, 03:46:27 PM »
All right, well, I was hoping there was some way in one of the popular libraries to use sprites as text without writing basic text functions from scratch.  Thanks for your input.  If I can get this working in python, maybe I'll share it as a library of my own.

Quendus

  • Rogueliker
  • ***
  • Posts: 447
  • Karma: +0/-0
  • $@ \in \{1,W\} \times \{1,H\}$
    • View Profile
    • Klein Roguelikes
Re: emulating a terminal, and how to create a custom font?
« Reply #11 on: February 26, 2013, 06:08:28 PM »
libtcod does text as sprites (with an image file containing the font) on a regular grid with basic text functions out of the box (based on SDL and easily used in python).

If like me you don't fancy using the library's other functions, it still works very well as a simple replacement for curses. All I used in my libtcod project were the functions to:
  • make a window
  • set a background colour
  • set a foreground colour
  • put a character at a location
  • put a string at a location
  • get a character from the keyboard
  • get a string from the keyboard (actually I didn't use this one but it exists)
. The only effect of the unused roguelike utility functions is the additional download size, which is negligible. It's not like GLUT, forcing you to use its own event loop.

joeclark77

  • Rogueliker
  • ***
  • Posts: 90
  • Karma: +0/-0
    • View Profile
Re: emulating a terminal, and how to create a custom font?
« Reply #12 on: February 28, 2013, 11:02:37 PM »
In case anybody is interested in the solution to importing the images as a bitmap "font", this is what I did, using the pyglet library and Python 3.3.  The money function here is generate_sprite_string() which not only returns the correct images, it also returns them as sprites specifying their positions on the screen relative to the x,y coordinates where you want to plot them.  Also maybe of interest is generate_colored_tile(), which generates a new image (not a reference to the image grid) with any foreground/background colors you want.  
I haven't yet bothered with implementing a cursor and text input.

Code: [Select]
import pyglet
"imports a tileset from a 16x16 image grid; includes functions for turning text into ascii-character images/sprites"


# IMPORT GRAPHICS
tilegrid = pyglet.resource.image("curses_800x600.png")
tileset = pyglet.image.ImageGrid(tilegrid,16,16)  # split into equal sized tiles, assuming they are in a 16x16 grid
# pyglet starts from the bottom left, not top left, so we need to re-order the list just a bit
tileset = tuple(zip(*[iter(tileset)]*16))[::-1] # this one-liner should split the sequence into (rows) groups of 16 and then reverse their order
#tileset = [tileset[x:x+16] for x in range(0,len(tileset),16)][::-1] # alternate way to do the same thing
#access the images by tileset[row][column]

tw,th = tilegrid.width//16, tilegrid.height//16 # tile width/height, assuming a 16x16 grid




def get_char_tile(a,b=None):
    "returns the (white on black) tile for a given ascii code (1 arg) or row,column position (2 args)"
    if b==None:
        return tileset[a//16][a%16] # "a" indexes tileset as if it were one long array of tiles
    else:
        return tileset[a][b] # "a,b" indexes row, column

def get_tile_string(text):
    "returns a list of tiles for a given string; does not keep track of text wrapping or anything like that"
    return [get_char_tile(ord(x)) for x in text]

    
def generate_sprite_string(text,x,y,batch):
    "returns a set of sprites (with position and batch assigned) for a text string; lower-left corner at x,y"
    return [pyglet.sprite.Sprite( get_char_tile(ord(text[i])), x=x+(i*tw), y=y, batch=batch) for i in range(len(text))]

    
def generate_colored_tile(a,b=None,fg_color=b'\xff\xff\xff',bg_color=b'\x00\x00\x00'):  #a=ascii code or a,b = row, column position in grid
    "returns a new image by transforming the foreground and background colors of one of the default (white on balck) tiles"
    
    pic = get_char_tile(a,b) # get_char_tile works with ascii code or row,column position
    width, height = pic.width, pic.height
    format = "RGBA"
    picdata = pic.image_data.get_data(format,width*4) # as a byte string of RGBA bytes
    pixels = [picdata[x:x+4] for x in range(0,len(picdata),4)] # split that string into 4-byte strings representing pixels
    
    blackcolor = b'\x00\x00\x00\xff'
    whitecolor = b'\xff\xff\xff\xff'
    bgs = [y for y in range(len(pixels)) if pixels[y]==blackcolor] # indexes of background pixels
    fgs = [y for y in range(len(pixels)) if pixels[y]==whitecolor] # indexes of foreground pixels  
    for i in bgs: pixels[i] = bg_color + b'\xff' # transform background; bg_color expects a 3-byte string representing RGB values
    for i in fgs: pixels[i] = fg_color + b'\xff' # transform foreground; fg_color expects a 3-byte string representing RGB values

    pixel_out = b''
    for j in pixels: pixel_out += j # merge back into a single byte string
    return pyglet.image.ImageData(width,height,format,pixel_out) # generate a new image with the new data
    
« Last Edit: February 28, 2013, 11:06:35 PM by joeclark77 »