// license:BSD-3-Clause // copyright-holders:hap, Sean Riddle /*************************************************************************** ** subclass of hh_tms1k_state (includes/hh_tms1k.h, drivers/hh_tms1k.cpp) ** Milton Bradley Dark Tower * TMS1400NLL MP7332-N1.U1(Rev. B) or MP7332-N2LL(Rev. C), die label MP7332 (assume same ROM contents between revisions) * SN75494N MOS-to-LED digit driver * motorized rotating reel + lightsensor, 1bit-sound This is a board game, it obviously requires game pieces and the board. The emulated part is the centerpiece, a black tower with a rotating card panel and LED digits for displaying health, amount of gold, etc. As far as MAME is concerned, the game works fine. To start up the game, first press [MOVE], the machine now does a self-test. Then select level and number of players and the game will start. Read the official manual on how to play the game. ***************************************************************************/ #include "includes/hh_tms1k.h" #include "mbdtower.lh" class mbdtower_state : public hh_tms1k_state { public: mbdtower_state(const machine_config &mconfig, device_type type, const char *tag) : hh_tms1k_state(mconfig, type, tag) { } void prepare_display(); bool sensor_led_on() { return m_display_decay[0][0] != 0; } int m_motor_pos; int m_motor_pos_prev; int m_motor_decay; bool m_motor_on; bool m_sensor_blind; TIMER_DEVICE_CALLBACK_MEMBER(motor_sim_tick); DECLARE_WRITE16_MEMBER(write_r); DECLARE_WRITE16_MEMBER(write_o); DECLARE_READ8_MEMBER(read_k); protected: virtual void machine_start() override; }; /*************************************************************************** Display, Motor ***************************************************************************/ void mbdtower_state::prepare_display() { // declare display matrix size and the 2 7segs set_display_size(7, 3); set_display_segmask(6, 0x7f); // update current state if (~m_r & 0x10) { UINT8 o = BITSWAP8(m_o,7,0,4,3,2,1,6,5) & 0x7f; m_display_state[2] = (m_o & 0x80) ? o : 0; m_display_state[1] = (m_o & 0x80) ? 0 : o; m_display_state[0] = (m_r >> 8 & 1) | (m_r >> 4 & 0xe); display_update(); } else { // display items turned off display_matrix(7, 3, 0, 0); } } TIMER_DEVICE_CALLBACK_MEMBER(mbdtower_state::motor_sim_tick) { // it rotates counter-clockwise (when viewed from above) if (m_motor_on) { m_motor_pos = (m_motor_pos - 1) & 0x7f; // give it some time to spin out when it's turned off if (m_r & 0x200) m_motor_decay += (m_motor_decay < 4); else if (m_motor_decay > 0) m_motor_decay--; else m_motor_on = false; } // 8 evenly spaced holes in the rotation disc for the sensor to 'see' through. // The first hole is much bigger, enabling the game to determine the position. if ((m_motor_pos & 0xf) < 4 || m_motor_pos < 0xc) m_sensor_blind = false; else m_sensor_blind = true; // on change, output info if (m_motor_pos != m_motor_pos_prev) output().set_value("motor_pos", 100 * (m_motor_pos / (float)0x80)); /* 3 display cards per hole, like this: (0) <---- display increments this way <---- (7) CURSED VICTORY WIZARD DRAGON GOLD KEY SCOUT WARRIOR (void) LOST WARRIORS BAZAAR CLOSED SWORD SILVER KEY HEALER FOOD (void) PLAGUE BRIGANDS KEY MISSING PEGASUS BRASS KEY GOLD BEAST (void) */ int card_pos = m_motor_pos >> 4 & 7; if (card_pos != (m_motor_pos_prev >> 4 & 7)) output().set_value("card_pos", card_pos); m_motor_pos_prev = m_motor_pos; } /*************************************************************************** I/O ***************************************************************************/ WRITE16_MEMBER(mbdtower_state::write_r) { // R0-R2: input mux m_inp_mux = data & 7; // R9: motor on if ((m_r ^ data) & 0x200) output().set_value("motor_on", data >> 9 & 1); if (data & 0x200) m_motor_on = true; // R3: N/C // R4: 75494 /EN (speaker, lamps, digit select go through that IC) // R5-R7: tower lamps // R8: rotation sensor led m_r = data; prepare_display(); // R10: speaker out m_speaker->level_w(~data >> 4 & data >> 10 & 1); } WRITE16_MEMBER(mbdtower_state::write_o) { // O0-O6: led segments A-G // O7: digit select m_o = data; prepare_display(); } READ8_MEMBER(mbdtower_state::read_k) { // K: multiplexed inputs // K8: rotation sensor return read_inputs(3) | ((!m_sensor_blind && sensor_led_on()) ? 8 : 0); } /*************************************************************************** Inputs ***************************************************************************/ /* physical button layout and labels is like this: (green) (l.blue) (red) [YES/ [REPEAT] [NO/ BUY] END] (yellow) (blue) (white) [HAGGLE] [BAZAAR] [CLEAR] (blue) (blue) (blue) [TOMB/ [MOVE] [SANCTUARY/ RUIN] CITADEL] (orange) (blue) (d.yellow) [DARK [FRONTIER] [INVENTORY] TOWER] */ static INPUT_PORTS_START( mbdtower ) PORT_START("IN.0") // R0 PORT_BIT( 0x01, IP_ACTIVE_HIGH, IPT_KEYPAD ) PORT_CODE(KEYCODE_C) PORT_NAME("Inventory") PORT_BIT( 0x02, IP_ACTIVE_HIGH, IPT_KEYPAD ) PORT_CODE(KEYCODE_3) PORT_NAME("No/End") PORT_BIT( 0x04, IP_ACTIVE_HIGH, IPT_KEYPAD ) PORT_CODE(KEYCODE_E) PORT_NAME("Clear") PORT_BIT( 0x08, IP_ACTIVE_HIGH, IPT_KEYPAD ) PORT_CODE(KEYCODE_D) PORT_NAME("Sanctuary/Citadel") PORT_START("IN.1") // R1 PORT_BIT( 0x01, IP_ACTIVE_HIGH, IPT_KEYPAD ) PORT_CODE(KEYCODE_X) PORT_NAME("Frontier") PORT_BIT( 0x02, IP_ACTIVE_HIGH, IPT_KEYPAD ) PORT_CODE(KEYCODE_2) PORT_NAME("Repeat") PORT_BIT( 0x04, IP_ACTIVE_HIGH, IPT_KEYPAD ) PORT_CODE(KEYCODE_W) PORT_NAME("Bazaar") PORT_BIT( 0x08, IP_ACTIVE_HIGH, IPT_KEYPAD ) PORT_CODE(KEYCODE_S) PORT_NAME("Move") PORT_START("IN.2") // R2 PORT_BIT( 0x01, IP_ACTIVE_HIGH, IPT_KEYPAD ) PORT_CODE(KEYCODE_Z) PORT_NAME("Dark Tower") PORT_BIT( 0x02, IP_ACTIVE_HIGH, IPT_KEYPAD ) PORT_CODE(KEYCODE_1) PORT_NAME("Yes/Buy") PORT_BIT( 0x04, IP_ACTIVE_HIGH, IPT_KEYPAD ) PORT_CODE(KEYCODE_Q) PORT_NAME("Haggle") PORT_BIT( 0x08, IP_ACTIVE_HIGH, IPT_KEYPAD ) PORT_CODE(KEYCODE_A) PORT_NAME("Tomb/Ruin") INPUT_PORTS_END /*************************************************************************** Machine Config ***************************************************************************/ void mbdtower_state::machine_start() { hh_tms1k_state::machine_start(); // zerofill m_motor_pos = 0; m_motor_pos_prev = -1; m_motor_decay = 0; m_motor_on = false; m_sensor_blind = false; // register for savestates save_item(NAME(m_motor_pos)); /* save_item(NAME(m_motor_pos_prev)); */ // don't save! save_item(NAME(m_motor_decay)); save_item(NAME(m_motor_on)); save_item(NAME(m_sensor_blind)); } static MACHINE_CONFIG_START( mbdtower, mbdtower_state ) /* basic machine hardware */ MCFG_CPU_ADD("maincpu", TMS1400, 425000) // approximation - RC osc. R=43K, C=56pf MCFG_TMS1XXX_READ_K_CB(READ8(mbdtower_state, read_k)) MCFG_TMS1XXX_WRITE_R_CB(WRITE16(mbdtower_state, write_r)) MCFG_TMS1XXX_WRITE_O_CB(WRITE16(mbdtower_state, write_o)) MCFG_TIMER_DRIVER_ADD_PERIODIC("tower_motor", mbdtower_state, motor_sim_tick, attotime::from_msec(3500/0x80)) // ~3.5sec for a full rotation MCFG_TIMER_DRIVER_ADD_PERIODIC("display_decay", hh_tms1k_state, display_decay_tick, attotime::from_msec(1)) MCFG_DEFAULT_LAYOUT(layout_mbdtower) /* sound hardware */ MCFG_SPEAKER_STANDARD_MONO("mono") MCFG_SOUND_ADD("speaker", SPEAKER_SOUND, 0) MCFG_SOUND_ROUTE(ALL_OUTPUTS, "mono", 0.25) MACHINE_CONFIG_END /*************************************************************************** Game driver(s) ***************************************************************************/ ROM_START( mbdtower ) ROM_REGION( 0x1000, "maincpu", 0 ) ROM_LOAD( "mp7332", 0x0000, 0x1000, CRC(ebeab91a) SHA1(7edbff437da371390fa8f28b3d183f833eaa9be9) ) ROM_REGION( 867, "maincpu:mpla", 0 ) ROM_LOAD( "tms1100_common1_micro.pla", 0, 867, CRC(62445fc9) SHA1(d6297f2a4bc7a870b76cc498d19dbb0ce7d69fec) ) ROM_REGION( 557, "maincpu:opla", 0 ) ROM_LOAD( "tms1400_mbdtower_output.pla", 0, 557, CRC(64c84697) SHA1(72ce6d24cedf9c606f1742cd5620f75907246e87) ) ROM_END CONS( 1981, mbdtower, 0, 0, mbdtower, mbdtower, driver_device, 0, "Milton Bradley", "Dark Tower (Milton Bradley)", MACHINE_SUPPORTS_SAVE | MACHINE_CLICKABLE_ARTWORK | MACHINE_MECHANICAL )