Pokémon SDK Wiki
Crédits

Quelques règles de programmation

Dans PSDK, il y a certaines règles de programmation à suivre. Nous utilisons rubocop mais avec quelques options spéciales (car certaines règles sont ennuyeuses et ne correspondent pas à l'usage de PSDK). Vous pouvez trouver la configuration de rubocop ici : .rubocop.yml

Il fut un temps où rubocop n'était' pas du tout utilisé, nous n'avons pas essayé de passer les vieux scripts à la norme rubocop. Nous modifions l'aspect de programmation lorsque nous avons besoin de modifier un script.

Les itérateurs

Dans PSDK nous décourageons qui que ce soit d'utiliser les boucles for. Les boucles Ruby for sont créées juste pour appeler .each sans l'écrire explicitement. A la place vous pouvez utiliser :

Ici, l'utilisation d'itérations à la place d'une boucle for:

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

Ici, la version for :

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

Les valeurs nils

Dans Ruby les variables d'instances et les variables globales contiennent nil Si elles ne sont pas assignées. Cela cause souvent des problèmes comme NoMethodError (undefined method 'meth' for nil:NilClass) pour éviter cela, il y a d'autres façons de les utiliser.

Assignement des valeurs par défaut

Pour assigner une valeur par défaut à une variable (quand la valeur n'est pas censée être nil et que nous ne sommes pas dans #initialize) vous avez deux solutions legit :

@var = value if @var.nil?

ou

@var = value unless @var

La première n'est pas optimale si la variable n'est pas supposée être un booléen (Ruby appelera nil? avant d'appliquer la valeur). La seconde est plus performante mais n'est pas recommandée par rubocop.

Si vous avez besoin d'assigner une valeur à une variable contenant nil, utilisez l'opérateur ||= :

@var ||= value

Vous pouvez utiliser unless @var, c'est en effet plus rapide que ||= mais vous devez vous attendre à ce que rubocop soulève une erreur.

Navigation sécurisée

La navigation sécurisée est quelque chose qui a été introduit dans Ruby 2.3. Cela permet d'appeler des méthodes ou des méthodes d'appel en châines sans de nombreuses conditions grâce au mot-clé &..

Voici quelques exemples :

@system.update if @system

peut être écrit :

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

peut être écrit :

if @system&.things&.other

Le mot-clé &. est spécial car il vérifie uniquement si la valeur à sa gauche vaut nil. Si elle vaut nil, la chaîne de &. est rompue et le résultat renvoyé est nil qui ne valide pas la condition.
L'enchaînement &. n'est pas adapté à une comparaison puisqu'il peut retourner nil. Sauf si vous savez ce que vous faites, utilisez la navigation sécurisée pour appeler vos méthodes. Notez qu'en Ruby expr.> value est une syntaxe valide ainsi expr&.> value ne causera pas d'erreur si expr vaut nil mais c'est laid.

Créations d'Arrays et fonctionnement

Si vous venez d'Essentials, vous utilisez probablement la méthode push d'un Array vide pour le remplir (en raison de la boucle for ou juste parce que vous avez vu ça dans d'autres scripts).

Dans PSDK nous utilisons rarement la méthode push. Nous préférons utiliser << à la place (Cela marche pour les Arrays et les Strings) et dans Ruby 2.5 il a son propre opcode (opt_ltlt).

Pour la création d'Arrays nous préférons pré-remplir l'array plutôt que d'ajouter des valeurs une par une.

Exemple :

ary = [first_thing, second_thing, third_thing]

à la place de :

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

Dans Ruby les deux méthodes donnent le même résultat mais ne sont pas exécutées de la même façon. (La première utilise un opcode spécifique qui est un peu plus rapide que d'utiliser push).

Pour des Arrays qui contiennent des valeurs plus complexes comme des Pokemon_Info_Box dans une équipe Pokémon, nous utiliserons Array.new(size) { |index| thing_to_push }. Cette manière de créer un array nous permet d'exécuter du code avant d'ajouter chaque valeur. Exemple :

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

à la place de :

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

Pour les blocks multi-lignes, nous aurons tendance à utiliser next(value) pour dire explicitement ce qui est stocké.

Parfois il n'y a pas d'autre soltion que d'utiliser << pour ajouter une valeur à un Array (surtout quand les conditions qui confirme l'existence de la valeur dans l'Array sont complexes).

Explicitation

Comme vous avez pu l'observer dans Créations d'Arrays et fonctionnement nous préférons utiliser du code explicite. C'est pourquoi vous verrez dans le nouveau contenu un tas de méthodes comme create_things à la place de dizaine de lignes de code dans les blocks.

Quand vous le pouvez, essayez de factoriser votre code dans des méthodes plus petites. Cela permet plusieurs choses : Nous savons ce que votre code est censé faire grâce à son nom. Nous pouvons cutomiser le tout en utilisant le monkey patch/specialization sans avoir besoin de tout réécrire.

Dans les règles de rubocop utilisées par PSDK, nous avons désactivé les règles à propos des return/next implicits. Si une méthode ou un block doit retourner une valeur à ce qui l'a appelé et est sur plus d'une ligne, mettez le mot-clef return/next (selon s'il s'agit d'une méthode ou d'un block). Cela aide beaucoup pour lire le code.

Factorisation

Dans un nouveau script, nous tentons de factoriser le code à chaque fois que cela est possible. La factorisation est le processus de découpage d'une seule méthode ou d'une partie de méthode en différentes méthodes. Le meilleur exemple de factorisation est celui de la méthode create_graphics dans les scènes GamePlay :

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

Sera factorisé en :

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

La factorisation prendra plus de lignes (et sera probablement un peu plus lente) mais donne plussieurs avantages. Cela permet des monkey-patch plus précis et rend le processus de spécialisation plus facile.

Changer des paramètres possibles en méthodes

Même si la factorisation donne un peu d'aide pour la spécialisation, ce n'est pas une solution complète. Il y a une chose que l'on peut ajouter à cela. Dans la méthode précédente, nous avions factorisé un bloc de code dans la méthode create_trainer_sprite, mais dans cette méthode nous avons un paramètre très important : l'image utilisée. La version actuelle ne peut pas montrer les modifications du joueur (les cheveux coupés par exemple) ou d'autres dresseurs parce que nous utilisons une constante pour le nom de l'image du dresseur. Voici la version qui est un peu plus spécifique et qui permet de créer des variations d'apparence du dresseur :

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

Cette version prend aussi plus de lignes que la précédente mais vous pouvez maintenant changer trainer_image pour utiliser la bonne image de dresseur selon vos propres règles (au lieu d'uniquement une différence masculin/féminin).

Nous aurions pu mettre la méthode trainer_image dans PFM::Trainer pour créer une Carte de Dresseur un peu plus dynamique. Vous devriez vous poser cette question Suis-je en train de mettre cette méthode dans la bonne classe ? avant de créer simplement la même méthode dans la même class avec quelques paramètres.