Proyek game Ruby Mastermind dengan AI

Aug 19 2020

Saya belajar mandiri dan baru mengenal coding dan membuat game Mastermind di Ruby. Umpan balik atau saran umum apa pun akan sangat dihargai. Gim ini sepenuhnya berfungsi sekarang, dan memiliki AI sederhana. Pada awalnya pemain memilih peran yang ingin mereka pilih (pemecah kode atau pembuat kode).

Bersulang

https://repl.it/repls/WeirdFrankButtons

edit: tautan tetap

class Game

  def initialize
    puts "---------------------------------"
    puts "Welcome to Mastermind"
    puts "The goal is to either create a 4 digit code (Code maker role) containing numbers ranging from 1 through 6 or to guess a code (Codebreaker role) created by the computer within 12 turns to win."
    puts "After each guess you will be given an accuracy score indicating how close you were to guessing the code correctly."
    puts "The letter \"H\" indicates one of the numbers you guessed is in the correct position. The letter \"h\" indicates you guessed a correct number but it is NOT in the correct position"
    puts "----------------------------------"
    @game_over = false
    @turn = 1
    until @comp_guess_mode === "Y" || @comp_guess_mode === "N"
      print "Is the computer the code breaker? Y/N"
      @comp_guess_mode = gets.chomp.upcase
    end
    game_mode
    turn_sequence
  end

  def game_mode
    if @comp_guess_mode == "Y"
      human_code_generator
    else
      code_generator
    end
  end

  def code_generator
    @code = Array.new(4) {rand(1..6)}
  end

  def human_code_generator
    @code = ""
    puts "Please enter a 4 digit code"
    until @code.length == 4
      @code = gets.chomp.each_char.map(&:to_i)
    end
  end
# computer_guesser method that tests if the computer's guess matches the human's
# by iterating through the array, if a direct match ('H') is found it will keep that number in the next guess
  def computer_guesser
    @updated_comp_guess = [" "," "," "," "]
    if @turn == 1
      @guess = Array.new(4) {rand(1..6)}
    else
      i = 0
      while i <4
        if @guess[i] == @code[i]
          @updated_comp_guess[i] = @guess[i]
          i+=1
        else
          i +=1
        end
      end
    end
      @guess = Array.new(4) {rand(1..6)}
      @updated_comp_guess.each_with_index do |value, idx|
        if value != " "
          @guess[idx] = value
        end
      end
      puts "Guess: #{@guess.join}"
  end


  def codebreaker_guess
    @guess = []
    until @guess.length == 4
      puts "Enter your 4 digit guess"
      @guess = gets.chomp.each_char.map(&:to_i)
      puts "Guess: #{@guess.join}"
      if @guess.length != 4
        print "Your guess was not 4 digits long, please guess again \n"
      end
    end
  end

  def turn_display
    puts "-------------------------"
    puts "It's turn number: #{@turn}" 
  end

  #Repeats the following guess/check sequence for 12 turns
  # or until the code and guess are matched
  def turn_sequence
    while @turn <13 && @game_over == false
      turn_display
      if @comp_guess_mode == "Y"
        computer_guesser
      else
        codebreaker_guess
      end       
      guess_checker
      @turn += 1
      victory_check
    end
  end

  def guess_checker
    @guess_accuracy = []
    @i=0
    @h_counter = 0
    while @i<4
      if @guess[@i] == @code[@i]
        @guess_accuracy.push("H")
        @h_counter += 1
        @i+=1
      else
        @i+=1
      end
    end
    if @i == 4
      i = 0
      compare_array = @code.clone
      while i < 4
        if compare_array.include?(@guess[i]) 
          compare_array[(compare_array.index(@guess[i]))]= " "
          @guess_accuracy.push("h")
          i+=1
        else
          i+=1
       end
      end
    @guess_accuracy.pop(@h_counter)
    puts "Guess accuracy: #{@guess_accuracy.join}"
    end
  end


  def victory_check
    if @guess[0..3] == @code[0..3]
      puts "Code was guessed correctly, it's #{@code}, codebreaker wins"
      @game_over = true
    elsif @turn == 13 && @game_over == false
      puts "Code was not guessed correctly, code maker wins"
      @game_over = true
    end
  end

end

game = Game.new

