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
|