// license:BSD-3-Clause // copyright-holders:David Haywood /*** Tecmo Bowl (c)1987 Tecmo driver by David Haywood wip 20/01/2002 Tecmo Bowl was a popular 4 player American Football game with two screens and attractive graphics --- Current Issues Might be some priority glitches ***/ #include "emu.h" #include "video/tecmo_spr.h" #include "cpu/z80/z80.h" #include "machine/gen_latch.h" #include "sound/msm5205.h" #include "sound/ymopl.h" #include "emupal.h" #include "layout/generic.h" #include "screen.h" #include "speaker.h" #include "tilemap.h" namespace { class tbowl_state : public driver_device { public: tbowl_state(const machine_config &mconfig, device_type type, const char *tag) : driver_device(mconfig, type, tag), m_maincpu(*this, "maincpu"), m_audiocpu(*this, "audiocpu"), m_msm(*this, "msm%u", 1U), m_gfxdecode(*this, "gfxdecode"), m_palette(*this, "palette"), m_sprgen(*this, "spritegen"), m_soundlatch(*this, "soundlatch"), m_txvideoram(*this, "txvideoram"), m_bgvideoram(*this, "bgvideoram%u", 1U), m_spriteram(*this, "spriteram"), m_mainbank(*this, "mainbank"), m_subbank(*this, "subbank"), m_adpcm_rom(*this, "adpcm") { } void tbowl(machine_config &config); protected: virtual void machine_start() override; virtual void machine_reset() override; virtual void video_start() override; private: required_device m_maincpu; required_device m_audiocpu; required_device_array m_msm; required_device m_gfxdecode; required_device m_palette; required_device m_sprgen; required_device m_soundlatch; required_shared_ptr m_txvideoram; required_shared_ptr_array m_bgvideoram; required_shared_ptr m_spriteram; required_memory_bank m_mainbank; required_memory_bank m_subbank; required_region_ptr m_adpcm_rom; tilemap_t *m_tx_tilemap = nullptr; tilemap_t *m_bg_tilemap[2]{}; uint16_t m_bgxscroll[2]{}; uint16_t m_bgyscroll[2]{}; uint16_t m_adpcm_pos[2]{}; uint32_t m_adpcm_end[2]{}; int16_t m_adpcm_data[2]{}; void coincounter_w(uint8_t data); void boardb_bankswitch_w(uint8_t data); void boardc_bankswitch_w(uint8_t data); void trigger_nmi(uint8_t data); template void adpcm_start_w(uint8_t data); template void adpcm_end_w(uint8_t data); template void adpcm_vol_w(uint8_t data); void txvideoram_w(offs_t offset, uint8_t data); template void bgvideoram_w(offs_t offset, uint8_t data); template void bgxscroll_lo(uint8_t data); template void bgxscroll_hi(uint8_t data); template void bgyscroll_lo(uint8_t data); template void bgyscroll_hi(uint8_t data); TILE_GET_INFO_MEMBER(get_tx_tile_info); template TILE_GET_INFO_MEMBER(get_bg_tile_info); uint32_t screen_update_left(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect); uint32_t screen_update_right(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect); template DECLARE_WRITE_LINE_MEMBER(adpcm_int); void _6206A_map(address_map &map); void _6206B_map(address_map &map); void _6206C_map(address_map &map); }; // video // Foreground Layer (tx) Tilemap TILE_GET_INFO_MEMBER(tbowl_state::get_tx_tile_info) { int tileno = m_txvideoram[tile_index] | ((m_txvideoram[tile_index + 0x800] & 0x07) << 8); int col = (m_txvideoram[tile_index + 0x800] & 0xf0) >> 4; tileinfo.set(0, tileno, col, 0); } void tbowl_state::txvideoram_w(offs_t offset, uint8_t data) { m_txvideoram[offset] = data; m_tx_tilemap->mark_tile_dirty(offset & 0x7ff); } // Bottom BG Layer (0) and Middle BG Layer (1) Tilemaps template TILE_GET_INFO_MEMBER(tbowl_state::get_bg_tile_info) { int tileno = m_bgvideoram[Which][tile_index] | ((m_bgvideoram[Which][tile_index + 0x1000] & 0x0f) << 8); int col = (m_bgvideoram[Which][tile_index + 0x1000] & 0xf0) >> 4; tileinfo.set(Which + 1, Which ? tileno ^= 0x400 : tileno, col, 0); } template void tbowl_state::bgvideoram_w(offs_t offset, uint8_t data) { m_bgvideoram[Which][offset] = data; m_bg_tilemap[Which]->mark_tile_dirty(offset & 0xfff); } template void tbowl_state::bgxscroll_lo(uint8_t data) { m_bgxscroll[Which] = (m_bgxscroll[Which] & 0xff00) | data; } template void tbowl_state::bgxscroll_hi(uint8_t data) { m_bgxscroll[Which] = (m_bgxscroll[Which] & 0x00ff) | (data << 8); } template void tbowl_state::bgyscroll_lo(uint8_t data) { m_bgyscroll[Which] = (m_bgyscroll[Which] & 0xff00) | data; } template void tbowl_state::bgyscroll_hi(uint8_t data) { m_bgyscroll[Which] = (m_bgyscroll[Which] & 0x00ff) | (data << 8); } //*** Video Start / Update *** void tbowl_state::video_start() { m_tx_tilemap = &machine().tilemap().create(*m_gfxdecode, tilemap_get_info_delegate(*this, FUNC(tbowl_state::get_tx_tile_info)), TILEMAP_SCAN_ROWS, 8, 8, 64,32); m_bg_tilemap[0] = &machine().tilemap().create(*m_gfxdecode, tilemap_get_info_delegate(*this, FUNC(tbowl_state::get_bg_tile_info<0>)), TILEMAP_SCAN_ROWS, 16,16, 128,32); m_bg_tilemap[1] = &machine().tilemap().create(*m_gfxdecode, tilemap_get_info_delegate(*this, FUNC(tbowl_state::get_bg_tile_info<1>)), TILEMAP_SCAN_ROWS, 16,16, 128,32); m_tx_tilemap->set_transparent_pen(0); m_bg_tilemap[0]->set_transparent_pen(0); m_bg_tilemap[1]->set_transparent_pen(0); save_item(NAME(m_bgxscroll)); save_item(NAME(m_bgyscroll)); } uint32_t tbowl_state::screen_update_left(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect) { m_bg_tilemap[0]->set_scrollx(0, m_bgxscroll[0]); m_bg_tilemap[0]->set_scrolly(0, m_bgyscroll[0]); m_bg_tilemap[1]->set_scrollx(0, m_bgxscroll[1]); m_bg_tilemap[1]->set_scrolly(0, m_bgyscroll[1]); m_tx_tilemap->set_scrollx(0, 0); m_tx_tilemap->set_scrolly(0, 0); bitmap.fill(0x100, cliprect); // is there a register controlling the colour? looks odd when screen is blank m_bg_tilemap[0]->draw(screen, bitmap, cliprect, 0, 0); m_sprgen->tbowl_draw_sprites(bitmap, cliprect, m_gfxdecode->gfx(3), 0, m_spriteram); m_bg_tilemap[1]->draw(screen, bitmap, cliprect, 0, 0); m_tx_tilemap->draw(screen, bitmap, cliprect, 0, 0); return 0; } uint32_t tbowl_state::screen_update_right(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect) { m_bg_tilemap[0]->set_scrollx(0, m_bgxscroll[0] + 32*8); m_bg_tilemap[0]->set_scrolly(0, m_bgyscroll[0]); m_bg_tilemap[1]->set_scrollx(0, m_bgxscroll[1] + 32*8); m_bg_tilemap[1]->set_scrolly(0, m_bgyscroll[1]); m_tx_tilemap->set_scrollx(0, 32*8); m_tx_tilemap->set_scrolly(0, 0); bitmap.fill(0x100, cliprect); // is there a register controlling the colour? looks odd when screen is blank m_bg_tilemap[0]->draw(screen, bitmap, cliprect, 0, 0); m_sprgen->tbowl_draw_sprites(bitmap, cliprect, m_gfxdecode->gfx(3), 32*8, m_spriteram); m_bg_tilemap[1]->draw(screen, bitmap, cliprect, 0, 0); m_tx_tilemap->draw(screen, bitmap, cliprect, 0, 0); return 0; } // machine void tbowl_state::coincounter_w(uint8_t data) { machine().bookkeeping().coin_counter_w(0, data & 1); } /*** Banking note: check this, its borrowed from tecmo.cpp / wc90.cpp at the moment and could well be wrong ***/ void tbowl_state::boardb_bankswitch_w(uint8_t data) { m_mainbank->set_entry(data >> 3); } void tbowl_state::boardc_bankswitch_w(uint8_t data) { m_subbank->set_entry(data >> 3); } /*** Memory Structures Board B is the main board, reading inputs, and in control of the 2 bg layers & text layer etc. Board C is the sub board, main job is the sprites Board A is for the sound ***/ // Board B void tbowl_state::_6206B_map(address_map &map) { map(0x0000, 0x7fff).rom(); map(0x8000, 0x9fff).ram(); map(0xa000, 0xbfff).ram().w(FUNC(tbowl_state::bgvideoram_w<1>)).share(m_bgvideoram[1]); map(0xc000, 0xdfff).ram().w(FUNC(tbowl_state::bgvideoram_w<0>)).share(m_bgvideoram[0]); map(0xe000, 0xefff).ram().w(FUNC(tbowl_state::txvideoram_w)).share(m_txvideoram); // map(0xf000, 0xf000).w(FUNC(tbowl_state::unknown_write)); // written during start-up, not again map(0xf000, 0xf7ff).bankr(m_mainbank); map(0xf800, 0xfbff).ram().share("shared_ram"); // check map(0xfc00, 0xfc00).portr("P1").w(FUNC(tbowl_state::boardb_bankswitch_w)); map(0xfc01, 0xfc01).portr("P2"); // map(0xfc01, 0xfc01).w(FUNC(tbowl_state::unknown_write)); // written during start-up, not again map(0xfc02, 0xfc02).portr("P3"); // map(0xfc02, 0xfc02).w(FUNC(tbowl_state::unknown_write)); // written during start-up, not again map(0xfc03, 0xfc03).portr("P4").w(FUNC(tbowl_state::coincounter_w)); // map(0xfc05, 0xfc05).w(FUNC(tbowl_state::unknown_write)); // no idea // map(0xfc06, 0xfc06).r(FUNC(tbowl_state::dummy_r)); // Read During NMI map(0xfc07, 0xfc07).portr("SYSTEM"); map(0xfc08, 0xfc08).portr("DSW1"); // map(0xfc08, 0xfc08).w(FUNC(tbowl_state::unknown_write)); // hardly used .. map(0xfc09, 0xfc09).portr("DSW2"); map(0xfc0a, 0xfc0a).portr("DSW3"); // map(0xfc0a, 0xfc0a).w(FUNC(tbowl_state::unknown_write)); // hardly used .. map(0xfc0d, 0xfc0d).w(m_soundlatch, FUNC(generic_latch_8_device::write)); map(0xfc10, 0xfc10).w(FUNC(tbowl_state::bgxscroll_lo<1>)); map(0xfc11, 0xfc11).w(FUNC(tbowl_state::bgxscroll_hi<1>)); map(0xfc12, 0xfc12).w(FUNC(tbowl_state::bgyscroll_lo<1>)); map(0xfc13, 0xfc13).w(FUNC(tbowl_state::bgyscroll_hi<1>)); map(0xfc14, 0xfc14).w(FUNC(tbowl_state::bgxscroll_lo<0>)); map(0xfc15, 0xfc15).w(FUNC(tbowl_state::bgxscroll_hi<0>)); map(0xfc16, 0xfc16).w(FUNC(tbowl_state::bgyscroll_lo<0>)); map(0xfc17, 0xfc17).w(FUNC(tbowl_state::bgyscroll_hi<0>)); } // Board C void tbowl_state::trigger_nmi(uint8_t data) { // trigger NMI on 6206B's Cpu? (guess but seems to work..) m_maincpu->pulse_input_line(INPUT_LINE_NMI, attotime::zero); } void tbowl_state::_6206C_map(address_map &map) { map(0x0000, 0xbfff).rom(); map(0xc000, 0xd7ff).ram(); map(0xd800, 0xdfff).ram().share(m_spriteram); map(0xe000, 0xefff).ram().w(m_palette, FUNC(palette_device::write8)).share("palette"); // 2x palettes, one for each monitor? map(0xf000, 0xf7ff).bankr(m_subbank); map(0xf800, 0xfbff).ram().share("shared_ram"); map(0xfc00, 0xfc00).w(FUNC(tbowl_state::boardc_bankswitch_w)); map(0xfc01, 0xfc01).nopw(); // ? map(0xfc02, 0xfc02).w(FUNC(tbowl_state::trigger_nmi)); // ? map(0xfc03, 0xfc03).nopw(); // ? map(0xfc06, 0xfc06).nopw(); // ? } // Board A template void tbowl_state::adpcm_start_w(uint8_t data) { m_adpcm_pos[Which] = data << 8; m_msm[Which]->reset_w(0); } template void tbowl_state::adpcm_end_w(uint8_t data) { m_adpcm_end[Which] = (data + 1) << 8; } template void tbowl_state::adpcm_vol_w(uint8_t data) { m_msm[Which]->set_output_gain(ALL_OUTPUTS, (data & 127) / 127.0); } template WRITE_LINE_MEMBER(tbowl_state::adpcm_int) { if (m_adpcm_pos[Which] >= m_adpcm_end[Which] || m_adpcm_pos[Which] >= m_adpcm_rom.bytes() / 2) m_msm[Which]->reset_w(1); else if (m_adpcm_data[Which] != -1) { m_msm[Which]->data_w(m_adpcm_data[Which] & 0x0f); m_adpcm_data[Which] = -1; } else { uint8_t *rom = &m_adpcm_rom[0x10000 * Which]; m_adpcm_data[Which] = rom[m_adpcm_pos[Which]++]; m_msm[Which]->data_w(m_adpcm_data[Which] >> 4); } } void tbowl_state::_6206A_map(address_map &map) { map(0x0000, 0x7fff).rom(); map(0xc000, 0xc7ff).ram(); map(0xd000, 0xd001).w("ym1", FUNC(ym3812_device::write)); map(0xd800, 0xd801).w("ym2", FUNC(ym3812_device::write)); map(0xe000, 0xe000).w(FUNC(tbowl_state::adpcm_end_w<0>)); map(0xe001, 0xe001).w(FUNC(tbowl_state::adpcm_end_w<1>)); map(0xe002, 0xe002).w(FUNC(tbowl_state::adpcm_start_w<0>)); map(0xe003, 0xe003).w(FUNC(tbowl_state::adpcm_start_w<1>)); map(0xe004, 0xe004).w(FUNC(tbowl_state::adpcm_vol_w<0>)); map(0xe005, 0xe005).w(FUNC(tbowl_state::adpcm_vol_w<1>)); map(0xe006, 0xe006).w(m_soundlatch, FUNC(generic_latch_8_device::acknowledge_w)); map(0xe007, 0xe007).nopw(); // sound watchdog map(0xe010, 0xe010).r(m_soundlatch, FUNC(generic_latch_8_device::read)); } /*** Input Ports Haze's notes : There are controls for 4 players, each player has 4 directions and 2 buttons as well as coin and start. The service switch acts as inserting a coin for all 4 players, the dipswitches are listed in the manual Steph's notes (2002.02.12) : I haven't found any manual, so my notes only rely on the Z80 code ... -- Inputs -- According to the Z80 code, here is the list of controls for each player : - NO START button (you need to press BUTTON1n to start a game for player n) - 4 or 8 directions (I can't tell for the moment, so I've chosen 8 directions) - 2 buttons (I can't tell for the moment what they do) There are also 1 coin slot and a "Service" button for each player. 1 "credit" will mean <