C ++换行符中的ascii艺术生成器

ascii art generator in c++ newlining

我正在尝试生成给定字符串的 ASCII 艺术作品。

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);

};

这是一个 str 引用,然后遍历每个字符并在映射中找到该字符并获取其对应的 ASCII 艺术字母,然后将其添加到最终字符串中。
这就提出了一个问题。当我想打印字符串 Art::display() returns.
时,它会在新行上打印每个字符 我试过什么?我试过删除换行符,但那会弄乱一些零碎的东西 together/apart.
我想让它做什么?我希望它从左到右打印一个单词,而不是在新行上打印每个字符。

在控制台中,您通常是逐行编写。当您打印其中一个字符时,您将获取字符串,并且将打印带有 \n 的每一行。一行一行。

每个字符一个接一个地打印,这很正常,这就是输出的方式:逐行输出。

跳跃只是将几行附加到同一行。对于字符 a,不是用 X 行来绘制字符,而是将它们全部合并为一行。这不是你想要的,但你正在朝着我认为正确的想法前进。

你需要做的是对所有需要的字符,逐行合并它们,并像你已经在做的那样逐行输出结果,但不是一次一个字符,而是合并所有需要的字符。

我正在添加一个“视觉”,希望能让事情变得更清楚。

目前你有

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

跳转是因为删除 \n 这样做(对于 a):

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

你想要的是

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

对于您的输出,保留一个包含 5 行的数组,用于 Art 字符的 5 行不同的行。追加艺术字时,换行拆分,将艺术字的第一行追加到输出的第一行,艺术字的第二行追加到输出的第二行,依此类推


与此同时,您可以使用 string_view 而不是 string 来制作地图,并通过使用 UDLs 和仅使用 { } 而不是 [=15] 来简化您的源代码=].

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

变为:

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

我会添加一个 Letter class 来简化解析和打印字母。

示例:

#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");
}

输出:

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

注意:MSVC(包含在 Visual Studio 中的编译器)出于某种原因在我的 string_view 中出现问题。这是一个使用 std::strings 来让 MSVC 满意的版本:

#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");
}