Rudimentary Light Casting

Twine Version: 2.3.9
Story Format: 2.31.1

Hi folks,

I am trying to extend the “navigating a dungeon” tutorial from the Twine Cookbook to include a rudimentary light casting system, as if the player character had a flashlight. My aim is to only have the dungeon tiles that the player character can “see” be printed. I’ve implemented something similar with the libtcod library in the past, but that library has built in support for this sort of thing.

In any case, I am not trying to build a complex roguelike so much as to use a simple roguelike as the vehicle to tell my story.

Here’s what I have so far:

"<span id="map">
<<nobr>>
<<for $i to 0; $i lt $mapArray.length; $i++>>
	<<for $k to 0; $k lt $mapArray[$i].length; $k++>>
		<<if $k eq $positionX and $i eq $positionY>>
			<<print "@">>
		<<elseif $mapArray[$i][$k] eq 1 and $lightRadius.includesAny($mapArray[$i][$k])>>
			<<print ".">>
		<<elseif $mapArray[$i][$k] eq 0 and $lightRadius.includesAny($mapArray.indexOf($i,$k))>>
			<<print "#">>
		<<elseif $mapArray[$i][$k] eq 2 and $lightRadius.includesAny($mapArray.indexOf($i,$k))>>
			<<print "E">>
		<<else>>
			<<print "\xB0">>
		<</if>>
	<</for>>
	<<print "<br>">>
<</for>>
<</nobr>>
</span>"

And here is $lightRadius (initialized in another passage):

<<set $lightRadius to [
[$positionX-1, $positionY-1],[$positionX-1, $positionY,],[$positionX-1, $positioY+1],[$positionX, $positionY-1],[$positionX, $positionY],[$positionX, $positionY+1],[$positionX+1, $positionY-1],[$positionX+1, $positionY],[$positionX+1, $positionY+1]]>>"

Here is the output I get:

° ° ° ° ° ° ° ° ° ° °
° ° ° @ ° ° ° ° ° ° °
° ° ° ° ° ° ° ° ° ° °
° ° ° ° ° ° ° ° ° ° °
° ° ° ° ° ° ° ° ° ° °
° ° ° ° ° ° ° ° ° ° °
° ° ° ° ° ° ° ° ° ° °
° ° ° ° ° ° ° ° ° ° °
° ° ° ° ° ° ° ° ° ° °
° ° ° ° ° ° ° ° ° ° °
° ° ° ° ° ° ° ° ° ° ° 

So, am I barking up the right tree, here? What I am attempting is to access the indices of the nested mapArray array and check it against another array that has the xy positions around the player character. Then the Location passage would only print those squares, with the remainder being another ASCII character (I was aiming for the one that is like a dot filled rectangle, so still working on figuring out how to display extended ASCII).

Obviously I am not understanding how to properly access arrays and/or use the appropriate methods. Or, perhaps my approach is completely wrong.

Any tips or direction would be greatly appreciated – thank!

–Matt

1 Like

If you look at your post you’ll see that this forum tends to “eat” code inside of angle brackets, unless you put it inside of a “Preformatted text” marker (using the </> button in the editor toolbar).

You should edit your post, select any code, and use the </> button so we can read your code.

As for displaying the ASCII “map”, you should use a monospace font, since every character in a monospace font has the same width.

Also, the .includesAny() method doesn’t work like you apparently think it does. For example, on this line:

<<elseif $mapArray[$i][$k] eq 1 and $lightRadius.includesAny($mapArray[$i][$k])>>

Since $mapArray[$i][$k] has to be set to 1 to get to the part after the “and”, that means that you’re searching the $lightRadius to see if it includes a 1, which it won’t, since it contains an array of arrays.

Also, since arrays are objects, you can’t simply compare two arrays to see if they’re the same, because, instead of being stored by value, objects are stored by reference. “Stored by reference” basically means that the object variable refers to the location in memory where the data exists. Unless two objects are referring to the same piece of memory, they won’t show up as equal if you just do a basic comparison, even if the values within the data that they point to are otherwise the same. Only primitives (i.e. numbers, strings, Booleans (true / false), null, and undefined) are stored by value, thus can be directly compared like that.

