使用程序输出/绘制图形真的非常有趣,而且只需要用到简单的数学知识。
不知道可以写些什么,今天突然看到很久以前写的一些代码,他们是用来输出或者绘制图形的,于是想给大家展示一下。

热身/矩形

Java是我最熟悉的编程语言,所以大部分内容我将使用Java来进行演示。
其实核心的内容并不是编程语言,而是算法本身。

先从最简单的开始,比如说输出一个矩形。
我知道这个实在太简单了,根本不用思考。

1
2
3
4
5
6
for (int i = 0; i <= 5; i++) {
for (int j = 0; j <= 5; j++) {
System.out.print("*");
}
System.out.println(); /* 换行 */
}

这样将会输出一个矩形,并且可以通过自定义 i 和 j 的值来控制矩形的大小。


开始吧/圆形

跳过三角形那些不说,现在我们想要稍微增加难度,如何输出一个圆形呢?

思路很简单,需要用到圆的方程式,即 x^2 + y^2 = r^2

我们假想这个圆圈的中心在坐标轴的(0, 0),且 r = 1,于是 x^2 + y^2 = 1
并假象有一个正方形 [A(-1, 1), B(-1, -1), C(1, -1), D(1, 1)],显然圆与正方形相切。

我们遍历正方形内所有的坐标。
满足 x^2 + y^2 <= 1 则说明在圆内,输出某个(比如*)字符,否则输出空格。

于是不难写出

1
2
3
4
5
6
7
for (double y = 1.0; y >= -1.05; y -= 0.05) {
for (double x = -1.0; x <= 1.025; x += 0.025) {
double r = Math.sqrt((x * x) + (y * y)); /* 获取 x 和 y 的平方和并开根号 */
System.out.print(r <= 1 ? "*" : " ");
}
System.out.println();
}

这样就可以输出一个非常粗糙的圆形。
还可以通过改变 x 和 y 的值来对输出图形造成变化。


然后/爱心

接下来我们将要输出一颗爱心。
这个难度也不算大,只要知道爱心的方程式,原理和输出圆形是一样的。
不用多解释,直接贴出代码吧。

1
2
3
4
5
6
7
8
double a;
for (double y = 1.5; y > -1.5; y -= 0.1) {
for (double x = -1.5; x < 1.5; x += 0.05) {
a = x * x + y * y - 1;
System.out.print(a * a * a - x * x * y * y * y <= 0 ? "%" : "/");
}
System.out.println();
}

这里我输出的符号是 “%” 和 “/“,我认为这样输出的效果看起来更好。


进阶/立体图形

我们已经输出了圆和爱心,但是它们都是平面的,那么如何输出立体的图形呢,比如说球和立体爱心。

这个问题真是困扰了我很久,后来参考了一些资料,又在知乎看了@Milo Yip的相关回答,才知道如何做到。
这里直接移植了他的代码。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <math.h>
#include <stdio.h>
int main(void)
{
double x, y;
for (y = 1; y >= -1; y -= 0.05, putchar('\n'))
{
for (x = -1; x <= 1; x += 0.025)
{
putchar(x * x + y * y > 1 ? 'M' : "@@%#*+=;:. "[(int)(((x + y + sqrt(1 - (x * x + y * y))) * 0.5773502692 + 1)* 5.0 + 0.5)]);
}
}
getchar();
return 0;
}

立体爱心

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <stdio.h>
#include <math.h>

double f(double x, double y, double z)
{
double a = x * x + 9.0 / 4.0 * y * y + z * z - 1;
return a * a * a - x * x * z * z * z - 9.0 / 80.0 * y * y * z * z * z;
}
double h(double x, double z)
{
for (double y = 1.0; y >= 0; y -= 0.001)
if (f(x, y, z) <= 0)
return y;
return 0;
}
int main(void)
{
for (double z = 1.5; z > -1.5; z -= 0.05)
{
for (double x = -1.5; x < 1.5; x += 0.025)
{
double v = f(x, 0, z);
if (v <= 0)
{
double y0 = h(x, z);
double ny = 0.01;
double nx = h(x + ny, z) - y0;
double nz = h(x, z + ny) - y0;
double nd = 1.0 / sqrtf(nx * nx + ny * ny + nz * nz);
double d = (nx + ny - nz) * nd * 0.5 + 0.5;
putchar(".:-=+*#%@"[(int)(d * 5.0)]);
}
else
putchar(' ');
}
putchar('\n');
}
getchar();
}


