lewas-devlog


A game by Karu & Adel Faure. Music by Yuichi Broccoli


Posts


ASCII is cool, ASCII is hot.

adelfaure 2019-04-22 ↑Posts

Can text have a temperature ?

beware : work in progress, expect crappy English and incomprehensible content

It’s been two weeks seen last post, meanwhile I’ve continued to work on the “ASCII engine” from the text / color layers system and the possibility to overlap multiple “ascii objects” wihout erasing or trail either of them from the text grid.

Now we’ve got a sort of ASCII elements model defining js generic objects involving both ASCII display and interaction.

Element = function(unlist){
    this.pt = [0,0]
    this.displays = {}
    this.display = "main"
    this.behaviour = []
    if(!unlist) elements.push(this)
}

//    create
var sword = new Element()

//    position [x,y]
sword.pt = [10,20]

//    build
sword.build("main",assets["sword"])

//    build consist to parse a tuned ASCII text file
//    into color frames ready for display 
//    (see my previous post)
//    
//    this.displays[display] = {
//        name : display,
//        direction : 1,
//        frame : 0,
//        frames : [],
//        shape : {
//            anchor : [],
//            size : []
//        } 
//    }

//    basic behaviour  
sword.behaviour.push( active(self,other) )
sword.behaviour.push( visible(self,other) )

One of my main concerns from which derives most of this “engine” was how user grid position (from mouse or keyboard) will be detected by these ASCII objects and in the same way, how they will detect each other.

So at first I’ve implemented a box-zone trigger function :

Element.prototype.hit = function(pt,display){
    
    display = this.displays[display]
    
    return 
       pt[0] < display.size[0] + this.pt[0] - display.anchor[0] 
    && pt[0] > this.pt[0] - display.anchor[0] 
    && pt[1] < display.size[1] + this.pt[1] - display.anchor[1] 
    && pt[1] > this.pt[1] - display.anchor[1]
    ? true
    : false

}

sword.hit(user.pt,"main") 
// user.pt as the point that define 
// user cursor position on the grid

but beside the fact that it will consider transparent glyphs as part of the ascii it tend to complexify inter-ascii collisions ( may give some trouble with precise selection or hitzone in shooting mini-game or whatsoever )

Thinkin a little bit on this I realize that ASCII shape is allready known in the grid wihout any box calulation by the fact that its gylphs are already present in the same text-grid as the cursor position. In this case It is sufficient to retreive the exact glyph pointing by the user and by this open the possibility to have a specific event relatif to each character, e.g considering transparent considered glyph e.g as not part of the ASCII shape.

Element.prototype.hit = function(pt,parse,display){
    
    display = this.displays[display]
    
    // display.frames[0][0] call first frame black layer 
    // an invisible layer convenient for now but problematic
    // it maybe derserve a specific ascii matrix
    hit = display.frames[0][0] 
    [pt[1] - this.pin[1] + display.anchor[1]] // y
    [pt[0] - this.pin[0] + display.anchor[0]] // x
    
    return parse(hit)
    ? true
    : false

}

// parser example
function visible_glyph(hit){
    return hit && hit != '█'
    ? true
    : false    
}

sword.hit(user.pt,visible_glyph,"main")

From this ASCII glyphs basic propretie to be “hit” or not I started to work on a behaviour system maybe class-like leading to differents events depending the way that ascii glyphs are hit by user or something else (e.g another ascii).

Element.prototype.trigger = function(self,other){
    this.behaviour.forEach( f => f(self,other) )
}

A basic behaviour to add to ASCII will be the capacity to tell if it is focused or not, hold or not and maybe specificly in a focus in or out stat or hold start or end stat, we gonna call that behaviour active.

function active (self,other) {
    if ( !self.focus ) {
        if( !other.down && self.hold ) {
            self.holdEnd = true
            self.hold = false
        }
        if (    self.hit(other.pt,visible_glyph,self.display) 
             && !other.aim 
             && !other.down ) {
                self.focusIn = true
                self.focus = true
                other.aim = true
        } 
    } else {
        if ( !self.hold ){
            if ( self.hit(other.pt,self.display) ) {
                if ( other.down && other.focus == el ){
                    self.focusOut = true
                    self.focus = false
                    self.holdStart = true
                    self.hold = true
                } 
            }else if ( !other.down ) {
                self.focusOut = true
                other.focus = false
                self.focus = false
            }
        }
    }
}

This active behaviour lead to others behaviours like drag and drop as you can see it in the video

sword.behaviour.push( draggable(self,other) )