So, rather than creating a difficult to compare $lightRadius array, you could simply do this:

<<elseif ($mapArray[$i][$k] eq 1) and ((Math.abs($i - $positionY) eq 1) or (Math.abs($k - $positionX) eq 1))>>

That checks to see if the current position is +/-1 on the X or Y axis. (Math.abs() gives you the absolute value, so the result will always be positive.)

I think that should do the trick.

A few other suggestions:

If you’re not using the value of a variable again in a later passage, then instead of using a story variable (one that starts with a $) to hold that value, you should make sure you use a temporary variable (one that starts with an _) for that. The reason why is that story variables are stored in the game’s history, while temporary variables are not, and the larger the game’s history is, the slower saves, loads, and passage transitions will be. So you want to avoid bloating up the game’s history with unnecessary data. Thus, for example, you should probably change $i and $k to _i and _k, since you don’t need the values in those variables in any later passages.

The other suggestion would be that, instead of storing numbers in the $mapArray, why not simply store the ASCII character that you want displayed? That way you could simply do this:

<<if (_k eq $positionX) and (_i eq $positionY)>>
	@
<<elseif (Math.abs(_i - $positionY) eq 1) or (Math.abs(_k - $positionX) eq 1)>>
	<<= $mapArray[_i][_k]>>
<<else>>
	<<= "\xB0">>
<</if>>

(<<=>> is shorthand for <<print>>.)

And finally, instead of doing:

<<print "
">>

you can just do:

<br>

which is an HTML line break, which is what your code gets turned into anyways.

Hope that helps! :grinning:

1 Like

Yes! Super helpful! Thanks for all the examples and explanations. I did not realize that arrays, being objects, can not be directly compared. I made most of the changes you recommended, and I tweaked the code a little bit to get it functioning just how I want it. I really appreciate you help and input!

<span id="map">
<<nobr>>
<<for _i to 0; _i lt $mapArray.length; _i++>>
	<<for _k to 0; _k lt $mapArray[_i].length; _k++>>
		<<if _k eq $positionX and _i eq $positionY>>
			<<print "@">>
		<<elseif $mapArray[_i][_k] eq 1 and ((Math.abs(_i-$positionY) lt 2) and (Math.abs(_k - $positionX) lt 2))>>
			<<print ".">>
		<<elseif $mapArray[_i][_k] eq 0 and ((Math.abs(_i-$positionY) lt 2) and (Math.abs(_k - $positionX) lt 2))>>
			<<print "#">>
		<<elseif $mapArray[_i][_k] eq 2 and ((Math.abs(_i-$positionY) lt 2) and (Math.abs(_k - $positionX) lt 2))>>
			<<print "E">>
		<<else>>
			<<print "\x96">>
		<</if>>
	<</for>>
	<<print "<br>">>
<</for>>
<</nobr>>
</span>

– – – – – – – – – – –
– – – – # # # – – – –
– – – – . @ . – – – –
– – – – # # # – – – –
– – – – – – – – – – –
– – – – – – – – – – –
– – – – – – – – – – –
– – – – – – – – – – –

Glad I could help, but, just so you’re aware, you don’t need to use any of those <<print>> macros, other than the <<print "\x96">> one (though you could just do &#x96; instead in that case). If you just put the character or HTML there, then it will display that character or use that HTML. You don’t need to use the <<print>> macro to display things, except for a few specific cases, such as displaying the output of functions and the like (e.g. code like: You're currently in the "<<print passage()>>" passage.).

Anything which isn’t within angle brackets will normally be displayed automatically as-is, with the exceptions of markup and “naked variables”.

Also, if you want to get rid of the spaces between those characters, I’d recommend adding the characters to a string variable, and then displaying that string at the end of the loop.

Have fun! :grinning: