C++中的ASCII艺术生成器换行功能

3

我希望能根据给定的字符串生成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);

};

这段代码接收一个字符串引用,然后循环遍历每个字符,在映射表中查找该字符并获取其对应的ASCII艺术字母,然后将其添加到最终字符串中。
这会带来一个问题。当我想要打印Art::display()返回的字符串时,它会将每个字符都打印在新行上。
我尝试了什么?我尝试删除换行符,但这样会混淆一些部分。
我想要它做什么?我想要它从左到右打印单词,而不是将每个字符都打印在新行上。


提示:您可以创建一个“art”目录,并为每个字母创建一个专用文件,然后使用“#include“art / A”而不是在文件中使用艺术品。这使代码更清晰,更易于理解。 - DadiBit
1
也许原始字符串不是正确的工具。使用一个字符串数组,每个字符串代表一行,怎么样?这样很容易将一行与下一行分开,并且您可以打印 letters['a'][0] << letters['b'][0] << '\n' << letters['a'][1] << letters['b'][1] << '\n' << ... - user4581301
多次迭代所有字符。第一次打印字符的第一行,第二次打印第二行等等,如果每个字符数据都存储为 std::string[5],那么打印结果会更容易些。 - fabian
3个回答

2
对于你的输出,请保留一个由5行组成的数组,用于存储艺术字符的5个不同行。当添加艺术字符时,将其拆分为换行符,并将艺术字符的第一行附加到输出的第一行,第二行附加到输出的第二行,以此类推。
与此同时,您可以在地图中使用string_view而不是string,并通过使用UDLs并仅使用{ }而不是make_pair来简化您的源代码。
        std::make_pair('a', std::string(R"(  
    _
   / \
  / _ \
 / ___ \
/__/ \__\
)")),

becomes:

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

""sv 的 "v" 代表什么? - Martin York
你有跟随答案中的链接吗? - JDługosz

2
我建议添加一个Letter类来简化字母的解析和打印。
例如:
#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");
}

输出:

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

注意:由于某些原因,Visual Studio自带的编译器MSVC对我的string_view存在问题。以下是使用std::string代替的版本,以便让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");
}

想知道您使用的是哪个 C++ 版本,因为如果使用 VS2019 和 C++20 标准,这段代码无法编译,并且会提示“未找到 construct_at 的重载函数”。 - noah
@noah MSVC不是最符合标准的编译器。我提供的代码应该可以被一个正常的C++17或C++20编译器接受。 - Ted Lyngmo
@noah 是的,我其实没想到 MSVC 会失败。如果你几个小时内无法让它正常工作,只需在这里提醒我,我会添加一个 MSVC 可以理解的版本。 - Ted Lyngmo
看起来一个编译错误源于另一个错误,只剩下两个实际错误,其中一个是没有重载的construct_at函数和无法将std::string_view转换为const char*。我遇到的问题是它没有显示出错行,而是显示了xmemory.h内的函数,710行是第一个重载错误,714行是第二个错误。 - noah
@noah 是的,我注意到了。我添加了一个使用 std::string 的版本。 - Ted Lyngmo
显示剩余2条评论

1
在控制台中,通常是逐行编写代码的。当您打印其中一个字符时,将打印带有其 \n 的每一行。逐行打印。
每个字符依次打印是正常的,这就是输出的工作原理:逐行打印。
换行只是将行附加到同一行。对于字符 a,不是用X行来绘制该字符,而是将它们合并成一行。这不是您想要的,但我认为您已经接近正确的想法了。
您需要做的是将所有所需字符按行合并,并像您已经在做的那样逐行输出结果,但不是一个字符,而是所有所需字符的合并。
我正在添加一个“视觉效果”,希望能使事情更清晰明了。
目前您拥有的是:
    _\n
   / \\n
  / _ \\n
 / ___ \\n
/__/ \__\\n
  ____\n
 | __ ) \n
 |  _ \ \n
 | |_) |\n
 |____/ \n

跳行是由于去除 \n 导致的(对于 a):

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

你想要的是:
    _         ____ \n
   / \       | __ ) \n
  / _ \      |  _ \ \n
 / ___ \     | |_) | \n
/__/ \__\    |____/ \n

所以每个字符在同一行上需要有相同的长度吗? - noah
@noah 在你的情况下不一定需要,但这样做会简化事情。否则,只需要找出字符中最长的子串,然后在需要时添加填充即可。但在你的情况下,确保所有\n对齐将使事情变得更容易。 - ShadowMitia

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接