Pokémon SDK Wiki
Credits

Some coding rules

In PSDK there's some coding rule to follow. We use rubocop but with special settings (because some stuff are anoying or doesn't fit the PSDK use). You can find the Rubocop configuration here : .rubocop.yml

There was a time where rubocop wasn't used at all, we didn't try to make the script from that time comply on the rubocop rules. We usually update the script coding aspect when we need to modify it.

The iterations

In PSDK we strongly discourage anyone to use for loops. Ruby for loops are syntaxic sugar used to write .each call without writing it explicitely. Instead of for you should use :

Here's an example of using iterations instead of for loops :

def find_height(img)
  first_y = img.height.times.find do |y|
    img.width.times.any? { |x| img.get_pixel_alpha(x, y) != 0 }
  end
  return img.height - first_y.to_i
end

Here's the for version :

def find_height(img)
  first_y = nil
  for y in 0...img.height
    for x in 0...img.width
      break first_y = y if img.get_pixel_alpha(x, y) != 0
    end
    break if first_y
  end
  return img.height - first_y.to_i
end

The nil values

In Ruby the instance variables, global variables contain nil if they're not assigned. This often cause issues like NoMethodError (undefined method 'meth' for nil:NilClass) to prevent that there's various way that can be used.

Default value asignment

To assign a default value to a variable (when it's not supposed to be nil and we're not in #initialize) you have two legit solution :

@var = value if @var.nil?

or

@var = value unless @var

The first way is sub-optimal if the variable isn't supposed to be a boolean (ruby will have to call nil? before setting the value). The second way is better but not the recommanded way according to rubocop.

If you need to un-nil a variable use the ||= operator :

@var ||= value

You can use unless @var, it's actually faster than ||= but you should expect rubocop to raise an error.

Safe navigation

The safe navigation is something that was introduced in Ruby 2.3. It allows to call methods or chain call methods without adding a bunch of condition thanks to the &. keyword.

Here's some example :

@system.update if @system

can be written like :

@system&.update
if @system && @system.things && @system.things.other

can be written like :

if @system&.things&.other

The &. keyword is special because it only check if the value on left is nil. If it's nil, the &. chain is broken and the result value is nil which doesn't validate the condition.
The &. chaining is not suitable for comparison since it can return nil unless you know what you're doing, use the safe navigation for method calling. Note that in Ruby expr.> value is valid syntax thus `expr&.> value won't cause an error if expr is nil but it's ugly.

Array creation and operation

If you come from the Essentials you probably use the push method from an empty array to start filling information (because of the for thing or just because you saw that in other scripts).

In PSDK we'll rarely use the push method. We prefer using << instead (it works for both array and strings) and in Ruby 2.5 it has its own opcode (opt_ltlt).

For the array creation we prefer pre-filling the array instead of pushing each value one-by-one.

Example :

ary = [first_thing, second_thing, third_thing]

Instead of :

ary = []
ary.push first_thing
ary.push second_thing
ary.push third_thing

In Ruby the two methods produce the same result but aren't executed the same way. (The first one use specific opcode that are a bit faster than calling push).

For array that contain complex values like the list of Pokémon Info Box (in the party for example) we'll use Array.new(size) { |index| thing_to_push }. This way of creating an array allows us to execute code before adding each value. Example :

party_boxes = Array.new(6) { |index| create_info_box(index) }

Instead of :

party_boxes = []
for index in 0...6
  party_boxes.push create_info_box(index)
end

For multi-line blocks, we'll tend to use next(value) to explicitely say what's stored.

Sometimes there's no other solution than using << to add value to an array (especially when the conditions that gives the value existence in the array are complexe).

Explicitness

As you may have seen in Array creation and operation we'll prefer using explicit code. This is why in the newly produced code you'll see a bunch of method like create_things instead of dosen of line of codes in the blocks.

When you can, try to factorize your code in smaller methods. This allow various thing : We know what the code is supposed to do thanks to the method name. We can customize the things using monkey patch/specialization without having to rewrite everything.

In the rubocop rule used by PSDK we disabled the rules about implicit return/next. If a method or a block should return a value to the thing that called it and use more than one line, put the return/next keyword (according to if it's a method or a block). It helps a lot to read the code.

Factorization

In the new script, we try to factorize the code as much as possible. Factorization is the process of spliting a single method or a part of a method into various methods. The best example of factorization would be the create_graphics method in GamePlay scenes :

def create_graphics
  @viewport = Viewport.create(:main, 500)
  @base_ui = UI::GenericBase.new(@viewport, button_texts)
  @mouse_button_cancel = @base_ui.ctrl.last
  @sub_background = Sprite.new(@viewport).set_bitmap('tcard/background', :interface)
  @trainer_sprite = Sprite.new(@viewport)
                          .set_bitmap(PLAYER_SPRITE_NAME[$trainer.playing_girl], :interface)
  # Adjust the origin of the sprite since the TCard has a smaller surface for the sprite
  @trainer_sprite.set_origin((@trainer_sprite.width - PLAYER_SURFACE.first) / 2,
                              (@trainer_sprite.height - PLAYER_SURFACE.last) / 2)
  @trainer_sprite.set_position(*PLAYER_COORDINATES)
end

Will be factorized into :

def create_graphics
  create_viewport
  create_base_ui
  create_sub_background
  create_trainer_sprite
end

def create_viewport
  @viewport = Viewport.create(:main, 500)
end

def create_base_ui
  @base_ui = UI::GenericBase.new(@viewport, button_texts)
  @mouse_button_cancel = @base_ui.ctrl.last
end

def create_sub_background
  @sub_background = Sprite.new(@viewport).set_bitmap('tcard/background', :interface)
end

def create_trainer_sprite
  @trainer_sprite = Sprite.new(@viewport)
                          .set_bitmap(PLAYER_SPRITE_NAME[$trainer.playing_girl], :interface)
  # Adjust the origin of the sprite since the TCard has a smaller surface for the sprite
  @trainer_sprite.set_origin((@trainer_sprite.width - PLAYER_SURFACE.first) / 2,
                              (@trainer_sprite.height - PLAYER_SURFACE.last) / 2)
  @trainer_sprite.set_position(*PLAYER_COORDINATES)
end

The factorization takes more lines (and is probably a bit slower) but it gives various advantages. It allows thiner monkey-patch and makes the specialization process a bit more easy.

Turn possible parameters into methods

Even if the factorization gives a bit help for specialization, that's not the complete solution. There's one thing we can add to this. In the previous method we factorized a block of code into the create_trainer_sprite method, but in this method we have a really important parameter : the image used. The current version cannot show player variation (haircut for example) or other trainer because we used a constant for the trainer image name. Here's a version that is a bit more specific and allows to create trainer variations :

def create_trainer_sprite
  @trainer_sprite = Sprite.new(@viewport).set_bitmap(trainer_image, :interface)
  # [...]
end

def trainer_image
  PLAYER_SPRITE_NAME[$trainer.playing_girl]
end

This version also takes more line than the previous one but you can now change trainer_image to use the right trainer image according to your own rules (instead of just male/female).

We could have put the trainer_image method inside PFM::Trainer to make the trainer card a bit more dynamic. You should try to answer the question Am I putting this method in the right class ? before simply create a method in the same class for some parameters.