diff options
| author | 2026-04-12 23:00:45 +0200 | |
|---|---|---|
| committer | 2026-04-12 23:01:02 +0200 | |
| commit | 149871cb7097679ee67236324ecca65631fd82ae (patch) | |
| tree | 16ce62fb1f14a051d311bdf79935fc2477016f1f /src/devices | |
| parent | 0f5872aa2eeb46ae87c6a715946cce1704749d84 (diff) | |
grandmas/emirage: move chessboard to a device
Diffstat (limited to 'src/devices')
| -rw-r--r-- | src/devices/machine/gmboard.cpp | 356 | ||||
| -rw-r--r-- | src/devices/machine/gmboard.h | 86 | ||||
| -rw-r--r-- | src/devices/machine/smartboard.cpp | 7 |
3 files changed, 445 insertions, 4 deletions
diff --git a/src/devices/machine/gmboard.cpp b/src/devices/machine/gmboard.cpp new file mode 100644 index 00000000000..0ba17d6bab1 --- /dev/null +++ b/src/devices/machine/gmboard.cpp @@ -0,0 +1,356 @@ +// license:BSD-3-Clause +// copyright-holders:hap +/* + +Milton Bradley Grand Master motorized self-moving chessboard + +Electronic chessboard with motors underneath, that move a magnet around for +automatically moving chesspieces (each piece has a magnet underneath). + +Used in: +- Milton Bradley Grand Master +- Fidelity Phantom / Chesster Phantom +- Excalibur Mirage +- Excalibur Phantom Force + +Hardware notes: +- electronic chessboard, with room on each side for captured pieces +- X/Y plotter motors, electromagnet (optionally including a hall affect sensor) + +Concept/design by Milton Bradley, for use in the Grand Master. Fidelity licensed +or bought the design, and applied it nearly unchanged to Fidelity Phantom. Years +later, the ex chief engineer of by then defunct Fidelity used the same technology +while working for Excalibur. + +TODO: +- optionally change output finders to callbacks, currently not needed +- sensorboard undo buffer goes out of control, probably not worth solving this issue + +BTANB: +- Motors gradually drift in Mirage, causing it to place/pick up pieces off-center. + It recalibrates itself once in a while but it's not enough. MAME's sensorboard + device can't deal with it, so there's a workaround (see realign_magnet_pos). + The programmer anecdotally blamed it on the hardware engineer, but it's mainly + a software bug. + +*/ + +#include "emu.h" +#include "gmboard.h" + + +DEFINE_DEVICE_TYPE(MB_GMBOARD, gmboard_device, "mb_gmboard", "Milton Bradley Grand Master chessboard") + +//------------------------------------------------- +// gmboard_device - constructor +//------------------------------------------------- + +gmboard_device::gmboard_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock) : + device_t(mconfig, MB_GMBOARD, tag, owner, clock), + m_board(*this, "board"), + m_piece_hand(*this, "cpu_hand"), + m_out_motor(*this, "motor%u", 0U), + m_out_pos(*this, "pos_%c", unsigned('x')), + m_quad_cb(*this) +{ + // (just to prevent a divide by 0 if it's not configured) + m_width = 1; + m_height = 1; + m_square = 1; + m_x_offset = 0; + m_y_offset = 0; + + m_speed = attotime::from_msec(200); +} + + +//------------------------------------------------- +// initialization +//------------------------------------------------- + +void gmboard_device::device_start() +{ + // resolve outputs + m_piece_hand.resolve(); + m_out_motor.resolve(); + m_out_pos.resolve(); + + // zerofill + m_magnet = 0; + memset(m_pieces_map, 0, sizeof(m_pieces_map)); + memset(m_motor_dir, 0, sizeof(m_motor_dir)); + memset(m_motor_max, 0, sizeof(m_motor_max)); + memset(m_motor_pos, 0, sizeof(m_motor_pos)); + memset(m_motor_quad, 0, sizeof(m_motor_quad)); + memset(m_motor_drift, 0, sizeof(m_motor_drift)); + + // register for savestates + save_item(NAME(m_magnet)); + save_item(NAME(m_pieces_map)); + save_item(NAME(m_motor_dir)); + save_item(NAME(m_motor_pos)); + save_item(NAME(m_motor_quad)); + save_item(NAME(m_motor_remain)); + save_item(NAME(m_motor_drift)); + + init_motors(); +} + +void gmboard_device::device_reset() +{ + memset(m_motor_drift, 0, sizeof(m_motor_drift)); + m_magnet = 0; + + output_magnet_pos(); +} + +void gmboard_device::init_board(u8 data) +{ + m_board->preset_chess(data); + + // reposition pieces if board will be rotated + if (data & 2) + { + for (int y = 0; y < 8; y++) + for (int x = 7; x >= 0; x--) + { + m_board->write_piece(x + 4, y, m_board->read_piece(x, y)); + m_board->write_piece(x, y, 0); + } + } +} + +void gmboard_device::clear_board(u8 data) +{ + memset(m_pieces_map, 0, sizeof(m_pieces_map)); + m_piece_hand = 0; + m_board->clear_board(data); +} + + +//------------------------------------------------- +// motor sim +//------------------------------------------------- + +void gmboard_device::init_motors() +{ + m_motor_max[0] = m_width - 1; + m_motor_max[1] = m_height - 1; + + // start at A1 + m_motor_pos[0] = std::min<u32>(2 * m_square + m_x_offset, m_motor_max[0]); + m_motor_pos[1] = std::min<u32>(7 * m_square + m_y_offset, m_motor_max[1]); + + m_motor_period = m_speed / m_square; + + for (int i = 0; i < 2; i++) + { + m_motor_timer[i] = timer_alloc(FUNC(gmboard_device::motor_count), this); + m_motor_remain[i] = m_motor_period / 2; + } +} + +void gmboard_device::get_scaled_pos(double *x, double *y) +{ + // scale down to 4 counts per square + *x = std::clamp(double(int(m_motor_pos[0]) - m_x_offset) / (m_square / 4.0) + 2.0, 0.0, 48.0); + *y = std::clamp(double(int(m_motor_pos[1]) - m_y_offset) / (m_square / 4.0) + 2.0, 0.0, 32.0); +} + +void gmboard_device::output_magnet_pos() +{ + double x, y; + get_scaled_pos(&x, &y); + + // put active state on x bit 11 + const int active = m_magnet ? 0x800 : 0; + m_out_pos[0] = int(x * 25.0 + 0.5) | active; + m_out_pos[1] = int(y * 25.0 + 0.5); + m_out_motor[4] = m_magnet; +} + +void gmboard_device::realign_magnet_pos() +{ + // compensate for gradual drift, see BTANB + for (int i = 0; i < 2; ) + { + double pos[2]; + get_scaled_pos(&pos[0], &pos[1]); + + const double limit = 1.0 / (m_square / 16.0); + int inc = 0; + + if ((round(pos[i]) - pos[i]) > limit && m_motor_pos[i] < m_motor_max[i] - 4) + inc = 1; + else if ((round(pos[i]) - pos[i]) < -limit && m_motor_pos[i] > 4) + inc = -1; + else + i++; + + if (inc != 0) + { + m_motor_pos[i] += inc * 4; + m_motor_drift[i] -= inc; + + logerror("motor %c drift error (%d total)\n", 'X' + i, m_motor_drift[i]); + } + } +} + +void gmboard_device::magnet_w(int state) +{ + state = state ? 1 : 0; + + if (state == m_magnet) + return; + m_magnet = state; + + if (m_piece_hand != 0) + realign_magnet_pos(); + output_magnet_pos(); + + double dx, dy; + get_scaled_pos(&dx, &dy); + + int mx = dx + 0.5; + int my = dy + 0.5; + + // convert motors position into board coordinates + int x = mx / 4 - 2; + int y = 7 - (my / 4); + + if (x < 0) + x += 12; + + const bool valid_pos = (mx & 3) == 2 && (my & 3) == 2; + + // sensorboard handling is almost the same as fidelity/phantom.cpp + if (state) + { + if (valid_pos) + { + // pick up piece, unless it was picked up by the user + const int pos = (y << 4 & 0xf0) | (x & 0x0f); + if (pos != m_board->get_handpos()) + { + m_piece_hand = m_board->read_piece(x, y); + + if (m_piece_hand != 0) + { + m_board->write_piece(x, y, 0); + m_board->refresh(); + } + } + } + else + { + // pick up piece from internal pieces map + m_piece_hand = m_pieces_map[my][mx]; + m_pieces_map[my][mx] = 0; + } + } + else if (m_piece_hand != 0) + { + if (valid_pos) + { + // collision with piece on board (user interference) + if (m_board->read_piece(x, y) != 0) + popmessage("Collision at %c%d!", x + 'A', y + 1); + else + { + m_board->write_piece(x, y, m_piece_hand); + m_board->refresh(); + } + } + else + { + // collision with internal pieces map (shouldn't happen) + if (m_pieces_map[my][mx] != 0) + popmessage("Internal collision!"); + else + m_pieces_map[my][mx] = m_piece_hand; + } + + m_piece_hand = 0; + } +} + +TIMER_CALLBACK_MEMBER(gmboard_device::motor_count) +{ + const int m = param ? 1 : 0; + assert(m_motor_dir[m] & 3); + + // 1 quarter rotation per period + int inc = 0; + if (m_motor_dir[m] & 2) + { + if (m_motor_pos[m] < m_motor_max[m]) + inc = 1; + } + else if (m_motor_pos[m] > 0) + inc = -1; + + m_motor_remain[m] = m_motor_period; + + if (inc == 0) + return; + + m_motor_pos[m] += inc; + m_motor_timer[m]->adjust(m_motor_period, m); + + output_magnet_pos(); + + // update quadrature encoder + static const u8 lut_quad[4] = { 0,1,3,2 }; + m_motor_quad[m] = lut_quad[m_motor_pos[m] & 3]; + + m_quad_cb[m](m_motor_quad[m]); +} + +void gmboard_device::motor_w(offs_t offset, u8 data) +{ + const int m = offset & 1; + data &= 3; + + for (int i = 0; i < 2; i++) + m_out_motor[offset * 2 + i] = BIT(data, i); + + // it's not moving when both directions are set + if (data == 3) + data = 0; + + if (data == m_motor_dir[m]) + return; + + // remember remaining time + if (m_motor_dir[m] & 3 && !m_motor_timer[m]->remaining().is_never()) + { + m_motor_remain[m] = m_motor_timer[m]->remaining(); + m_motor_timer[m]->adjust(attotime::never); + } + + // invert remaining time if direction flipped + if ((m_motor_dir[m] ^ data) & 1) + m_motor_remain[m] = m_motor_period - m_motor_remain[m]; + + m_motor_dir[m] = data; + + // (re)start the timer + if (data & 3) + m_motor_timer[m]->adjust(m_motor_remain[m], m); +} + + +//------------------------------------------------- +// device_add_mconfig - add device-specific +// machine configuration +//------------------------------------------------- + +void gmboard_device::device_add_mconfig(machine_config &config) +{ + SENSORBOARD(config, m_board).set_type(sensorboard_device::BUTTONS); + m_board->set_size(8+4, 8); + m_board->clear_cb().set(FUNC(gmboard_device::clear_board)); + m_board->init_cb().set(FUNC(gmboard_device::init_board)); + m_board->set_delay(attotime::from_msec(150)); +} diff --git a/src/devices/machine/gmboard.h b/src/devices/machine/gmboard.h new file mode 100644 index 00000000000..dd5fa59156c --- /dev/null +++ b/src/devices/machine/gmboard.h @@ -0,0 +1,86 @@ +// license:BSD-3-Clause +// copyright-holders:hap +/* + + Milton Bradley Grand Master motorized self-moving chessboard + +*/ + +#ifndef MAME_MACHINE_GMBOARD_H +#define MAME_MACHINE_GMBOARD_H + +#pragma once + +#include "machine/sensorboard.h" + +class gmboard_device : public device_t +{ +public: + gmboard_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock = 0); + + // configuration helpers + template <std::size_t N> auto quad_cb() { return m_quad_cb[N].bind(); } // x/y motor quadrature encoder state + gmboard_device &set_size(u16 width, u16 height, u16 square) { m_width = width; m_height = height; m_square = square; return *this; } + gmboard_device &set_offsets(u16 x_offset, u16 y_offset) { m_x_offset = x_offset; m_y_offset = y_offset; return *this; } + gmboard_device &set_speed(attotime speed) { m_speed = speed; return *this; } + + sensorboard_device &get() { return *m_board; } + + // external read/write handlers + int magnet_r() { return (started() && m_piece_hand) ? 1 : 0; } + void magnet_w(int state); + void motor_w(offs_t offset, u8 data); + u8 quad_r(offs_t offset) { return m_motor_quad[offset & 1]; } + + u16 read_file(u8 x, bool reverse = false) { return m_board->read_file(x, reverse); } + u16 read_rank(u8 y, bool reverse = false) { return m_board->read_rank(y, reverse); } + +protected: + // device-level overrides + virtual void device_start() override ATTR_COLD; + virtual void device_reset() override ATTR_COLD; + virtual void device_add_mconfig(machine_config &config) override ATTR_COLD; + +private: + required_device<sensorboard_device> m_board; + output_finder<> m_piece_hand; + output_finder<5> m_out_motor; + output_finder<2> m_out_pos; + + u8 m_magnet; + u8 m_pieces_map[0x40][0x40]; + + u8 m_motor_dir[2]; + u32 m_motor_max[2]; + u32 m_motor_pos[2]; + u8 m_motor_quad[2]; + s32 m_motor_drift[2]; // diagnostics + + attotime m_motor_period; + attotime m_motor_remain[2]; + emu_timer *m_motor_timer[2]; + + u16 m_width; // motor range + u16 m_height; // " + u16 m_square; // number of quarter rotations per square + u16 m_x_offset; // unscaled offset relative to the bottom-left corner + u16 m_y_offset; // " + attotime m_speed; // time per square at full speed + + devcb_write8::array<2> m_quad_cb; + + void init_board(u8 data); + void clear_board(u8 data); + void init_motors(); + + void get_scaled_pos(double *x, double *y); + void output_magnet_pos(); + void realign_magnet_pos(); + + TIMER_CALLBACK_MEMBER(motor_count); +}; + + +DECLARE_DEVICE_TYPE(MB_GMBOARD, gmboard_device) + +#endif // MAME_MACHINE_GMBOARD_H diff --git a/src/devices/machine/smartboard.cpp b/src/devices/machine/smartboard.cpp index d699b1306df..22a73032075 100644 --- a/src/devices/machine/smartboard.cpp +++ b/src/devices/machine/smartboard.cpp @@ -1,6 +1,6 @@ // license:BSD-3-Clause // copyright-holders:Sandro Ronco, hap -/****************************************************************************** +/* Tasc SmartBoard SB30 (analog) @@ -18,7 +18,7 @@ SB20 and the newer SB30 are not emulated. They're on different hardware, with embedded CPU to reduce I/O overhead. Note, those are not compatible with old versions of Tasc R30. -******************************************************************************/ +*/ #include "emu.h" #include "smartboard.h" @@ -128,8 +128,7 @@ ioport_constructor tasc_sb30_device::device_input_ports() const void tasc_sb30_device::device_add_mconfig(machine_config &config) { - SENSORBOARD(config, m_board); - m_board->set_type(sensorboard_device::INDUCTIVE); + SENSORBOARD(config, m_board).set_type(sensorboard_device::INDUCTIVE); m_board->set_max_id(32); m_board->init_cb().set(FUNC(tasc_sb30_device::init_cb)); m_board->spawn_cb().set(FUNC(tasc_sb30_device::spawn_cb)); |
