From 5fa0c40102a9c56c450ea53219cc4625aab0b2ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Valor=20Mart=C3=ADnez?= Date: Wed, 24 May 2023 21:21:39 +0200 Subject: [PATCH] =?UTF-8?q?demo:=20A=C3=B1adido=20efecto=20de=20fuego=20en?= =?UTF-8?q?=20el=20fondo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.cpp | 67 +++++++- quickcg.cpp | 428 ++++++++++++++++++++++++++++++++++++++++++++++++++++ quickcg.h | 189 +++++++++++++++++++++++ 3 files changed, 676 insertions(+), 8 deletions(-) create mode 100644 quickcg.cpp create mode 100644 quickcg.h diff --git a/main.cpp b/main.cpp index 814804f..553a6f5 100644 --- a/main.cpp +++ b/main.cpp @@ -15,7 +15,10 @@ Código fuente creado por JailDesigner #include "units/texture.h" #include "units/screen.h" #include "units/input.h" +#include "quickcg.h" +using namespace QuickCG; +// Punteros SDL_Event *event; SDL_Window *window; SDL_Renderer *renderer; @@ -30,24 +33,36 @@ Text *debugText; Texture *texture; MovingSprite *sprite; +// Listas enum e_fx // Tipos de efectos disponibles para el fondo { fx_fire, fx_gradient }; -Uint32 ticks = 0; // Variable para la frecuencia de actualización de la lógica del programa -Uint32 ticksSpeed = 15; // Variable para la frecuencia de actualización de la lógica del programa -bool should_exit = false; // Variable para saber si ha terminado el progra,a -int counter = 0; // Contador para lo que se necesite +// Variables de uso general +Uint32 ticks = 0; // Variable para la frecuencia de actualización de la lógica del programa +const Uint32 ticksSpeed = 15; // Variable para la frecuencia de actualización de la lógica del programa +bool should_exit = false; // Variable para saber si ha terminado el progra,a +int counter = 0; // Contador para lo que se necesite +string controllerName; // Nombre del primer mando detectado +string inputPressed; // Texto con el último input que se ha pulsado +e_fx fx = fx_fire; // Efecto seleccionado para el fondo +int fxTotal = 2; // Cantidad total de efectos disponibles para el fondo + +// Variables para el efecto del degradado int gradColorMin = 64; // Minimo color más alto del degradado int gradColorMax = 192; // Minimo color más alto del degradado int gradCurrentColor = 192; // Color actual más alto del degradado int gradBreathDirection = 0; // Indica si gradCurrentColor crece o decrece -string controllerName; // Nombre del primer mando detectado -string inputPressed; // Texto con el último input que se ha pulsado -e_fx fx = fx_gradient; // Efecto seleccionado para el fondo -int fxTotal = 2; // Cantidad total de efectos disponibles para el fondo + +// Variables para el efecto de fuego +const int fireScreenWidth = 320; // Ancho del efecto de fuego +const int fireScreenHeight = 180; // Alto del efecto de fuego +Uint32 fire[fireScreenHeight][fireScreenWidth]; // Buffer con el fuego +Uint32 buffer[fireScreenHeight][fireScreenWidth]; // Buffer para dibujar en pantalla +ColorRGB palette[256]; // Paleta de color para el fuego +const int fireDesp = 240 - fireScreenHeight + 3; // Fila donde comienza a dibujarse el efecto de fuego // Inicializa las opciones void initOptions(); @@ -240,6 +255,23 @@ void initSprite() // Inicializa el efecto de fuego void initFire() { + // Inicializa el buffer de fuego + for (int y = 0; y < fireScreenHeight; ++y) + for (int x = 0; x < fireScreenWidth; ++x) + fire[y][x] = 0; + + // Inicializa la paleta + for (int x = 0; x < 256; ++x) + { + // HSLtoRGB is used to generate colors: + // Hue goes from 0 to 85: red to yellow + // Saturation is always the maximum: 255 + // Lightness is 0..255 for x=0..128, and 255 for x=128..255 + ColorRGB color = HSLtoRGB(ColorHSL(x / 3, 255, std::min(255, x * 2))); + // set the palette to the calculated RGB value + // palette[x] = RGBtoINT(color); + palette[x] = color; + } } // Inicializa todo @@ -399,6 +431,18 @@ void updateGradient() // Actualiza el efecto de fuego void updateFire() { + // randomize the bottom row of the fire buffer + const int w = fireScreenWidth; + const int h = fireScreenHeight; + for (int x = 0; x < w; ++x) + fire[h - 1][x] = abs(32768 + rand()) % 256; + // do the fire calculations for every pixel, from top to bottom + for (int y = 0; y < h - 1; ++y) + for (int x = 0; x < w; ++x) + { + fire[y][x] = + ((fire[(y + 1) % h][(x - 1 + w) % w] + fire[(y + 1) % h][(x) % w] + fire[(y + 1) % h][(x + 1) % w] + fire[(y + 2) % h][(x) % w]) * 32) / 129; + } } // Actualiza el efecto de fondo @@ -476,6 +520,13 @@ void renderGradient() // Dibuja el efecto de fuego void renderFire() { + for (int y = 0; y < fireScreenHeight; ++y) + for (int x = 0; x < fireScreenWidth; ++x) + { + const ColorRGB c = palette[fire[y][x]]; + SDL_SetRenderDrawColor(renderer, c.r, c.g, c.b, 255); + SDL_RenderDrawPoint(renderer, x, y + fireDesp); + } } // Dibuja el efecto de fondo diff --git a/quickcg.cpp b/quickcg.cpp new file mode 100644 index 0000000..96d1f54 --- /dev/null +++ b/quickcg.cpp @@ -0,0 +1,428 @@ +/* +QuickCG 20191227 + +Copyright (c) 2004-2019, Lode Vandevenne + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/* +QuickCG is an SDL codebase that wraps some of the SDL functionality. +It' used by Lode's Computer Graphics Tutorial to work with simple C++ calls +to demonstrate graphical programs. + +QuickCG can handle some things that standard C++ doesn't but that are commonly useful, such as: +-drawing graphics +-a bitmap font +-simplified saving and loading of files +-reading keyboard and mouse input +-playing sound +-color models +-loading images +*/ + +#include "quickcg.h" + +#include +#include +#include +#include +#include +#include + +namespace QuickCG +{ + //////////////////////////////////////////////////////////////////////////////// + // COLOR STRUCTS///////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + ColorRGB::ColorRGB(Uint8 r, Uint8 g, Uint8 b) + { + this->r = r; + this->g = g; + this->b = b; + } + ColorRGB::ColorRGB(const ColorRGB8bit &color) + { + this->r = color.r; + this->g = color.g; + this->b = color.b; + } + ColorRGB::ColorRGB() + { + this->r = 0; + this->g = 0; + this->b = 0; + } + ColorRGB8bit::ColorRGB8bit(Uint8 r, Uint8 g, Uint8 b) + { + this->r = r; + this->g = g; + this->b = b; + } + ColorRGB8bit::ColorRGB8bit(const ColorRGB &color) + { + this->r = color.r; + this->g = color.g; + this->b = color.b; + } + ColorRGB8bit::ColorRGB8bit() + { + this->r = 0; + this->g = 0; + this->b = 0; + } + + // Add two colors + ColorRGB operator+(const ColorRGB &color, const ColorRGB &color2) + { + ColorRGB c; + c.r = color.r + color2.r; + c.g = color.g + color2.g; + c.b = color.b + color2.b; + return c; + } + + // Subtract two colors + ColorRGB operator-(const ColorRGB &color, const ColorRGB &color2) + { + ColorRGB c; + c.r = color.r - color2.r; + c.g = color.g - color2.g; + c.b = color.b - color2.b; + return c; + } + + // Multiplies a color with an integer + ColorRGB operator*(const ColorRGB &color, int a) + { + ColorRGB c; + c.r = color.r * a; + c.g = color.g * a; + c.b = color.b * a; + return c; + } + + // Multiplies a color with an integer + ColorRGB operator*(int a, const ColorRGB &color) + { + ColorRGB c; + c.r = color.r * a; + c.g = color.g * a; + c.b = color.b * a; + return c; + } + + // Divides a color through an integer + ColorRGB operator/(const ColorRGB &color, int a) + { + if (a == 0) + return color; + ColorRGB c; + c.r = color.r / a; + c.g = color.g / a; + c.b = color.b / a; + return c; + } + + // Are both colors equal? + bool operator==(const ColorRGB &color, const ColorRGB &color2) + { + return (color.r == color2.r && color.g == color2.g && color.b == color2.b); + } + + // Are both colors not equal? + bool operator!=(const ColorRGB &color, const ColorRGB &color2) + { + return (!(color.r == color2.r && color.g == color2.g && color.b == color2.b)); + } + + ColorHSL::ColorHSL(Uint8 h, Uint8 s, Uint8 l) + { + this->h = h; + this->s = s; + this->l = l; + } + ColorHSL::ColorHSL() + { + this->h = 0; + this->s = 0; + this->l = 0; + } + ColorHSV::ColorHSV(Uint8 h, Uint8 s, Uint8 v) + { + this->h = h; + this->s = s; + this->v = v; + } + ColorHSV::ColorHSV() + { + this->h = 0; + this->s = 0; + this->v = 0; + } + + //////////////////////////////////////////////////////////////////////////////// + // COLOR CONVERSIONS///////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + + /* + Convert colors from one type to another + r=red g=green b=blue h=hue s=saturation l=lightness v=value + Color components from the color structs are Uint8's between 0 and 255 + color components used in the calculations are normalized between 0.0-1.0 + */ + + // Converts an RGB color to HSL color + ColorHSL RGBtoHSL(const ColorRGB &colorRGB) + { + float r, g, b, h = 0, s = 0, l; // this function works with floats between 0 and 1 + r = colorRGB.r / 256.0; + g = colorRGB.g / 256.0; + b = colorRGB.b / 256.0; + + float maxColor = std::max(r, std::max(g, b)); + float minColor = std::min(r, std::min(g, b)); + + if (minColor == maxColor) // R = G = B, so it's a shade of grey + { + h = 0; // it doesn't matter what value it has + s = 0; + l = r; // doesn't matter if you pick r, g, or b + } + else + { + l = (minColor + maxColor) / 2; + + if (l < 0.5) + s = (maxColor - minColor) / (maxColor + minColor); + if (l >= 0.5) + s = (maxColor - minColor) / (2.0 - maxColor - minColor); + + if (r == maxColor) + h = (g - b) / (maxColor - minColor); + if (g == maxColor) + h = 2.0 + (b - r) / (maxColor - minColor); + if (b == maxColor) + h = 4.0 + (r - g) / (maxColor - minColor); + + h /= 6; // to bring it to a number between 0 and 1 + if (h < 0) + h += 1; + } + + ColorHSL colorHSL; + colorHSL.h = int(h * 255.0); + colorHSL.s = int(s * 255.0); + colorHSL.l = int(l * 255.0); + return colorHSL; + } + + // Converts an HSL color to RGB color + ColorRGB HSLtoRGB(const ColorHSL &colorHSL) + { + float r, g, b, h, s, l; // this function works with floats between 0 and 1 + float temp1, temp2, tempr, tempg, tempb; + h = colorHSL.h / 256.0; + s = colorHSL.s / 256.0; + l = colorHSL.l / 256.0; + + // If saturation is 0, the color is a shade of grey + if (s == 0) + r = g = b = l; + // If saturation > 0, more complex calculations are needed + else + { + // set the temporary values + if (l < 0.5) + temp2 = l * (1 + s); + else + temp2 = (l + s) - (l * s); + temp1 = 2 * l - temp2; + tempr = h + 1.0 / 3.0; + if (tempr > 1.0) + tempr--; + tempg = h; + tempb = h - 1.0 / 3.0; + if (tempb < 0.0) + tempb++; + + // red + if (tempr < 1.0 / 6.0) + r = temp1 + (temp2 - temp1) * 6.0 * tempr; + else if (tempr < 0.5) + r = temp2; + else if (tempr < 2.0 / 3.0) + r = temp1 + (temp2 - temp1) * ((2.0 / 3.0) - tempr) * 6.0; + else + r = temp1; + + // green + if (tempg < 1.0 / 6.0) + g = temp1 + (temp2 - temp1) * 6.0 * tempg; + else if (tempg < 0.5) + g = temp2; + else if (tempg < 2.0 / 3.0) + g = temp1 + (temp2 - temp1) * ((2.0 / 3.0) - tempg) * 6.0; + else + g = temp1; + + // blue + if (tempb < 1.0 / 6.0) + b = temp1 + (temp2 - temp1) * 6.0 * tempb; + else if (tempb < 0.5) + b = temp2; + else if (tempb < 2.0 / 3.0) + b = temp1 + (temp2 - temp1) * ((2.0 / 3.0) - tempb) * 6.0; + else + b = temp1; + } + + ColorRGB colorRGB; + colorRGB.r = int(r * 255.0); + colorRGB.g = int(g * 255.0); + colorRGB.b = int(b * 255.0); + return colorRGB; + } + + // Converts an RGB color to HSV color + ColorHSV RGBtoHSV(const ColorRGB &colorRGB) + { + float r, g, b, h = 0.0, s = 0.0, v; // this function works with floats between 0 and 1 + r = colorRGB.r / 256.0; + g = colorRGB.g / 256.0; + b = colorRGB.b / 256.0; + + float maxColor = std::max(r, std::max(g, b)); + float minColor = std::min(r, std::min(g, b)); + + v = maxColor; + + if (maxColor != 0.0) // avoid division by zero when the color is black + { + s = (maxColor - minColor) / maxColor; + } + + if (s == 0.0) + { + h = 0.0; // it doesn't matter what value it has + } + else + { + if (r == maxColor) + h = (g - b) / (maxColor - minColor); + if (g == maxColor) + h = 2.0 + (b - r) / (maxColor - minColor); + if (b == maxColor) + h = 4.0 + (r - g) / (maxColor - minColor); + + h /= 6.0; // to bring it to a number between 0 and 1 + if (h < 0.0) + h++; + } + + ColorHSV colorHSV; + colorHSV.h = int(h * 255.0); + colorHSV.s = int(s * 255.0); + colorHSV.v = int(v * 255.0); + return colorHSV; + } + + // Converts an HSV color to RGB color + ColorRGB HSVtoRGB(const ColorHSV &colorHSV) + { + float r, g, b, h, s, v; // this function works with floats between 0 and 1 + h = colorHSV.h / 256.0; + s = colorHSV.s / 256.0; + v = colorHSV.v / 256.0; + + // if saturation is 0, the color is a shade of grey + if (s == 0.0) + r = g = b = v; + // if saturation > 0, more complex calculations are needed + else + { + float f, p, q, t; + int i; + h *= 6.0; // to bring hue to a number between 0 and 6, better for the calculations + i = int(floor(h)); // e.g. 2.7 becomes 2 and 3.01 becomes 3 or 4.9999 becomes 4 + f = h - i; // the fractional part of h + + p = v * (1.0 - s); + q = v * (1.0 - (s * f)); + t = v * (1.0 - (s * (1.0 - f))); + + switch (i) + { + case 0: + r = v; + g = t; + b = p; + break; + case 1: + r = q; + g = v; + b = p; + break; + case 2: + r = p; + g = v; + b = t; + break; + case 3: + r = p; + g = q; + b = v; + break; + case 4: + r = t; + g = p; + b = v; + break; + case 5: + r = v; + g = p; + b = q; + break; + default: + r = g = b = 0; + break; + } + } + ColorRGB colorRGB; + colorRGB.r = int(r * 255.0); + colorRGB.g = int(g * 255.0); + colorRGB.b = int(b * 255.0); + return colorRGB; + } + + Uint32 RGBtoINT(const ColorRGB &colorRGB) + { + return 65536 * colorRGB.r + 256 * colorRGB.g + colorRGB.b; + } + + ColorRGB INTtoRGB(Uint32 colorINT) + { + ColorRGB colorRGB; + colorRGB.r = (colorINT / 65536) % 256; + colorRGB.g = (colorINT / 256) % 256; + colorRGB.b = colorINT % 256; + return colorRGB; + } +} \ No newline at end of file diff --git a/quickcg.h b/quickcg.h new file mode 100644 index 0000000..efdb030 --- /dev/null +++ b/quickcg.h @@ -0,0 +1,189 @@ +/* +QuickCG 20191227 + +Copyright (c) 2004-2019, Lode Vandevenne + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/* +QuickCG is an SDL 1.2 codebase that wraps some of the SDL 1.2 functionality. +It's used by Lode's Computer Graphics Tutorial to work with simple function calls +to demonstrate graphical programs. It may or may not be of industrial strength +for games, though I've actually used it for some. + +QuickCG can handle some things that standard C++ does not but that are useful, such as: +-drawing graphics +-a bitmap font +-simplified saving and loading of files +-reading keyboard and mouse input +-playing sound +-color models +-loading images + +Contact info: +My email address is (puzzle the account and domain together with an @ symbol): +Domain: gmail dot com. +Account: lode dot vandevenne. +*/ + +#ifndef _quickcg_h_included +#define _quickcg_h_included + +#include +#include +#include +#include +#include +#include //std::min and std::max + +namespace QuickCG +{ + + //////////////////////////////////////////////////////////////////////////////// + // useful templates////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + + // don't know why, but the standard C++ abs sometimes gives cryptic errors? if so use this :D + template + const T template_abs(const T &a) + { + return (a < 0) ? -a : a; + } + + // usage: std::string str = valtostr(25454.91654654f); + template + std::string valtostr(const T &val) + { + std::ostringstream sstream; + sstream << val; + return sstream.str(); + } + + // usage: double val = strtoval("465498.654"); + template + T strtoval(const std::string &s) + { + std::istringstream sstream(s); + T val; + sstream >> val; + return val; + } + + // length is decimal precision of the floating point number + template + std::string valtostr(const T &val, int length, bool fixed = true) + { + std::ostringstream sstream; + if (fixed) + sstream << std::fixed; + sstream << std::setprecision(length) << val; + return sstream.str(); + } + + //////////////////////////////////////////////////////////////////////////////// + // COLOR STRUCTS///////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + + struct ColorRGB8bit; + // a color with 3 components: r, g and b + struct ColorRGB + { + int r; + int g; + int b; + + ColorRGB(Uint8 r, Uint8 g, Uint8 b); + ColorRGB(const ColorRGB8bit &color); + ColorRGB(); + }; + + ColorRGB operator+(const ColorRGB &color, const ColorRGB &color2); + ColorRGB operator-(const ColorRGB &color, const ColorRGB &color2); + ColorRGB operator*(const ColorRGB &color, int a); + ColorRGB operator*(int a, const ColorRGB &color); + ColorRGB operator/(const ColorRGB &color, int a); + bool operator==(const ColorRGB &color, const ColorRGB &color2); + bool operator!=(const ColorRGB &color, const ColorRGB &color2); + + static const ColorRGB RGB_Black(0, 0, 0); + static const ColorRGB RGB_Red(255, 0, 0); + static const ColorRGB RGB_Green(0, 255, 0); + static const ColorRGB RGB_Blue(0, 0, 255); + static const ColorRGB RGB_Cyan(0, 255, 255); + static const ColorRGB RGB_Magenta(255, 0, 255); + static const ColorRGB RGB_Yellow(255, 255, 0); + static const ColorRGB RGB_White(255, 255, 255); + static const ColorRGB RGB_Gray(128, 128, 128); + static const ColorRGB RGB_Grey(192, 192, 192); + static const ColorRGB RGB_Maroon(128, 0, 0); + static const ColorRGB RGB_Darkgreen(0, 128, 0); + static const ColorRGB RGB_Navy(0, 0, 128); + static const ColorRGB RGB_Teal(0, 128, 128); + static const ColorRGB RGB_Purple(128, 0, 128); + static const ColorRGB RGB_Olive(128, 128, 0); + + // a color with 3 components: r, g and b + struct ColorRGB8bit + { + Uint8 r; + Uint8 g; + Uint8 b; + + ColorRGB8bit(Uint8 r, Uint8 g, Uint8 b); + ColorRGB8bit(const ColorRGB &color); + ColorRGB8bit(); + }; + + // a color with 3 components: h, s and l + struct ColorHSL + { + int h; + int s; + int l; + + ColorHSL(Uint8 h, Uint8 s, Uint8 l); + ColorHSL(); + }; + + // a color with 3 components: h, s and v + struct ColorHSV + { + int h; + int s; + int v; + + ColorHSV(Uint8 h, Uint8 s, Uint8 v); + ColorHSV(); + }; + + //////////////////////////////////////////////////////////////////////////////// + // COLOR CONVERSIONS///////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + ColorHSL RGBtoHSL(const ColorRGB &colorRGB); + ColorRGB HSLtoRGB(const ColorHSL &colorHSL); + ColorHSV RGBtoHSV(const ColorRGB &colorRGB); + ColorRGB HSVtoRGB(const ColorHSV &colorHSV); + Uint32 RGBtoINT(const ColorRGB &colorRGB); + ColorRGB INTtoRGB(Uint32 colorINT); + +} + +#endif \ No newline at end of file