最终/动态立体爱心

这是一个会动的控制台程序,但仅支持Windows。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#include <stdio.h>
#include <math.h>
#include <windows.h>
#include <tchar.h>

double f(double x, double y, double z)
{
double a = x * x + 9.0 / 4.0 * y * y + z * z - 1;
return a * a * a - x * x * z * z * z - 9.0 / 80.0 * y * y * z * z * z;
}

double h(double x, double z)
{
for (double y = 1.0; y >= 0.0; y -= 0.001)
if (f(x, y, z) <= 0)
return y;
return 0;
}

int main(void)
{
HANDLE o = GetStdHandle(STD_OUTPUT_HANDLE);
_TCHAR buffer[25][80] = { _T(' ') };
_TCHAR ramp[] = _T(".:-=+*#%@");
for (double t = 0; ;t += 0.1)
{
int sy = 0;
double s = sinf(t);
double a = s * s * s * s * 0.2;
for (double z = 1.3; z > -1.2; z -= 0.1)
{
_TCHAR* p = &buffer[sy++][0];
double tz = z * (1.2 - a);
for (double x = -1.5; x < 1.5; x += 0.05)
{
double tx = x * (1.2 + a);
double v = f(tx, 0, tz);
if (v <= 0.0f)
{
double y0 = h(tx, tz);
double ny = 0.01;
double nx = h(tx + ny, tz) - y0;
double nz = h(tx, tz + ny) - y0;
double nd = 1.0 / sqrtf(nx * nx + ny * ny + nz * nz);
double d = (nx + ny - nz) * nd * 0.5 + 0.5;
*p++ = ramp[(int)(d * 5.0)];
}
else
{
*p++ = ' ';
}
}
}
for (sy = 0; sy < 25; sy++)
{
COORD coord = {0, sy};
SetConsoleCursorPosition(o, coord);
WriteConsole(o, buffer[sy], 79, NULL, 0);
}
Sleep(33);
}
}


番外/绘画

使用Python画了一张小猪佩奇嘻嘻嘻。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
import turtle as t

t.pensize(4)
t.hideturtle()
t.colormode(255)
t.color((255,155,192),"pink")
t.setup(840,500)
t.speed(10)

#鼻子
t.pu()
t.goto(-100,100)
t.pd()
t.seth(-30)
t.begin_fill()
a=0.4
for i in range(120):
if 0<=i<30 or 60<=i<90:
a=a+0.08
t.lt(3) #向左转3度
t.fd(a) #向前走a的步长
else:
a=a-0.08
t.lt(3)
t.fd(a)
t.end_fill()

t.pu()
t.seth(90)
t.fd(25)
t.seth(0)
t.fd(10)
t.pd()
t.pencolor(255,155,192)
t.seth(10)
t.begin_fill()
t.circle(5)
t.color(160,82,45)
t.end_fill()

t.pu()
t.seth(0)
t.fd(20)
t.pd()
t.pencolor(255,155,192)
t.seth(10)
t.begin_fill()
t.circle(5)
t.color(160,82,45)
t.end_fill()

#头
t.color((255,155,192),"pink")
t.pu()
t.seth(90)
t.fd(41)
t.seth(0)
t.fd(0)
t.pd()
t.begin_fill()
t.seth(180)
t.circle(300,-30)
t.circle(100,-60)
t.circle(80,-100)
t.circle(150,-20)
t.circle(60,-95)
t.seth(161)
t.circle(-300,15)
t.pu()
t.goto(-100,100)
t.pd()
t.seth(-30)
a=0.4
for i in range(60):
if 0<=i<30 or 60<=i<90:
a=a+0.08
t.lt(3) #向左转3度
t.fd(a) #向前走a的步长
else:
a=a-0.08
t.lt(3)
t.fd(a)
t.end_fill()

