1/*
2 * Copyright (c) 2019, 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 * Thanks to Michele Hiki Falcone for the original image that inspired
36 * the image, part of his game, Plaguemon.
37 *
38 * Thanks to the Shhh computer art collective for the help in tuning the
39 * output to have a better artistic effect.
40 */
41
42#include "server.h"
43#include "lolwut.h"
44
45/* Render the canvas using the four gray levels of the standard color
46 * terminal: they match very well to the grayscale display of the gameboy. */
47static sds renderCanvas(lwCanvas *canvas) {
48 sds text = sdsempty();
49 for (int y = 0; y < canvas->height; y++) {
50 for (int x = 0; x < canvas->width; x++) {
51 int color = lwGetPixel(canvas,x,y);
52 char *ce; /* Color escape sequence. */
53
54 /* Note that we set both the foreground and background color.
55 * This way we are able to get a more consistent result among
56 * different terminals implementations. */
57 switch(color) {
58 case 0: ce = "0;30;40m"; break; /* Black */
59 case 1: ce = "0;90;100m"; break; /* Gray 1 */
60 case 2: ce = "0;37;47m"; break; /* Gray 2 */
61 case 3: ce = "0;97;107m"; break; /* White */
62 default: ce = "0;30;40m"; break; /* Just for safety. */
63 }
64 text = sdscatprintf(text,"\033[%s \033[0m",ce);
65 }
66 if (y != canvas->height-1) text = sdscatlen(text,"\n",1);
67 }
68 return text;
69}
70
71/* Draw a skyscraper on the canvas, according to the parameters in the
72 * 'skyscraper' structure. Window colors are random and are always one
73 * of the two grays. */
74struct skyscraper {
75 int xoff; /* X offset. */
76 int width; /* Pixels width. */
77 int height; /* Pixels height. */
78 int windows; /* Draw windows if true. */
79 int color; /* Color of the skyscraper. */
80};
81
82void generateSkyscraper(lwCanvas *canvas, struct skyscraper *si) {
83 int starty = canvas->height-1;
84 int endy = starty - si->height + 1;
85 for (int y = starty; y >= endy; y--) {
86 for (int x = si->xoff; x < si->xoff+si->width; x++) {
87 /* The roof is four pixels less wide. */
88 if (y == endy && (x <= si->xoff+1 || x >= si->xoff+si->width-2))
89 continue;
90 int color = si->color;
91 /* Alter the color if this is a place where we want to
92 * draw a window. We check that we are in the inner part of the
93 * skyscraper, so that windows are far from the borders. */
94 if (si->windows &&
95 x > si->xoff+1 &&
96 x < si->xoff+si->width-2 &&
97 y > endy+1 &&
98 y < starty-1)
99 {
100 /* Calculate the x,y position relative to the start of
101 * the window area. */
102 int relx = x - (si->xoff+1);
103 int rely = y - (endy+1);
104
105 /* Note that we want the windows to be two pixels wide
106 * but just one pixel tall, because terminal "pixels"
107 * (characters) are not square. */
108 if (relx/2 % 2 && rely % 2) {
109 do {
110 color = 1 + rand() % 2;
111 } while (color == si->color);
112 /* Except we want adjacent pixels creating the same
113 * window to be the same color. */
114 if (relx % 2) color = lwGetPixel(canvas,x-1,y);
115 }
116 }
117 lwDrawPixel(canvas,x,y,color);
118 }
119 }
120}
121
122/* Generate a skyline inspired by the parallax backgrounds of 8 bit games. */
123void generateSkyline(lwCanvas *canvas) {
124 struct skyscraper si;
125
126 /* First draw the background skyscraper without windows, using the
127 * two different grays. We use two passes to make sure that the lighter
128 * ones are always in the background. */
129 for (int color = 2; color >= 1; color--) {
130 si.color = color;
131 for (int offset = -10; offset < canvas->width;) {
132 offset += rand() % 8;
133 si.xoff = offset;
134 si.width = 10 + rand()%9;
135 if (color == 2)
136 si.height = canvas->height/2 + rand()%canvas->height/2;
137 else
138 si.height = canvas->height/2 + rand()%canvas->height/3;
139 si.windows = 0;
140 generateSkyscraper(canvas, &si);
141 if (color == 2)
142 offset += si.width/2;
143 else
144 offset += si.width+1;
145 }
146 }
147
148 /* Now draw the foreground skyscraper with the windows. */
149 si.color = 0;
150 for (int offset = -10; offset < canvas->width;) {
151 offset += rand() % 8;
152 si.xoff = offset;
153 si.width = 5 + rand()%14;
154 if (si.width % 4) si.width += (si.width % 3);
155 si.height = canvas->height/3 + rand()%canvas->height/2;
156 si.windows = 1;
157 generateSkyscraper(canvas, &si);
158 offset += si.width+5;
159 }
160}
161
162/* The LOLWUT 6 command:
163 *
164 * LOLWUT [columns] [rows]
165 *
166 * By default the command uses 80 columns, 40 squares per row
167 * per column.
168 */
169void lolwut6Command(client *c) {
170 long cols = 80;
171 long rows = 20;
172
173 /* Parse the optional arguments if any. */
174 if (c->argc > 1 &&
175 getLongFromObjectOrReply(c,c->argv[1],&cols,NULL) != C_OK)
176 return;
177
178 if (c->argc > 2 &&
179 getLongFromObjectOrReply(c,c->argv[2],&rows,NULL) != C_OK)
180 return;
181
182 /* Limits. We want LOLWUT to be always reasonably fast and cheap to execute
183 * so we have maximum number of columns, rows, and output resolution. */
184 if (cols < 1) cols = 1;
185 if (cols > 1000) cols = 1000;
186 if (rows < 1) rows = 1;
187 if (rows > 1000) rows = 1000;
188
189 /* Generate the city skyline and reply. */
190 lwCanvas *canvas = lwCreateCanvas(cols,rows,3);
191 generateSkyline(canvas);
192 sds rendered = renderCanvas(canvas);
193 rendered = sdscat(rendered,
194 "\nDedicated to the 8 bit game developers of past and present.\n"
195 "Original 8 bit image from Plaguemon by hikikomori. Redis ver. ");
196 rendered = sdscat(rendered,REDIS_VERSION);
197 rendered = sdscatlen(rendered,"\n",1);
198 addReplyVerbatim(c,rendered,sdslen(rendered),"txt");
199 sdsfree(rendered);
200 lwFreeCanvas(canvas);
201}
202