diff options
Diffstat (limited to 'lib/nes/app')
| -rw-r--r-- | lib/nes/app/CMakeLists.txt | 7 | ||||
| -rw-r--r-- | lib/nes/app/application.cc | 38 | ||||
| -rw-r--r-- | lib/nes/app/application.hh | 37 | ||||
| -rw-r--r-- | lib/nes/app/input/CMakeLists.txt | 8 | ||||
| -rw-r--r-- | lib/nes/app/input/input-device-controller.hh | 38 | ||||
| -rw-r--r-- | lib/nes/app/input/input-device-keyboard.hh | 16 | ||||
| -rw-r--r-- | lib/nes/app/input/input-device.hh | 44 | ||||
| -rw-r--r-- | lib/nes/app/input/input-manager.cc | 102 | ||||
| -rw-r--r-- | lib/nes/app/input/input-manager.hh | 45 |
9 files changed, 335 insertions, 0 deletions
diff --git a/lib/nes/app/CMakeLists.txt b/lib/nes/app/CMakeLists.txt new file mode 100644 index 0000000..18b2a2c --- /dev/null +++ b/lib/nes/app/CMakeLists.txt @@ -0,0 +1,7 @@ +target_sources( + nes + PRIVATE + application.hh + application.cc) + +add_subdirectory(input)
\ No newline at end of file diff --git a/lib/nes/app/application.cc b/lib/nes/app/application.cc new file mode 100644 index 0000000..db43854 --- /dev/null +++ b/lib/nes/app/application.cc @@ -0,0 +1,38 @@ +#include "nes/app/application.hh" +#include "nes/app/input/input-device-keyboard.hh" +#include "nes/app/input/input-device-controller.hh" +#include <iostream> + +namespace nes +{ + namespace + { + constexpr auto file = std::string_view{ "/Users/hannes/Documents/temp/supermario.nes" }; + } // namespace + + application::application(display& display, input_device_keyboard& keyboard) + : display_{ display } + , input_manager_{ keyboard } + , console_{ cartridge::from_file(file), display_ } + { + if (console_.get_status() != status::success) + { + std::cerr << "Unable to load cartridge: " << to_string(console_.get_status()) << std::endl; + std::abort(); + } + } + + auto application::frame(std::chrono::microseconds const elapsed_time) -> void + { + console_.ref_controller_1().set_pressed(input_manager_.get_input_1().read_mask()); + console_.ref_controller_2().set_pressed(input_manager_.get_input_2().read_mask()); + + console_.step(elapsed_time); + + if (console_.get_status() != status::success) + { + std::cerr << "Invalid state: " << to_string(console_.get_status()) << std::endl; + std::abort(); + } + } +} // namespace nes
\ No newline at end of file diff --git a/lib/nes/app/application.hh b/lib/nes/app/application.hh new file mode 100644 index 0000000..9f6bd16 --- /dev/null +++ b/lib/nes/app/application.hh @@ -0,0 +1,37 @@ +#pragma once + +#include "nes/app/input/input-manager.hh" +#include "nes/sys/nes.hh" +#include <chrono> + +namespace nes +{ + class display; + + /// Entrypoint for the application. + /// + /// This class takes care of presenting a UI screen or the emulator, handling any errors and implementing the main + /// application logic. + /// + /// It delegates some platform-specific tasks to interfaces. + class application + { + display& display_; + input_manager input_manager_; + nes console_; + + public: + explicit application(display&, input_device_keyboard&); + + application(application const&) = delete; + application(application&&) = delete; + auto operator=(application const&) -> application& = delete; + auto operator=(application&&) -> application& = delete; + + /// Request a new frame for the application. + auto frame(std::chrono::microseconds elapsed_time) -> void; + + auto add_controller(input_device_controller& c) -> void { input_manager_.add_controller(c); } + auto remove_controller(input_device_controller& c) -> void { input_manager_.remove_controller(c); } + }; +} // namespace nes
\ No newline at end of file diff --git a/lib/nes/app/input/CMakeLists.txt b/lib/nes/app/input/CMakeLists.txt new file mode 100644 index 0000000..7150745 --- /dev/null +++ b/lib/nes/app/input/CMakeLists.txt @@ -0,0 +1,8 @@ +target_sources( + nes + PRIVATE + input-device.hh + input-device-keyboard.hh + input-device-controller.hh + input-manager.hh + input-manager.cc)
\ No newline at end of file diff --git a/lib/nes/app/input/input-device-controller.hh b/lib/nes/app/input/input-device-controller.hh new file mode 100644 index 0000000..6adb79c --- /dev/null +++ b/lib/nes/app/input/input-device-controller.hh @@ -0,0 +1,38 @@ +#pragma once + +#include "nes/app/input/input-device.hh" +#include "nes/common/list.hh" +#include <string_view> + +namespace nes +{ + /// A game controller input device. + class input_device_controller : public input_device, public list<input_device_controller>::node + { + public: + enum class index + { + unused = -1, + player_1 = 0, + player_2 = 1, + }; + + /// Indicates whether the controller can be trusted to actually exist. + /// + /// If this returns false, the controller is not automatically selected on startup if there is no other + /// controller already selected. + virtual auto is_reliable() const -> bool = 0; + + /// Human-readable name for the controller. + virtual auto get_name() const -> std::string_view = 0; + + /// Get the (persistent) player index for the controller, if already set before. + virtual auto get_index() const -> index = 0; + + /// Set the (persistent) player index for the controller. + virtual auto set_index(index) -> void = 0; + + protected: + explicit input_device_controller() = default; + }; +} // namespace nes
\ No newline at end of file diff --git a/lib/nes/app/input/input-device-keyboard.hh b/lib/nes/app/input/input-device-keyboard.hh new file mode 100644 index 0000000..d61417d --- /dev/null +++ b/lib/nes/app/input/input-device-keyboard.hh @@ -0,0 +1,16 @@ +#pragma once + +#include "nes/app/input/input-device.hh" + +namespace nes +{ + /// A keyboard input device. + class input_device_keyboard : public input_device + { + public: + virtual auto read_key_escape() -> bool = 0; + + protected: + explicit input_device_keyboard() = default; + }; +} // namespace nes
\ No newline at end of file diff --git a/lib/nes/app/input/input-device.hh b/lib/nes/app/input/input-device.hh new file mode 100644 index 0000000..9ac7423 --- /dev/null +++ b/lib/nes/app/input/input-device.hh @@ -0,0 +1,44 @@ +#pragma once + +#include "nes/sys/types/button-mask.hh" + +namespace nes +{ + /// An abstract input device which can be used to play games. + class input_device + { + public: + virtual ~input_device() = default; + + input_device(input_device const&) = delete; + input_device(input_device&&) = delete; + auto operator=(input_device const&) -> input_device& = delete; + auto operator=(input_device&&) -> input_device& = delete; + + virtual auto read_key_up() -> bool = 0; + virtual auto read_key_down() -> bool = 0; + virtual auto read_key_left() -> bool = 0; + virtual auto read_key_right() -> bool = 0; + virtual auto read_key_a() -> bool = 0; + virtual auto read_key_b() -> bool = 0; + virtual auto read_key_select() -> bool = 0; + virtual auto read_key_start() -> bool = 0; + + auto read_mask() -> button_mask + { + auto res = button_mask{}; + if (read_key_up()) { res.add(buttons::up); } + if (read_key_down()) { res.add(buttons::down); } + if (read_key_left()) { res.add(buttons::left); } + if (read_key_right()) { res.add(buttons::right); } + if (read_key_a()) { res.add(buttons::a); } + if (read_key_b()) { res.add(buttons::b); } + if (read_key_select()) { res.add(buttons::select); } + if (read_key_start()) { res.add(buttons::start); } + return res; + } + + protected: + explicit input_device() = default; + }; +} // namespace nes
\ No newline at end of file diff --git a/lib/nes/app/input/input-manager.cc b/lib/nes/app/input/input-manager.cc new file mode 100644 index 0000000..1c9db9e --- /dev/null +++ b/lib/nes/app/input/input-manager.cc @@ -0,0 +1,102 @@ +#include "nes/app/input/input-manager.hh" + +namespace nes +{ + namespace + { + class input_device_dummy final : public input_device + { + public: + explicit input_device_dummy() = default; + + static auto get_instance() -> input_device_dummy& + { + static auto res = input_device_dummy{}; + return res; + } + + auto read_key_up() -> bool override { return false; } + auto read_key_down() -> bool override { return false; } + auto read_key_left() -> bool override { return false; } + auto read_key_right() -> bool override { return false; } + auto read_key_a() -> bool override { return false; } + auto read_key_b() -> bool override { return false; } + auto read_key_select() -> bool override { return false; } + auto read_key_start() -> bool override { return false; } + }; + } // namespace + + input_manager::input_manager(input_device_keyboard& keyboard) + : keyboard_{ &keyboard } + { + fill_unassigned_inputs(); + } + + auto input_manager::add_controller(input_device_controller& controller) -> void + { + controllers_.append(controller); + if (controller.is_reliable()) + { + auto const index = controller.get_index(); + if (index != input_device_controller::index::unused) + { + // This controller was previously used and assigned an index -> replace the current device with this + // (known) controller. + auto const i = static_cast<std::size_t>(index); + if (inputs_[i].controller) + { + inputs_[i].controller->set_index(input_device_controller::index::unused); + } + + inputs_[i].controller = &controller; + inputs_[i].device = &controller; + } + + // There may be another free controller now. + fill_unassigned_inputs(); + } + } + + auto input_manager::remove_controller(input_device_controller& controller) -> void + { + controllers_.remove(controller); + fill_unassigned_inputs(); + } + + auto input_manager::fill_unassigned_inputs() -> void + { + auto it = controllers_.begin(); + input_device* fallback = keyboard_; + + for (auto i = std::size_t{ 0 }; i < input_count; ++i) + { + if (inputs_[i].controller != nullptr) { continue; } + + // This input is not assigned -> use the next available controller if available. + while (it != controllers_.end()) + { + auto controller = *it; + if (controller->is_reliable() && controller->get_index() == input_device_controller::index::unused) + { + break; + } + ++it; + } + + if (it != controllers_.end()) + { + // We've found an unused controller -> assign it. + inputs_[i].controller = *it; + inputs_[i].controller->set_index(static_cast<input_device_controller::index>(i)); + inputs_[i].device = *it; + } + else + { + // We're all out of controllers -> fall back to the keyboard (if this is the first missing controller) + // or nothing. + inputs_[i].device = fallback; + fallback = &input_device_dummy::get_instance(); // Only use the keyboard as a fallback once. + } + } + } +} // namespace nes
\ No newline at end of file diff --git a/lib/nes/app/input/input-manager.hh b/lib/nes/app/input/input-manager.hh new file mode 100644 index 0000000..10308c7 --- /dev/null +++ b/lib/nes/app/input/input-manager.hh @@ -0,0 +1,45 @@ +#pragma once + +#include "nes/app/input/input-device-keyboard.hh" +#include "nes/app/input/input-device-controller.hh" +#include "nes/common/list.hh" + +namespace nes +{ + /// Manages input devices. + /// + /// The main purpose of this class is to assign newly plugged in controllers as the primary/secondary input and fall + /// back to the keyboard if needed. + class input_manager + { + static constexpr auto input_count = std::size_t{ 2 }; + + input_device_keyboard* keyboard_; + list<input_device_controller> controllers_; + struct + { + input_device* device{ nullptr }; + input_device_controller* controller{ nullptr }; + } inputs_[input_count]{}; + + public: + explicit input_manager(input_device_keyboard&); + + input_manager(input_manager const&) = delete; + input_manager(input_manager&&) = delete; + auto operator=(input_manager const&) -> input_manager& = delete; + auto operator=(input_manager&&) -> input_manager& = delete; + + auto get_input_1() const -> input_device& { return *inputs_[0].device; } + auto get_input_2() const -> input_device& { return *inputs_[1].device; } + + auto get_keyboard() const -> input_device_keyboard& { return *keyboard_; } + auto get_controllers() const -> list<input_device_controller> const& { return controllers_; } + + auto add_controller(input_device_controller&) -> void; + auto remove_controller(input_device_controller&) -> void; + + private: + auto fill_unassigned_inputs() -> void; + }; +} // namespace nes
\ No newline at end of file |
