1 | /* |
2 | * Copyright (c) 2018, Salvatore Sanfilippo <antirez at gmail dot com> |
3 | * All rights reserved. |
4 | * |
5 | * Redistribution and use in source and binary forms, with or without |
6 | * modification, are permitted provided that the following conditions are met: |
7 | * |
8 | * * Redistributions of source code must retain the above copyright notice, |
9 | * this list of conditions and the following disclaimer. |
10 | * * Redistributions in binary form must reproduce the above copyright |
11 | * notice, this list of conditions and the following disclaimer in the |
12 | * documentation and/or other materials provided with the distribution. |
13 | * * Neither the name of Redis nor the names of its contributors may be used |
14 | * to endorse or promote products derived from this software without |
15 | * specific prior written permission. |
16 | * |
17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
18 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
19 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
20 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE |
21 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
22 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
23 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
24 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
25 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
26 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
27 | * POSSIBILITY OF SUCH DAMAGE. |
28 | * |
29 | * ---------------------------------------------------------------------------- |
30 | * |
31 | * This file implements the LOLWUT command. The command should do something |
32 | * fun and interesting, and should be replaced by a new implementation at |
33 | * each new version of Redis. |
34 | */ |
35 | |
36 | #include "server.h" |
37 | #include "lolwut.h" |
38 | #include <math.h> |
39 | |
40 | /* Translate a group of 8 pixels (2x4 vertical rectangle) to the corresponding |
41 | * braille character. The byte should correspond to the pixels arranged as |
42 | * follows, where 0 is the least significant bit, and 7 the most significant |
43 | * bit: |
44 | * |
45 | * 0 3 |
46 | * 1 4 |
47 | * 2 5 |
48 | * 6 7 |
49 | * |
50 | * The corresponding utf8 encoded character is set into the three bytes |
51 | * pointed by 'output'. |
52 | */ |
53 | #include <stdio.h> |
54 | void lwTranslatePixelsGroup(int byte, char *output) { |
55 | int code = 0x2800 + byte; |
56 | /* Convert to unicode. This is in the U0800-UFFFF range, so we need to |
57 | * emit it like this in three bytes: |
58 | * 1110xxxx 10xxxxxx 10xxxxxx. */ |
59 | output[0] = 0xE0 | (code >> 12); /* 1110-xxxx */ |
60 | output[1] = 0x80 | ((code >> 6) & 0x3F); /* 10-xxxxxx */ |
61 | output[2] = 0x80 | (code & 0x3F); /* 10-xxxxxx */ |
62 | } |
63 | |
64 | /* Schotter, the output of LOLWUT of Redis 5, is a computer graphic art piece |
65 | * generated by Georg Nees in the 60s. It explores the relationship between |
66 | * caos and order. |
67 | * |
68 | * The function creates the canvas itself, depending on the columns available |
69 | * in the output display and the number of squares per row and per column |
70 | * requested by the caller. */ |
71 | lwCanvas *lwDrawSchotter(int console_cols, int squares_per_row, int squares_per_col) { |
72 | /* Calculate the canvas size. */ |
73 | int canvas_width = console_cols*2; |
74 | int padding = canvas_width > 4 ? 2 : 0; |
75 | float square_side = (float)(canvas_width-padding*2) / squares_per_row; |
76 | int canvas_height = square_side * squares_per_col + padding*2; |
77 | lwCanvas *canvas = lwCreateCanvas(canvas_width, canvas_height, 0); |
78 | |
79 | for (int y = 0; y < squares_per_col; y++) { |
80 | for (int x = 0; x < squares_per_row; x++) { |
81 | int sx = x * square_side + square_side/2 + padding; |
82 | int sy = y * square_side + square_side/2 + padding; |
83 | /* Rotate and translate randomly as we go down to lower |
84 | * rows. */ |
85 | float angle = 0; |
86 | if (y > 1) { |
87 | float r1 = (float)rand() / (float) RAND_MAX / squares_per_col * y; |
88 | float r2 = (float)rand() / (float) RAND_MAX / squares_per_col * y; |
89 | float r3 = (float)rand() / (float) RAND_MAX / squares_per_col * y; |
90 | if (rand() % 2) r1 = -r1; |
91 | if (rand() % 2) r2 = -r2; |
92 | if (rand() % 2) r3 = -r3; |
93 | angle = r1; |
94 | sx += r2*square_side/3; |
95 | sy += r3*square_side/3; |
96 | } |
97 | lwDrawSquare(canvas,sx,sy,square_side,angle,1); |
98 | } |
99 | } |
100 | |
101 | return canvas; |
102 | } |
103 | |
104 | /* Converts the canvas to an SDS string representing the UTF8 characters to |
105 | * print to the terminal in order to obtain a graphical representation of the |
106 | * logical canvas. The actual returned string will require a terminal that is |
107 | * width/2 large and height/4 tall in order to hold the whole image without |
108 | * overflowing or scrolling, since each Barille character is 2x4. */ |
109 | static sds renderCanvas(lwCanvas *canvas) { |
110 | sds text = sdsempty(); |
111 | for (int y = 0; y < canvas->height; y += 4) { |
112 | for (int x = 0; x < canvas->width; x += 2) { |
113 | /* We need to emit groups of 8 bits according to a specific |
114 | * arrangement. See lwTranslatePixelsGroup() for more info. */ |
115 | int byte = 0; |
116 | if (lwGetPixel(canvas,x,y)) byte |= (1<<0); |
117 | if (lwGetPixel(canvas,x,y+1)) byte |= (1<<1); |
118 | if (lwGetPixel(canvas,x,y+2)) byte |= (1<<2); |
119 | if (lwGetPixel(canvas,x+1,y)) byte |= (1<<3); |
120 | if (lwGetPixel(canvas,x+1,y+1)) byte |= (1<<4); |
121 | if (lwGetPixel(canvas,x+1,y+2)) byte |= (1<<5); |
122 | if (lwGetPixel(canvas,x,y+3)) byte |= (1<<6); |
123 | if (lwGetPixel(canvas,x+1,y+3)) byte |= (1<<7); |
124 | char unicode[3]; |
125 | lwTranslatePixelsGroup(byte,unicode); |
126 | text = sdscatlen(text,unicode,3); |
127 | } |
128 | if (y != canvas->height-1) text = sdscatlen(text,"\n" ,1); |
129 | } |
130 | return text; |
131 | } |
132 | |
133 | /* The LOLWUT command: |
134 | * |
135 | * LOLWUT [terminal columns] [squares-per-row] [squares-per-col] |
136 | * |
137 | * By default the command uses 66 columns, 8 squares per row, 12 squares |
138 | * per column. |
139 | */ |
140 | void lolwut5Command(client *c) { |
141 | long cols = 66; |
142 | long squares_per_row = 8; |
143 | long squares_per_col = 12; |
144 | |
145 | /* Parse the optional arguments if any. */ |
146 | if (c->argc > 1 && |
147 | getLongFromObjectOrReply(c,c->argv[1],&cols,NULL) != C_OK) |
148 | return; |
149 | |
150 | if (c->argc > 2 && |
151 | getLongFromObjectOrReply(c,c->argv[2],&squares_per_row,NULL) != C_OK) |
152 | return; |
153 | |
154 | if (c->argc > 3 && |
155 | getLongFromObjectOrReply(c,c->argv[3],&squares_per_col,NULL) != C_OK) |
156 | return; |
157 | |
158 | /* Limits. We want LOLWUT to be always reasonably fast and cheap to execute |
159 | * so we have maximum number of columns, rows, and output resolution. */ |
160 | if (cols < 1) cols = 1; |
161 | if (cols > 1000) cols = 1000; |
162 | if (squares_per_row < 1) squares_per_row = 1; |
163 | if (squares_per_row > 200) squares_per_row = 200; |
164 | if (squares_per_col < 1) squares_per_col = 1; |
165 | if (squares_per_col > 200) squares_per_col = 200; |
166 | |
167 | /* Generate some computer art and reply. */ |
168 | lwCanvas *canvas = lwDrawSchotter(cols,squares_per_row,squares_per_col); |
169 | sds rendered = renderCanvas(canvas); |
170 | rendered = sdscat(rendered, |
171 | "\nGeorg Nees - schotter, plotter on paper, 1968. Redis ver. " ); |
172 | rendered = sdscat(rendered,REDIS_VERSION); |
173 | rendered = sdscatlen(rendered,"\n" ,1); |
174 | addReplyVerbatim(c,rendered,sdslen(rendered),"txt" ); |
175 | sdsfree(rendered); |
176 | lwFreeCanvas(canvas); |
177 | } |
178 | |