noah
noah

Reputation: 107

ascii art generator in c++ newlining

I am trying to generate ASCII art given a string.

art.cpp

#pragma once

#include <string>
#include <vector>

#include "art.h"

std::string Art::display(std::string& text) {
    std::string final_text;
    final_text.reserve(text.length());
    for (char letter: text) {
        std::string single = letters[letter];
        /* tried this */ single.erase(std::remove(single.begin(), single.end(), '\n'), single.end());
        final_text += single;
    }
    return final_text;
}

art.h

#pragma once

#include <string>
#include <vector>
#include <map>

class Art {
public:
    std::map<char, std::string> letters = { 
        std::make_pair('a', std::string(R"(  
    _
   / \
  / _ \
 / ___ \
/__/ \__\
)")),
        std::make_pair('b', std::string(R"(
  ____
 | __ ) 
 |  _ \ 
 | |_) |
 |____/ 
)")),
        std::make_pair('c', std::string(R"(
   ____ 
  / ___|
 | |    
 | |___ 
  \____|
)")),
        std::make_pair('d', std::string(R"(
  ____  
 |  _ \ 
 | | | |
 | |_| |
 |____/  
)")),
        std::make_pair('e', std::string(R"(
  _____ 
 | ____|
 |  _|  
 | |___ 
 |_____|
)")),
        std::make_pair('f', std::string("asdf")),
        std::make_pair('g', std::string("asdf")),
        std::make_pair('h', std::string(R"(
  _   _ 
 | | | |
 | |_| |
 |  _  |
 |_| |_|
)")),
        std::make_pair('i', std::string("asdf")),
        std::make_pair('j', std::string("asdf")),
        std::make_pair('k', std::string(R"(
  _  __
 | |/ /
 | ' / 
 | . \ 
 |_|\_\
)")),
        std::make_pair('l', std::string("asdf")),
        std::make_pair('m', std::string("asdf")),
        std::make_pair('n', std::string("asdf")),
        std::make_pair('o', std::string(R"(
   ___  
  / _ \ 
 | | | |
 | |_| |
  \___/ 
)")),
        std::make_pair('p', std::string("asdf")),
        std::make_pair('q', std::string("asdf")),
        std::make_pair('r', std::string(R"(
  ____  
 |  _ \ 
 | |_) |
 |  _ < 
 |_| \_\
)")),
        std::make_pair('s', std::string("asdf")),
        std::make_pair('t', std::string("asdf")),
        std::make_pair('u', std::string("asdf")),
        std::make_pair('v', std::string("asdf")),
        std::make_pair('w', std::string("asdf")),
        std::make_pair('x', std::string("asdf")),
        std::make_pair('y', std::string(R"(
 __   __
 \ \ / /
  \ V / 
   | |  
   |_|  
)")),
        std::make_pair('z', std::string("asdf"))

    };

    std::string display(std::string& text);

};

This is taking a str reference in then looping over each character and finding that character in the map and getting its corresponding ASCII art letter then adding it to a final string.
This presents a problem. It prints each character on a new line when I want to print the string Art::display() returns.
What I have tried? I have tried removing the newlines but that jumbles bits and pieces together/apart.
What i want it to do? I want it to print a word left to right and not each char on a new line.

Upvotes: 3

Views: 1758

Answers (3)

Ted Lyngmo
Ted Lyngmo

Reputation: 117832

I'd add a Letter class to simplify parsing and printing letters.

Example:

#include <iomanip>
#include <iostream>
#include <map>
#include <string_view>
#include <vector>

class Letter {
public:
    // The constructor takes a string_view and parses it into lines that
    // it stores in a vector<string_view>
    Letter(std::string_view l) : width_(0) {
        auto b = l.begin();
        for(auto it = b; it != l.end(); ++it) {
            if(*it == '\n') {
                auto w = it - b;
                if(w > width_) width_ = w;
                svs.emplace_back(b, w);
                b = it + 1;
            }
        }
        auto w = l.end() - b;
        if(w > width_) width_ = w;
        svs.emplace_back(b, w);
    }

    // some convenience functions
    unsigned width() const { return width_; }
    unsigned height() const { return svs.size(); }

    // An operator to get a specific line to render from this Letter.
    // It returns a string with the proper width (the max width of all the
    // lines for this Letter). If `line` is out of bounds, it returns a
    // blank line.    
    std::string operator()(unsigned line) const {
        if(line >= svs.size()) return std::string(width_, ' ');
        return std::string(svs[line]) + std::string(width_ - svs[line].size(), ' ');
    }

    // If you just want to print one letter:
    friend std::ostream& operator<<(std::ostream& os, const Letter& l) {
        for(auto& sv : l.svs) os << sv << '\n';
        return os;
    }

private:
    std::vector<std::string_view> svs;
    unsigned width_;
};

// A user defined literal operator to simplify creating the map:
Letter operator "" _L(const char* str, std::size_t len) {
    return std::string_view{str, len};
}

//---------------------------------------------------------------
std::map<char, Letter> letters = {
{' ', "   "_L }, // space
{'a',
R"(    _
   / \
  / _ \
 / ___ \
/__/ \__\
)"_L},
{'b',
R"( ____
| __ ) 
|  _ \ 
| |_) |
|____/ 
)"_L}
}; // note that each Letter ends with _L to trigger the user defined literal

// A printing function that can either be called with a specified height,
// or be made to find the appropriate height for this string.
void print(std::string_view txt, unsigned height = 0) {
    if(height == 0) {
        for(char ch : txt) {
            try {
                auto w = letters.at(ch).height();
                if(w > height) height = w;
            }
            catch(...) {} // don't mind non-existing letters
        }
    }
    for(unsigned i = 0; i < height; ++i) {
        for(char ch : txt) {
            const Letter& l = letters.at(ch);
            std::cout << l(i);
        }
        std::cout << '\n';
    }
}

int main() {
    print("abab baba");
}

Output:

    _     ____      _     ____      ____      _     ____      _    
   / \   | __ )    / \   | __ )    | __ )    / \   | __ )    / \   
  / _ \  |  _ \   / _ \  |  _ \    |  _ \   / _ \  |  _ \   / _ \  
 / ___ \ | |_) | / ___ \ | |_) |   | |_) | / ___ \ | |_) | / ___ \ 