```

Jawaban

2 user985366 Aug 19 2020 at 18:44

Kode 1

while i <4
    if @guess[i] == @code[i]
      @updated_comp_guess[i] = @guess[i]
      i+=1
    else
      i +=1
    end
  end

Baik di ifdan elseAnda menambah i1. Itu bisa dipersingkat.

while i <4
    if @guess[i] == @code[i]
      @updated_comp_guess[i] = @guess[i]
    end
    i += 1
  end

Mirip di bagian guess_checkerbawah, dan dicompare_array

1 JörgWMittag Sep 04 2020 at 03:58

Konsistensi

Terkadang Anda menggunakan 1 spasi untuk indentasi, terkadang Anda menggunakan 2. Terkadang Anda menggunakan spasi kosong di sekitar operator, terkadang tidak, terkadang Anda menggunakan spasi putih di satu sisi operator, tetapi tidak di sisi lainnya. Terkadang, Anda menggunakan spasi setelah koma, terkadang tidak. Terkadang Anda menggunakan satu baris kosong setelah metode, terkadang dua, terkadang tidak ada.

Anda harus memilih satu gaya dan mematuhinya. Jika Anda mengedit beberapa kode yang ada, Anda harus menyesuaikan gaya Anda agar sama dengan kode yang ada. Jika Anda adalah bagian dari sebuah tim, Anda harus menyesuaikan gaya Anda agar cocok dengan anggota tim lainnya.

Sebagian besar komunitas telah mengembangkan panduan gaya komunitas standar. Di Ruby, ada beberapa panduan gaya seperti itu. Mereka semua setuju pada dasarnya (misalnya lekukan adalah 2 spasi), tetapi mereka mungkin tidak setuju pada poin yang lebih spesifik (tanda kutip tunggal atau tanda kutip ganda).

Lekukan

Gaya indentasi standar di Ruby adalah dua spasi. Anda kebanyakan menggunakan 2 spasi, tetapi ada satu tempat di mana Anda menggunakan 1 spasi. Tetap dengan dua.

Ruang kosong di sekitar operator

Harus ada 1 spasi di kedua sisi operator. Anda terkadang menggunakan 1 spasi, terkadang tidak ada spasi, dan terkadang spasi hanya di satu sisi.

Misalnya di sini, Anda memiliki ekspresi yang sama persis dalam tiga baris dengan dua gaya spasi berbeda:

  i+=1
else
  i +=1

Keduanya tidak konsisten satu sama lain, dan keduanya tidak sesuai dengan pedoman komunitas. Keduanya harus:

  i += 1

Spasi setelah koma

Harus ada 1 spasi setelah koma. Anda terkadang menggunakan 1 spasi, terkadang tidak ada spasi.

Misal disini:

@updated_comp_guess = [" "," "," "," "]

seharusnya

@updated_comp_guess = [" ", " ", " ", " "]

Ruang dalam balok

Dalam literal blok, harus ada spasi setelah kurung kurawal buka dan spasi sebelum kurung kurawal tutup:

@code = Array.new(4) { rand(1..6) }

String yang dikutip tunggal

Jika Anda tidak menggunakan interpolasi string, akan membantu jika Anda menggunakan tanda kutip tunggal untuk string Anda. Dengan begitu, terlihat jelas bahwa tidak ada interpolasi string yang terjadi.

Secara khusus, ini juga akan menghapus pelolosan yang perlu Anda lakukan di sini:

puts 'The letter "H" indicates one of the numbers you guessed is in the correct position. The letter "h" indicates you guessed a correct number but it is NOT in the correct position'

Literal string beku

Struktur data yang tidak dapat diubah dan kode fungsional murni selalu disukai, kecuali mutabilitas dan efek samping diperlukan untuk kejelasan atau kinerja. Di Ruby, string selalu bisa berubah, tetapi ada komentar ajaib yang dapat Anda tambahkan ke file Anda (juga tersedia sebagai opsi baris perintah untuk mesin Ruby), yang secara otomatis akan membuat semua string literal tidak berubah:

# frozen_string_literal: true

Biasanya lebih disukai untuk menambahkan komentar ini ke semua file Anda.

Pengubah bersyarat

Ketika Anda memiliki kondisional yang hanya mengeksekusi satu ekspresi, Anda harus menggunakan bentuk pengubah sebagai gantinya, misalnya ini:

if value != " "
  @guess[idx] = value
end

seharusnya

@guess[idx] = value if value != " "

Sama disini:

until @code.length == 4
  @code = gets.chomp.each_char.map(&:to_i)
end

seharusnya

@code = gets.chomp.each_char.map(&:to_i) until @code.length == 4

Tanda kurung yang tidak perlu

compare_array[(compare_array.index(@guess[i]))]= " "

Tanda kurung compare_array.index(@guess[i])tidak diperlukan.

Linting

Anda harus menjalankan semacam linter atau penganalisis statis pada kode Anda. Rubocop adalah salah satu yang populer, tetapi ada yang lain.

Rubocop mampu mendeteksi semua pelanggaran gaya yang saya tunjukkan, dan juga mampu mengoreksi semuanya secara otomatis.

Izinkan saya mengulanginya: Saya baru saja menghabiskan dua halaman yang menunjukkan bagaimana memperbaiki banyak hal yang sebenarnya dapat Anda perbaiki dalam milidetik dengan menekan sebuah tombol. Saya telah mengatur editor saya sedemikian rupa sehingga secara otomatis menjalankan Rubocop dengan perbaikan otomatis segera setelah saya menekan "simpan".

Secara khusus, dengan menjalankan Rubocop pada kode Anda, ia mendeteksi 98 pelanggaran, yang mana secara otomatis dapat memperbaiki 76. Ini membuat Anda memiliki 22 pelanggaran, 11 di antaranya sangat sederhana.

Berikut hasil dari perbaikan otomatis:

# frozen_string_literal: true

class Game
  def initialize
    puts '---------------------------------'
    puts 'Welcome to Mastermind'
    puts 'The goal is to either create a 4 digit code (Code maker role) containing numbers ranging from 1 through 6 or to guess a code (Codebreaker role) created by the computer within 12 turns to win.'
    puts 'After each guess you will be given an accuracy score indicating how close you were to guessing the code correctly.'
    puts 'The letter "H" indicates one of the numbers you guessed is in the correct position. The letter "h" indicates you guessed a correct number but it is NOT in the correct position'
    puts '----------------------------------'
    @game_over = false
    @turn = 1
    until @comp_guess_mode === 'Y' || @comp_guess_mode === 'N'
      print 'Is the computer the code breaker? Y/N'
      @comp_guess_mode = gets.chomp.upcase
    end
    game_mode
    turn_sequence
  end

  def game_mode
    if @comp_guess_mode == 'Y'
      human_code_generator
    else
      code_generator
    end
  end

  def code_generator
    @code = Array.new(4) { rand(1..6) }
  end

  def human_code_generator
    @code = ''
    puts 'Please enter a 4 digit code'
    @code = gets.chomp.each_char.map(&:to_i) until @code.length == 4
  end

  # computer_guesser method that tests if the computer's guess matches the human's
  # by iterating through the array, if a direct match ('H') is found it will keep that number in the next guess
  def computer_guesser
    @updated_comp_guess = [' ', ' ', ' ', ' ']
    if @turn == 1
      @guess = Array.new(4) { rand(1..6) }
    else
      i = 0
      while i < 4
        if @guess[i] == @code[i]
          @updated_comp_guess[i] = @guess[i]
          i += 1
        else
          i += 1
        end
      end
    end
    @guess = Array.new(4) { rand(1..6) }
    @updated_comp_guess.each_with_index do |value, idx|
      @guess[idx] = value if value != ' '
    end
    puts "Guess: #{@guess.join}"
  end

  def codebreaker_guess
    @guess = []
    until @guess.length == 4
      puts 'Enter your 4 digit guess'
      @guess = gets.chomp.each_char.map(&:to_i)
      puts "Guess: #{@guess.join}"
      print "Your guess was not 4 digits long, please guess again \n" if @guess.length != 4
    end
  end

  def turn_display
    puts '-------------------------'
    puts "It's turn number: #{@turn}"
  end

  # Repeats the following guess/check sequence for 12 turns
  # or until the code and guess are matched
  def turn_sequence
    while @turn < 13 && @game_over == false
      turn_display
      if @comp_guess_mode == 'Y'
        computer_guesser
      else
        codebreaker_guess
      end
      guess_checker
      @turn += 1
      victory_check
    end
  end

  def guess_checker
    @guess_accuracy = []
    @i = 0
    @h_counter = 0
    while @i < 4
      if @guess[@i] == @code[@i]
        @guess_accuracy.push('H')
        @h_counter += 1
        @i += 1
      else
        @i += 1
      end
    end
    if @i == 4
      i = 0
      compare_array = @code.clone
      while i < 4
        if compare_array.include?(@guess[i])
          compare_array[compare_array.index(@guess[i])] = ' '
          @guess_accuracy.push('h')
          i += 1
        else
          i += 1
       end
      end
      @guess_accuracy.pop(@h_counter)
      puts "Guess accuracy: #{@guess_accuracy.join}"
    end
  end

  def victory_check
    if @guess[0..3] == @code[0..3]
      puts "Code was guessed correctly, it's #{@code}, codebreaker wins"
      @game_over = true
    elsif @turn == 13 && @game_over == false
      puts 'Code was not guessed correctly, code maker wins'
      @game_over = true
    end
  end
end

game = Game.new

Dan inilah pelanggaran yang Rubocop tidak dapat secara otomatis memperbaiki:

Offenses:

game.rb:3:1: C: Metrics/ClassLength: Class has too many lines. [116/100]
class Game ...
^^^^^^^^^^
game.rb:3:1: C: Style/Documentation: Missing top-level class documentation comment.
class Game
^^^^^
game.rb:4:3: C: Metrics/MethodLength: Method has too many lines. [14/10]
  def initialize ...
  ^^^^^^^^^^^^^^
game.rb:7:121: C: Layout/LineLength: Line is too long. [202/120]
    puts 'The goal is to either create a 4 digit code (Code maker role) containing numbers ranging from 1 through 6 or to guess a code (Codebreaker role) created by the computer within 12 turns to win.'
                                                                                                                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
game.rb:8:121: C: Layout/LineLength: Line is too long. [125/120]
    puts 'After each guess you will be given an accuracy score indicating how close you were to guessing the code correctly.'
                                                                                                                        ^^^^^
game.rb:9:121: C: Layout/LineLength: Line is too long. [186/120]
    puts 'The letter "H" indicates one of the numbers you guessed is in the correct position. The letter "h" indicates you guessed a correct number but it is NOT in the correct position'
                                                                                                                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
game.rb:13:28: C: Style/CaseEquality: Avoid the use of the case equality operator ===.
    until @comp_guess_mode === 'Y' || @comp_guess_mode === 'N'
                           ^^^
game.rb:13:56: C: Style/CaseEquality: Avoid the use of the case equality operator ===.
    until @comp_guess_mode === 'Y' || @comp_guess_mode === 'N'
                                                       ^^^
game.rb:41:3: C: Metrics/AbcSize: Assignment Branch Condition size for computer_guesser is too high. [<12, 12, 11> 20.22/17]
  def computer_guesser ...
  ^^^^^^^^^^^^^^^^^^^^
game.rb:41:3: C: Metrics/MethodLength: Method has too many lines. [19/10]
  def computer_guesser ...
  ^^^^^^^^^^^^^^^^^^^^
game.rb:50:11: C: Style/IdenticalConditionalBranches: Move i += 1 out of the conditional.
          i += 1
          ^^^^^^
game.rb:52:11: C: Style/IdenticalConditionalBranches: Move i += 1 out of the conditional.
          i += 1
          ^^^^^^
game.rb:80:3: C: Metrics/MethodLength: Method has too many lines. [11/10]
  def turn_sequence ...
  ^^^^^^^^^^^^^^^^^
game.rb:94:3: C: Metrics/AbcSize: Assignment Branch Condition size for guess_checker is too high. [<16, 13, 11> 23.37/17]
  def guess_checker ...
  ^^^^^^^^^^^^^^^^^
game.rb:94:3: C: Metrics/MethodLength: Method has too many lines. [27/10]
  def guess_checker ...
  ^^^^^^^^^^^^^^^^^
game.rb:102:9: C: Style/IdenticalConditionalBranches: Move @i += 1 out of the conditional.
        @i += 1
        ^^^^^^^
game.rb:104:9: C: Style/IdenticalConditionalBranches: Move @i += 1 out of the conditional.
        @i += 1
        ^^^^^^^
game.rb:107:5: C: Style/GuardClause: Use a guard clause (return unless @i == 4) instead of wrapping the code inside a conditional expression.
    if @i == 4
    ^^
game.rb:114:11: C: Style/IdenticalConditionalBranches: Move i += 1 out of the conditional.
          i += 1
          ^^^^^^
game.rb:116:11: C: Style/IdenticalConditionalBranches: Move i += 1 out of the conditional.
          i += 1
          ^^^^^^
game.rb:117:8: W: Layout/EndAlignment: end at 117, 7 is not aligned with if at 111, 8.
       end
       ^^^
game.rb:135:1: W: Lint/UselessAssignment: Useless assignment to variable - game.
game = Game.new
^^^^

1 file inspected, 22 offenses detected

Mari kita lihat yang sederhana dulu.

Operator persamaan kasus

Anda menggunakan operator persamaan kasus di beberapa tempat. Karena cara persamaan huruf didefinisikan untuk string, kode Anda hanya berfungsi "secara tidak sengaja". Anda sebaiknya menggunakan operator persamaan normal sebagai gantinya.

Ini:

until @comp_guess_mode === "Y" || @comp_guess_mode === "N"

harus seperti ini:

until @comp_guess_mode == "Y" || @comp_guess_mode == "N"

Perhatikan bahwa Anda menggunakan operator persamaan yang benar untuk pemeriksaan yang sama persis di sini:

if @comp_guess_mode == "Y"

Ekspresi identik di semua cabang

Ada tiga tempat di mana Anda memiliki ekspresi yang sama di kedua cabang ekspresi kondisional. Itu adalah kekacauan yang tidak perlu, Anda bisa menarik ekspresi keluar dari kondisional:

if @guess[i] == @code[i]
  @updated_comp_guess[i] = @guess[i]
  i+=1
else
  i +=1
end

seharusnya

if @guess[i] == @code[i]
  @updated_comp_guess[i] = @guess[i]
end

i +=1

Dan seperti yang kami katakan di atas, kondisional dengan hanya satu ekspresi harus menggunakan bentuk pengubah (perhatikan bahwa transformasi ini akan dilakukan lagi secara otomatis oleh Rubocop jika Anda menjalankannya lagi):

@updated_comp_guess[i] = @guess[i] if @guess[i] == @code[i]

i +=1

Variabel lokal yang tidak digunakan

game = Game.new

gametidak pernah digunakan di mana pun. Hapus saja:

Game.new

Klausul penjaga

Jika Anda memiliki kasus di mana seluruh metode atau blok digabungkan dalam bersyarat, Anda dapat menggantinya dengan "klausa penjaga" dan mengurangi tingkat penumpukan.

Misalnya ini:

def something
  if foo
    bar
    baz
    quux
  else
    42
  end
end

bisa menjadi ini:

def something
  return 42 unless foo

  bar
  baz
  quux
end

Ada beberapa peluang untuk melakukan ini dalam kode Anda, dan beberapa lagi dibuat dengan mengikuti saran Rubocop.

Berikut adalah salah satu contoh di mana peningkatannya tidak terlalu besar:

def game_mode
  if @comp_guess_mode == "Y"
    human_code_generator
  else
    code_generator
  end
end
def game_mode
  return human_code_generator if @comp_guess_mode == "Y"

  code_generator
end

tetapi di sini keuntungannya agak lebih besar:

def guess_checker
  @guess_accuracy = []
  @i=0
  @h_counter = 0
  while @i<4
    if @guess[@i] == @code[@i]
      @guess_accuracy.push("H")
      @h_counter += 1
      @i+=1
    else
      @i+=1
    end
  end
  if @i == 4
    i = 0
    compare_array = @code.clone
    while i < 4
      if compare_array.include?(@guess[i]) 
        compare_array[(compare_array.index(@guess[i]))]= " "
        @guess_accuracy.push("h")
        i+=1
      else
        i+=1
     end
    end
  @guess_accuracy.pop(@h_counter)
  puts "Guess accuracy: #{@guess_accuracy.join}"
  end
end
def guess_checker
  @guess_accuracy = []
  @i = 0
  @h_counter = 0

  while @i < 4
    if @guess[@i] == @code[@i]
      @guess_accuracy.push('H')
      @h_counter += 1
    end
    @i += 1
  end

  return unless @i == 4

  i = 0
  compare_array = @code.clone

  while i < 4
    if compare_array.include?(@guess[i]) 
      compare_array[compare_array.index(@guess[i])]= ' '
      @guess_accuracy.push('h')
    end

    i += 1
  end

  @guess_accuracy.pop(@h_counter)
  puts "Guess accuracy: #{@guess_accuracy.join}"
end

Pemeriksaan yang berlebihan

Tetapi sebenarnya, semuanya bahkan lebih sederhana: karena Anda mengulang dan menaikkan i4 kali, itu akan selalu demikian 4, jadi kondisinya akan selalu benar dan Anda bisa menghapusnya sama sekali.

Kesetaraan dengan boolean

@game_over == false

@game_oversudah menjadi boolean, tidak perlu memeriksa kesetaraan false. Ini hanya

!@game_over

Variabel contoh yang tidak perlu

Variabel instance @updated_comp_guess, @i, @h_counter, dan @guess_accuracyhanya pernah digunakan dalam satu metode. Mereka harus menjadi variabel lokal sebagai gantinya.

Loop

Di Ruby, Anda hampir tidak membutuhkan loop. Faktanya, saya akan melangkah lebih jauh dan mengatakan bahwa jika Anda menggunakan loop di Ruby, Anda salah melakukannya.

Berikut contohnya:

i = 0
while i < 4
  updated_comp_guess[i] = @guess[i] if @guess[i] == @code[i]

  i += 1
end

akan jauh lebih baik ditulis sebagai

4.times do |i|
  updated_comp_guess[i] = @guess[i] if @guess[i] == @code[i]
end

Ini akan membuat guess_checkermetodenya terlihat seperti ini:

def guess_checker
  guess_accuracy = []
  h_counter = 0

  4.times do |i|
    if @guess[i] == @code[i]
      guess_accuracy.push('H')
      h_counter += 1
    end
  end

  compare_array = @code.clone

  4.times do |i|
    if compare_array.include?(@guess[i])
      compare_array[compare_array.index(@guess[i])] = ' '
      guess_accuracy.push('h')
    end
  end

  guess_accuracy.pop(h_counter)
  puts "Guess accuracy: #{guess_accuracy.join}"
end

yang memberi kita sekali lagi kesempatan untuk menggunakan klausul penjaga:

def guess_checker
  guess_accuracy = []
  h_counter = 0

  4.times do |i|
    next unless @guess[i] == @code[i]

    guess_accuracy.push('H')
    h_counter += 1
  end

  compare_array = @code.clone

  4.times do |i|
    next unless compare_array.include?(@guess[i])

    compare_array[compare_array.index(@guess[i])] = ' '
    guess_accuracy.push('h')
  end

  guess_accuracy.pop(h_counter)
  puts "Guess accuracy: #{guess_accuracy.join}"
end

Ekspresi yang berlebihan

Dalam computer_guesser, jika @turn == 1, Anda menginisialisasi @guess, lalu Anda menginisialisasi lagi tanpa pernah menggunakannya di antaranya. Inisialisasi pertama hanya dapat dihapus, dengan memutarnya:

if @turn == 1
  @guess = Array.new(4) { rand(1..6) }
else
  4.times do |i|
    updated_comp_guess[i] = @guess[i] if @guess[i] == @code[i]
  end
end

@guess = Array.new(4) { rand(1..6) }

ke dalam ini:

unless @turn == 1
  4.times do |i|
    updated_comp_guess[i] = @guess[i] if @guess[i] == @code[i]
  end
end

@guess = Array.new(4) { rand(1..6) }

Duplikasi kode

Array.new(4) { rand(1..6) }

Muncul beberapa kali dalam kode Anda. Ini harus diekstraksi menjadi sebuah metode.

length vs. size

Banyak koleksi Ruby memiliki kedua metode lengthdan size, tetapi beberapa hanya memiliki satu. Secara umum, IFF kumpulan memiliki sizemetode, maka metode itu dijamin "efisien" (biasanya waktu konstan), sedangkan lengthmungkin atau mungkin tidak efisien (waktu linier untuk iterasi melalui pengumpulan dan penghitungan semua elemen), tergantung pada koleksi.

Dalam kasus Anda, Anda menggunakan array dan string, yang keduanya memiliki waktu konstan, tetapi jika Anda ingin menjamin efisiensi, lebih baik menggunakan secara eksplisit size.

Gajah di dalam kamar

Satu hal yang belum saya bahas sejauh ini, dan sayangnya saya tidak memiliki waktu untuk membahasnya, adalah desain dasar kode. Semua yang saya sebutkan sejauh ini hanyalah kosmetik.

Semua pekerjaan dilakukan di penginisialisasi. Yang harus dilakukan penginisialisasi hanyalah menginisialisasi objek. Seharusnya tidak meminta masukan pengguna, tidak boleh mencetak apa pun, tidak boleh memainkan permainan.

Selain itu, Anda mencampur I / O dan logika di mana-mana. Suatu metode harus mencetak sesuatu atau melakukan sesuatu. Desain Anda tidak memungkinkan untuk menguji kode tanpa benar-benar memainkan game. Saya tidak bisa menyiapkan file dengan kode dan tebakan dan memberikannya ke pelari uji, saya sebenarnya harus memainkan game secara manual.

Juga aneh bahwa Anda hanya memiliki satu "objek", yaitu permainan, yang melakukan sesuatu. Jika Anda berpikir tentang bagaimana permainan biasanya dimainkan, bukankah objek yang secara aktif melakukan sesuatu adalah pemainnya dan bukan permainannya? Di manakah pemain dalam desain Anda?

Sayangnya, saya tidak punya waktu untuk menyelami ini.

Di sinilah kode saat ini berdiri:

# frozen_string_literal: true

class Game
  def initialize
    puts '---------------------------------'
    puts 'Welcome to Mastermind'
    puts 'The goal is to either create a 4 digit code (Code maker role) containing numbers ranging from 1 through 6 or to guess a code (Codebreaker role) created by the computer within 12 turns to win.'
    puts 'After each guess you will be given an accuracy score indicating how close you were to guessing the code correctly.'
    puts 'The letter "H" indicates one of the numbers you guessed is in the correct position. The letter "h" indicates you guessed a correct number but it is NOT in the correct position'
    puts '----------------------------------'

    @game_over = false
    @turn = 1

    until @comp_guess_mode == 'Y' || @comp_guess_mode == 'N'
      print 'Is the computer the code breaker? Y/N'
      @comp_guess_mode = gets.chomp.upcase
    end

    game_mode
    turn_sequence
  end

  def game_mode
    return human_code_generator if @comp_guess_mode == 'Y'

    code_generator
  end

  def code_generator
    @code = Array.new(4) { rand(1..6) }
  end

  def human_code_generator
    @code = ''
    puts 'Please enter a 4 digit code'
    @code = gets.chomp.each_char.map(&:to_i) until @code.size == 4
  end

  # computer_guesser method that tests if the computer's guess matches the human's
  # by iterating through the array, if a direct match ('H') is found it will keep that number in the next guess
  def computer_guesser
    updated_comp_guess = [' ', ' ', ' ', ' ']

    unless @turn == 1
      4.times do |i|
        updated_comp_guess[i] = @guess[i] if @guess[i] == @code[i]
      end
    end

    @guess = Array.new(4) { rand(1..6) }
    updated_comp_guess.each_with_index do |value, idx|
      @guess[idx] = value if value != ' '
    end

    puts "Guess: #{@guess.join}"
  end

  def codebreaker_guess
    @guess = []

    until @guess.size == 4
      puts 'Enter your 4 digit guess'
      @guess = gets.chomp.each_char.map(&:to_i)
      puts "Guess: #{@guess.join}"
      print "Your guess was not 4 digits long, please guess again \n" if @guess.size != 4
    end
  end

  def turn_display
    puts '-------------------------'
    puts "It's turn number: #{@turn}"
  end

  # Repeats the following guess/check sequence for 12 turns
  # or until the code and guess are matched
  def turn_sequence
    while @turn < 13 && !@game_over
      turn_display
      if @comp_guess_mode == 'Y'
        computer_guesser
      else
        codebreaker_guess
      end
      guess_checker
      @turn += 1
      victory_check
    end
  end

  def guess_checker
    guess_accuracy = []
    h_counter = 0

    4.times do |i|
      next unless @guess[i] == @code[i]

      guess_accuracy.push('H')
      h_counter += 1
    end

    compare_array = @code.clone

    4.times do |i|
      next unless compare_array.include?(@guess[i])

      compare_array[compare_array.index(@guess[i])] = ' '
      guess_accuracy.push('h')
    end

    guess_accuracy.pop(h_counter)
    puts "Guess accuracy: #{guess_accuracy.join}"
  end

  def victory_check
    if @guess == @code
      puts "Code was guessed correctly, it's #{@code}, codebreaker wins"
      @game_over = true
    elsif @turn == 13 && !@game_over
      puts 'Code was not guessed correctly, code maker wins'
      @game_over = true
    end
  end
end

Game.new
```