openMSX
ArkanoidPad.cc
Go to the documentation of this file.
1 #include "ArkanoidPad.hh"
2 #include "MSXEventDistributor.hh"
4 #include "Event.hh"
5 #include "StateChange.hh"
6 #include "serialize.hh"
7 #include "serialize_meta.hh"
8 #include <algorithm>
9 
10 // Implemented mostly according to the info here: http://www.msx.org/forumtopic7661.html
11 // This is absolutely not accurate, but good enough to make the pad work in the
12 // Arkanoid games.
13 
14 // The hardware works with a 556 dual timer that's unemulated here, it requires
15 // short delays at each shift, and a long delay at latching. Too short delays
16 // will cause garbage reads, and too long delays at shifting will eventually
17 // cause the shift register bits to return to 0.
18 
19 namespace openmsx {
20 
21 constexpr int POS_MIN = 55; // measured by hap
22 constexpr int POS_MAX = 325; // measured by hap
23 constexpr int POS_CENTER = 236; // approx. middle used by games
24 constexpr int SCALE = 2;
25 
26 
27 class ArkanoidState final : public StateChange
28 {
29 public:
30  ArkanoidState() = default; // for serialize
31  ArkanoidState(EmuTime::param time_, int delta_, bool press_, bool release_)
32  : StateChange(time_)
33  , delta(delta_), press(press_), release(release_) {}
34  [[nodiscard]] int getDelta() const { return delta; }
35  [[nodiscard]] bool getPress() const { return press; }
36  [[nodiscard]] bool getRelease() const { return release; }
37 
38  template<typename Archive> void serialize(Archive& ar, unsigned /*version*/)
39  {
40  ar.template serializeBase<StateChange>(*this);
41  ar.serialize("delta", delta,
42  "press", press,
43  "release", release);
44  }
45 private:
46  int delta;
47  bool press, release;
48 };
50 
52  StateChangeDistributor& stateChangeDistributor_)
53  : eventDistributor(eventDistributor_)
54  , stateChangeDistributor(stateChangeDistributor_)
55  , shiftreg(0) // the 9 bit shift degrades to 0
56  , dialpos(POS_CENTER)
57  , buttonStatus(0x3E)
58  , lastValue(0)
59 {
60 }
61 
63 {
64  if (isPluggedIn()) {
65  ArkanoidPad::unplugHelper(EmuTime::dummy());
66  }
67 }
68 
69 
70 // Pluggable
71 std::string_view ArkanoidPad::getName() const
72 {
73  return "arkanoidpad";
74 }
75 
76 std::string_view ArkanoidPad::getDescription() const
77 {
78  return "Arkanoid pad";
79 }
80 
81 void ArkanoidPad::plugHelper(Connector& /*connector*/, EmuTime::param /*time*/)
82 {
83  eventDistributor.registerEventListener(*this);
84  stateChangeDistributor.registerListener(*this);
85 }
86 
87 void ArkanoidPad::unplugHelper(EmuTime::param /*time*/)
88 {
89  stateChangeDistributor.unregisterListener(*this);
90  eventDistributor.unregisterEventListener(*this);
91 }
92 
93 // JoystickDevice
94 byte ArkanoidPad::read(EmuTime::param /*time*/)
95 {
96  return buttonStatus | ((shiftreg & 0x100) >> 8);
97 }
98 
99 void ArkanoidPad::write(byte value, EmuTime::param /*time*/)
100 {
101  byte diff = lastValue ^ value;
102  lastValue = value;
103 
104  if (diff & value & 0x4) {
105  // pin 8 from low to high: copy dial position into shift reg
106  shiftreg = dialpos;
107  }
108  if (diff & value & 0x1) {
109  // pin 6 from low to high: shift the shift reg
110  shiftreg = (shiftreg << 1) | (shiftreg & 1); // bit 0's value is 'shifted in'
111  }
112 }
113 
114 // MSXEventListener
115 void ArkanoidPad::signalMSXEvent(const Event& event,
116  EmuTime::param time) noexcept
117 {
119  [&](const MouseMotionEvent& e) {
120  int newPos = std::min(POS_MAX,
122  dialpos + e.getX() / SCALE));
123  int delta = newPos - dialpos;
124  if (delta != 0) {
125  stateChangeDistributor.distributeNew<ArkanoidState>(
126  time, delta, false, false);
127  }
128  },
129  [&](const MouseButtonDownEvent& /*e*/) {
130  // any button will press the Arkanoid Pad button
131  if (buttonStatus & 2) {
132  stateChangeDistributor.distributeNew<ArkanoidState>(
133  time, 0, true, false);
134  }
135  },
136  [&](const MouseButtonUpEvent& /*e*/) {
137  // any button will unpress the Arkanoid Pad button
138  if (!(buttonStatus & 2)) {
139  stateChangeDistributor.distributeNew<ArkanoidState>(
140  time, 0, false, true);
141  }
142  },
143  [](const EventBase&) { /*ignore */}
144  }, event);
145 }
146 
147 // StateChangeListener
148 void ArkanoidPad::signalStateChange(const StateChange& event)
149 {
150  const auto* as = dynamic_cast<const ArkanoidState*>(&event);
151  if (!as) return;
152 
153  dialpos += as->getDelta();
154  if (as->getPress()) buttonStatus &= ~2;
155  if (as->getRelease()) buttonStatus |= 2;
156 }
157 
158 void ArkanoidPad::stopReplay(EmuTime::param time) noexcept
159 {
160  // TODO Get actual mouse button(s) state. Is it worth the trouble?
161  int delta = POS_CENTER - dialpos;
162  bool release = (buttonStatus & 2) == 0;
163  if ((delta != 0) || release) {
164  stateChangeDistributor.distributeNew<ArkanoidState>(
165  time, delta, false, release);
166  }
167 }
168 
169 
170 // version 1: Initial version, the variables dialpos and buttonStatus were not
171 // serialized.
172 // version 2: Also serialize the above variables, this is required for
173 // record/replay, see comment in Keyboard.cc for more details.
174 template<typename Archive>
175 void ArkanoidPad::serialize(Archive& ar, unsigned version)
176 {
177  ar.serialize("shiftreg", shiftreg,
178  "lastValue", lastValue);
179  if (ar.versionAtLeast(version, 2)) {
180  ar.serialize("dialpos", dialpos,
181  "buttonStatus", buttonStatus);
182  }
183  if constexpr (Archive::IS_LOADER) {
184  if (isPluggedIn()) {
185  plugHelper(*getConnector(), EmuTime::dummy());
186  }
187  }
188 }
191 
192 } // namespace openmsx
~ArkanoidPad() override
Definition: ArkanoidPad.cc:62
void serialize(Archive &ar, unsigned version)
Definition: ArkanoidPad.cc:175
ArkanoidPad(MSXEventDistributor &eventDistributor, StateChangeDistributor &stateChangeDistributor)
Definition: ArkanoidPad.cc:51
bool getRelease() const
Definition: ArkanoidPad.cc:36
ArkanoidState(EmuTime::param time_, int delta_, bool press_, bool release_)
Definition: ArkanoidPad.cc:31
bool getPress() const
Definition: ArkanoidPad.cc:35
void serialize(Archive &ar, unsigned)
Definition: ArkanoidPad.cc:38
void registerEventListener(MSXEventListener &listener)
Registers a given object to receive certain events.
void unregisterEventListener(MSXEventListener &listener)
Unregisters a previously registered event listener.
bool isPluggedIn() const
Returns true if this pluggable is currently plugged into a connector.
Definition: Pluggable.hh:49
Connector * getConnector() const
Get the connector this Pluggable is plugged into.
Definition: Pluggable.hh:43
void registerListener(StateChangeListener &listener)
(Un)registers the given object to receive state change events.
void unregisterListener(StateChangeListener &listener)
Base class for all external MSX state changing events.
Definition: StateChange.hh:20
constexpr vecN< N, T > min(const vecN< N, T > &x, const vecN< N, T > &y)
Definition: gl_vec.hh:269
constexpr vecN< N, T > max(const vecN< N, T > &x, const vecN< N, T > &y)
Definition: gl_vec.hh:287
This file implemented 3 utility functions:
Definition: Autofire.cc:9
constexpr int POS_CENTER
Definition: ArkanoidPad.cc:23
REGISTER_POLYMORPHIC_INITIALIZER(Pluggable, CassettePlayer, "CassettePlayer")
auto visit(Visitor &&visitor, const Event &event)
Definition: Event.hh:653
constexpr int SCALE
Definition: ArkanoidPad.cc:24
REGISTER_POLYMORPHIC_CLASS(StateChange, AutofireStateChange, "AutofireStateChange")
constexpr int POS_MIN
Definition: ArkanoidPad.cc:21
constexpr int POS_MAX
Definition: ArkanoidPad.cc:22
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
Definition: serialize.hh:998