function draggable (self,other) {
    if ( self.hold ) {
        el.print("focus",above)
        el.holding = false
    }
    if ( el.holdEnd ) {
        el.grab = false
        user.focus = false
        el.print("main",below)  
        el.holdEnd = false
    }
    if ( el.focusIn ) {
        var index = elements.indexOf(el)
        elements.splice( index, 1 )
        elements.push(el)
        el.index = elements.length
        el.print("focus",below)  
        user.focus = el
        el.focusIn = false
    }
    if ( el.focusOut ) {
        el.print("focus",below,true)  
        elements.forEach( el => {
            if(el.setup && !el.hold ) {
                el.print("main",below) 
            }
        })
        el.focusOut = false
    }
    if ( el.hold ){
        el.pin = user.pt
    }
}

Ok now that we have describe basic behaviour system lets restart from the begin to discuss more specific behaviour and gameplay related topic leading to this post question, can text have a temperature ?

Lets take two ascii objects, a furnace and a sword.

Sword will be draggable depending on a hit_zone display (handle), and heatable depending to his main display (full sword) while furnace will get the propriete to be source of other and ephemeral ascii objets : flames.

sword.txt

6,24
13,26
$8
██████.
█████/ \
████/   \
████| | |
████| | |
████| | |
████| | |
████| | |
████| | |
████| | |
████| | |
████| | |
████| | |
████| | |
████| | |
████| | |
████| | |
████| | |
<===[(*)]===>
█████|/|
█████|\|
█████|/|
█████|\|
█████|_|
█████(_)
██████°

sword_handle.txt

6,24
13,26
$0
███████
████████
█████████
█████████
█████████
█████████
█████████
█████████
█████████
█████████
█████████
█████████
█████████
█████████
█████████
█████████
█████████
█████████
█████████████
█████XXX
█████XXX
█████XXX
█████XXX
█████XXX
█████XXX
██████X

sword_focus.txt

6,24
13,26
$15
██████.
█████/ \
████/   \
████| | |
████| | |
████| | |
████| | |
████| | |
████| | |
████| | |
████| | |
████| | |
████| | |
████| | |
████| | |
████| | |
████| | |
████| | |
<===[(*)]===>
█████|/|
█████|\|
█████|/|
█████|\|
█████|_|
█████(_)
██████°

furnace.txt

0,0 36,19 € $8 ███████ _________________ ███████|_____
███████| _
_|
||__  ███████||_    
███████|| |
|
_|||| █_//||||||_ |___/|||_||__
|| __/|
||||__
| ||_|_|_|_|_|_|_|| | ||||||||| ||||_|_| |_|_|| ||||| ||| | ||_| |_|| | || .|| ||||| .||| |||| .|| | ||| .||| █||| .|| ██|||^^^^^^^^^^^^^^^^^^^^^ |||

test