/__/ \__\|____/ /__/ \__\|____/    |____/ /__/ \__\|____/ /__/ \__\

Note: MSVC (the compiler included with Visual Studio) had problems with my string_views for some reason. Here's a version that uses std::strings instead to make MSVC happy:

#include <iomanip>
#include <iostream>
#include <iterator>
#include <map>
#include <string>
#include <vector>

class Letter {
public:
    // The constructor takes a string_view and parses it into lines that
    // it stores in a vector<string_view>
    Letter() = default;
    Letter(const std::string& str) {
        auto b = str.cbegin();
        auto end = str.cend();
        for(std::string::const_iterator it = b; it != end; ++it) {
            if(*it == '\n') {
                std::size_t w = std::distance(b, it);
                if(w > width_) width_ = w;
                svs.emplace_back(b, it);
                b = it + 1;
            }
        }
        std::size_t w = std::distance(b, str.cend());
        if(w > width_) width_ = w;
        svs.emplace_back(b, str.cend());
    }

    // some convenience functions
    std::size_t width() const { return width_; }
    std::size_t height() const { return svs.size(); }

    // An operator to get a specific line to render from this Letter.
    // It returns a string with the proper width (the max width of all the
    // lines for this Letter). If `line` is out of bounds, it returns a
    // blank line.    
    std::string operator()(std::size_t line) const {
        if(line >= svs.size()) return std::string(width_, ' ');
        return svs[line] + std::string(width_ - svs[line].size(), ' ');
    }

    // If you just want to print one letter:
    friend std::ostream& operator<<(std::ostream& os, const Letter& l) {
        for(auto& sv : l.svs) os << sv << '\n';
        return os;
    }

private:
    std::vector<std::string> svs;
    std::size_t width_ = 0;
};

// A user defined literal operator to simplify creating the map:
Letter operator "" _L(const char* str, std::size_t len) {
    return std::string(str, str + len);
}

//---------------------------------------------------------------
std::map<char, Letter> letters = {
{' ', "   "_L },
{'a',
R"(    _
   / \
  / _ \
 / ___ \
/__/ \__\
)"_L},
{'b',
R"( ____
| __ ) 
|  _ \ 
| |_) |
|____/ 
)"_L}
}; // note that each Letter ends with _L to trigger the user defined literal

// A printing function that can either be called with a specified height,
// or be made to find the appropriate height for this string.
void print(std::string_view txt, std::size_t height = 0) {
    if(height == 0) {
        for(char ch : txt) {
            try {
                auto w = letters.at(ch).height();
                if(w > height) height = w;
            }
            catch(...) {} // don't mind non-existing letters
        }
    }
    for(std::size_t i = 0; i < height; ++i) {
        for(char ch : txt) {
            const Letter& l = letters.at(ch);
            std::cout << l(i);
        }
        std::cout << '\n';
    }
}

int main() {
    print("abab baba");
}

Upvotes: 2

JDługosz
JDługosz

Reputation: 5652

For your output, keep an array of 5 lines, for the 5 distinct lines of the Art characters. When appending an art character, split it at the newlines, and append the first line of the art character to the first line of output, the second line of the art character to the second line of output, etc.


Meanwhile, you can use string_view instead of string for your map, and streamline your source by using UDLs and just using { } instead of make_pair.

        std::make_pair('a', std::string(R"(  
    _
   / \
  / _ \
 / ___ \
/__/ \__\
)")),

becomes:

        {'a', R"(  
    _
   / \
  / _ \
 / ___ \
/__/ \__\
)"sv },

Upvotes: 2

ShadowMitia
ShadowMitia

Reputation: 2533

In the console you are writing line by line typically. When you print one of the characters, you take the string, and each line with its \n will be printed. Line by line.

The fact that each character is printed one after the other, is just normal, this is how outputing works: line by line.

The jumbing is just the lines been appended to the same line. For the charachter a, instead of having X lines to draw the character, they all get merged into one line. Which is not what you want, but you were edging towards the right idea I think.

What you need to do is for all the required characters, merge them line-wise, and output the result line by line as you are already doing, but instead of one character at a time, it's with the merge of all the needed characters.

I'm adding a "visual" to hopefully make things clearer.

Currently you have

    _\n
   / \\n
  / _ \\n
 / ___ \\n
/__/ \__\\n
  ____\n
 | __ ) \n
 |  _ \ \n
 | |_) |\n
 |____/ \n

The jumbing is caused because removing the \n does this (for a):

_/ \/ _ \/ ___ \/__/ \__\

What you want is

    _         ____ \n
   / \       | __ ) \n
  / _ \      |  _ \ \n
 / ___ \     | |_) | \n
/__/ \__\    |____/ \n

Upvotes: 1

Related Questions