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
40void lolwut5Command(client *c);
41void lolwut6Command(client *c);
42
43/* The default target for LOLWUT if no matching version was found.
44 * This is what unstable versions of Redis will display. */
45void lolwutUnstableCommand(client *c) {
46 sds rendered = sdsnew("Redis ver. ");
47 rendered = sdscat(rendered,REDIS_VERSION);
48 rendered = sdscatlen(rendered,"\n",1);
49 addReplyVerbatim(c,rendered,sdslen(rendered),"txt");
50 sdsfree(rendered);
51}
52
53/* LOLWUT [VERSION <version>] [... version specific arguments ...] */
54void lolwutCommand(client *c) {
55 char *v = REDIS_VERSION;
56 char verstr[64];
57
58 if (c->argc >= 3 && !strcasecmp(c->argv[1]->ptr,"version")) {
59 long ver;
60 if (getLongFromObjectOrReply(c,c->argv[2],&ver,NULL) != C_OK) return;
61 snprintf(verstr,sizeof(verstr),"%u.0.0",(unsigned int)ver);
62 v = verstr;
63
64 /* Adjust argv/argc to filter the "VERSION ..." option, since the
65 * specific LOLWUT version implementations don't know about it
66 * and expect their arguments. */
67 c->argv += 2;
68 c->argc -= 2;
69 }
70
71 if ((v[0] == '5' && v[1] == '.' && v[2] != '9') ||
72 (v[0] == '4' && v[1] == '.' && v[2] == '9'))
73 lolwut5Command(c);
74 else if ((v[0] == '6' && v[1] == '.' && v[2] != '9') ||
75 (v[0] == '5' && v[1] == '.' && v[2] == '9'))
76 lolwut6Command(c);
77 else
78 lolwutUnstableCommand(c);
79
80 /* Fix back argc/argv in case of VERSION argument. */
81 if (v == verstr) {
82 c->argv -= 2;
83 c->argc += 2;
84 }
85}
86
87/* ========================== LOLWUT Canvas ===============================
88 * Many LOLWUT versions will likely print some computer art to the screen.
89 * This is the case with LOLWUT 5 and LOLWUT 6, so here there is a generic
90 * canvas implementation that can be reused. */
91
92/* Allocate and return a new canvas of the specified size. */
93lwCanvas *lwCreateCanvas(int width, int height, int bgcolor) {
94 lwCanvas *canvas = zmalloc(sizeof(*canvas));
95 canvas->width = width;
96 canvas->height = height;
97 canvas->pixels = zmalloc((size_t)width*height);
98 memset(canvas->pixels,bgcolor,(size_t)width*height);
99 return canvas;
100}
101
102/* Free the canvas created by lwCreateCanvas(). */
103void lwFreeCanvas(lwCanvas *canvas) {
104 zfree(canvas->pixels);
105 zfree(canvas);
106}
107
108/* Set a pixel to the specified color. Color is 0 or 1, where zero means no
109 * dot will be displayed, and 1 means dot will be displayed.
110 * Coordinates are arranged so that left-top corner is 0,0. You can write
111 * out of the size of the canvas without issues. */
112void lwDrawPixel(lwCanvas *canvas, int x, int y, int color) {
113 if (x < 0 || x >= canvas->width ||
114 y < 0 || y >= canvas->height) return;
115 canvas->pixels[x+y*canvas->width] = color;
116}
117
118/* Return the value of the specified pixel on the canvas. */
119int lwGetPixel(lwCanvas *canvas, int x, int y) {
120 if (x < 0 || x >= canvas->width ||
121 y < 0 || y >= canvas->height) return 0;
122 return canvas->pixels[x+y*canvas->width];
123}
124
125/* Draw a line from x1,y1 to x2,y2 using the Bresenham algorithm. */
126void lwDrawLine(lwCanvas *canvas, int x1, int y1, int x2, int y2, int color) {
127 int dx = abs(x2-x1);
128 int dy = abs(y2-y1);
129 int sx = (x1 < x2) ? 1 : -1;
130 int sy = (y1 < y2) ? 1 : -1;
131 int err = dx-dy, e2;
132
133 while(1) {
134 lwDrawPixel(canvas,x1,y1,color);
135 if (x1 == x2 && y1 == y2) break;
136 e2 = err*2;
137 if (e2 > -dy) {
138 err -= dy;
139 x1 += sx;
140 }
141 if (e2 < dx) {
142 err += dx;
143 y1 += sy;
144 }
145 }
146}
147
148/* Draw a square centered at the specified x,y coordinates, with the specified
149 * rotation angle and size. In order to write a rotated square, we use the
150 * trivial fact that the parametric equation:
151 *
152 * x = sin(k)
153 * y = cos(k)
154 *
155 * Describes a circle for values going from 0 to 2*PI. So basically if we start
156 * at 45 degrees, that is k = PI/4, with the first point, and then we find
157 * the other three points incrementing K by PI/2 (90 degrees), we'll have the
158 * points of the square. In order to rotate the square, we just start with
159 * k = PI/4 + rotation_angle, and we are done.
160 *
161 * Of course the vanilla equations above will describe the square inside a
162 * circle of radius 1, so in order to draw larger squares we'll have to
163 * multiply the obtained coordinates, and then translate them. However this
164 * is much simpler than implementing the abstract concept of 2D shape and then
165 * performing the rotation/translation transformation, so for LOLWUT it's
166 * a good approach. */
167void lwDrawSquare(lwCanvas *canvas, int x, int y, float size, float angle, int color) {
168 int px[4], py[4];
169
170 /* Adjust the desired size according to the fact that the square inscribed
171 * into a circle of radius 1 has the side of length SQRT(2). This way
172 * size becomes a simple multiplication factor we can use with our
173 * coordinates to magnify them. */
174 size /= 1.4142135623;
175 size = round(size);
176
177 /* Compute the four points. */
178 float k = M_PI/4 + angle;
179 for (int j = 0; j < 4; j++) {
180 px[j] = round(sin(k) * size + x);
181 py[j] = round(cos(k) * size + y);
182 k += M_PI/2;
183 }
184
185 /* Draw the square. */
186 for (int j = 0; j < 4; j++)
187 lwDrawLine(canvas,px[j],py[j],px[(j+1)%4],py[(j+1)%4],color);
188}
189