quickerNES/source/inputParser.hpp

349 lines
10 KiB
C++
Raw Normal View History

2024-07-28 14:43:09 +00:00
#pragma once
// Base controller class
// by eien86
#include <cstdint>
#include <jaffarCommon/exceptions.hpp>
#include <jaffarCommon/json.hpp>
2024-07-28 14:52:05 +00:00
#include <sstream>
#include <string>
2024-07-28 14:43:09 +00:00
namespace jaffar
{
typedef uint32_t port_t;
struct input_t
{
bool power = false;
bool reset = false;
port_t port1 = 0;
port_t port2 = 0;
2024-08-07 19:11:24 +00:00
port_t arkanoidLatch = 0;
uint8_t arkanoidFire = 0;
2024-07-28 14:43:09 +00:00
};
class InputParser
{
2024-07-28 14:52:05 +00:00
public:
enum controller_t
{
none,
joypad,
fourscore1,
2024-08-07 19:11:24 +00:00
fourscore2,
arkanoidNES,
arkanoidFamicom
2024-07-28 14:52:05 +00:00
};
2024-07-28 14:43:09 +00:00
2024-08-07 19:11:24 +00:00
controller_t _controller1Type;
controller_t _controller2Type;
2024-07-28 14:43:09 +00:00
InputParser(const nlohmann::json &config)
{
// Parsing controller 1 type
{
bool isTypeRecognized = false;
const auto controller1Type = jaffarCommon::json::getString(config, "Controller 1 Type");
2024-07-28 14:52:05 +00:00
if (controller1Type == "None")
{
_controller1Type = controller_t::none;
isTypeRecognized = true;
}
if (controller1Type == "Joypad")
{
_controller1Type = controller_t::joypad;
isTypeRecognized = true;
}
if (controller1Type == "FourScore1")
{
_controller1Type = controller_t::fourscore1;
isTypeRecognized = true;
}
if (controller1Type == "FourScore2")
{
_controller1Type = controller_t::fourscore2;
isTypeRecognized = true;
}
2024-08-07 19:11:24 +00:00
if (controller1Type == "ArkanoidNES")
{
_controller1Type = controller_t::arkanoidNES;
isTypeRecognized = true;
}
if (controller1Type == "ArkanoidFamicom")
{
_controller1Type = controller_t::arkanoidFamicom;
isTypeRecognized = true;
}
2024-07-28 14:43:09 +00:00
if (isTypeRecognized == false) JAFFAR_THROW_LOGIC("Controller 1 type not recognized: '%s'\n", controller1Type.c_str());
}
// Parsing controller 2 type
{
bool isTypeRecognized = false;
const auto controller2Type = jaffarCommon::json::getString(config, "Controller 2 Type");
2024-07-28 14:52:05 +00:00
if (controller2Type == "None")
{
_controller2Type = controller_t::none;
isTypeRecognized = true;
}
if (controller2Type == "Joypad")
{
_controller2Type = controller_t::joypad;
isTypeRecognized = true;
}
if (controller2Type == "FourScore1")
{
_controller2Type = controller_t::fourscore1;
isTypeRecognized = true;
}
if (controller2Type == "FourScore2")
{
_controller2Type = controller_t::fourscore2;
isTypeRecognized = true;
}
2024-07-28 14:43:09 +00:00
if (isTypeRecognized == false) JAFFAR_THROW_LOGIC("Controller 2 type not recognized: '%s'\n", controller2Type.c_str());
}
}
2024-07-28 14:52:05 +00:00
inline input_t parseInputString(const std::string &inputString) const
2024-07-28 14:43:09 +00:00
{
// Storage for the input
input_t input;
// Converting input into a stream for parsing
std::istringstream ss(inputString);
// Start separator
if (ss.get() != '|') reportBadInputString(inputString);
// Parsing console inputs
parseConsoleInputs(input.reset, input.power, ss, inputString);
// Parsing controller 1 inputs
2024-08-08 15:54:55 +00:00
if (_controller1Type == arkanoidNES) parseArkanoidNESInput(input, ss, inputString);
if (_controller1Type == arkanoidFamicom) parseArkanoidFamicomInput(input, ss, inputString);
2024-08-07 19:11:24 +00:00
if (_controller1Type == joypad || _controller1Type == fourscore1) parseControllerInputs(_controller1Type, input.port1, ss, inputString);
2024-07-28 14:43:09 +00:00
2024-08-07 19:11:24 +00:00
// Parsing controller 2 inputs
if (_controller2Type == joypad || _controller2Type == fourscore2) parseControllerInputs(_controller2Type, input.port2, ss, inputString);
2024-07-28 14:43:09 +00:00
// End separator
if (ss.get() != '|') reportBadInputString(inputString);
// If its not the end of the stream, then extra values remain and its invalid
ss.get();
if (ss.eof() == false) reportBadInputString(inputString);
return input;
}
private:
2024-07-28 14:52:05 +00:00
static inline void reportBadInputString(const std::string &inputString)
2024-07-28 14:43:09 +00:00
{
JAFFAR_THROW_LOGIC("Could not decode input string: '%s'\n", inputString.c_str());
}
2024-07-28 14:52:05 +00:00
static void parseJoyPadInput(uint8_t &code, std::istringstream &ss, const std::string &inputString)
2024-07-28 14:43:09 +00:00
{
// Currently read character
char c;
// Cleaning code
code = 0;
// Up
c = ss.get();
if (c != '.' && c != 'U') reportBadInputString(inputString);
if (c == 'U') code |= 0b00010000;
// Down
c = ss.get();
if (c != '.' && c != 'D') reportBadInputString(inputString);
if (c == 'D') code |= 0b00100000;
// Left
c = ss.get();
if (c != '.' && c != 'L') reportBadInputString(inputString);
if (c == 'L') code |= 0b01000000;
// Right
c = ss.get();
if (c != '.' && c != 'R') reportBadInputString(inputString);
if (c == 'R') code |= 0b10000000;
// Start
c = ss.get();
if (c != '.' && c != 'S') reportBadInputString(inputString);
if (c == 'S') code |= 0b00001000;
// Select
c = ss.get();
if (c != '.' && c != 's') reportBadInputString(inputString);
if (c == 's') code |= 0b00000100;
// B
c = ss.get();
if (c != '.' && c != 'B') reportBadInputString(inputString);
if (c == 'B') code |= 0b00000010;
// A
c = ss.get();
if (c != '.' && c != 'A') reportBadInputString(inputString);
if (c == 'A') code |= 0b00000001;
}
2024-08-07 19:11:24 +00:00
static inline void parseArkanoidInput(input_t& input, std::istringstream& ss, const std::string& inputString)
{
uint8_t potentiometer = 0;
uint8_t fire = 0;
// Controller separator
if (ss.get() != '|') reportBadInputString(inputString);
if (ss.get() != ' ') reportBadInputString(inputString);
if (ss.get() != ' ') reportBadInputString(inputString);
char c = ss.get(); // Hundreds
if (c != ' ' && c < 48 && c > 57) reportBadInputString(inputString);
if (c != ' ') potentiometer += 100 * ( (uint8_t)c - 48 );
c = ss.get(); // Tenths
if (c != ' ' && c < 48 && c > 57) reportBadInputString(inputString);
if (c != ' ') potentiometer += 10 * ( (uint8_t)c - 48 );
c = ss.get(); // Units
if (c != ' ' && c < 48 && c > 57) reportBadInputString(inputString);
if (c != ' ') potentiometer += (uint8_t)c - 48;
// Comma
if (ss.get() != ',') reportBadInputString(inputString);
// Fire
c = ss.get();
if (c != '.' && c != 'F') reportBadInputString(inputString);
if (c == 'F') fire = 1;
// Fire is encoded in port 1
input.arkanoidFire = fire;
// Potentiometer is encoded in port 2 - MSB and adding one bit for signalling the presence of the potentiometer, subtracted from 173
uint8_t subtracter = 171 - potentiometer;
input.arkanoidLatch = 0;
if ((subtracter & 128) > 0) input.arkanoidLatch += 1;
if ((subtracter & 64) > 0) input.arkanoidLatch += 2;
if ((subtracter & 32) > 0) input.arkanoidLatch += 4;
if ((subtracter & 16) > 0) input.arkanoidLatch += 8;
if ((subtracter & 8) > 0) input.arkanoidLatch += 16;
if ((subtracter & 4) > 0) input.arkanoidLatch += 32;
if ((subtracter & 2) > 0) input.arkanoidLatch += 64;
if ((subtracter & 1) > 0) input.arkanoidLatch += 128;
}
2024-07-28 14:52:05 +00:00
static void parseControllerInputs(const controller_t type, port_t &port, std::istringstream &ss, const std::string &inputString)
2024-07-28 14:43:09 +00:00
{
// If no controller assigned then, its port is all zeroes.
2024-07-28 14:52:05 +00:00
if (type == controller_t::none)
{
port = 0;
return;
}
2024-07-28 14:43:09 +00:00
// Controller separator
if (ss.get() != '|') reportBadInputString(inputString);
// If normal joypad, parse its code now
2024-07-28 14:52:05 +00:00
if (type == controller_t::joypad)
2024-07-28 14:43:09 +00:00
{
// Storage for joypad's code
uint8_t code = 0;
// Parsing joypad code
parseJoyPadInput(code, ss, inputString);
// Pushing input code into the port
port = code;
// Adding joypad signature
// Per https://www.nesdev.org/wiki/Standard_controller, the joypad reports 1s after the first 8 bits
port |= ~0xFF;
}
// If its fourscore, its like two joypads separated by a |
2024-07-28 14:52:05 +00:00
if (type == controller_t::fourscore1 || type == controller_t::fourscore2)
2024-07-28 14:43:09 +00:00
{
// Storage for joypad's code
uint8_t code1 = 0;
uint8_t code2 = 0;
// Parsing joypad code1
parseJoyPadInput(code1, ss, inputString);
// Separator
if (ss.get() != '|') reportBadInputString(inputString);
// Parsing joypad code1
parseJoyPadInput(code2, ss, inputString);
// Creating code
port = code1;
port |= (uint32_t)0 | code2 << 8;
if (type == controller_t::fourscore1) port |= (uint32_t)0 | 1 << 19;
if (type == controller_t::fourscore2) port |= (uint32_t)0 | 1 << 18;
port |= (uint32_t)0 | 1 << 24;
port |= (uint32_t)0 | 1 << 25;
port |= (uint32_t)0 | 1 << 26;
port |= (uint32_t)0 | 1 << 27;
port |= (uint32_t)0 | 1 << 28;
port |= (uint32_t)0 | 1 << 29;
port |= (uint32_t)0 | 1 << 30;
port |= (uint32_t)0 | 1 << 31;
}
}
2024-08-08 15:54:55 +00:00
static inline void parseArkanoidNESInput(input_t& input, std::istringstream& ss, const std::string& inputString)
{
// Simply parse the arkanoid controller input
parseArkanoidInput(input, ss, inputString);
}
static inline void parseArkanoidFamicomInput(input_t& input, std::istringstream& ss, const std::string& inputString)
{
// Parsing joypad controller
parseControllerInputs(controller_t::joypad, input.port1, ss, inputString);
// Controller separator
if (ss.get() != '|') reportBadInputString(inputString);
// Advancing 7 positions (this input is not supported)
for (size_t i = 0; i < 7; i++) if (ss.get() != '.') reportBadInputString(inputString);
// Then, parse the arkanoid controller input
parseArkanoidInput(input, ss, inputString);
}
2024-07-28 14:52:05 +00:00
static void parseConsoleInputs(bool &reset, bool &power, std::istringstream &ss, const std::string &inputString)
2024-07-28 14:43:09 +00:00
{
// Currently read character
char c;
// Power trigger
c = ss.get();
if (c != '.' && c != 'P') reportBadInputString(inputString);
if (c == 'P') power = true;
if (c == '.') power = false;
// Reset trigger
c = ss.get();
if (c != '.' && c != 'r') reportBadInputString(inputString);
if (c == 'r') reset = true;
if (c == '.') reset = false;
}
}; // class InputParser
} // namespace jaffar