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>
54void 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. */
71lwCanvas *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. */
109static 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 */
140void 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