1 // license:BSD-3-Clause
2 // copyright-holders:hap
3 /***************************************************************************
4 
5   Atari Quiz Show
6   released under their Kee Games label, 04/1976
7 
8   S2650 CPU, 512 bytes RAM, B&W tilemapped video. It uses a tape player to
9   stream questions, totaling 1000, divided into 4 categories.
10 
11 TODO:
12 - preserve tape and hook it up, the game is not playable without it
13 - is timing accurate?
14 
15 ***************************************************************************/
16 
17 #include "emu.h"
18 #include "cpu/s2650/s2650.h"
19 #include "machine/timer.h"
20 #include "sound/dac.h"
21 #include "emupal.h"
22 #include "screen.h"
23 #include "speaker.h"
24 #include "tilemap.h"
25 #include "quizshow.lh"
26 
27 
28 static constexpr XTAL MASTER_CLOCK  = 12.096_MHz_XTAL;
29 static constexpr XTAL PIXEL_CLOCK   = MASTER_CLOCK / 2;
30 
31 #define HTOTAL          ((32+8+4+1) * 8)
32 #define HBEND           (0)
33 #define HBSTART         (256)
34 
35 #define VTOTAL          (256+8+4)
36 #define VBEND           (0)
37 #define VBSTART         (240)
38 
39 
40 class quizshow_state : public driver_device
41 {
42 public:
quizshow_state(const machine_config & mconfig,device_type type,const char * tag)43 	quizshow_state(const machine_config &mconfig, device_type type, const char *tag) :
44 		driver_device(mconfig, type, tag),
45 		m_maincpu(*this, "maincpu"),
46 		m_dac(*this, "dac"),
47 		m_main_ram(*this, "main_ram"),
48 		m_gfxdecode(*this, "gfxdecode"),
49 		m_palette(*this, "palette"),
50 		m_screen(*this, "screen"),
51 		m_lamps(*this, "lamp%u", 0U)
52 	{ }
53 
54 	DECLARE_CUSTOM_INPUT_MEMBER(tape_headpos_r);
55 	DECLARE_INPUT_CHANGED_MEMBER(category_select);
56 	void init_quizshow();
57 	void quizshow(machine_config &config);
58 
59 private:
machine_start()60 	virtual void machine_start() override { m_lamps.resolve(); }
61 	virtual void machine_reset() override;
62 	virtual void video_start() override;
63 	void mem_map(address_map &map);
64 
65 	void lamps1_w(uint8_t data);
66 	void lamps2_w(uint8_t data);
67 	void lamps3_w(uint8_t data);
68 	void tape_control_w(uint8_t data);
69 	void audio_w(uint8_t data);
70 	void video_disable_w(uint8_t data);
71 	uint8_t timing_r();
72 	DECLARE_READ_LINE_MEMBER(tape_signal_r);
73 	DECLARE_WRITE_LINE_MEMBER(flag_output_w);
74 	void main_ram_w(offs_t offset, uint8_t data);
75 	TILE_GET_INFO_MEMBER(get_tile_info);
76 	void quizshow_palette(palette_device &palette) const;
77 	uint32_t screen_update(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect);
78 	TIMER_DEVICE_CALLBACK_MEMBER(clock_timer_cb);
79 
80 	required_device<s2650_device> m_maincpu;
81 	required_device<dac_bit_interface> m_dac;
82 	required_shared_ptr<uint8_t> m_main_ram;
83 	required_device<gfxdecode_device> m_gfxdecode;
84 	required_device<palette_device> m_palette;
85 	required_device<screen_device> m_screen;
86 	output_finder<11> m_lamps;
87 
88 	tilemap_t *m_tilemap;
89 	uint32_t m_clocks;
90 	int m_blink_state;
91 	int m_category_enable;
92 	int m_tape_head_pos;
93 };
94 
95 
96 /***************************************************************************
97 
98   Video
99 
100 ***************************************************************************/
101 
quizshow_palette(palette_device & palette) const102 void quizshow_state::quizshow_palette(palette_device &palette) const
103 {
104 	palette.set_indirect_color(0, rgb_t::black());
105 	palette.set_indirect_color(1, rgb_t::white());
106 
107 	// normal, blink/off, invert, blink+invert
108 	constexpr int lut_pal[16] = {
109 		0, 0, 1, 0,
110 		0, 0, 0, 0,
111 		1, 0, 0, 0,
112 		1, 0, 1, 0
113 	};
114 
115 	for (int i = 0; i < 16 ; i++)
116 		palette.set_pen_indirect(i, lut_pal[i]);
117 }
118 
TILE_GET_INFO_MEMBER(quizshow_state::get_tile_info)119 TILE_GET_INFO_MEMBER(quizshow_state::get_tile_info)
120 {
121 	uint8_t const code = m_main_ram[tile_index];
122 
123 	// d6: blink, d7: invert
124 	uint8_t const color = (code & (m_blink_state | 0x80)) >> 6;
125 
126 	tileinfo.set(0, code & 0x3f, color, 0);
127 }
128 
video_start()129 void quizshow_state::video_start()
130 {
131 	m_tilemap = &machine().tilemap().create(*m_gfxdecode, tilemap_get_info_delegate(*this, FUNC(quizshow_state::get_tile_info)), TILEMAP_SCAN_ROWS, 8, 16, 32, 16);
132 }
133 
screen_update(screen_device & screen,bitmap_ind16 & bitmap,const rectangle & cliprect)134 uint32_t quizshow_state::screen_update(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect)
135 {
136 	m_tilemap->draw(screen, bitmap, cliprect, TILEMAP_DRAW_OPAQUE, 0);
137 	return 0;
138 }
139 
140 
141 /***************************************************************************
142 
143   I/O
144 
145 ***************************************************************************/
146 
lamps1_w(uint8_t data)147 void quizshow_state::lamps1_w(uint8_t data)
148 {
149 	// d0-d3: P1 answer button lamps
150 	for (int i = 0; i < 4; i++)
151 		m_lamps[i] = BIT(data, i);
152 
153 	// d4-d7: N/C
154 }
155 
lamps2_w(uint8_t data)156 void quizshow_state::lamps2_w(uint8_t data)
157 {
158 	// d0-d3: P2 answer button lamps
159 	for (int i = 0; i < 4; i++)
160 		m_lamps[i + 4] = BIT(data, i);
161 
162 	// d4-d7: N/C
163 }
164 
lamps3_w(uint8_t data)165 void quizshow_state::lamps3_w(uint8_t data)
166 {
167 	// d0-d1: start button lamps
168 	m_lamps[8] = BIT(data, 0);
169 	m_lamps[9] = BIT(data, 1);
170 
171 	// d2-d3: unused? (chip is shared with tape_control_w)
172 	// d4-d7: N/C
173 }
174 
tape_control_w(uint8_t data)175 void quizshow_state::tape_control_w(uint8_t data)
176 {
177 	// d2: enable user category select (changes tape head position)
178 	m_lamps[10] = BIT(data, 2);
179 	m_category_enable = (data & 0xc) == 0xc;
180 
181 	// d3: tape motor
182 	// TODO
183 
184 	// d0-d1: unused? (chip is shared with lamps3_w)
185 	// d4-d7: N/C
186 }
187 
audio_w(uint8_t data)188 void quizshow_state::audio_w(uint8_t data)
189 {
190 	// d1: audio out
191 	m_dac->write(BIT(data, 1));
192 
193 	// d0, d2-d7: N/C
194 }
195 
video_disable_w(uint8_t data)196 void quizshow_state::video_disable_w(uint8_t data)
197 {
198 	// d0: video disable (looked glitchy when I implemented it, maybe there's more to it)
199 	// d1-d7: N/C
200 }
201 
timing_r()202 uint8_t quizshow_state::timing_r()
203 {
204 	uint8_t ret = 0x80;
205 
206 	// d0-d3: 1R-8R (16-line counter)
207 	ret |= m_clocks >> 1 & 0xf;
208 
209 	// d4: 8VAC?, use 8V instead
210 	ret |= m_clocks << 4 & 0x10;
211 
212 	// d5-d6: 4F-8F
213 	ret |= m_clocks >> 2 & 0x60;
214 
215 	// d7: display busy/idle, during in-between tilerows(?) and blanking
216 	if (m_screen->vpos() >= VBSTART || (m_screen->vpos() + 4) & 8)
217 		ret &= 0x7f;
218 
219 	return ret;
220 }
221 
READ_LINE_MEMBER(quizshow_state::tape_signal_r)222 READ_LINE_MEMBER(quizshow_state::tape_signal_r)
223 {
224 	// TODO (for now, hold INS to fastforward and it'll show garbage questions where D is always(?) the right answer)
225 	return BIT(machine().rand(), 7); // better than machine().rand() & 1 for some reason
226 }
227 
WRITE_LINE_MEMBER(quizshow_state::flag_output_w)228 WRITE_LINE_MEMBER(quizshow_state::flag_output_w)
229 {
230 	logerror("Flag output: %d\n", state);
231 }
232 
main_ram_w(offs_t offset,uint8_t data)233 void quizshow_state::main_ram_w(offs_t offset, uint8_t data)
234 {
235 	m_main_ram[offset]=data;
236 	m_tilemap->mark_tile_dirty(offset);
237 }
238 
239 
mem_map(address_map & map)240 void quizshow_state::mem_map(address_map &map)
241 {
242 	map.global_mask(0x7fff);
243 	map(0x0000, 0x0bff).rom();
244 	map(0x1802, 0x1802).w(FUNC(quizshow_state::audio_w));
245 	map(0x1804, 0x1804).w(FUNC(quizshow_state::lamps1_w));
246 	map(0x1808, 0x1808).w(FUNC(quizshow_state::lamps2_w));
247 	map(0x1810, 0x1810).w(FUNC(quizshow_state::lamps3_w));
248 	map(0x1820, 0x1820).w(FUNC(quizshow_state::tape_control_w));
249 	map(0x1840, 0x1840).w(FUNC(quizshow_state::video_disable_w));
250 	map(0x1881, 0x1881).portr("IN0");
251 	map(0x1882, 0x1882).portr("IN1");
252 	map(0x1884, 0x1884).portr("IN2");
253 	map(0x1888, 0x1888).portr("IN3");
254 	map(0x1900, 0x1900).r(FUNC(quizshow_state::timing_r));
255 	map(0x1e00, 0x1fff).ram().w(FUNC(quizshow_state::main_ram_w)).share("main_ram");
256 }
257 
258 
259 /***************************************************************************
260 
261   Inputs
262 
263 ***************************************************************************/
264 
CUSTOM_INPUT_MEMBER(quizshow_state::tape_headpos_r)265 CUSTOM_INPUT_MEMBER(quizshow_state::tape_headpos_r)
266 {
267 	return 1 << m_tape_head_pos;
268 }
269 
INPUT_CHANGED_MEMBER(quizshow_state::category_select)270 INPUT_CHANGED_MEMBER(quizshow_state::category_select)
271 {
272 	if (newval)
273 	{
274 		if (m_category_enable)
275 			m_tape_head_pos = (m_tape_head_pos + 1) & 3;
276 	}
277 }
278 
279 static INPUT_PORTS_START( quizshow )
280 	PORT_START("IN0") // ADR strobe 0
281 	PORT_BIT( 0x0f, IP_ACTIVE_HIGH, IPT_CUSTOM ) PORT_CUSTOM_MEMBER(quizshow_state, tape_headpos_r)
282 	PORT_BIT( 0x10, IP_ACTIVE_HIGH, IPT_START1 )
283 	PORT_BIT( 0x20, IP_ACTIVE_HIGH, IPT_START2 )
284 	PORT_BIT( 0x40, IP_ACTIVE_HIGH, IPT_COIN1 )
285 	PORT_BIT( 0x80, IP_ACTIVE_HIGH, IPT_COIN2 )
286 
287 	PORT_START("IN1") // ADR strobe 1
288 	PORT_BIT( 0x01, IP_ACTIVE_HIGH, IPT_BUTTON1 ) PORT_NAME("P1 Answer A")
289 	PORT_BIT( 0x02, IP_ACTIVE_HIGH, IPT_BUTTON2 ) PORT_NAME("P1 Answer B")
290 	PORT_BIT( 0x04, IP_ACTIVE_HIGH, IPT_BUTTON3 ) PORT_NAME("P1 Answer C")
291 	PORT_BIT( 0x08, IP_ACTIVE_HIGH, IPT_BUTTON4 ) PORT_NAME("P1 Answer D")
292 	PORT_BIT( 0x10, IP_ACTIVE_HIGH, IPT_BUTTON1 ) PORT_NAME("P2 Answer A") PORT_PLAYER(2)
293 	PORT_BIT( 0x20, IP_ACTIVE_HIGH, IPT_BUTTON2 ) PORT_NAME("P2 Answer B") PORT_PLAYER(2)
294 	PORT_BIT( 0x40, IP_ACTIVE_HIGH, IPT_BUTTON3 ) PORT_NAME("P2 Answer C") PORT_PLAYER(2)
295 	PORT_BIT( 0x80, IP_ACTIVE_HIGH, IPT_BUTTON4 ) PORT_NAME("P2 Answer D") PORT_PLAYER(2)
296 
297 	PORT_START("IN2") // ADR strobe 2
298 	PORT_DIPNAME( 0x0f, 0x05, "Game Duration" )         PORT_DIPLOCATION("SW3:4,3,2,1")
299 	PORT_DIPSETTING( 0x00, "50 sec. / 5 questions" )
300 	PORT_DIPSETTING( 0x01, "60 sec. / 6 questions" )
301 	PORT_DIPSETTING( 0x02, "70 sec. / 7 questions" )
302 	PORT_DIPSETTING( 0x03, "80 sec. / 8 questions" )
303 	PORT_DIPSETTING( 0x04, "90 sec. / 9 questions" )
304 	PORT_DIPSETTING( 0x05, "100 sec. / 10 questions" )
305 	PORT_DIPSETTING( 0x06, "110 sec. / 11 questions" )
306 	PORT_DIPSETTING( 0x07, "120 sec. / 12 questions" )
307 	PORT_DIPSETTING( 0x08, "130 sec. / 13 questions" )
308 	PORT_DIPSETTING( 0x09, "140 sec. / 14 questions" )
309 	PORT_DIPSETTING( 0x0a, "150 sec. / 15 questions" ) // not listed in manual
310 	PORT_DIPSETTING( 0x0b, "160 sec. / 16 questions" ) // "
311 	PORT_DIPSETTING( 0x0c, "170 sec. / 17 questions" ) // "
312 	PORT_DIPSETTING( 0x0d, "180 sec. / 18 questions" ) // "
313 	PORT_DIPSETTING( 0x0e, "190 sec. / 19 questions" ) // "
314 	PORT_DIPSETTING( 0x0f, "200 sec. / 20 questions" ) // "
315 
316 	PORT_DIPNAME( 0x10, 0x00, DEF_STR( Coinage ) )      PORT_DIPLOCATION("SW1:4")
317 	PORT_DIPSETTING( 0x00, DEF_STR( 1C_1C ) )
318 	PORT_DIPSETTING( 0x10, DEF_STR( 1C_2C ) )
319 	PORT_DIPNAME( 0x20, 0x00, "Duration Mode" )         PORT_DIPLOCATION("SW1:3")
320 	PORT_DIPSETTING(    0x00, "Question Count" )
321 	PORT_DIPSETTING(    0x20, "Timed" )
322 	PORT_BIT( 0x40, IP_ACTIVE_HIGH, IPT_UNKNOWN )
323 	PORT_BIT( 0x80, IP_ACTIVE_HIGH, IPT_UNUSED ) // N/C
324 
325 	PORT_START("IN3") // ADR strobe 3
326 	PORT_DIPNAME( 0x0f, 0x05, "Bonus Questions" )       PORT_DIPLOCATION("SW2:4,3,2,1")
327 	PORT_DIPSETTING( 0x00, "0" )
328 	PORT_DIPSETTING( 0x01, "1" )
329 	PORT_DIPSETTING( 0x02, "2" )
330 	PORT_DIPSETTING( 0x03, "3" )
331 	PORT_DIPSETTING( 0x04, "4" )
332 	PORT_DIPSETTING( 0x05, "5" )
333 	PORT_DIPSETTING( 0x06, "6" )
334 	PORT_DIPSETTING( 0x07, "7" )
335 	PORT_DIPSETTING( 0x08, "8" )
336 	PORT_DIPSETTING( 0x09, "9" )
337 	PORT_DIPSETTING( 0x0a, "10" ) // not listed in manual
338 	PORT_DIPSETTING( 0x0b, "11" ) // "
339 	PORT_DIPSETTING( 0x0c, "12" ) // "
340 	PORT_DIPSETTING( 0x0d, "13" ) // "
341 	PORT_DIPSETTING( 0x0e, "14" ) // "
342 	PORT_DIPSETTING( 0x0f, "15" ) // "
343 	PORT_BIT( 0xf0, IP_ACTIVE_HIGH, IPT_UNUSED )
344 
345 	PORT_START("CAT")
346 	PORT_BIT( 0x01, IP_ACTIVE_HIGH, IPT_BUTTON5 ) PORT_NAME("Category Select") PORT_CHANGED_MEMBER(DEVICE_SELF, quizshow_state, category_select, 0)
347 
348 INPUT_PORTS_END
349 
350 
351 /***************************************************************************
352 
353   Machine Config
354 
355 ***************************************************************************/
356 
357 static const gfx_layout tile_layout =
358 {
359 	8, 16,
360 	RGN_FRAC(1,2),
361 	2,
362 	{ RGN_FRAC(0,2), RGN_FRAC(1,2) },
363 	{ 0, 1, 2, 3, 4, 5, 6, 7 },
364 	{
365 		0*8, 1*8, 2*8, 3*8, 4*8, 5*8, 6*8, 7*8,
366 		8*8, 9*8,10*8,11*8,12*8,13*8,14*8,15*8,
367 	},
368 	8*16
369 };
370 
371 static GFXDECODE_START( gfx_quizshow )
372 	GFXDECODE_ENTRY( "gfx1", 0, tile_layout, 0, 4 )
373 GFXDECODE_END
374 
375 
TIMER_DEVICE_CALLBACK_MEMBER(quizshow_state::clock_timer_cb)376 TIMER_DEVICE_CALLBACK_MEMBER(quizshow_state::clock_timer_cb)
377 {
378 	m_clocks++;
379 
380 	// blink is on 4F and 8F
381 	int blink_old = m_blink_state;
382 	m_blink_state = (m_clocks >> 2 & m_clocks >> 1) & 0x40;
383 	if (m_blink_state != blink_old)
384 		m_tilemap->mark_all_dirty();
385 }
386 
machine_reset()387 void quizshow_state::machine_reset()
388 {
389 	m_category_enable = 0;
390 	m_tape_head_pos = 0;
391 }
392 
quizshow(machine_config & config)393 void quizshow_state::quizshow(machine_config &config)
394 {
395 	/* basic machine hardware */
396 	S2650(config, m_maincpu, MASTER_CLOCK / 16); // divider guessed
397 	m_maincpu->set_addrmap(AS_PROGRAM, &quizshow_state::mem_map);
398 	m_maincpu->sense_handler().set(FUNC(quizshow_state::tape_signal_r));
399 	m_maincpu->flag_handler().set(FUNC(quizshow_state::flag_output_w));
400 
401 	TIMER(config, "clock_timer").configure_periodic(FUNC(quizshow_state::clock_timer_cb), attotime::from_hz(PIXEL_CLOCK / (HTOTAL * 8))); // 8V
402 
403 	/* video hardware */
404 	SCREEN(config, m_screen, SCREEN_TYPE_RASTER);
405 	m_screen->set_raw(PIXEL_CLOCK, HTOTAL, HBEND, HBSTART, VTOTAL, VBEND, VBSTART);
406 	m_screen->set_screen_update(FUNC(quizshow_state::screen_update));
407 	m_screen->set_palette("palette");
408 
409 	GFXDECODE(config, m_gfxdecode, m_palette, gfx_quizshow);
410 	PALETTE(config, m_palette, FUNC(quizshow_state::quizshow_palette), 8*2, 2);
411 
412 	/* sound hardware (discrete) */
413 	SPEAKER(config, "speaker").front_center();
414 
415 	DAC_1BIT(config, m_dac, 0).add_route(ALL_OUTPUTS, "speaker", 0.25);
416 }
417 
418 
419 /***************************************************************************
420 
421   Game drivers
422 
423 ***************************************************************************/
424 
425 ROM_START( quizshow )
426 	ROM_REGION( 0x1000, "maincpu", 0 )
CRC(c9da809a)427 	ROM_LOAD( "005464-01.a1", 0x00000, 0x0200, CRC(c9da809a) SHA1(0d16e552398069a4389c34cc9fb6dcc89eb05b9b) )
428 	ROM_LOAD( "005464-02.c1", 0x00200, 0x0200, CRC(42237134) SHA1(2932d4820f6c9a383cb5a4e504e043e2d479d474) )
429 	ROM_LOAD( "005464-03.d1", 0x00400, 0x0200, CRC(0c58fee9) SHA1(c7b081bc4f274a29eb758c8758877b15c9e54d79) )
430 	ROM_LOAD( "005464-04.f1", 0x00600, 0x0200, CRC(4c6cffd4) SHA1(c291d0fa140faa78b807af72c677d53c620b3103) )
431 	ROM_LOAD( "005464-05.h1", 0x00800, 0x0200, CRC(b8d61b96) SHA1(eb437a5deaf2fc2a9acebbc506321f3151b4eafa) )
432 	ROM_LOAD( "005464-06.k1", 0x00a00, 0x0200, CRC(200023b2) SHA1(271d0b2b2f985a6c7b7146869ed00990a52dd653) )
433 
434 	ROM_REGION( 0x0800, "gfx1", ROMREGION_ERASEFF )
435 
436 	ROM_REGION( 0x0200, "user1", 0 ) // gfx1
437 	ROM_LOAD_NIB_HIGH( "005466-01.m2", 0x0000, 0x0200, CRC(03017820) SHA1(fd118aa706bdc6976e527ed63388fad01e66270e) )
438 	ROM_LOAD_NIB_LOW ( "005466-02.n2", 0x0000, 0x0200, CRC(cd554367) SHA1(04da83eb6e2f86f88a3495072b98fbdaca485ae8) )
439 
440 	ROM_REGION( 0x0200, "proms", 0 )
441 	ROM_LOAD( "005465-01.f2", 0x0000, 0x0200, CRC(0fe46552) SHA1(d79b1ff0abfaba1ef2d564d1166c3696e0a1a3f1) ) // memory timing
442 ROM_END
443 
444 
445 void quizshow_state::init_quizshow()
446 {
447 	uint8_t *gfxdata = memregion("user1")->base();
448 	uint8_t *dest = memregion("gfx1")->base();
449 
450 	// convert gfx data to 8*16(actually 8*12), and 2bpp for masking inverted colors
451 	for (int tile = 0; tile < 0x40; tile++)
452 	{
453 		for (int line = 2; line < 14; line ++)
454 		{
455 			dest[tile << 4 | line] = 0;
456 			dest[tile << 4 | line | 0x400] = 0;
457 
458 			if (line >= 4 && line < 12)
459 				dest[tile << 4 | line] = gfxdata[(tile ^ 0x3f) << 3 | (line - 4)];
460 		}
461 	}
462 }
463 
464 
465 GAMEL( 1976, quizshow, 0, quizshow, quizshow, quizshow_state, init_quizshow, ROT0, "Atari (Kee Games)", "Quiz Show", MACHINE_NOT_WORKING, layout_quizshow )
466