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