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. */ |
47 | static 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. */ |
74 | struct 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 | |
82 | void 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. */ |
123 | void 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 | */ |
169 | void 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 | |