Visualising a full tank game.

How to visualise a full tank game? Those tanks can be in any position; it would be impractical to display a grid able to contain any x/y number coordinates. For simplicity, we will just display the grid with x,y ranging from zero to nine inclusive. To do this, we will use mutation:

Since each tank is represented as 3 lines, we will have a grid 30 * 10. Initially, every cell will contain : seven spaces, representing the absence of a tank. This is because each tank line is 7 characters.

// ----------------------------------
//File _tank_game/print_game.fear
TanksToS: F[List[Tank],Str]{ ts -> Block#
  .let res = {0 =~~ 30 .flow.map{_->this.newLine}.list }
  .do{ ts.flow.forEach{ t -> Block#
    .let x= { t.position.x }
    .let y= { t.position.y }
    .if {x.inRange(0=~~10).not} .done
    .if {y.inRange(0=~~10).not} .done
    .do{ res.get(y * 3)    .get(x).set(t.repr1) }
    .do{ res.get(y * 3 + 1).get(x).set(t.repr2) }
    .do{ res.get(y * 3 + 2).get(x).set(t.repr3) }
    .done
    }}
  .return { res.flow.map{::.flow.map{::.get}.join(``)}.join(``|) };
  read .newLine: mut List[mut Var[Str]] -> 0 =~~ 10 .flow.map{ _ -> Vars#(`       `)}.list;
  }
PrintGame: {
  mut .out: mut Output;
  mut .singleLine(ts: List[Tank]): Void -> this.out.println(TanksToS#(ts));
  mut .lines(rounds: Nat, ts: List[Tank]): Void -> Block#
    .var current= {ts}
    .return{ 0 =~~ rounds .flow.forEach{step -> Block#(
      this.out.println(`Step `+step|),
      this.out.println(`------------------------------------------------------------`|),
      this.singleLine(current.get),
      this.out.println(`------------------------------------------------------------`|),
      current.set(NextState#(current.get))
      )}}
  }
// ----------------------------------
//File _tank_game/_rank_app.fear
Test: Main {sys -> Block#
  .let out= {sys.out}
  .let in= {sys.inputCursor# !}
  .let game= {mut ReadGame{in}.read}//ReadGame omitted for now
  .return{ mut PrintGame{out}.lines(50,game) }
  }

As you can see, we omitted the code reading the initial game state. This is because in order to read data from files there is still quite some content that we need to learn. We will handle that in Chapter 4. Assuming a properly implemented ReadGame, this code could print something like the following:


Step 5
------------------------------------------------------------
 / - \       / - \
 | > |       - V |
 \ | /       \ _ /



       / - \
       | V |
       \ | /
                                     / - \
                                     | < |
                                     \ | /
             / | \
             | > |
             \ _ /



 / - \       / - \
 | > |       - V |
 \ | /       \ _ /



                                     / - \
                                     | < |
                                     \ | /
------------------------------------------------------------

Step 6
------------------------------------------------------------
       / - \
       | > |
       \ | /
             / - \
             - V |
             \ _ /



       / - \                   / - \
       | V |                   | < |
       \ | /                   \ | /
                   / | \
                   | > |
                   \ _ /



       / - \
       | > |
       \ | /
             / - \
             - V |
             \ _ /
                               / - \
                               | < |
                               \ | /
------------------------------------------------------------

Where tanks can be displayed on the screen, showing the various steps of the game

We now focus on those two lines:

  .let res = {0 =~~ 30 .flow.map{_->this.newLine}.list }
  ...
  read .newLine: mut List[mut Var[Str]] -> 0 =~~ 10 .flow.map{ _ -> Vars#(`       `)}.list;

The code shown below uses res to represent a grid of information, that can be updated as needed.

    .let x= { t.position.x }
    .let y= { t.position.y }
    .if {x.inRange(0=~~10).not} .done
    .if {y.inRange(0=~~10).not} .done
    .do{ res.get(y * 3)    .get(x).set(t.repr1) }
    .do{ res.get(y * 3 + 1).get(x).set(t.repr2) }
    .do{ res.get(y * 3 + 2).get(x).set(t.repr3) }

This code runs .forEach of the tanks t in ts x/y are just short names for the coordinates of t. If x or y are .not in the visualized range, we do not represent tank t on our board res. Otherwise, we write the three lines representing t on the appropriate position on res. Note how we call .get(..).get(..).set(..) to access two layers of List and then set a new value in our variable.

What we are creating now is basically a 'text art' based game. Those were popular in the (far) past. Of course Fearless supports proper graphics, and we will see how to render nice looking images of tanks later on; but this way of printing the 'current screen' line by line is how those more fancy graphic systems work too under the hood. Here we use characters as graphical symbols, they use (much smaller) coloured pixels as graphical symbols.

But the idea of doing graphics by using a grid of graphical symbols is the same, and the struggle to decide what symbol to place in each location is very similar too.

That is, the techniques and mindset shown here do scale to full modern 2-D, or even 3-D graphics. The computer screen is conceptually accessed as a large mut List[mut List[mut Var[Color]]] and the computer is simply insanely fast at switching those colours around creating the illusion of movement.

When wanting to display shapes on the screen, the logic will look a lot like what we had for our tanks: forall shapes to display, display the shape. To display an individual shape: for all the parts of the shape: display the individual part (the three lines of the tank in our example). The act of displaying a shape part is the act of setting new colours in specific places in the large mut List[mut List[mut Var[Color]]] screen.

Hopefully this removes another layer of mystery on how computers work, and the realisation that those little pixels are indeed explicit entities that operations in the computer are able to update fast enough to create the illusion of movement clarifies in a visceral way how fast those computers are.