// license:BSD-3-Clause // copyright-holders:Aaron Giles /*************************************************************************** Slick Shot input handling Unlike the other 8-bit Strata games, Slick Shot has an interesting and fairly complex input system. The actual cabinet has a good-sized gap underneath the monitor, from which a small pool table emerges. An actual cue ball and pool sticks were included with the game. To "control" the game, players actually put the cue ball on the pool table and shot the ball into the gap. Four sensors underneath the monitor would count how long they saw the ball, and from this data, the velocity and crossing point of the ball could be derived. In order to read these sensors, an extra Z80 was added to the board. The Z80 program is astoundingly simple: on reset, it writes a value of $00 to the output port, then waits for either sensor 0 or 1 to fire. As soon as one of those sensors fires, it begins counting how long it takes for the bits corresponding to those sensors, as well as sensors 2 and 3, to return to their 0 state. It then writes a $ff to the output port to signal that data is ready and waits for the main CPU to clock the data through. On the main program side of things, the result from the Z80 is periodically polled. Once a $ff is seen, 3 words and 1 bytes' worth of data is read from the Z80, after which the Z80 goes into an infinite loop. When the main program is ready to read a result again, it resets the Z80 to start the read going again. The way the Z80 reads the data, is as follows: - write $00 to output - wait for sensor 0 or 1 to fire (go to the 1 state) - count how long that sensor takes to return to 0 - count how long sensors 2 and 3 take to return to 0 - write $ff to output - wait for data to be clocked through - return 3 words + 1 byte of data: - word 0 = (value of larger of sensor 2/3 counts) - (value of smaller) - word 1 = value of smaller of sensor 2/3 counts - word 2 = value of sensor 0/1 - byte = beam data - bit 0 = 1 if sensor 0 fired; 0 if sensor 1 fired - bit 1 = 1 if sensor 3 value > sensor 2 value; 0 otherwise - enter infinite loop Once this data is read from the Z80, it is converted to an intermediate form, and then processed using 32-bit math (yes, on a 6809!) to produce the final velocity and X position of the crossing. Because it is not understood exactly where the sensors are placed and how to simulate the actual behavior, this module attempts to do the next best thing: given a velocity and X position, figure out raw sensor values that will travel from the Z80 to the main 6809 and through the calculations produce approximately the correct results. There are several stages of data: - sens0, sens1, sens2, sens3 = raw sensor values - word1, word2, word3, beam = values from the Z80 (beam = byte val) - inter1, inter2, inter3, beam = intermediate forms in the 6809 - vx, vy, x = final X,Y velocities and X crossing point And all the functions here are designed to take you through the various stages, both forwards and backwards, replicating the operations in the 6809 or reversing them. ***************************************************************************/ #include "emu.h" #include "cpu/z80/z80.h" #include "includes/itech8.h" #define MINDY 100 /************************************* * * sensors_to_words * * converts from raw sensor data to * the three words + byte that the * Z80 sends to the main 6809 * *************************************/ #ifdef STANDALONE static void sensors_to_words(uint16_t sens0, uint16_t sens1, uint16_t sens2, uint16_t sens3, uint16_t *word1, uint16_t *word2, uint16_t *word3, uint8_t *beams) { /* word 1 contains the difference between the larger of sensors 2 & 3 and the smaller */ *word1 = (sens3 > sens2) ? (sens3 - sens2) : (sens2 - sens3); /* word 2 contains the value of the smaller of sensors 2 & 3 */ *word2 = (sens3 > sens2) ? sens2 : sens3; /* word 3 contains the value of sensor 0 or 1, depending on which fired */ *word3 = sens0 ? sens0 : sens1; /* set the beams bits */ *beams = 0; /* if sensor 1 fired first, set bit 0 */ if (!sens0) *beams |= 1; /* if sensor 3 has the larger value, set bit 1 */ if (sens3 > sens2) *beams |= 2; } #endif /************************************* * * words_to_inters * * converts the three words + byte * data from the Z80 into the three * intermediate values used in the * final calculations * *************************************/ #ifdef STANDALONE static void words_to_inters(uint16_t word1, uint16_t word2, uint16_t word3, uint8_t beams, uint16_t *inter1, uint16_t *inter2, uint16_t *inter3) { /* word 2 is scaled up by 0x1.6553 */ uint16_t word2mod = ((uint64_t)word2 * 0x16553) >> 16; /* intermediate values 1 and 2 are determined based on the beams bits */ switch (beams) { case 0: *inter1 = word1 + word2mod; *inter2 = word2mod + word3; break; case 1: *inter1 = word1 + word2mod + word3; *inter2 = word2mod; break; case 2: *inter1 = word2mod; *inter2 = word1 + word2mod + word3; break; case 3: *inter1 = word2mod + word3; *inter2 = word1 + word2mod; break; } /* intermediate value 3 is always equal to the third word */ *inter3 = word3; } #endif /************************************* * * inters_to_vels * * converts the three intermediate * values to the final velocity and * X position values * *************************************/ void itech8_state::inters_to_vels(uint16_t inter1, uint16_t inter2, uint16_t inter3, uint8_t beams, uint8_t *xres, uint8_t *vxres, uint8_t *vyres) { uint32_t _27d8, _27c2; uint32_t vx, vy, _283a, _283e; uint8_t vxsgn; uint16_t xoffs = 0x0016; uint8_t xscale = 0xe6; uint16_t x; /* compute Vy */ vy = inter1 ? (0x31c28 / inter1) : 0; /* compute Vx */ _283a = inter2 ? (0x30f2e / inter2) : 0; _27d8 = ((uint64_t)vy * 0xfbd3) >> 16; _27c2 = _283a - _27d8; vxsgn = 0; if ((int32_t)_27c2 < 0) { vxsgn = 1; _27c2 = _27d8 - _283a; } vx = ((uint64_t)_27c2 * 0x58f8c) >> 16; /* compute X */ _27d8 = ((uint64_t)(inter3 << 16) * _283a) >> 16; _283e = ((uint64_t)_27d8 * 0x4a574b) >> 16; /* adjust X based on the low bit of the beams */ if (beams & 1) x = 0x7a + (_283e >> 16) - xoffs; else x = 0x7a - (_283e >> 16) - xoffs; /* apply a constant X scale */ if (xscale) x = ((xscale * (x & 0xff)) >> 8) & 0xff; /* clamp if out of range */ if ((vx & 0xffff) >= 0x80) x = 0; /* put the sign back in Vx */ vx &= 0xff; if (!vxsgn) vx = -vx; /* clamp VY */ if ((vy & 0xffff) > 0x7f) vy = 0x7f; else vy &= 0xff; /* copy the results */ *xres = x; *vxres = vx; *vyres = vy; } /************************************* * * vels_to_inters * * converts from the final velocity * and X position values back to * three intermediate values that * will produce the desired result * *************************************/ void itech8_state::vels_to_inters(uint8_t x, uint8_t vx, uint8_t vy, uint16_t *inter1, uint16_t *inter2, uint16_t *inter3, uint8_t *beams) { uint32_t _27d8; uint16_t xoffs = 0x0016; uint8_t xscale = 0xe6; uint8_t x1, vx1, vy1; uint8_t x2, vx2, vy2; uint8_t diff1, diff2; uint16_t inter2a; /* inter1 comes from Vy */ *inter1 = vy ? 0x31c28 / vy : 0; /* inter2 can be derived from Vx and Vy */ _27d8 = ((uint64_t)vy * 0xfbd3) >> 16; *inter2 = 0x30f2e / (_27d8 + (((uint32_t)abs((int8_t)vx) << 16) / 0x58f8c)); inter2a = 0x30f2e / (_27d8 - (((uint32_t)abs((int8_t)vx) << 16) / 0x58f8c)); /* compute it back both ways and pick the closer */ inters_to_vels(*inter1, *inter2, 0, 0, &x1, &vx1, &vy1); inters_to_vels(*inter1, inter2a, 0, 0, &x2, &vx2, &vy2); diff1 = (vx > vx1) ? (vx - vx1) : (vx1 - vx); diff2 = (vx > vx2) ? (vx - vx2) : (vx2 - vx); if (diff2 < diff1) *inter2 = inter2a; /* inter3: (beams & 1 == 1), inter3a: (beams & 1) == 0 */ if (((x << 8) / xscale) + xoffs >= 0x7a) { *beams = 1; *inter3 = (((((((uint64_t)(((x << 8) / xscale) + xoffs - 0x7a)) << 16) << 16) / 0x4a574b) << 16) / (0x30f2e / *inter2)) >> 16; } else { *beams = 0; *inter3 = (((((((uint64_t)(((x << 8) / xscale) + xoffs - 0x7a) * -1) << 16) << 16) / 0x4a574b) << 16) / (0x30f2e / *inter2)) >> 16; } } /************************************* * * inters_to_words * * converts the intermediate values * used in the final calculations * back to the three words + byte * data from the Z80 * *************************************/ void itech8_state::inters_to_words(uint16_t inter1, uint16_t inter2, uint16_t inter3, uint8_t *beams, uint16_t *word1, uint16_t *word2, uint16_t *word3) { uint16_t word2mod; /* intermediate value 3 is always equal to the third word */ *word3 = inter3; /* on input, it is expected that the low bit of beams has already been determined */ if (*beams & 1) { /* make sure we can do it */ if (inter3 <= inter1) { /* always go back via case 3 */ *beams |= 2; /* compute an appropriate value for the scaled version of word 2 */ word2mod = inter1 - inter3; /* compute the other values from that */ *word1 = inter2 - word2mod; *word2 = ((uint64_t)word2mod << 16) / 0x16553; } else logerror("inters_to_words: unable to convert %04x %04x %04x %02x\n", (uint32_t)inter1, (uint32_t)inter2, (uint32_t)inter3, (uint32_t)*beams); } /* handle the case where low bit of beams is 0 */ else { /* make sure we can do it */ if (inter3 <= inter2) { /* always go back via case 0 */ /* compute an appropriate value for the scaled version of word 2 */ word2mod = inter2 - inter3; /* compute the other values from that */ *word1 = inter1 - word2mod; *word2 = ((uint64_t)word2mod << 16) / 0x16553; } else logerror("inters_to_words: unable to convert %04x %04x %04x %02x\n", (uint32_t)inter1, (uint32_t)inter2, (uint32_t)inter3, (uint32_t)*beams); } } /************************************* * * words_to_sensors * * converts from the three words + * byte that the Z80 sends to the * main 6809 back to raw sensor data * *************************************/ void itech8_state::words_to_sensors(uint16_t word1, uint16_t word2, uint16_t word3, uint8_t beams, uint16_t *sens0, uint16_t *sens1, uint16_t *sens2, uint16_t *sens3) { /* if bit 0 of the beams is set, sensor 1 fired first; otherwise sensor 0 fired */ if (beams & 1) *sens0 = 0, *sens1 = word3; else *sens0 = word3, *sens1 = 0; /* if bit 1 of the beams is set, sensor 3 had a larger value */ if (beams & 2) *sens3 = word2 + word1, *sens2 = word2; else *sens2 = word2 + word1, *sens3 = word2; } /************************************* * * compute_sensors * *************************************/ void itech8_state::compute_sensors() { uint16_t inter1, inter2, inter3; uint16_t word1 = 0, word2 = 0, word3 = 0; uint8_t beams; /* skip if we're not ready */ if (m_sensor0 != 0 || m_sensor1 != 0 || m_sensor2 != 0 || m_sensor3 != 0) return; /* reverse map the inputs */ vels_to_inters(m_curx, m_curvx, m_curvy, &inter1, &inter2, &inter3, &beams); inters_to_words(inter1, inter2, inter3, &beams, &word1, &word2, &word3); words_to_sensors(word1, word2, word3, beams, &m_sensor0, &m_sensor1, &m_sensor2, &m_sensor3); logerror("%15f: Sensor values: %04x %04x %04x %04x\n", machine().time().as_double(), m_sensor0, m_sensor1, m_sensor2, m_sensor3); } /************************************* * * slikz80_port_r * *************************************/ READ8_MEMBER(itech8_state::slikz80_port_r ) { int result = 0; /* if we have nothing, return 0x03 */ if (!m_sensor0 && !m_sensor1 && !m_sensor2 && !m_sensor3) return 0x03 | (m_z80_clear_to_send << 7); /* 1 bit for each sensor */ if (m_sensor0) result |= 1, m_sensor0--; if (m_sensor1) result |= 2, m_sensor1--; if (m_sensor2) result |= 4, m_sensor2--; if (m_sensor3) result |= 8, m_sensor3--; result |= m_z80_clear_to_send << 7; return result; } /************************************* * * slikz80_port_w * *************************************/ WRITE8_MEMBER(itech8_state::slikz80_port_w ) { m_z80_port_val = data; m_z80_clear_to_send = 0; } /************************************* * * slikshot_z80_r * *************************************/ READ8_MEMBER(itech8_state::slikshot_z80_r ) { /* allow the Z80 to send us stuff now */ m_z80_clear_to_send = 1; return m_z80_port_val; } /************************************* * * slikshot_z80_control_r * *************************************/ READ8_MEMBER(itech8_state::slikshot_z80_control_r ) { return m_z80_ctrl; } /************************************* * * slikshot_z80_control_w * *************************************/ TIMER_CALLBACK_MEMBER( itech8_state::delayed_z80_control_w ) { int data = param; /* bit 4 controls the reset line on the Z80 */ /* this is a big kludge: only allow a reset if the Z80 is stopped */ /* at its endpoint; otherwise, we never get a result from the Z80 */ if ((data & 0x10) || m_subcpu->state_int(Z80_PC) == 0x13a) { m_subcpu->set_input_line(INPUT_LINE_RESET, (data & 0x10) ? CLEAR_LINE : ASSERT_LINE); /* on the rising edge, make the crosshair visible again */ if ((data & 0x10) && !(m_z80_ctrl & 0x10)) m_crosshair_vis = 1; } /* boost the interleave whenever this is written to */ machine().scheduler().boost_interleave(attotime::zero, attotime::from_usec(100)); /* stash the new value */ m_z80_ctrl = data; } WRITE8_MEMBER(itech8_state::slikshot_z80_control_w ) { synchronize(TIMER_DELAYED_Z80_CONTROL, data); } VIDEO_START_MEMBER(itech8_state,slikshot) { itech8_state::video_start(); m_z80_ctrl = 0; m_z80_port_val = 0; m_z80_clear_to_send = 0; m_sensor0 = m_sensor1 = m_sensor2 = m_sensor3 = 0; m_curvx = 0, m_curvy = 1, m_curx = 0; m_ybuffer_next = 0; m_curxpos = 0; m_last_ytotal = 0; m_crosshair_vis = 0; } /************************************* * * SCREEN_UPDATE( slikshot ) * *************************************/ uint32_t itech8_state::screen_update_slikshot(screen_device &screen, bitmap_rgb32 &bitmap, const rectangle &cliprect) { int totaldy, totaldx; int temp, i; /* draw the normal video first */ screen_update_2page(screen, bitmap, cliprect); /* add the current X,Y positions to the list */ m_xbuffer[m_ybuffer_next % YBUFFER_COUNT] = m_fakex->read(); m_ybuffer[m_ybuffer_next % YBUFFER_COUNT] = m_fakey->read(); m_ybuffer_next++; /* determine where to draw the starting point */ m_curxpos += m_xbuffer[(m_ybuffer_next + 1) % YBUFFER_COUNT]; if (m_curxpos < -0x80) m_curxpos = -0x80; if (m_curxpos > 0x80) m_curxpos = 0x80; /* compute the total X/Y movement */ totaldx = totaldy = 0; for (i = 0; i < YBUFFER_COUNT - 1; i++) { totaldx += m_xbuffer[(m_ybuffer_next + i + 1) % YBUFFER_COUNT]; totaldy += m_ybuffer[(m_ybuffer_next + i + 1) % YBUFFER_COUNT]; } /* if the shoot button is pressed, fire away */ if (totaldy < m_last_ytotal && m_last_ytotal > 50 && m_crosshair_vis) { /* compute the updated values */ temp = totaldx; if (temp <= -0x80) temp = -0x7f; if (temp >= 0x80) temp = 0x7f; m_curvx = temp; temp = m_last_ytotal - 50; if (temp <= 0x10) temp = 0x10; if (temp >= 0x7f) temp = 0x7f; m_curvy = temp; temp = 0x60 + (m_curxpos * 0x30 / 0x80); if (temp <= 0x30) temp = 0x30; if (temp >= 0x90) temp = 0x90; m_curx = temp; compute_sensors(); // popmessage("V=%02x,%02x X=%02x", m_curvx, m_curvy, m_curx); m_crosshair_vis = 0; } m_last_ytotal = totaldy; /* clear the buffer while the crosshair is not visible */ if (!m_crosshair_vis) { memset(m_xbuffer, 0, sizeof(m_xbuffer)); memset(m_ybuffer, 0, sizeof(m_ybuffer)); } return 0; } /************************************* * * main * * uncomment this to make a stand * alone version for testing * *************************************/ #ifdef STANDALONE int main(int argc, char *argv[]) { uint16_t word1, word2, word3; uint16_t inter1, inter2, inter3; uint8_t beams, x, vx, vy; if (argc == 5) { uint32_t sens0, sens1, sens2, sens3; sscanf(argv[1], "%x", &sens0); sscanf(argv[2], "%x", &sens1); sscanf(argv[3], "%x", &sens2); sscanf(argv[4], "%x", &sens3); osd_printf_debug("sensors: %04x %04x %04x %04x\n", sens0, sens1, sens2, sens3); if (sens0 && sens1) { osd_printf_debug("error: sensor 0 or 1 must be 0\n"); return 1; } sensors_to_words(sens0, sens1, sens2, sens3, &word1, &word2, &word3, &beams); osd_printf_debug("word1 = %04x word2 = %04x word3 = %04x beams = %d\n", (uint32_t)word1, (uint32_t)word2, (uint32_t)word3, (uint32_t)beams); words_to_inters(word1, word2, word3, beams, &inter1, &inter2, &inter3); osd_printf_debug("inter1 = %04x inter2 = %04x inter3 = %04x\n", (uint32_t)inter1, (uint32_t)inter2, (uint32_t)inter3); inters_to_vels(inter1, inter2, inter3, beams, &x, &vx, &vy); osd_printf_debug("x = %02x vx = %02x vy = %02x\n", (uint32_t)x, (uint32_t)vx, (uint32_t)vy); } else if (argc == 4) { uint32_t xin, vxin, vyin; uint16_t sens0, sens1, sens2, sens3; sscanf(argv[1], "%x", &xin); sscanf(argv[2], "%x", &vxin); sscanf(argv[3], "%x", &vyin); x = xin; vx = vxin; vy = vyin; osd_printf_debug("x = %02x vx = %02x vy = %02x\n", (uint32_t)x, (uint32_t)vx, (uint32_t)vy); vels_to_inters(x, vx, vy, &inter1, &inter2, &inter3, &beams); osd_printf_debug("inter1 = %04x inter2 = %04x inter3 = %04x beams = %d\n", (uint32_t)inter1, (uint32_t)inter2, (uint32_t)inter3, (uint32_t)beams); inters_to_words(inter1, inter2, inter3, &beams, &word1, &word2, &word3); osd_printf_debug("word1 = %04x word2 = %04x word3 = %04x beams = %d\n", (uint32_t)word1, (uint32_t)word2, (uint32_t)word3, (uint32_t)beams); words_to_sensors(word1, word2, word3, beams, &sens0, &sens1, &sens2, &sens3); osd_printf_debug("sensors: %04x %04x %04x %04x\n", sens0, sens1, sens2, sens3); } return 0; } #endif