// license:BSD-3-Clause
// copyright-holders:Mariusz Wojcieszek
/*
Galaxy Game
Driver by Mariusz Wojcieszek
First commercial video game.
*/
#include "emu.h"
#include "cpu/t11/t11.h"
#include "emupal.h"
#include "screen.h"
#define MAX_POINTS 2048
class galaxygame_state : public driver_device
{
public:
galaxygame_state(const machine_config &mconfig, device_type type, const char *tag) :
driver_device(mconfig, type, tag),
m_maincpu(*this, "maincpu"),
m_palette(*this, "palette")
{ }
void galaxygame(machine_config &config);
void init_galaxygame();
private:
uint16_t m_clk;
uint16_t m_x;
uint16_t m_y;
int16_t m_mq;
int16_t m_ac;
struct
{
uint16_t x;
uint16_t y;
} m_point_work_list[MAX_POINTS], m_point_display_list[MAX_POINTS];
int m_point_work_list_index;
int m_point_display_list_index;
int m_interrupt;
DECLARE_READ16_MEMBER(ke_r);
DECLARE_WRITE16_MEMBER(ke_w);
DECLARE_READ16_MEMBER(x_r);
DECLARE_WRITE16_MEMBER(x_w);
DECLARE_READ16_MEMBER(y_r);
DECLARE_WRITE16_MEMBER(y_w);
DECLARE_WRITE16_MEMBER(clk_w);
virtual void machine_reset() override;
uint32_t screen_update_galaxygame(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect);
INTERRUPT_GEN_MEMBER(galaxygame_irq);
IRQ_CALLBACK_MEMBER(galaxygame_irq_callback);
required_device<t11_device> m_maincpu;
required_device<palette_device> m_palette;
void galaxygame_map(address_map &map);
};
/*************************************
*
* KE11 Extended Artithmetic Element
*
*************************************/
READ16_MEMBER(galaxygame_state::ke_r)
{
uint16_t ret;
switch( offset )
{
case 0: // DIV
ret = 0;
break;
case 1: // AC
ret = m_ac;
break;
case 2: // MQ
ret = m_mq;
break;
case 7: // ASH
ret = 0;
break;
default:
ret = 0;
logerror("Unhandled KE read (%d)\n", offset );
break;
}
return ret;
}
WRITE16_MEMBER(galaxygame_state::ke_w)
{
switch( offset )
{
case 0: // DIV
{
if ( data != 0 )
{
int32_t dividend = (int32_t)((uint32_t)((uint16_t)m_ac << 16) | (uint16_t)(m_mq));
m_mq = dividend / (int16_t)data;
m_ac = dividend % (int16_t)data;
}
else
{
m_mq = 0;
m_ac = 0;
}
}
break;
case 1: // AC
m_ac = (int16_t)data;
break;
case 2: // MQ
m_mq = (int16_t)data;
if (m_mq < 0)
{
m_ac = -1;
}
else
{
m_ac = 0;
}
break;
case 3: // X
{
int32_t mulres = (int32_t)m_mq*(int32_t)(int16_t)data;
m_ac = mulres >> 16;
m_mq = mulres & 0xffff;
}
break;
case 6: // LSH
{
data &= 63;
int32_t val = (int32_t)((uint32_t)((uint16_t)m_ac << 16) | (uint16_t)(m_mq));
if ( data < 32 )
{
val = val << data;
}
else
{
val = val >> (64 - data);
}
m_mq = val & 0xffff;
m_ac = (val >> 16) & 0xffff;
}
break;
case 7: // ASH
{
data &= 63;
int32_t val = (int32_t)((uint32_t)((uint16_t)m_ac << 16) | (uint16_t)(m_mq));
if ( data < 32 )
{
val = val << data;
}
else
{
val = val >> (64 - data);
}
m_mq = val & 0xffff;
m_ac = (val >> 16) & 0xffff;
}
break;
default:
logerror("Unhandled KE write (%d)\n", offset);
break;
}
}
/*************************************
*
* Video
*
*************************************/
uint32_t galaxygame_state::screen_update_galaxygame(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect)
{
bitmap.fill(m_palette->black_pen(), cliprect);
for (int i = 0; i < m_point_display_list_index; i++ )
{
bitmap.pix16(m_point_display_list[i].x >> 7, m_point_display_list[i].y >> 7) = 1;
}
return 0;
}
READ16_MEMBER(galaxygame_state::x_r)
{
return m_x;
}
WRITE16_MEMBER(galaxygame_state::x_w)
{
m_x = data;
}
READ16_MEMBER(galaxygame_state::y_r)
{
return m_y;
}
WRITE16_MEMBER(galaxygame_state::y_w)
{
m_y = data;
if ( data == 0x0101 )
{
// send points list to display device
// seems to happen on first 0x0101 write after interrupt
if ( m_interrupt )
{
for ( int i = 0; i < m_point_work_list_index ; i++ )
{
m_point_display_list[i].x = m_point_work_list[i].x;
m_point_display_list[i].y = m_point_work_list[i].y;
}
m_point_display_list_index = m_point_work_list_index;
m_point_work_list_index = 0;
m_interrupt = 0;
}
}
else
{
if ( m_point_work_list_index >= MAX_POINTS )
{
logerror("Work list overflow\n");
}
else
{
m_point_work_list[m_point_work_list_index].x = m_x;
m_point_work_list[m_point_work_list_index].y = m_y;
m_point_work_list_index++;
}
}
}
/*************************************
*
* Inputs
*
*************************************/
static INPUT_PORTS_START( galaxygame )
PORT_START("COINAC")
PORT_BIT(0x0001, IP_ACTIVE_HIGH, IPT_COIN3 ) // 25 cents, left
PORT_BIT(0x0002, IP_ACTIVE_HIGH, IPT_COIN1 ) // 10 cents, left
PORT_BIT(0x0100, IP_ACTIVE_HIGH, IPT_COIN4 ) // 25 cents, right
PORT_BIT(0x0200, IP_ACTIVE_HIGH, IPT_COIN2 ) // 10 cents, right
PORT_START("SR")
PORT_DIPNAME( 0x8000, 0x0000, "Gravity" )
PORT_DIPSETTING( 0x0000, "Positive Gravity" )
PORT_DIPSETTING( 0x8000, "Negative Gravity" )
PORT_DIPNAME( 0x4000, 0x0000, "Sun" )
PORT_DIPSETTING( 0x0000, "Sun (& Gravity)" )
PORT_DIPSETTING( 0x4000, "No Sun (& No Gravity)" )
PORT_BIT( 0x2000, IP_ACTIVE_HIGH, IPT_BUTTON1 ) PORT_PLAYER(1) // fire
PORT_BIT( 0x1000, IP_ACTIVE_HIGH, IPT_BUTTON3 ) PORT_PLAYER(1) // hyper
PORT_BIT( 0x0800, IP_ACTIVE_HIGH, IPT_BUTTON2 ) PORT_PLAYER(1) // thrust
PORT_BIT( 0x0200, IP_ACTIVE_HIGH, IPT_JOYSTICK_LEFT ) PORT_2WAY PORT_PLAYER(1)
PORT_BIT( 0x0100, IP_ACTIVE_HIGH, IPT_JOYSTICK_RIGHT ) PORT_2WAY PORT_PLAYER(1)
PORT_DIPNAME( 0x0080, 0x0000, "Speed" )
PORT_DIPSETTING( 0x0000, "Slow Speed" )
PORT_DIPSETTING( 0x0080, "Fast Speed" )
PORT_DIPNAME( 0x0040, 0x0000, DEF_STR( Players ) )
PORT_DIPSETTING( 0x0000, "Two Players" )
PORT_DIPSETTING( 0x0040, "One Player" )
PORT_BIT( 0x0020, IP_ACTIVE_HIGH, IPT_BUTTON1 ) PORT_PLAYER(2)
PORT_BIT( 0x0010, IP_ACTIVE_HIGH, IPT_BUTTON3 ) PORT_PLAYER(2)
PORT_BIT( 0x0008, IP_ACTIVE_HIGH, IPT_BUTTON2 ) PORT_PLAYER(2)
PORT_BIT( 0x0002, IP_ACTIVE_HIGH, IPT_JOYSTICK_LEFT ) PORT_2WAY PORT_PLAYER(2)
PORT_BIT( 0x0001, IP_ACTIVE_HIGH, IPT_JOYSTICK_RIGHT ) PORT_2WAY PORT_PLAYER(2)
INPUT_PORTS_END
/*************************************
*
* Machine
*
*************************************/
WRITE16_MEMBER(galaxygame_state::clk_w)
{
m_clk = data;
}
void galaxygame_state::galaxygame_map(address_map &map)
{
map(0x0000, 0x1fff).ram();
map(0xfec0, 0xfecf).rw(FUNC(galaxygame_state::ke_r), FUNC(galaxygame_state::ke_w));
map(0xff52, 0xff53).rw(FUNC(galaxygame_state::y_r), FUNC(galaxygame_state::y_w)); // 177522 Y
map(0xff54, 0xff55).portr("COINAC"); // 177524 COINAC
map(0xff5a, 0xff5b).rw(FUNC(galaxygame_state::x_r), FUNC(galaxygame_state::x_w)); // 177532 X
map(0xff5c, 0xff5d).portr("SR"); // 177534 SR
map(0xff66, 0xff67).w(FUNC(galaxygame_state::clk_w)); // 177546 KW11 line frequency clock
}
IRQ_CALLBACK_MEMBER(galaxygame_state::galaxygame_irq_callback)
{
device.execute().set_input_line(0, CLEAR_LINE);
return 0x40;
}
INTERRUPT_GEN_MEMBER(galaxygame_state::galaxygame_irq)
{
if ( m_clk & 0x40 )
{
device.execute().set_input_line(0, ASSERT_LINE);
m_interrupt = 1;
}
}
void galaxygame_state::machine_reset()
{
m_clk = 0x00;
m_point_work_list_index = 0;
m_point_display_list_index = 0;
m_interrupt = 0;
}
void galaxygame_state::galaxygame(machine_config &config)
{
T11(config, m_maincpu, 3000000);
m_maincpu->set_addrmap(AS_PROGRAM, &galaxygame_state::galaxygame_map);
m_maincpu->set_initial_mode(5 << 13);
m_maincpu->set_irq_acknowledge_callback(FUNC(galaxygame_state::galaxygame_irq_callback));
m_maincpu->set_periodic_int(FUNC(galaxygame_state::galaxygame_irq), attotime::from_hz(60));
/* video hardware */
screen_device &screen(SCREEN(config, "screen", SCREEN_TYPE_RASTER));
screen.set_refresh_hz(60);
screen.set_vblank_time(ATTOSECONDS_IN_USEC(0));
screen.set_size(512, 512);
screen.set_visarea(0, 511, 0, 511);
screen.set_screen_update(FUNC(galaxygame_state::screen_update_galaxygame));
screen.set_palette("palette");
PALETTE(config, m_palette, palette_device::MONOCHROME);
}
ROM_START(galgame)
// Original Galaxy Game listing, the one used in the 2nd hardware revision (blue dual cabinet)
// PALX11 V413R 19-NOV-71 8:46
ROM_REGION( 0x20000, "code", ROMREGION_ERASE00 )
ROM_LOAD( "sw97.lst", 0x00000, 0x1f062, CRC(838018a5) SHA1(e3c47c5cf78299650b031ec49fde7d9e4024a759) )
ROM_END
/*************************************
*
* Code loading
*
*************************************/
static uint8_t read_uint16(uint16_t *pval, int pos, const uint8_t* line, int linelen)
{
*pval = 0;
if (linelen < (pos + 6))
{
return 0;
}
for (int i = 0; i < 6; i++)
{
*pval <<= 3;
*pval |= line[pos + i] - 0x30;
}
return 1;
}
static uint8_t read_uint8(uint8_t *pval, int pos, const uint8_t* line, int linelen)
{
*pval = 0;
if (linelen < (pos + 3))
{
return 0;
}
for (int i = 0; i < 3; i++)
{
*pval <<= 3;
*pval |= line[pos + i] - 0x30;
}
return 1;
}
void galaxygame_state::init_galaxygame()
{
address_space &main = m_maincpu->space(AS_PROGRAM);
uint8_t *code = memregion("code")->base();
int filepos = 0;
//load lst file
while (code[filepos] != 0)
{
uint8_t line[256];
int linepos = 0;
while (code[filepos] != 0x0d)
{
line[linepos++] = code[filepos++];
}
line[linepos] = 0;
filepos += 2;
int linelen = linepos;
if (linelen == 0)
{
continue;
}
uint16_t address;
if ((line[8] != ' ') && read_uint16(&address, 7, line, linelen))
{
if ((linelen >= 15+6) && (line[15] != ' '))
{
uint16_t val;
read_uint16(&val, 15, line, linelen);
main.write_word(address, val, 0xffff);
address += 2;
if ((linelen >= 22+6) && (line[22] != ' '))
{
read_uint16(&val, 22, line, linelen);
main.write_word(address, val, 0xffff);
address += 2;
}
if ((linelen >= 29+6) && (line[29] != ' '))
{
read_uint16(&val, 29, line, linelen);
main.write_word(address, val, 0xffff);
address += 2;
}
}
else
{
if ((linelen >= 18+3) && (line[18] != ' '))
{
uint8_t val8;
read_uint8(&val8, 18, line, linelen);
main.write_byte(address, val8);
address += 1;
}
}
}
}
// set startup code
main.write_word(0, 012700); /* MOV #0, R0 */
main.write_word(2, 0);
main.write_word(4, 0x8d00); /* MTPS R0 */
main.write_word(6, 000167); /* JMP 0500*/
main.write_word(8, 000500 - 10);
}
GAME(1971, galgame, 0, galaxygame, galaxygame, galaxygame_state, init_galaxygame, ROT270, "Computer Recreations, Inc", "Galaxy Game", MACHINE_NO_SOUND_HW )