0

I have a very specific error that I cannot seem to resolve. I have tried both the colorize plugin and the extended string class from

Colorized Ruby output

Now what happens is when I want color some text it will work for one character. However, if that character is written over again or sometimes just the character next to it is written with a color, the 2d array becomes corrupted with the colorizing string text.

Note that this problem occurs with the colorize gem in the above stack overflow link AND when I don't use it and just extend the string class myself.

Provided is some sample code so that you can replicate it yourself. Note that you can NEVER use Ruby's pretty print to print the 2d array after coloring text as it will always look corrupted. If you do not wish to install the gem to test it yourself you can just extend the string class like one of the responses of the above link shows. It doesn't matter which version you choose, it doesn't effect the outcome in any way. I will also link that code at the bottom.

EDIT: One color per horizontal line is all that is allowed. Anything more than that causes corruption.

This version works just fine.

private def color(screen_object, i, j, k)

  #Creates 2d
  screen2 = Array.new(25) { Array.new(25, 0) }
  for i in 0...25 do
    if i == 0 || i == 24
      screen2[i] = '$-----------------------$'
    else
      screen2[i] = '|                       |'
    end
  end

  screen2[0][0] = '~'.colorize(:red)

  for i in 0..25
    puts "#{screen2[i]}"
  end

end

This version causes corruption when overwriting the same location with the same char and same color.

private def color(screen_object, i, j, k)
  #Creates 2d
  screen2 = Array.new(25) { Array.new(25, 0) }
  for i in 0...25 do
    if i == 0 || i == 24
      screen2[i] = '$-----------------------$'
    else
      screen2[i] = '|                       |'
    end
  end

  screen2[0][0] = '~'.colorize(:red)
  screen2[0][0] = '~'.colorize(:red)

  for i in 0..25
    puts "#{screen2[i]}"
  end
end

This version doesn't overwrite any chars, but having chars next to each other that are colorized causes corruption.

private def color(screen_object, i, j, k)
  #Creates 2d
  screen2 = Array.new(25) { Array.new(25, 0) }
  for i in 0...25 do
    if i == 0 || i == 24
      screen2[i] = '$-----------------------$'
    else
      screen2[i] = '|                       |'
    end
  end

  screen2[0][0] = '~'.colorize(:red)
  screen2[0][1] = '~'.colorize(:red)

  for i in 0..25
    puts "#{screen2[i]}"
  end
end

But sometimes writing chars right next to each other is fine. Chars that are next to each other vertically don't seem to be affected as much.

private def color(screen_object, i, j, k)
  #Creates 2d
  screen2 = Array.new(25) { Array.new(25, 0) }
  for i in 0...25 do
    if i == 0 || i == 24
      screen2[i] = '$-----------------------$'
    else
     screen2[i] = '|                       |'
    end
  end

  screen2[0][0] = '~'.colorize(:red)
  screen2[1][0] = '~'.colorize(:red)

  for i in 0..25
    puts "#{screen2[i]}"
  end
end

Finally, many chars can have a color as long as they aren't net to each other.

private def color(screen_object, i, j, k)
  #Creates 2d
   screen2 = Array.new(25) { Array.new(25, 0) }
  for i in 0...25 do
    if i == 0 || i == 24
      screen2[i] = '$-----------------------$'
    else
      screen2[i] = '|                       |'
    end
 end

  screen2[0][0] = '~'.colorize(:red)
 screen2[14][22] = '~'.colorize(:red)
  screen2[7][22] = '~'.colorize(:red)
  screen2[2][14] = '~'.colorize(:red)
  screen2[24][24] = '~'.colorize(:red)
  screen2[9][11] = '~'.colorize(:red)

  for i in 0..25
    puts "#{screen2[i]}"
  end
end

Here is the string extension if you don't wish to install gem. Credit goes to user Ivan Black from the other post.

class String
def black;          "\e[30m#{self}\e[0m" end
def red;            "\e[31m#{self}\e[0m" end
def green;          "\e[32m#{self}\e[0m" end
def brown;          "\e[33m#{self}\e[0m" end
def blue;           "\e[34m#{self}\e[0m" end
def magenta;        "\e[35m#{self}\e[0m" end
def cyan;           "\e[36m#{self}\e[0m" end
def gray;           "\e[37m#{self}\e[0m" end

def bg_black;       "\e[40m#{self}\e[0m" end
def bg_red;         "\e[41m#{self}\e[0m" end
def bg_green;       "\e[42m#{self}\e[0m" end
def bg_brown;       "\e[43m#{self}\e[0m" end
def bg_blue;        "\e[44m#{self}\e[0m" end
def bg_magenta;     "\e[45m#{self}\e[0m" end
def bg_cyan;        "\e[46m#{self}\e[0m" end
def bg_gray;        "\e[47m#{self}\e[0m" end
def bold;           "\e[1m#{self}\e[22m" end
def italic;         "\e[3m#{self}\e[23m" end
def underline;      "\e[4m#{self}\e[24m" end
def blink;          "\e[5m#{self}\e[25m" end
def reverse_color;  "\e[7m#{self}\e[27m" end
end
frillybob
  • 600
  • 2
  • 12
  • 26

1 Answers1

2

The reason this is happening is because you're changing a single character into a string of more than one character. For instance, let's consider a simpler example (using the provided String class):

string = "1"
puts [string.length, string[0], string.inspect, string]
puts '---'
string[0] = "1".red
puts [string.length, string[0], string.inspect, string]

The output of this is:

1
1
"1"
1
---
10
  # nothing output, but string[0] = "\e"
\e[31m1\e[0m
1 # (this line is red)

While running this, it's easier to see, the length of the string changes from 1 to 10, so string[0] changes accordingly, from the digit "1" to the character "\e". So now, if we were to change the first character again by re-running just the last couple lines of that script:

puts '---'
string[0] = "1".red
puts [string.length, string[0], string.inspect, string]

the output for this section is:

---
19

\e[31m1\e[0m[31m1\e[0m
1[31m1 # (first character is red, but then we have the random string)

This string is now 'corrupted' because we have replaced only the character "\e" with the string "\e[31m1\e[0m" and then we still have the rest of the original string "[31m1\e[0m" at the end of it.

You can add more than one color per line, you just have to make sure you're accounting for the new length of the string, after you do so, for instance:

string = "12"
string[0] = "1".red
string[-1] = "2".blue
puts string
# outputs "12" with the "1" red and the "2" blue.

This one works because we're making sure not to step on the color codes' toes, by targeting the first character of the string and then targeting the last character, which wasn't affected by adding the color escapes around the first one. If you want the second character in a string, you have to check the length of the colorized string:

string = "123"
string[0] = "1".red
string["1".red.length] = "2".blue
puts string
# '123' with '1' being red, '2' being blue and '3' being unaffected.

Re-reading your example, you might just be confused by the fact that you think your array, screen2 is an array of arrays, and while it starts out that way, it doesn't end that way:

screen2 = Array.new(25) { Array.new(25, 0) }
# at this point, screen2 is an array of arrays, with values of 0
for i in 0...25 do
  # this conditional changes the elements in the outer array to be strings
  # not arrays!
  if i == 0 || i == 24
    screen2[i] = '$-----------------------$'
  else
    screen2[i] = '|                       |'
  end
end
# here, screen2 is an array of strings, not an array of arrays. This
# allows my above explanation to become relevant.

Hope this all makes sense and helps

Simple Lime
  • 10,790
  • 2
  • 17
  • 32