//      anvil.txt
0,0
20,13
$7
+-----------+ 
|\ + + + + + \
\ \ + + + + + \
█\ \ + + + + + \
██\ \ + + + + + \
██/\ \ + + + + + \
█|\ \ \ + + + + + \
█\ \ \ +-----------+
██\ \ \| ~-.-~-.-~ |
███\ \  \ ' .-. ` /
████\ \  ) (█\ ) (
█████\ \/   )█(   \
██████\|___/███\___|
$8
+-----------+
█\███████████\
██\███████████\
███\███████████\
████\███████████\
█████\███████████\
██████\███████████\
███████+-----------+
███████|███████████|
████████\███.-.███/
█████████)█(███)█(
████████/███)█(███\
███████|___/███\___|

//      anvil_hit.txt 
0,0
20,8
$0
XXXXXXXXXXXXX
█XXXXXXXXXXXXX
██XXXXXXXXXXXXX
███XXXXXXXXXXXXX
████XXXXXXXXXXXXX
█████XXXXXXXXXXXXX
██████XXXXXXXXXXXXX
███████XXXXXXXXXXXXX

Drawing random letters on a web page with blacksmith’s ascii tongs.

adelfaure 2019-04-08 ↑Posts

Drawing with a pair of tongs is not as easy as it looks.

Today, I worked on a web user interface in color ascii. At the end I’ve got an ascii display system with a custom ascii cursor and a mouse-down “paint” interaction.

The DOM architecture is made up of multi <pre> elements as colored text layers. This avoids, for example, to wrap each letter in a <font> or <div> element which could tend to orient js scripting towards a complex dom/objects mapping. Besides, why not make it possible to superimpose the color text layers?

<pre class="black"></pre>
<pre class="red"></pre>
<pre class="green"></pre>
<pre class="yellow"></pre>
<pre class="blue"></pre>
<pre class="magenta"></pre>
<pre class="cyan"></pre>
<pre class="white"></pre>
...

On the javascript side I’ve got a time updated pair of arrays used to animate text and part what is definitely “painted” on the text-grid from whatʼs just passing through (like blacksmith’s tongs).

var layers = document.getElementsByTagName("pre")
var colors = []
var displays = []

...
function draw(){
    ...
    // setup display
    for( var i = 0; i < layers.length; i++ ){
        displays[i] = colors[i]
    }
    ...
    // apply to dom
    for( var i = 0; i < layers.length; i++ ){
        layers[i].textContent = displays[i]
    }
    // next frame
    setTimeout(function(){
        draw()
    },50)
}

Still in javacript, I’ve implemented a sort of ad hoc file format parser using js fetch requests to load ascii drawings in the above-mentioned system.

function load(file){
    var graphic = {}
    return fetch("./src/txt/interface/"+file+".txt")
    .then(response => response.text())
    .then(text => {
        text = text.split('\n')
        graphic.x = parseInt(text[0].split(',')[0])
        graphic.y = parseInt(text[0].split(',')[1])
        graphic.template = {}
        var color_index = false
        var color_layer = ""
        text.forEach((l,i) => {
            if(l[0] == '$'){
                if(color_index) {
                    graphic.template[color_index] = color_layer
                    color_layer = ""
                }
                color_index = l.substring(1)  
            }else{
                if(color_index){
                    color_layer += l+'\n'
                }
            }
        })
        graphic.template[color_index] = color_layer
        return graphic
    })
}

Which result in json objects like this :

graphic : {
    template: {
        8: "███_._███_._\n█-~_--°█°--_~-\n| |█████████| |\n█\\ `-.___.-'
            /█\n██`-._ ¤ `\\-'\n████/ `°\\ \\\n███( (██) )\n███| |██| |\n███| )██(
            |\n███| |████| |\n███| |████| |\n███| |████| |\n███| |████| |\n███|
            |████| |\n███| |████| |\n███| )████(
            |\n███||██████||\n███||██████||\n███||██████||\n███||██████||\n███||██████||\n███|
            )████( |\n███| )████( |\n███| )████( |\n███| )████( |\n███| )████(
            |\n███| )████( |\n███| )████( |\n███\\ \\████/ /\n████| )██(
            |\n████(/████\\)\n"i,
       15: "███_._███_._\n█-~███°█████~-\n██████████████|\n███`-.___████/█\n███████¤█`\\██\n
            ████/██████\\\n███(██(█████)\n██████|█████|\n██████)█████|\n█████|██████|\n
            █████|██████|\n█████|██████|\n█████|██████|\n█████|██████|\n█████|██████|\n
            █████)██████|\n████|███████|\n████|███████|\n████|███████|\n████|███████|\n
            ████|███████|\n█████)██████|\n█████)██████|\n█████)██████|\n█████)██████|\n
            █████)██████|\n█████)██████|\n█████)██████|\n█████\\██████/\n██████)████|\n█████/█████)\n\n"
    },
    x: 7,
    y: 1
}

From .txt files like this :

7,1
$8
███_._███_._
█-~_--°█°--_~-
| |█████████| |
█\ `-.___.-' /█
██`-._ ¤ `\-'
████/ `°\  \
███(  (██)  )
███|  |██|  |
███|  )██(  |
███| |████| |
███| |████| |
███| |████| |
███| |████| |
███| |████| |
███| |████| |
███| )████( |
███||██████||
███||██████||
███||██████||
███||██████||
███||██████||
███| )████( |
███| )████( |
███| )████( |
███| )████( |
███| )████( |
███| )████( |
███| )████( |
███\ \████/ /
████| )██( |
████(/████\)
$15
███_._███_._
█-~███°█████~-
██████████████|
███`-.___████/█
███████¤█`\██
████/██████\
███(██(█████)
██████|█████|
██████)█████|
█████|██████|
█████|██████|
█████|██████|
█████|██████|
█████|██████|
█████|██████|
█████)██████|
████|███████|
████|███████|
████|███████|
████|███████|
████|███████|
█████)██████|
█████)██████|
█████)██████|
█████)██████|
█████)██████|
█████)██████|
█████)██████|
█████\██████/
██████)████|
█████/█████)

foo, bar on the first line of the .txt file indicate the x,y anchor point from the ascii drawing. In the video above this is used to place the tongs in a position where the cursor is in the point of contact of the tongs when they move.

$foo indicate the color layer in which the following characters must be placed until the next $foo

$0 black           
$1 red             
$2 green           
$3 yellow          
$4 blue            
$5 magenta         
$6 cyan            
$7 brightBlack     
$8 white           
$9 brightRed       
$10 brightGreen     
$11 brightYellow    
$12 brightBlue      
$13 brightMagenta   
$14 brightCyan      
$15 brightWhite

Hoping it will be comprehensible or at least bareable to read.

If you enjoy animated ascii art I strongly recommand you to check Stone Story RPG press kit which is full of amazing gifs.

You can also find gorgeous ANSI art archives from 90s to today on 16colo.rs

See you !

faure.adel@gmail.com


Last update : lun. 22 avr. 2019 16:45:22