/** * paradiso - Paradigmen der Softwareentwicklung * * (c) Copyright 2023 Hartmut Seichter * */ #include #include #include #include #include #include #include #include #include #include #include struct PongStage { using Vec2 = paradiso::Vector2; Vec2 tl{Vec2::make(-1.0f, -1.0f)}; Vec2 br{Vec2::make(+1.0f, +1.0f)}; enum class TouchPoint { None, Left, Right, Bottom, Top }; TouchPoint touch(const Vec2& o, float eps = 0.001f) noexcept { std::unordered_map deltas; deltas[TouchPoint::Left] = std::abs(o.x() - tl.x()); deltas[TouchPoint::Right] = std::abs(o.x() - br.x()); deltas[TouchPoint::Bottom] = std::abs(o.y() - br.y()); deltas[TouchPoint::Top] = std::abs(o.y() - tl.y()); auto min_el = *std::min_element( std::begin(deltas), std::end(deltas), [](const auto& l, const auto& r) { return l.second < r.second; }); auto t = (min_el.second <= eps) ? min_el.first : TouchPoint::None; if (t != TouchPoint::None) { sprite.bitmap.pixel(0,0) = paradiso::RGBA::white(); } else { sprite.bitmap.pixel(0,0) = paradiso::RGBA::black(); } return t; }; // sprite paradiso::Sprite sprite{ .bitmap = paradiso::Bitmap::from_data(paradiso::Size{1, 1}, paradiso::RGBA::from_rgb(0x80,0xFF,0xFF)) }; void draw(const paradiso::Shader& shader) { renderer.draw(sprite, shader); } paradiso::Renderer renderer{}; }; struct PongPaddle { static constexpr float whoopiness = 0.95f; // sprite paradiso::Sprite sprite{ .bitmap = paradiso::Bitmap::from_data(paradiso::Size{1, 1}, paradiso::RGBA::white()), .pivot = paradiso::Vector2::make(0.0f, -0.9f), .scale = paradiso::Vector2::make(0.25f, 0.01f)}; // keyboard handler void on_keyboard(const paradiso::Window::KeyboardInputStack& input) { if (input.size()) { if (input.top().key == 'A' || input.top().key == 263) { velocity_horizontal -= 0.01; } else if (input.top().key == 'D' || input.top().key == 262) { velocity_horizontal += 0.01; } } } void draw(const paradiso::Shader& shader) { // update internal state velocity_horizontal *= whoopiness; sprite.pivot.x() += velocity_horizontal; std::clamp(sprite.pivot.x(), -0.5f, 0.5f); // update shader uniforms shader.set_uniform("pivot", sprite.pivot); shader.set_uniform("scale", sprite.scale); shader.set_uniform("rotation", sprite.rotation); renderer.draw(sprite, shader); } float velocity_horizontal{}; paradiso::Renderer renderer{}; }; struct PongBall { // sprite paradiso::Sprite sprite{ .bitmap = paradiso::Bitmap::from_data(paradiso::Size{1, 1}, paradiso::RGBA::white()), .pivot = paradiso::Vector2::make(0.0f, 0.0f), .scale = paradiso::Vector2::make(0.0125f, 0.0125f), }; void interact(PongStage& stage) { auto touch = stage.touch(sprite.pivot); switch (touch) { case PongStage::TouchPoint::Top: case PongStage::TouchPoint::Bottom: velocity.y() *= -1; break; case PongStage::TouchPoint::Left: case PongStage::TouchPoint::Right: velocity.x() *= -1; break; default: break; } } // interact ball & paddle void interact(const PongPaddle& paddle) { const auto& ps = paddle.sprite.scale; const auto& pp = paddle.sprite.pivot; auto left_x = pp.x() - ps.x(); auto right_x = pp.x() + ps.x(); static constexpr float eps{0.01f}; if (sprite.pivot.x() >= left_x && sprite.pivot.x() <= right_x && std::abs(pp.y() - sprite.pivot.y()) < eps) { velocity.y() *= -1; velocity.x() += paddle.velocity_horizontal * paddle.whoopiness; } } void update() { sprite.pivot += velocity; } void draw(const paradiso::Shader& shader) { std::clamp(sprite.pivot.x(), -0.5f, 0.5f); // update shader uniforms shader.set_uniform("pivot", sprite.pivot); shader.set_uniform("scale", sprite.scale); shader.set_uniform("rotation", sprite.rotation); renderer.draw(sprite, shader); } paradiso::Vector2 velocity{}; paradiso::Renderer renderer{}; constexpr void push(const auto& impulse) noexcept { velocity += impulse; } }; auto main() -> int { // Ausgabefenster ... sieht aus als wäre es auf dem Stack auto window = paradiso::Window(); // wir bauen ein Fenster ... window .set_size(paradiso::Size{.width = 720, .height = 720}) // ... Grösse .set_position(paradiso::Point{.x = 100, .y = 100}) // ... Position .set_title("PardiSO.Pong") // ... Titel .set_visible(true); // ... und jetzt anzeigen! // der Fenster Kontext auto ctx = paradiso::Context{}; // ein Shader (Schattierungsprogramm) auto shader = paradiso::Shader{}; // wir nutzen einen vorgefertigten shader shader.load_preset(paradiso::Shader::Preset::Sprite); auto paddle = PongPaddle{}; auto ball = PongBall{}; auto stage = PongStage{}; ball.push(paradiso::Vector2::make(0.0001f, -0.0005f)); // ein viewport stellt die Sicht der Kamera dar, d.h. bei quadratischen // Pixeln sollte hier auch eine dementsprechende Grösse eingestellt // werden ctx.set_viewport(paradiso::Rectangle{ .position = paradiso::Point{.x = 0, .y = 0}, .size = window.client_size() .maximal_extent() // wir wollen das // Seitenverhältnis beibehalten }); // nothing beats a classic look ctx.set_clearcolor(paradiso::RGBA::from_rgb(0x00, 0x00, 0x00)); // das update führt den hier mitgegebnen Ausdruck innerhalb der internen // Updates des Fensters auf. Es wird hier auch explizit ein bool gefordert // damit das update auch zu jederzeit unterbrochen werden kann while (window.update([&](paradiso::Window& w) -> bool { auto me = window.client_size().maximal_extent(); ctx.set_viewport(paradiso::Rectangle{ .position = paradiso::Point{.x = 0, .y = 0}, .size = me // wir wollen das // Seitenverhältnis beibehalten }); // hier wird das eigentliche löschen des vorherigen Inhalts ausgelöst ctx.clear(); paddle.on_keyboard(w.keyboard_input()); // still here bool want_close = (w.keyboard_input().size() && w.keyboard_input().top().key == 'Q'); if (!w.keyboard_input().empty()) { if (w.keyboard_input().top().key == 'R') { ball.sprite.pivot.x() = 0.0f; ball.sprite.pivot.y() = 0.9f; } } ball.interact(stage); ball.interact(paddle); ball.update(); stage.draw(shader); paddle.draw(shader); ball.draw(shader); // ... signalisiere ob wir weitermachen wollen ... return !want_close; })) { }; return 0; }