diff --git a/README.de.md b/README.de.md index 6b3d0d6..274cee6 100644 --- a/README.de.md +++ b/README.de.md @@ -1,40 +1,41 @@ # ParadiSO - eine minimale 2D-Grafikengine -**ParadiSO** wurde als stark abgespeckte 2D-Version meiner `pixwerx`-Engine konzipiert. *ParadiSO* verfolgt einen minimalistischen Ansatz f�r 2D-Grafik zu Bildungszwecken. Es verwendet modernes C++ und ein datengetriebenes Design, jedoch keine ECS (Entity Component System). +**ParadiSO** wurde als stark abgespeckte 2D-Version meiner `pixwerx`-Engine konzipiert. _ParadiSO_ verfolgt einen minimalistischen Ansatz für 2D-Grafik zu Bildungszwecken. Es verwendet modernes C++ und ein datengetriebenes Design, jedoch keine ECS (Entity Component System). ## Bildungszwecke -Einige Argumente f�r seinen Bildungsaspekt: +Einige Argumente für seinen Bildungsaspekt: -- Kombination verschiedener Konzepte und Paradigmen zur Erstellung ausdrucksstarker, aber knappen Codes -- Stark von Rust-Code inspiriert -- F�r den mathematischen Code wird eine sofortige Auswertung verwendet (keine Expression-Templates), jedoch mit der Verwendung von `constexpr`, um eventuelle Performance-Overheads auszugleichen und optimale Vektorisierung zu erreichen. -- Versteckt alte `C`-APIs hinter einer modernisierten Fassade -- Es lehnt sich stark an die STL und ihre Algorithmen an +- Kombination verschiedener Konzepte und Paradigmen zur Erstellung ausdrucksstarker, aber knappen Codes +- Stark von Rust-Code inspiriert +- Für den mathematischen Code wird eine sofortige Auswertung verwendet (keine Expression-Templates), jedoch mit der Verwendung von `constexpr`, um eventuelle Performance-Overheads auszugleichen und optimale Vektorisierung zu erreichen. +- Versteckt alte `C`-APIs hinter einer modernisierten Fassade +- Es lehnt sich stark an die STL und ihre Algorithmen an ## Minimalistisch -Da diese Engine einige Muster und Designkonzepte zeigen soll, versucht sie, unn�tigen Ballast zu vermeiden. +Da diese Engine einige Muster und Designkonzepte zeigen soll, versucht sie, unnötigen Ballast zu vermeiden. -## Abh�ngigkeiten +## Abhängigkeiten -ParadiSO wird mit den notwendigen Komponenten geliefert. Diese sollte jedoch hier erw�hnt werden: +ParadiSO wird mit den notwendigen Komponenten geliefert. Diese sollte jedoch hier erwähnt werden: -- [GLFW 3.3.8](https://github.com/glfw/glfw) -- [GLAD](https://github.com/Dav1dde/glad) -- [STB image](https://github.com/nothings/stb) +- [GLFW 3.3.8](https://github.com/glfw/glfw) +- [GLAD](https://github.com/Dav1dde/glad) +- [STB image](https://github.com/nothings/stb) ## Toolchains ParadiSO kann auf verschiedenen Plattformen gebaut werden -- Windows (Visual Studio Build Tools 2022) -- MacOS (clang 14 or later) -- Linux (clang 14 or later, gcc 13.2.1) +- Windows (Visual Studio Build Tools 2022) +- MacOS (clang 14 or later) +- Linux (clang 14 or later, gcc 13.2.1) Andere Kombinationen sind möglich aber nicht getestet. -## Beitr�ge +## Beiträge -* [Tim G�tzelmann](https://code.technotecture.net/Timeplex) Windows Build -* [Robin Rottst�dt](https://code.technotecture.net/robin_rottstaedt) Flappy Bird Clone, Bitmap Loader \ No newline at end of file +- [Tim Götzelmann](https://code.technotecture.net/Timeplex) Windows Build +- [Robin Rottstädt](https://code.technotecture.net/robin_rottstaedt) Flappy Bird Clone, Bitmap Loader +- [brxxh](https://code.technotecture.net/brxxh) Flappy Bird Clone diff --git a/README.md b/README.md index 6f3f006..07828bc 100644 --- a/README.md +++ b/README.md @@ -1,41 +1,41 @@ # ParadiSO - a minimal 2D graphics engine -**ParadiSO** was conceived as a heavily stripped down 2D version of my `pixwerx` engine. *ParadiSO* mimics a minimalistic approach to 2D graphics for educational purposes. It uses modern C++ and a data-driven design, but no ECS. +**ParadiSO** was conceived as a heavily stripped down 2D version of my `pixwerx` engine. _ParadiSO_ mimics a minimalistic approach to 2D graphics for educational purposes. It uses modern C++ and a data-driven design, but no ECS. ## Educational Some arguments for its educational side: -* mix and match of various concepts and paradigms to write expressive but concise code -* heavily inspired by Rust code -* math code is eager evaluation but `constexpr` to compensate overheads -* hides old-style `C` APIs behind a renovated facade -* it leans heavily on the STL and its algorithms +- mix and match of various concepts and paradigms to write expressive but concise code +- heavily inspired by Rust code +- math code is eager evaluation but `constexpr` to compensate overheads +- hides old-style `C` APIs behind a renovated facade +- it leans heavily on the STL and its algorithms ## Minimal -Because this engine should show some patterns and design concepts it tries to avoid adding unnecessary bloat. +Because this engine should show some patterns and design concepts it tries to avoid adding unnecessary bloat. ## Dependencies ParadiSO comes with batteries included. However, it should be mentioned here: -- [GLFW 3.3.8](https://github.com/glfw/glfw) -- [GLAD](https://github.com/Dav1dde/glad) -- [STB image](https://github.com/nothings/stb) +- [GLFW 3.3.8](https://github.com/glfw/glfw) +- [GLAD](https://github.com/Dav1dde/glad) +- [STB image](https://github.com/nothings/stb) ## Toolchains ParadiSO is being developed to work on all major desktop systems. -- Windows (Visual Studio Build Tools 2022) -- MacOS (clang 14 or later) -- Linux (clang 14 or later, gcc 13.2.1) +- Windows (Visual Studio Build Tools 2022) +- MacOS (clang 14 or later) +- Linux (clang 14 or later, gcc 13.2.1) Other combinations might work but are untested. - ## Contributors -* [Tim Götzelmann](https://code.technotecture.net/Timeplex) Windows Build -* [Robin Rottstädt](https://code.technotecture.net/robin_rottstaedt) Flappy Bird Clone, Bitmap Loader \ No newline at end of file +- [Tim Götzelmann](https://code.technotecture.net/Timeplex) Windows Build +- [Robin Rottstädt](https://code.technotecture.net/robin_rottstaedt) Flappy Bird Clone, Bitmap Loader +- [brxxh](https://code.technotecture.net/brxxh) Flappy Bird Clone diff --git a/examples/quickwings/assets/background-day.png b/examples/quickwings/assets/background-day.png index afcb5ec..f9ed139 100644 Binary files a/examples/quickwings/assets/background-day.png and b/examples/quickwings/assets/background-day.png differ diff --git a/examples/quickwings/assets/base.png b/examples/quickwings/assets/base.png index c374f2b..26fa391 100644 Binary files a/examples/quickwings/assets/base.png and b/examples/quickwings/assets/base.png differ diff --git a/examples/quickwings/assets/gameover.png b/examples/quickwings/assets/gameover.png new file mode 100644 index 0000000..b1df7f5 Binary files /dev/null and b/examples/quickwings/assets/gameover.png differ diff --git a/examples/quickwings/assets/message.png b/examples/quickwings/assets/message.png new file mode 100644 index 0000000..9243ab5 Binary files /dev/null and b/examples/quickwings/assets/message.png differ diff --git a/examples/quickwings/quickwings.cpp b/examples/quickwings/quickwings.cpp index cd25735..2d8bd96 100644 --- a/examples/quickwings/quickwings.cpp +++ b/examples/quickwings/quickwings.cpp @@ -1,10 +1,14 @@ /** * paradiso - Paradigmen der Softwareentwicklung * - * (c) Copyright 2023 Hartmut Seichter, Robin Rottstädt + * (c) Copyright 2023 Hartmut Seichter, Robin Rottstädt, brxxh (Hannes Brothuhn) * */ +#include +#include +#include +#include #include #include #include @@ -20,10 +24,19 @@ #include #include #include +#include +#include // #include "lib/image_loader.hpp" const int frame_rate = 60; +bool game_over = false; +float risky_pos_x; +float risky_pos_max_x; +float risky_pos_bottom_y; +float risky_pos_top_y; +float risky_pos_bottom_max_y; +float risky_pos_top_max_y; struct Background { paradiso::Sprite backgroundLeft; @@ -33,81 +46,179 @@ struct Background { paradiso::Sprite* scrolling[2] = {&backgroundLeft, &backgroundRight}; + paradiso::Renderer renderer{}; + Background() { auto backgroundImage = paradiso::BitmapIO::get().load("background-day.png"); backgroundLeft = paradiso::Sprite{ .bitmap = backgroundImage, - .pivot = paradiso::Vector2::make(0.0f, 0.0f), - .scale = paradiso::Vector2::make(1.01f, 1.0f)}; + .pivot = {paradiso::Vector2::make(0.0f, 0.16f)}, + .scale = {paradiso::Vector2::make(1.01f, 1.3f)}}; backgroundRight = paradiso::Sprite{ .bitmap = backgroundImage, - .pivot = paradiso::Vector2::make(2.0f, 0.0f), - .scale = paradiso::Vector2::make(1.01f, 1.0f)}; + .pivot = {paradiso::Vector2::make(2.018f, 0.16f)}, + .scale = {paradiso::Vector2::make(1.01f, 1.3f)}}; auto grassImage = paradiso::BitmapIO::get().load("base.png"); grassLeft = paradiso::Sprite{ .bitmap = grassImage, - .pivot = paradiso::Vector2::make(0.0f, -1.0f), - .scale = paradiso::Vector2::make(1.0f, 0.33333f)}; + .pivot = {paradiso::Vector2::make(0.0f, -1.0f)}, + .scale = {paradiso::Vector2::make(1.0f, 0.33333f)}}; grassRight = paradiso::Sprite{ .bitmap = grassImage, - .pivot = paradiso::Vector2::make(2.0f, -1.0f), - .scale = paradiso::Vector2::make(1.0f, 0.33333f)}; + .pivot = {paradiso::Vector2::make(2.0f, -1.0f)}, + .scale = {paradiso::Vector2::make(1.0f, 0.33333f)}}; } void draw(const paradiso::Shader& shader) { for (auto sprite : scrolling) { - if (sprite->pivot.x() <= -2.0f) { - sprite->pivot.x() += 4.0f; + if (game_over == false) { + if (sprite->pivot.x() <= -2.0f) { + sprite->pivot.x() += 4.0f; + } + sprite->pivot.x() -= 0.002f; } - sprite->pivot.x() -= 0.001f; shader.set_uniform("pivot", sprite->pivot); shader.set_uniform("scale", sprite->scale); shader.set_uniform("rotation", sprite->rotation); renderer.draw(*sprite, shader); } } +}; - paradiso::Renderer renderer{}; +struct Pipe { + paradiso::Sprite pipe_top; + paradiso::Sprite pipe_bottom; + + paradiso::Renderer renderer1{}; + paradiso::Renderer renderer2{}; + + bool paused = false; + + int pipe_spawn_rand_int = rand() % 80 + 15; + float pipe_spawn_rand = float(pipe_spawn_rand_int) / 100; + + bool pos_reset = false; + + Pipe() { + auto pipe_image = paradiso::BitmapIO::get().load("pipe-green.png"); + + pipe_top = paradiso::Sprite{ + .bitmap = pipe_image, + .pivot = {paradiso::Vector2::make(1.4f, pipe_spawn_rand + 1.0f)}, + .scale = {paradiso::Vector2::make(((500.0f - (500.0f - 52.0f)) / 500.0f) * 2.25f, ((700.0f - (700.0f - 320.0f)) / 700.0f) * 2.25f)}, + .rotation = 3.1415926f}; + pipe_bottom = paradiso::Sprite{ + .bitmap = pipe_image, + .pivot = {paradiso::Vector2::make(1.4f, pipe_spawn_rand - 1.5f)}, + .scale = {paradiso::Vector2::make(((500.0f - (500.0f - 52.0f)) / 500.0f) * 2.25f, ((700.0f - (700.0f - 320.0f)) / 700.0f) * 2.25f)}}; + + paused = true; + + } + + void update() { + + if (game_over == true) { + paused = true; + } + + if (paused == true) { + return; + } + else { + pipe_spawn_rand_int = rand() % 80 + 15; + pipe_spawn_rand = float(pipe_spawn_rand_int) / 100; + + pipe_top.pivot.x() -= 0.02f; + pipe_bottom.pivot.x() -= 0.02f; + + risky_pos_x = pipe_top.pivot.x(); + risky_pos_max_x = pipe_top.pivot.x() + ((500.0f - (500.0f - 52.0f)) / 500.0f) * 2.25f; + + risky_pos_top_y = pipe_top.pivot.y(); + risky_pos_top_max_y = pipe_top.pivot.y() + 10.0f; + + risky_pos_bottom_y = pipe_bottom.pivot.y(); + risky_pos_bottom_max_y = pipe_bottom.pivot.y() - 10.0f; + + if (pipe_top.pivot.x() <= -1.4f || pipe_bottom.pivot.x() <= -1.4f) { + pos_reset = true; + + if (pos_reset == true) { + pipe_top.pivot.y() = pipe_spawn_rand + 1.0f; + pipe_bottom.pivot.y() = pipe_spawn_rand - 1.5; + + pos_reset = false; + } + + pipe_top.pivot.x() = 1.4f; + pipe_bottom.pivot.x() = 1.4f; + } + } + } + + void draw(const paradiso::Shader& shader) { + shader.set_uniform("pivot", pipe_bottom.pivot); + shader.set_uniform("scale", pipe_bottom.scale); + renderer1.draw(pipe_bottom, shader); + + shader.set_uniform("pivot", pipe_top.pivot); + shader.set_uniform("scale", pipe_top.scale); + shader.set_uniform("rotation", pipe_top.rotation); + renderer1.draw(pipe_top, shader); + } }; struct Grass { paradiso::Sprite grassLeft; paradiso::Sprite grassRight; - paradiso::Sprite* scrolling[2] = {&grassLeft, &grassRight}; + paradiso::Renderer renderer1{}; + paradiso::Renderer renderer2{}; + Grass() { - auto grassImage = paradiso::BitmapIO::get().load(std::string("base.png")); + auto grassImage = paradiso::BitmapIO::get().load("base.png"); grassLeft = paradiso::Sprite{ .bitmap = grassImage, - .pivot = paradiso::Vector2::make(0.0f, -0.9f), - .scale = paradiso::Vector2::make(1.0f, 0.33333f)}; + .pivot = {paradiso::Vector2::make(0.0f, -0.9f)}, + .scale = {paradiso::Vector2::make(((500.0f - (500.0f - 504.0f)) / 500.0f) * 2.25f, ((700.0f - (700.0f - 112.0f)) / 700.0f) * 2.25f)}}; grassRight = paradiso::Sprite{ .bitmap = grassImage, - .pivot = paradiso::Vector2::make(2.0f, -0.9f), - .scale = paradiso::Vector2::make(1.0f, 0.33333f)}; + .pivot = {paradiso::Vector2::make(1.002f, -0.9f)}, + .scale = {paradiso::Vector2::make(((500.0f - (500.0f - 504.0f)) / 500.0f) * 2.25f, ((700.0f - (700.0f - 112.0f)) / 700.0f) * 2.25f)}}; } void draw(const paradiso::Shader& shader) { - for (auto sprite : scrolling) { - if (sprite->pivot.x() <= -2.0f) { - sprite->pivot.x() += 4.0f; - } - sprite->pivot.x() -= 0.035f; - shader.set_uniform("pivot", sprite->pivot); - shader.set_uniform("scale", sprite->scale); - shader.set_uniform("rotation", sprite->rotation); - renderer.draw(*sprite, shader); - } - } + if (game_over == false) { + grassLeft.pivot.x() -= 0.02f; + grassRight.pivot.x() -= 0.02f; - paradiso::Renderer renderer{}; + if (grassRight.pivot.x() <= 0.0f) { + grassLeft.pivot.x() = 1.002f; + } + if (grassRight.pivot.x() <= -1.002f) { + grassRight.pivot.x() = 1.002f; + } + } + + shader.set_uniform("pivot", grassLeft.pivot); + shader.set_uniform("scale", grassLeft.scale); + shader.set_uniform("rotation", grassLeft.rotation); + + shader.set_uniform("pivot", grassRight.pivot); + shader.set_uniform("scale", grassRight.scale); + shader.set_uniform("rotation", grassRight.rotation); + + renderer1.draw(grassLeft, shader); + renderer2.draw(grassRight, shader); + } }; -struct QuickWings { +struct QuickWings { paradiso::Renderer renderer1{}; paradiso::Renderer renderer2{}; paradiso::Renderer renderer3{}; @@ -118,10 +229,10 @@ struct QuickWings { unsigned int flapCounter = 0; // How many ticks since last flap float velocity = 0.0f; - const float max_velocity = 0.05f; + const float max_velocity = 0.02f; - const float gravity = -0.004f; - const float move_up_velocity = 0.0055f; + const float gravity = -0.002f; + const float move_up_velocity = 0.02f; bool move_up = false; bool paused = true; @@ -129,30 +240,32 @@ struct QuickWings { const float max_pos = 0.95f; const float min_pos = -0.5f; - float pos = 0.0f; + float pos = 0.34f; float rotation = 0.0f; + int collision_counter = 0; + QuickWings() { - float scaleh = 0.07f; - float scalew = scaleh * 1.416666666666667f; + float scaleh = 0.08f; + float scalew = 0.158f; birds = { paradiso::Sprite{ .bitmap = paradiso::BitmapIO::get().load("yellowbird-downflap.png"), - .pivot = paradiso::Vector2::make(0.0f, 0.0f), - .scale = paradiso::Vector2::make(scalew, scaleh)}, + .pivot = {paradiso::Vector2::make(0.0f, 0.0f)}, + .scale = {paradiso::Vector2::make(scalew, scaleh)}}, paradiso::Sprite{ .bitmap = paradiso::BitmapIO::get().load("yellowbird-midflap.png"), - .pivot = paradiso::Vector2::make(0.0f, 0.0f), - .scale = paradiso::Vector2::make(scalew, scaleh)}, + .pivot = {paradiso::Vector2::make(0.0f, 0.0f)}, + .scale = {paradiso::Vector2::make(scalew, scaleh)}}, paradiso::Sprite{ .bitmap = paradiso::BitmapIO::get().load("yellowbird-upflap.png"), - .pivot = paradiso::Vector2::make(0.0f, 0.0f), - .scale = paradiso::Vector2::make(scalew, scaleh)}}; + .pivot = {paradiso::Vector2::make(0.0f, 0.0f)}, + .scale = {paradiso::Vector2::make(scalew, scaleh)}}}; } void draw(const paradiso::Shader& shader) { @@ -185,53 +298,144 @@ struct QuickWings { } void update() { + + if (game_over == true) { + paused = true; + } + // Stop game - if (paused) + if (paused) { return; - - // Apply gravity - velocity += gravity; - - if (move_up) - velocity += move_up_velocity - gravity; - - // Cap velocity - if (velocity > max_velocity) - velocity = max_velocity; - if (velocity < -max_velocity) - velocity = -max_velocity; - - // Cap position - pos += velocity; - if (pos < min_pos) { - pos = min_pos; - velocity = 0.0f; } - if (pos > max_pos) { - pos = max_pos; - velocity = 0.0f; + else { + // Apply gravity + velocity += gravity; + + if (move_up) + velocity += move_up_velocity - gravity; + + // Cap velocity + if (velocity > max_velocity) + velocity = max_velocity; + if (velocity < -max_velocity) + velocity = -max_velocity; + + // Cap position + pos += velocity; + if (pos < min_pos) { + pos = min_pos; + velocity = 0.0f; + game_over = true; + } + if (pos > max_pos) { + pos = max_pos; + velocity = 0.0f; + game_over = true; + } + + // Update rotation + rotation = velocity * 10.0f; } - // Update rotation - rotation = velocity * 15.0f; + float final_risky_pos_top_y = risky_pos_top_y - 1.06f; + float final_risky_pos_bottom_y = risky_pos_bottom_y + 1.06f; + + if (risky_pos_x - 0.3f <= 0.0f && risky_pos_max_x >= 0.0f) { + if (pos >= final_risky_pos_top_y && pos <= risky_pos_top_max_y) { + game_over = true; + } + if (pos <= final_risky_pos_bottom_y && pos >= risky_pos_bottom_max_y) { + if (collision_counter == 0) { + collision_counter++; + } + else { + collision_counter = 2; + } + + if (collision_counter == 2) { + game_over = true; + } + } + } } // keyboard handler void on_keyboard(const paradiso::Window::KeyboardInputStack& input) { if (input.size()) { paused = false; - bool pressed_up = input.top().key == ' ' || input.top().key == 'W'; - if (input.top().action == 1 && pressed_up) { - move_up = true; - } else if (input.top().action == 0 && pressed_up) { - move_up = false; + if (paused == false) { + bool pressed_up = input.top().key == ' ' || input.top().key == 'W'; + + if (input.top().action == 1 && pressed_up) { + move_up = true; + } else if (input.top().action == 0 && pressed_up) { + move_up = false; + } + } + else { + return; } } } }; +struct Message { + paradiso::Sprite messageSprite; + paradiso::Renderer renderer{}; + bool start = false; + float pos = 100.0f; + + Message() { + auto messageImage = paradiso::BitmapIO::get().load("message.png"); + messageSprite = paradiso::Sprite{ + .bitmap = messageImage, + .pivot = {paradiso::Vector2::make(0.0f, 0.0f)}, + .scale = {paradiso::Vector2::make(0.8f, 0.8f)} + }; + }; + + void draw(const paradiso::Shader& shader) { + shader.set_uniform("pivot", messageSprite.pivot); + shader.set_uniform("scale", messageSprite.scale); + renderer.draw(messageSprite, shader); + } + + void update() { + if (start == true) { + messageSprite.pivot.y() = pos; + } + } + + void on_keyboard(const paradiso::Window::KeyboardInputStack& input) { + if (input.size()) { + start = true; + } + } +}; + +struct GameOverMessage { + paradiso::Sprite messageSprite; + paradiso::Renderer renderer{}; + + GameOverMessage() { + auto messageImage = paradiso::BitmapIO::get().load("gameover.png"); + messageSprite = paradiso::Sprite{ + .bitmap = messageImage, + .pivot = {paradiso::Vector2::make(0.0f, 0.4f)}, + .scale = {paradiso::Vector2::make(((500.0f - (500.0f - 192.0f)) / 500.0f) * 2.25f, ((700.0f - (700.0f - 42.0f)) / 700.0f) * 2.25f)} + }; + }; + + void draw(const paradiso::Shader& shader) { + shader.set_uniform("pivot", messageSprite.pivot); + shader.set_uniform("scale", messageSprite.scale); + renderer.draw(messageSprite, shader); + } +}; + auto main() -> int { + std::srand(std::time(nullptr)); // Ausgabefenster ... sieht aus als wäre es auf dem Stack auto window = paradiso::Window(); @@ -246,7 +450,7 @@ auto main() -> int { */ window .set_size(size) // ... Grösse - .set_position(paradiso::Point{.x = 100, .y = 100}) // ... Position + .set_position(paradiso::Point{.x = 1920 / 2 - 500 / 2, .y = 1080 / 2 - 700 / 2}) // ... Position .set_title("PardiSO.FlappyBird") // ... Titel .set_visible(true); // ... und jetzt anzeigen! @@ -276,8 +480,11 @@ auto main() -> int { // Load auto background = Background{}; auto grass = Grass{}; - auto quickwingsapp = QuickWings{}; + auto pipe = Pipe{}; + auto message = Message{}; + auto gameover = GameOverMessage{}; + // timer @@ -291,13 +498,29 @@ auto main() -> int { ctx.clear(); auto t1 = std::chrono::high_resolution_clock::now(); - // Keyboard and state change + // Keyboard and state change + update + if (quickwingsapp.paused == false) { + pipe.paused = false; + } + + pipe.update(); + quickwingsapp.on_keyboard(w.keyboard_input()); quickwingsapp.update(); + message.on_keyboard(w.keyboard_input()); + message.update(); + // Draw background.draw(shader); + pipe.draw(shader); grass.draw(shader); + message.draw(shader); + + if (game_over == true) { + gameover.draw(shader); + } + quickwingsapp.draw(shader); // wait for frame rate @@ -310,7 +533,7 @@ auto main() -> int { // Quit return !(w.keyboard_input().size() && w.keyboard_input().top().key == 'Q'); - })) { + })) { }; return 0;