#耳朵
t.color((255,155,192),"pink")
t.pu()
t.seth(90)
t.fd(-7)
t.seth(0)
t.fd(70)
t.pd()
t.begin_fill()
t.seth(100)
t.circle(-50,50)
t.circle(-10,120)
t.circle(-50,54)
t.end_fill()

t.pu()
t.seth(90)
t.fd(-12)
t.seth(0)
t.fd(30)
t.pd()
t.begin_fill()
t.seth(100)
t.circle(-50,50)
t.circle(-10,120)
t.circle(-50,56)
t.end_fill()

#眼睛
t.color((255,155,192),"white")
t.pu()
t.seth(90)
t.fd(-20)
t.seth(0)
t.fd(-95)
t.pd()
t.begin_fill()
t.circle(15)
t.end_fill()

t.color("black")
t.pu()
t.seth(90)
t.fd(12)
t.seth(0)
t.fd(-3)
t.pd()
t.begin_fill()
t.circle(3)
t.end_fill()

t.color((255,155,192),"white")
t.pu()
t.seth(90)
t.fd(-25)
t.seth(0)
t.fd(40)
t.pd()
t.begin_fill()
t.circle(15)
t.end_fill()

t.color("black")
t.pu()
t.seth(90)
t.fd(12)
t.seth(0)
t.fd(-3)
t.pd()
t.begin_fill()
t.circle(3)
t.end_fill()

#腮
t.color((255,155,192))
t.pu()
t.seth(90)
t.fd(-95)
t.seth(0)
t.fd(65)
t.pd()
t.begin_fill()
t.circle(30)
t.end_fill()

#嘴
t.color(239,69,19)
t.pu()
t.seth(90)
t.fd(15)
t.seth(0)
t.fd(-100)
t.pd()
t.seth(-80)
t.circle(30,40)
t.circle(40,80)

#身体
t.color("red",(255,99,71))
t.pu()
t.seth(90)
t.fd(-20)
t.seth(0)
t.fd(-78)
t.pd()
t.begin_fill()
t.seth(-130)
t.circle(100,10)
t.circle(300,30)
t.seth(0)
t.fd(230)
t.seth(90)
t.circle(300,30)
t.circle(100,3)
t.color((255,155,192),(255,100,100))
t.seth(-135)
t.circle(-80,63)
t.circle(-150,24)
t.end_fill()

#手
t.color((255,155,192))
t.pu()
t.seth(90)
t.fd(-40)
t.seth(0)
t.fd(-27)
t.pd()
t.seth(-160)
t.circle(300,15)
t.pu()
t.seth(90)
t.fd(15)
t.seth(0)
t.fd(0)
t.pd()
t.seth(-10)
t.circle(-20,90)

t.pu()
t.seth(90)
t.fd(30)
t.seth(0)
t.fd(237)
t.pd()
t.seth(-20)
t.circle(-300,15)
t.pu()
t.seth(90)
t.fd(20)
t.seth(0)
t.fd(0)
t.pd()
t.seth(-170)
t.circle(20,90)

#脚
t.pensize(10)
t.color((240,128,128))
t.pu()
t.seth(90)
t.fd(-75)
t.seth(0)
t.fd(-180)
t.pd()
t.seth(-90)
t.fd(40)
t.seth(-180)
t.color("black")
t.pensize(15)
t.fd(20)

t.pensize(10)
t.color((240,128,128))
t.pu()
t.seth(90)
t.fd(40)
t.seth(0)
t.fd(90)
t.pd()
t.seth(-90)
t.fd(40)
t.seth(-180)
t.color("black")
t.pensize(15)
t.fd(20)

#尾巴
t.pensize(4)
t.color((255,155,192))
t.pu()
t.seth(90)
t.fd(70)
t.seth(0)
t.fd(95)
t.pd()
t.seth(0)
t.circle(70,20)
t.circle(10,330)
t.circle(70,30)

END…