+1 vote
by (2.9k points)
edited by
Hi,

I'm making a game but i'm stuck on getting the arrow keys to move the map, i also need to get a working map aswel.

 

Here is what i want my map to look like.

https://usercontent1.hubstatic.com/8108494_f520.jpg

2 Answers

0 votes
by (6.2k points)
Please could you give some detail and actually tell us what the problem is, as well as your story format.
by (68.6k points)

There is not much "special" about array index access.

I never said there was much special about the handling of arrays.  Where are you getting that from?

The handling of array indices and treatment of the length property is basically it.  My point is, has ever been, that if you violate the limitations of that special handling, and there are limitations, then you're dealing with the array object as a regular object and not as an array (i.e. without the special handling).  I do not see what's hard to understand about that.

 

Even then, you can have plain objects act like arrays in this regard as well.

var notAnArray = {};
notAnArray.__proto__ = Array.prototype;

Everything else is identical. In particular, obj[-1] and arr[-1] work the same way and are valid access methods for properties of both.

That is not doing what you think it is.  All you've done there is add Array as your object's prototype.  The object will get access to the Array methods, sure enough.  It will not, however, receive any of the special array index/length handling.

For example, try the following:

var notAnArray = {};
notAnArray.__proto__ = Array.prototype;
console.log(notAnArray.length); // yields: 0
notAnArray.push('foo');
console.log(notAnArray.length); // yields: 1
notAnArray[9] = 'bar';
console.log(notAnArray.length); // yields: 1 (not 10)

var isAnArray = [];
console.log(isAnArray.length); // yields: 0
isAnArray.push('foo');
console.log(isAnArray.length); // yields: 1
isAnArray[9] = 'bar';
console.log(isAnArray.length); // yields: 10

Notice how adding the far member to both, thus attempting to making them sparse, did not update the length property of your object, while doing so with the array object did.

Additionally.  You can take that example further to show that out-of-range indices do not trigger the special array handling.   Like so:

var isAnArray = [];
console.log(isAnArray.length); // yields: 0
isAnArray.push('foo');
console.log(isAnArray.length); // yields: 1
isAnArray[1e12] = 'bar';
console.log(isAnArray.length); // yields: 1 (not 1e12+1)

/*
	Both of the following show only 'foo' because 1e12 is an
	invalid index and does not trigger the special array handling.
*/
isAnArray.forEach(m => console.log(m));
for (var m of isAnArray) console.log(m);

Does the 1e12 property exist on the object and have the value 'bar'?  Yes, certainly.  Is it considered part of the array by JavaScript?  Absolutely not.

 

As I've said multiple times now, while you may certainly use invalid array indices with an array, if you do, then you're not dealing with the array object as an array but as a regular object (i.e. the special array handling is not applied).

by (8.6k points)
As I've said multiple times now, this doesn't mean you can't use those array indices just fine in JavaScript, as opposed to what the video is claiming.

You don't get the benefit of the array functions (though that's relatively easy to "fix" using a Proxy if you really want to), but it won't break anything either.
by (68.6k points)
edited by

As I've said multiple times now, this doesn't mean you can't use those array indices just fine in JavaScript, as opposed to what the video is claiming.

They're regular object properties, not array indices, and claiming otherwise is both disingenuous and harmful to people without a firm grasp of JavaScript (likely most of the people who'll read this).

 

You don't get the benefit of the array functions (though that's relatively easy to "fix" using a Proxy if you really want to), but it won't break anything either.

It breaks both the length the property and thus iterators ("break" here meaning failing to work as intended).  That may not matter to you, but do not claim that it doesn't break anything.  That's disingenuous malarkey.

Beginners thinking that they'll be able to use invalid array indices as they can valid indices are going to be sadly surprised when anything other than absolutely basic access fails for no reason they'll be able to figure out.

These distinctions matter and you're not doing anyone any favors by claiming that they don't.

by (8.6k points)
If iterators matter to you, you can override the array's Symbol.iterator. If the other methods should work differently, you can override most of it too, in doubt packing it all in a Proxy. That's like basic JavaScript nowadays ...
by (68.6k points)
Don't shift the goalposts.  And do you honestly expect beginners, or even intermediates, to have any clue what you're talking about?

Beyond that, the entire idea that spawned this, that you even need negative indices, is silly.  If you really want to use negative Cartesian coordinates while using an array, it's both correct and easier (than overriding every bloody thing) to simply write a function or method which offsets for the size of the map, so the array indices need not be negative (which they cannot actually be anyway).  Or, if you really wanted to apply negative coordinates directly to the object access, just use a generic object (at least then, there's no mistaking that you're not using an array and/or using it properly).
0 votes
by (8.6k points)

Here's a partial answer, about how to make a map.

First the necessary style sheets: In my case, I decided to have the maps displayed in a separate div with the class "map", and each of the map "icons" be a 32x32 px in size <i> element with the background image being the map content - so you can still put something inside the <i>. The main work is done by the following style sheet declarations.

div.map {
  line-height: 0;
  font-size: 0;
}

div.map > i {
  display: inline-block;
  width: 32px;
  height: 32px;
}

Following that, each tile type gets their own small CSS with the "background" property set to an image. I won't paste them here, because of the size, but it's simply four of them for now: i.map-grass, i.map-hills, i.map-mountain and i.map-woods.

Now it's time to define our map. I created a separate passage for this (named "world map"), which will be read as text and contains the map icons represented by ASCII characters. One map line per map row. Accessible via Story.get("world map").text if you need it for further processing.

.........
...H.....
W..HH....
W...HMHH.
.W...HH..
W.WW...H.
.WW..W...

Now we just need a few widgets and some static data initialisers to finish our rendering off. One helper widget for rendering a single map icon (in case we ever need it separately), and one for rendering a given map ("world map" per default, but it can work with any given as an argument). All of this packed into a single passage tagged "widget" and "nobr".

<<set setup.MapIcons = {
	".": "grass",
	"M": "mountain",
	"H": "hills",
	"W": "woods",
}>>

<<widget "icon">>
<<= "<i class='map-" + $args[0] + "'></i>">>
<</widget>>

<<widget "showmap">>
  <<set _mapName = $args[0] || "world map", _mapResult = "">>
  <<if Story.has(_mapName)>>
	<<script>>
	  State.temporary.mapResult = "@@.map;\n";
	  for(var m of Story.get(State.temporary.mapName).text) {
	    if(m === "\n") {
		  State.temporary.mapResult += "<br>";
		} else {
		  State.temporary.mapResult += "<<icon '" + setup.MapIcons[m] + "'>>";
		}
	  }
	  State.temporary.mapResult += "\n@@";
	<</script>>
	<<= _mapResult>>
  <</if>>
<</widget>>

And that's it. Just include <<showmap>> (or <<showmap "world map">> or <<showmap "tamriel">> ...) anywhere in a passage and it will drop a <div class="map"> element with the map in that spot.

Showcase: standalone working example, Twine source file

...