Skip to content

Instantly share code, notes, and snippets.

@123-Notus
Last active August 10, 2019 08:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save 123-Notus/01f71ef57d3955dbd5d829ad80e2abe8 to your computer and use it in GitHub Desktop.
Save 123-Notus/01f71ef57d3955dbd5d829ad80e2abe8 to your computer and use it in GitHub Desktop.

Array extending in Starblast Ship Editor

This is an example of extending JavaScript native Array object with our own functions (methods), written in CoffeeScript:

Array::add = (mod) -> @map (val) -> val + mod
Array::mult = (mod) -> @map (val) -> val * mod
Array::div = (mod) -> @map (val) -> val / mod
Array::step = (step) -> i for i in @ by step
Array::fill = (val, len) -> val for i in (
  if len
  then [0...len]
  else if @length == 1
  then [0...@[0]]
  else @
)

Then we can use them like this:

[0, 1, 2].add(2)         # [2, 3, 4]
[0, 1, 2].mult(2)        # [0, 2, 4]
[0, 1, 2].add(2).mult(2) # [4, 6, 8]
[0, 2, 4].div(2)         # [0, 1, 2]

# ranges - dots matter!
[0...2]                  # [0, 1]
[0..2]                   # [0, 1, 2]

[0..2].add(2)            # [2, 3, 4]
[0..2].mult(2)           # [0, 2, 4]
[0..2].add(2).mult(2)    # [4, 6, 8]

[0..2].fill(0)           # [0, 0, 0]

[0..4].step(2)           # [0, 2, 4]
[0..4].step(2).fill(0)   # [0, 0, 0]

# fill specifics
[].fill(0)               # []
[].fill(0, 3)            # [0, 0, 0]
[3].fill(0)              # [0, 0, 0] - instead of just [0]

One can rename step to by - to make it look similar to for loop with "by step", but I just put more meaningful name.

And it works absolutely fine in plain CoffeeScript, but not in our Ship Editor.
Unfortunately Ship Editor has a bug, because of which you won't be able to make a Mod Export of your ship after adding these useful functions. But luckily, there are plenty of options to fix it nicely. I'll show you some of the most worthy.

Fixes for Ship Editor

There are two main types of fixes: post-fix and pre-fix, and two sub-types: simple-straightforward and universal-reliable.
All fixes are made in such a way that no matter how many functions you add in the future for Array, fixes will continue to work and nothing needs to be corrected.

1. Post-fix

It's when uwu oopsie woopsie we made a fucky wucky or ewen fucko boingo already, and now we wanna fix it after an incident. You should put this fix after extending Array with your functions.
The main advantage of this way is that we can keep original syntax of Array extending. E.g. if someone got used to it or is going to use it elsewhere.

a) Simple and straightforward

This method is about fixing one particular error, which happens currently in Ship Editor during Mod Export (and some other internal tasks). It won't help anymore, once after some Ship Editor update we might get more kind of errors.
But it's pretty simple! And works now.

for key of []
  Array::[key].toFixed = ->

b) Universal and reliable

This one is a complex and smart solution, which will make all our custom functions look similar to native Array methods. Ship Editor will always be happy enough to never produce an error because of them, no matter how many updates he gets in the future (if ever, welp).

for key, value of []
  delete Array::[key]
  Object.defineProperty Array::, key,
    value: value
    configurable: true
    enumerable: false

But since it's a post-fix and we already made an oopsie woopsie - it has to delete our fucko-wucko first, and only then it can go further to re-add our functions more covertly.
That's why here is the next section.

2. Pre-fix

So maybe one don't wanna oopsie woopsie at all? Here is mighty pre-paration actions in advance, which will help you avoid even creation of "bad" functions (despite they could be fixed a moment later using post-fix). You should put this fix before extending Array with your functions.
But also with the main drawback - the loss of the classic syntax of extending. So it's with usage examples of extending here.

a) Simple and straightforward

Remember, only against current particular error.

Array.extend = (properties) ->
  for own key, value of properties
    @::[key] = value
    @::[key].toFixed = ->

Array.extend
  add: (mod) -> @map (val) -> val + mod
  mult: (mod) -> @map (val) -> val * mod

b) Universal and reliable

And finally, maybe the most interesting methods, since both reliable and preliminary.
I will show you 3 options, with a slightly different usage syntax of extending - pay attention mainly to it:

Array.extend = (properties) ->
  for own key, value of properties
    Object.defineProperty @::, key,
      value: value
      configurable: true
      enumerable: false

Array.extend
  add: (mod) -> @map (val) -> val + mod
  mult: (mod) -> @map (val) -> val * mod
extend = (type, properties) ->
  for own key, value of properties
    Object.defineProperty type::, key,
      value: value
      configurable: true
      enumerable: false

extend Array,
  add: (mod) -> @map (val) -> val + mod
  mult: (mod) -> @map (val) -> val * mod
extend = (descriptor) ->
  for own type, properties of descriptor
    for own key, value of properties
      Object.defineProperty @[type]::, key,
        value: value
        configurable: true
        enumerable: false

extend Array:
  add: (mod) -> @map (val) -> val + mod
  mult: (mod) -> @map (val) -> val * mod

Another difference is that the last two options allow you to extend other types/objects besides Array. And the last one - even inside one shared call:

extend
  Array:
    add: ->   # some function
    mult: ->  # some function
  Object:
    merge: -> # some function
  String:
    oof: ->   # some function
  Number:
    welp: ->  # some function

But most probably you won't ever need this, because for other native types JavaScript/CoffeeScript have enough native methods you could use for ship building. So, just choose by preferred syntax. Although, who knows the future...

Also there are some possible optimizations, but they are just unnecessary code complications here because this code runs only once after compilation, works pretty fast and with a very small number of extending functions
(totally not some 1e100 cycles).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment