badline 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +167 -0
- data/CLAUDE.md +217 -0
- data/CODE_OF_CONDUCT.md +92 -0
- data/CONTRIBUTING.md +51 -0
- data/README.md +224 -31
- data/doc/pinned-behaviour.md +1117 -0
- data/exe/badline +22 -2
- data/exe/badline-sid +34 -0
- data/lib/badline/address_bus.rb +88 -23
- data/lib/badline/audio/aiff.rb +36 -0
- data/lib/badline/audio/bare_player.rb +129 -0
- data/lib/badline/audio/cli.rb +133 -0
- data/lib/badline/audio/console.rb +75 -0
- data/lib/badline/audio/jukebox.rb +82 -0
- data/lib/badline/audio/machine_player.rb +57 -0
- data/lib/badline/audio/options.rb +137 -0
- data/lib/badline/audio/pcm_writer.rb +57 -0
- data/lib/badline/audio/playback.rb +108 -0
- data/lib/badline/audio/renderer.rb +85 -0
- data/lib/badline/audio/sdl_sink.rb +102 -0
- data/lib/badline/audio/wav.rb +23 -0
- data/lib/badline/audio.rb +14 -0
- data/lib/badline/cartridge/action_replay.rb +111 -0
- data/lib/badline/cartridge/atomic_power.rb +24 -0
- data/lib/badline/cartridge/bank.rb +81 -0
- data/lib/badline/cartridge/comal80.rb +48 -0
- data/lib/badline/cartridge/dinamic.rb +34 -0
- data/lib/badline/cartridge/easy_flash.rb +122 -0
- data/lib/badline/cartridge/epyx_fastload.rb +71 -0
- data/lib/badline/cartridge/final_cartridge3.rb +77 -0
- data/lib/badline/cartridge/flash.rb +265 -0
- data/lib/badline/cartridge/freezer.rb +31 -0
- data/lib/badline/cartridge/fun_play.rb +38 -0
- data/lib/badline/cartridge/g_mod2.rb +62 -0
- data/lib/badline/cartridge/game_system.rb +40 -0
- data/lib/badline/cartridge/kcs_power.rb +72 -0
- data/lib/badline/cartridge/mach5.rb +34 -0
- data/lib/badline/cartridge/magic_desk.rb +6 -3
- data/lib/badline/cartridge/ocean.rb +11 -12
- data/lib/badline/cartridge/pagefox.rb +56 -0
- data/lib/badline/cartridge/retro_replay.rb +276 -0
- data/lib/badline/cartridge/rex_utility.rb +33 -0
- data/lib/badline/cartridge/rgcd.rb +48 -0
- data/lib/badline/cartridge/simons_basic.rb +40 -0
- data/lib/badline/cartridge/standard.rb +3 -19
- data/lib/badline/cartridge/super_games.rb +36 -0
- data/lib/badline/cartridge/westermann.rb +34 -0
- data/lib/badline/cartridge/zaxxon.rb +50 -0
- data/lib/badline/cartridge.rb +131 -15
- data/lib/badline/cia/interrupt_register.rb +88 -0
- data/lib/badline/cia/serial.rb +219 -0
- data/lib/badline/cia/timer.rb +43 -31
- data/lib/badline/cia.rb +109 -74
- data/lib/badline/color_memory.rb +14 -5
- data/lib/badline/computer.rb +68 -8
- data/lib/badline/control_ports.rb +54 -9
- data/lib/badline/cpu/addressing.rb +131 -0
- data/lib/badline/cpu/microcode.rb +171 -0
- data/lib/badline/cpu/operations.rb +88 -0
- data/lib/badline/cpu/stack_operations.rb +82 -0
- data/lib/badline/cpu.rb +108 -167
- data/lib/badline/cycleable.rb +3 -0
- data/lib/badline/datasette.rb +81 -0
- data/lib/badline/debug_register.rb +28 -0
- data/lib/badline/gui/application.rb +79 -18
- data/lib/badline/gui/gamepads.rb +97 -0
- data/lib/badline/gui/joy_map.rb +15 -7
- data/lib/badline/gui.rb +1 -0
- data/lib/badline/input/mouse1351.rb +41 -0
- data/lib/badline/input/paddles.rb +36 -0
- data/lib/badline/input.rb +4 -0
- data/lib/badline/instruction_set/arithmetic.rb +16 -21
- data/lib/badline/instruction_set/bitwise.rb +17 -22
- data/lib/badline/instruction_set/branch.rb +20 -19
- data/lib/badline/instruction_set/flag.rb +7 -7
- data/lib/badline/instruction_set/illegal.rb +14 -22
- data/lib/badline/instruction_set/inc_dec.rb +8 -10
- data/lib/badline/instruction_set/stack.rb +5 -57
- data/lib/badline/instruction_set/transfer.rb +9 -9
- data/lib/badline/instruction_set.rb +9 -31
- data/lib/badline/interrupts.rb +104 -0
- data/lib/badline/joystick.rb +9 -9
- data/lib/badline/kernal_trap/channel.rb +33 -0
- data/lib/badline/kernal_trap/drive.rb +239 -0
- data/lib/badline/kernal_trap/file.rb +4 -24
- data/lib/badline/kernal_trap/load.rb +2 -2
- data/lib/badline/kernal_trap/routine.rb +34 -0
- data/lib/badline/kernal_trap/save.rb +1 -1
- data/lib/badline/kernal_trap/serial.rb +139 -0
- data/lib/badline/kernal_trap.rb +4 -0
- data/lib/badline/keyboard.rb +40 -16
- data/lib/badline/media.rb +48 -10
- data/lib/badline/roms/eapi/LICENSE.md +15 -0
- data/lib/badline/roms/eapi/README +41 -0
- data/lib/badline/roms/eapi/eapi-am29f040-14 +0 -0
- data/lib/badline/sid/decimator.rb +46 -0
- data/lib/badline/sid/envelope.rb +232 -0
- data/lib/badline/sid/filter.rb +213 -0
- data/lib/badline/sid/voice.rb +61 -0
- data/lib/badline/sid/waveform/combined.rb +126 -0
- data/lib/badline/sid/waveform/fast_forward.rb +105 -0
- data/lib/badline/sid/waveform/noise_writeback.rb +82 -0
- data/lib/badline/sid/waveform.rb +289 -0
- data/lib/badline/sid.rb +274 -7
- data/lib/badline/status.rb +32 -27
- data/lib/badline/storage/crt_file.rb +18 -2
- data/lib/badline/storage/disk_image.rb +52 -5
- data/lib/badline/storage/host_directory.rb +18 -4
- data/lib/badline/storage/sid_file.rb +274 -0
- data/lib/badline/storage/song_lengths.rb +65 -0
- data/lib/badline/storage/t64.rb +67 -0
- data/lib/badline/storage/tap.rb +68 -0
- data/lib/badline/storage.rb +19 -0
- data/lib/badline/time_of_day.rb +105 -52
- data/lib/badline/version.rb +1 -1
- data/lib/badline/vic/bank.rb +14 -6
- data/lib/badline/vic/border_mask.rb +76 -0
- data/lib/badline/vic/collisions.rb +104 -0
- data/lib/badline/vic/color_patches.rb +33 -0
- data/lib/badline/vic/display_state.rb +92 -29
- data/lib/badline/vic/graphics_mode.rb +78 -53
- data/lib/badline/vic/graphics_shifter.rb +140 -0
- data/lib/badline/vic/register_log.rb +63 -0
- data/lib/badline/vic/registers.rb +3 -0
- data/lib/badline/vic/sequencer.rb +163 -137
- data/lib/badline/vic/sequencer_output.rb +116 -0
- data/lib/badline/vic/sprite/internal_bus.rb +49 -0
- data/lib/badline/vic/sprite/shifter.rb +129 -0
- data/lib/badline/vic/sprite.rb +275 -65
- data/lib/badline/vic/sprites.rb +220 -58
- data/lib/badline/vic.rb +373 -49
- data/lib/badline.rb +5 -0
- data/vendor/.gitkeep +0 -0
- metadata +103 -4
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
# The 1530 datasette. Plays a pulse stream into CIA 1's FLAG pin while the
|
|
5
|
+
# motor runs with a key pressed, and reports the keys through the cassette
|
|
6
|
+
# sense line on the $01 port.
|
|
7
|
+
class Datasette
|
|
8
|
+
attr_reader :tape
|
|
9
|
+
attr_writer :motor
|
|
10
|
+
|
|
11
|
+
def initialize(tape = nil)
|
|
12
|
+
@tape = tape
|
|
13
|
+
@playing = false
|
|
14
|
+
@motor = false
|
|
15
|
+
@countdown = 0
|
|
16
|
+
@flag_handler = nil
|
|
17
|
+
@sense_handler = nil
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Falling edges on the tape read line.
|
|
21
|
+
def on_flag(&handler)
|
|
22
|
+
@flag_handler = handler
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# The cassette sense line, which the $01 port reads back.
|
|
26
|
+
def on_sense_change(&handler)
|
|
27
|
+
@sense_handler = handler
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def insert(tape)
|
|
31
|
+
@tape = tape
|
|
32
|
+
rewind
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def eject
|
|
36
|
+
stop!
|
|
37
|
+
@tape = nil
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def rewind
|
|
41
|
+
@tape&.rewind
|
|
42
|
+
@countdown = 0
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def play! = press(true)
|
|
46
|
+
def stop! = press(false)
|
|
47
|
+
|
|
48
|
+
def playing? = @playing
|
|
49
|
+
|
|
50
|
+
# Sense reads low while a key is down on the deck.
|
|
51
|
+
def sense_low? = @playing
|
|
52
|
+
|
|
53
|
+
def motor? = @motor
|
|
54
|
+
|
|
55
|
+
def running? = @playing && @motor && !@tape.nil?
|
|
56
|
+
|
|
57
|
+
def cycle!
|
|
58
|
+
return unless running?
|
|
59
|
+
|
|
60
|
+
@countdown = @tape.next_pulse.to_i if @countdown.zero?
|
|
61
|
+
return if @countdown.zero?
|
|
62
|
+
|
|
63
|
+
@countdown -= 1
|
|
64
|
+
@flag_handler&.call if @countdown.zero?
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def inspect
|
|
68
|
+
"#<#{self.class.name} tape=#{@tape ? @tape.class.name : 'none'} " \
|
|
69
|
+
"playing=#{@playing} motor=#{@motor}>"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
private
|
|
73
|
+
|
|
74
|
+
def press(down)
|
|
75
|
+
return if down == @playing
|
|
76
|
+
|
|
77
|
+
@playing = down
|
|
78
|
+
@sense_handler&.call
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
# The VICE debug cartridge register, mapped at $D7FF on the C64. It is a
|
|
5
|
+
# VICE facility rather than real hardware, enabled there with -debugcart:
|
|
6
|
+
# writing a byte ends the run with that value as the exit code. The VICE
|
|
7
|
+
# test suites write $00 for success and $ff for failure.
|
|
8
|
+
#
|
|
9
|
+
# Wraps the SID's top mirror page, so every other access falls through.
|
|
10
|
+
class DebugRegister
|
|
11
|
+
ADDRESS = 0xd7ff
|
|
12
|
+
|
|
13
|
+
def initialize(sid, &handler)
|
|
14
|
+
@sid = sid
|
|
15
|
+
@handler = handler
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def peek(addr)
|
|
19
|
+
@sid.peek(addr)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def poke(addr, value)
|
|
23
|
+
return @handler.call(value) if addr == ADDRESS
|
|
24
|
+
|
|
25
|
+
@sid.poke(addr, value)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -6,20 +6,42 @@ module Badline
|
|
|
6
6
|
PAL_CLOCK_HZ = 985_248
|
|
7
7
|
TITLE = "Badline"
|
|
8
8
|
TOGGLE_SYM = SDL2::Key::TAB
|
|
9
|
+
REVERSE_MOD = SDL2::Key::Mod::SHIFT
|
|
9
10
|
|
|
10
|
-
SHARED_KEYS = %i[up left cursor_h cursor_v space].freeze
|
|
11
|
+
SHARED_KEYS = %i[up left cursor_h cursor_v space w a s d lshift].freeze
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
# Tab steps through the input modes and shift-Tab back; the title bar
|
|
14
|
+
# names the live one. The pot devices appear once per control port, since
|
|
15
|
+
# games disagree on which one they read.
|
|
16
|
+
MODES = {
|
|
17
|
+
keyboard: nil, joystick: "JOY",
|
|
18
|
+
mouse1: "MOUSE 1", mouse2: "MOUSE 2",
|
|
19
|
+
paddles1: "PADDLE 1", paddles2: "PADDLE 2"
|
|
20
|
+
}.freeze
|
|
15
21
|
|
|
16
|
-
|
|
22
|
+
# Both pot devices take their input from the host mouse. Motion turns the
|
|
23
|
+
# paddle knobs or steps the 1351's counters, and the host buttons go to
|
|
24
|
+
# whichever lines the device puts them on.
|
|
25
|
+
POT_DEVICES = {
|
|
26
|
+
mouse1: [Input::Mouse1351, 1], mouse2: [Input::Mouse1351, 2],
|
|
27
|
+
paddles1: [Input::Paddles, 1], paddles2: [Input::Paddles, 2]
|
|
28
|
+
}.freeze
|
|
29
|
+
MOUSE_BUTTONS = { 1 => :left, 3 => :right }.freeze
|
|
30
|
+
|
|
31
|
+
def initialize(media_path: nil, autostart: true, song: nil, sid_model: nil, debug: false)
|
|
32
|
+
@computer = Computer.new(debug:, sid_model: sid_model || Media.sid_model(media_path))
|
|
33
|
+
puts Media.attach(@computer, media_path, autostart:, song:) if media_path
|
|
34
|
+
|
|
35
|
+
@mode = :keyboard
|
|
36
|
+
@pot_device = nil
|
|
17
37
|
@panes = [ScreenPane.new(@computer)]
|
|
18
38
|
@window = Window.new(
|
|
19
39
|
title: TITLE,
|
|
20
40
|
width: canvas_width, height: canvas_height,
|
|
21
41
|
vsync: ENV["NOVSYNC"].nil?
|
|
22
42
|
)
|
|
43
|
+
@gamepads = Gamepads.new(@computer)
|
|
44
|
+
@gamepads.names.each { |name| puts "Gamepad: #{name}" }
|
|
23
45
|
|
|
24
46
|
rate = @window.refresh_rate
|
|
25
47
|
@cycles_per_frame = PAL_CLOCK_HZ / rate
|
|
@@ -30,10 +52,12 @@ module Badline
|
|
|
30
52
|
@running = true
|
|
31
53
|
while @running
|
|
32
54
|
handle_events
|
|
55
|
+
@gamepads.poll
|
|
33
56
|
@cycles_per_frame.times { @computer.cycle! }
|
|
34
57
|
@window.draw(@panes)
|
|
35
58
|
end
|
|
36
59
|
ensure
|
|
60
|
+
@gamepads.close
|
|
37
61
|
puts @computer.cpu.inspect
|
|
38
62
|
end
|
|
39
63
|
|
|
@@ -48,15 +72,22 @@ module Badline
|
|
|
48
72
|
handle_key_down(event)
|
|
49
73
|
when SDL2::Event::KeyUp
|
|
50
74
|
handle_key_up(event)
|
|
75
|
+
when SDL2::Event::MouseMotion
|
|
76
|
+
@pot_device&.move(event.xrel, event.yrel)
|
|
77
|
+
when SDL2::Event::MouseButton
|
|
78
|
+
handle_mouse_button(event)
|
|
79
|
+
when SDL2::Event::ControllerDevice
|
|
80
|
+
@gamepads.rescan
|
|
51
81
|
end
|
|
52
82
|
end
|
|
53
83
|
end
|
|
54
84
|
|
|
55
85
|
def handle_key_down(event)
|
|
56
|
-
if event.sym == TOGGLE_SYM
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
86
|
+
return cycle_mode(event.mod.anybits?(REVERSE_MOD) ? -1 : 1) if event.sym == TOGGLE_SYM
|
|
87
|
+
|
|
88
|
+
port, dir = JoyMap.parse(event) if @mode == :joystick
|
|
89
|
+
if port
|
|
90
|
+
joystick(port).press(dir)
|
|
60
91
|
else
|
|
61
92
|
@computer.keyboard.press(KeyMap.parse(event))
|
|
62
93
|
end
|
|
@@ -65,21 +96,51 @@ module Badline
|
|
|
65
96
|
def handle_key_up(event)
|
|
66
97
|
return if event.sym == TOGGLE_SYM
|
|
67
98
|
|
|
68
|
-
|
|
69
|
-
|
|
99
|
+
port, dir = JoyMap.parse(event) if @mode == :joystick
|
|
100
|
+
if port
|
|
101
|
+
joystick(port).release(dir)
|
|
70
102
|
else
|
|
71
103
|
@computer.keyboard.release(KeyMap.parse(event))
|
|
72
104
|
end
|
|
73
105
|
end
|
|
74
106
|
|
|
75
|
-
def
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
107
|
+
def handle_mouse_button(event)
|
|
108
|
+
button = MOUSE_BUTTONS[event.button]
|
|
109
|
+
return unless button && @pot_device
|
|
110
|
+
|
|
111
|
+
case event
|
|
112
|
+
when SDL2::Event::MouseButtonDown then @pot_device.press(button)
|
|
113
|
+
when SDL2::Event::MouseButtonUp then @pot_device.release(button)
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def joystick(port)
|
|
118
|
+
port == 1 ? @computer.joystick1 : @computer.joystick2
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def cycle_mode(step)
|
|
122
|
+
modes = MODES.keys
|
|
123
|
+
@mode = modes[(modes.index(@mode) + step) % modes.size]
|
|
124
|
+
release_inputs
|
|
125
|
+
attach_pot_device
|
|
126
|
+
@window.title = [TITLE, MODES[@mode] && "[#{MODES[@mode]}]"].compact.join(" ")
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def attach_pot_device
|
|
130
|
+
device_class, port = POT_DEVICES[@mode]
|
|
131
|
+
@pot_device = device_class&.new
|
|
132
|
+
ports = @computer.control_ports
|
|
133
|
+
ports.device1 = port == 1 ? @pot_device : nil
|
|
134
|
+
ports.device2 = port == 2 ? @pot_device : nil
|
|
135
|
+
SDL2::Mouse.relative_mode = !@pot_device.nil?
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def release_inputs
|
|
139
|
+
SHARED_KEYS.each { |key| @computer.keyboard.release(key) }
|
|
140
|
+
Joystick::DIRECTIONS.each_key do |dir|
|
|
141
|
+
@computer.joystick1.release(dir)
|
|
142
|
+
@computer.joystick2.release(dir)
|
|
81
143
|
end
|
|
82
|
-
@window.title = @joystick_mode ? "#{TITLE} [JOY]" : TITLE
|
|
83
144
|
end
|
|
84
145
|
|
|
85
146
|
def canvas_width
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module GUI
|
|
5
|
+
# Host game controllers wired onto the control ports.
|
|
6
|
+
#
|
|
7
|
+
# The first controller found drives joystick 2, the port most games read;
|
|
8
|
+
# a second one drives joystick 1. D-pad and left stick both steer, every
|
|
9
|
+
# face and shoulder button fires.
|
|
10
|
+
#
|
|
11
|
+
# SDL announces arrivals by device index but reports button presses by
|
|
12
|
+
# instance id, and the binding gives no way to line the two up, so this
|
|
13
|
+
# class polls the controllers every frame. It only releases directions it
|
|
14
|
+
# pressed itself, so the keyboard mapping keeps working with a pad
|
|
15
|
+
# connected.
|
|
16
|
+
class Gamepads
|
|
17
|
+
DEADZONE = 8_000
|
|
18
|
+
PORTS = [2, 1].freeze
|
|
19
|
+
|
|
20
|
+
BUTTONS = {
|
|
21
|
+
up: [SDL2::GameController::Button::DPAD_UP],
|
|
22
|
+
down: [SDL2::GameController::Button::DPAD_DOWN],
|
|
23
|
+
left: [SDL2::GameController::Button::DPAD_LEFT],
|
|
24
|
+
right: [SDL2::GameController::Button::DPAD_RIGHT],
|
|
25
|
+
fire: [SDL2::GameController::Button::A, SDL2::GameController::Button::B,
|
|
26
|
+
SDL2::GameController::Button::X, SDL2::GameController::Button::Y,
|
|
27
|
+
SDL2::GameController::Button::LEFTSHOULDER,
|
|
28
|
+
SDL2::GameController::Button::RIGHTSHOULDER]
|
|
29
|
+
}.freeze
|
|
30
|
+
|
|
31
|
+
AXES = {
|
|
32
|
+
up: [SDL2::GameController::Axis::LEFTY, -1],
|
|
33
|
+
down: [SDL2::GameController::Axis::LEFTY, 1],
|
|
34
|
+
left: [SDL2::GameController::Axis::LEFTX, -1],
|
|
35
|
+
right: [SDL2::GameController::Axis::LEFTX, 1]
|
|
36
|
+
}.freeze
|
|
37
|
+
|
|
38
|
+
def initialize(computer)
|
|
39
|
+
SDL2.init(SDL2::INIT_GAMECONTROLLER)
|
|
40
|
+
@computer = computer
|
|
41
|
+
@controllers = []
|
|
42
|
+
@pressed = Array.new(PORTS.size) { [] }
|
|
43
|
+
rescan
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def names = @controllers.map(&:name)
|
|
47
|
+
|
|
48
|
+
def rescan
|
|
49
|
+
close
|
|
50
|
+
@controllers = (0...SDL2::Joystick.num_connected_joysticks)
|
|
51
|
+
.select { |index| SDL2::Joystick.game_controller?(index) }
|
|
52
|
+
.first(PORTS.size)
|
|
53
|
+
.map { |index| SDL2::GameController.open(index) }
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def poll
|
|
57
|
+
@controllers.each_with_index { |controller, index| apply(controller, index) }
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def close
|
|
61
|
+
@controllers.each { |controller| controller.destroy unless controller.destroy? }
|
|
62
|
+
@controllers = []
|
|
63
|
+
@pressed.size.times { |index| release(index) }
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
|
|
68
|
+
def apply(controller, index)
|
|
69
|
+
active = Joystick::DIRECTIONS.keys.select { |dir| active?(controller, dir) }
|
|
70
|
+
(@pressed[index] - active).each { |dir| joystick(index).release(dir) }
|
|
71
|
+
active.each { |dir| joystick(index).press(dir) }
|
|
72
|
+
@pressed[index] = active
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def release(index)
|
|
76
|
+
@pressed[index].each { |dir| joystick(index).release(dir) }
|
|
77
|
+
@pressed[index] = []
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def active?(controller, direction)
|
|
81
|
+
BUTTONS.fetch(direction).any? { |button| controller.button_pressed?(button) } ||
|
|
82
|
+
tilted?(controller, direction)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def tilted?(controller, direction)
|
|
86
|
+
axis, sign = AXES[direction]
|
|
87
|
+
return false unless axis
|
|
88
|
+
|
|
89
|
+
controller.axis(axis) * sign > DEADZONE
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def joystick(index)
|
|
93
|
+
PORTS[index] == 1 ? @computer.joystick1 : @computer.joystick2
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
data/lib/badline/gui/joy_map.rb
CHANGED
|
@@ -2,17 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
module Badline
|
|
4
4
|
module GUI
|
|
5
|
+
# Maps host keys onto the two control ports while joystick mode is on.
|
|
6
|
+
#
|
|
7
|
+
# The arrow cluster plus space drives joystick 2, WASD plus left shift
|
|
8
|
+
# joystick 1, so both ports can be played at once.
|
|
5
9
|
class JoyMap
|
|
6
|
-
|
|
7
|
-
"Up" => :up,
|
|
8
|
-
"
|
|
9
|
-
"Left" => :left,
|
|
10
|
-
"Right" => :right,
|
|
11
|
-
"Space" => :fire
|
|
10
|
+
PORTS = {
|
|
11
|
+
2 => { "Up" => :up, "Down" => :down, "Left" => :left, "Right" => :right, "Space" => :fire }.freeze,
|
|
12
|
+
1 => { "W" => :up, "S" => :down, "A" => :left, "D" => :right, "Left Shift" => :fire }.freeze
|
|
12
13
|
}.freeze
|
|
13
14
|
|
|
14
15
|
def self.parse(event)
|
|
15
|
-
|
|
16
|
+
name = SDL2::Key.name_of(event.sym)
|
|
17
|
+
|
|
18
|
+
PORTS.each do |port, map|
|
|
19
|
+
direction = map[name]
|
|
20
|
+
return [port, direction] if direction
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
nil
|
|
16
24
|
end
|
|
17
25
|
end
|
|
18
26
|
end
|
data/lib/badline/gui.rb
CHANGED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Input
|
|
5
|
+
# Commodore 1351 mouse in proportional mode.
|
|
6
|
+
#
|
|
7
|
+
# Each axis drives a 6-bit counter that the mouse presents on POTX/POTY
|
|
8
|
+
# shifted up one bit. A driver recovers movement by masking off bit 7,
|
|
9
|
+
# subtracting the previous reading and halving the difference. The counters
|
|
10
|
+
# wrap, so a single reading says nothing on its own, and the driver has to
|
|
11
|
+
# sample before the mouse travels 32 counts.
|
|
12
|
+
#
|
|
13
|
+
# #move takes host deltas with y counting down the screen; the counter
|
|
14
|
+
# counts the other way. The left button sits on the joystick fire line, the
|
|
15
|
+
# right button on the up line.
|
|
16
|
+
class Mouse1351
|
|
17
|
+
BUTTONS = { left: :fire, right: :up }.freeze
|
|
18
|
+
|
|
19
|
+
def initialize
|
|
20
|
+
@lines = Joystick.new
|
|
21
|
+
@x = 0
|
|
22
|
+
@y = 0
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def press(button) = @lines.press(BUTTONS[button])
|
|
26
|
+
|
|
27
|
+
def release(button) = @lines.release(BUTTONS[button])
|
|
28
|
+
|
|
29
|
+
def port_bits = @lines.port_bits
|
|
30
|
+
|
|
31
|
+
def move(delta_x, delta_y)
|
|
32
|
+
@x = (@x + delta_x) & 0x3f
|
|
33
|
+
@y = (@y - delta_y) & 0x3f
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def pot_x = @x << 1
|
|
37
|
+
|
|
38
|
+
def pot_y = @y << 1
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Input
|
|
5
|
+
# A pair of paddles on one control port.
|
|
6
|
+
#
|
|
7
|
+
# Paddle A sits on POTX with its button on the joystick left line, paddle B
|
|
8
|
+
# on POTY with its button on the right line. Positions are the 0-255 counts
|
|
9
|
+
# SID reads back, and the knobs stop at either end.
|
|
10
|
+
#
|
|
11
|
+
# Buttons are named after the host mouse buttons, as on the 1351: the left
|
|
12
|
+
# one fires paddle A, the right one paddle B.
|
|
13
|
+
class Paddles
|
|
14
|
+
BUTTONS = { left: :left, right: :right }.freeze
|
|
15
|
+
|
|
16
|
+
attr_reader :pot_x, :pot_y
|
|
17
|
+
|
|
18
|
+
def initialize
|
|
19
|
+
@lines = Joystick.new
|
|
20
|
+
@pot_x = 0x80
|
|
21
|
+
@pot_y = 0x80
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def press(button) = @lines.press(BUTTONS[button])
|
|
25
|
+
|
|
26
|
+
def release(button) = @lines.release(BUTTONS[button])
|
|
27
|
+
|
|
28
|
+
def port_bits = @lines.port_bits
|
|
29
|
+
|
|
30
|
+
def move(turn_a, turn_b)
|
|
31
|
+
@pot_x = (@pot_x + turn_a).clamp(0, 0xff)
|
|
32
|
+
@pot_y = (@pot_y + turn_b).clamp(0, 0xff)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -15,19 +15,18 @@ module Badline
|
|
|
15
15
|
# $79 - absolute_y - 4+ cycles
|
|
16
16
|
# $7D - absolute_x - 4+ cycles
|
|
17
17
|
def adc(_addr, value)
|
|
18
|
-
|
|
19
|
-
result = a + v + status.carry
|
|
18
|
+
result = a + value + status.carry
|
|
20
19
|
status.zero = result.nobits?(0xff)
|
|
21
20
|
|
|
22
21
|
if status.decimal?
|
|
23
|
-
result = (a & 0x0f) + (
|
|
22
|
+
result = (a & 0x0f) + (value & 0x0f) + status.carry
|
|
24
23
|
result += 0x06 if result > 0x09
|
|
25
24
|
c = result > 0x0f ? 1 : 0
|
|
26
|
-
result = (a & 0xf0) + (
|
|
27
|
-
update_calculation_flags(
|
|
25
|
+
result = (a & 0xf0) + (value & 0xf0) + (c << 4) + (result & 0x0f)
|
|
26
|
+
update_calculation_flags(value, result)
|
|
28
27
|
result += 0x60 if result > 0x9f
|
|
29
28
|
else
|
|
30
|
-
update_calculation_flags(
|
|
29
|
+
update_calculation_flags(value, result)
|
|
31
30
|
end
|
|
32
31
|
|
|
33
32
|
status.carry = result > 0xff
|
|
@@ -46,9 +45,8 @@ module Badline
|
|
|
46
45
|
# $D9 - absolute_y - 4+ cycles
|
|
47
46
|
# $DD - absolute_x - 4+ cycles
|
|
48
47
|
def cmp(_addr, value)
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
update_number_flags(@a - v)
|
|
48
|
+
status.carry = (@a >= value)
|
|
49
|
+
update_number_flags(@a - value)
|
|
52
50
|
end
|
|
53
51
|
|
|
54
52
|
# Compare memory with X register.
|
|
@@ -58,9 +56,8 @@ module Badline
|
|
|
58
56
|
# $E4 - zeropage - 3 cycles
|
|
59
57
|
# $EC - absolute - 4 cycles
|
|
60
58
|
def cpx(_addr, value)
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
update_number_flags(@x - v)
|
|
59
|
+
status.carry = @x >= value
|
|
60
|
+
update_number_flags(@x - value)
|
|
64
61
|
end
|
|
65
62
|
|
|
66
63
|
# Compare memory with Y register.
|
|
@@ -70,9 +67,8 @@ module Badline
|
|
|
70
67
|
# $C4 - zeropage - 3 cycles
|
|
71
68
|
# $CC - absolute - 4 cycles
|
|
72
69
|
def cpy(_addr, value)
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
update_number_flags(@y - v)
|
|
70
|
+
status.carry = @y >= value
|
|
71
|
+
update_number_flags(@y - value)
|
|
76
72
|
end
|
|
77
73
|
|
|
78
74
|
# Subtract memory from accumulator with borrow.
|
|
@@ -87,20 +83,19 @@ module Badline
|
|
|
87
83
|
# $F9 - absolute_y - 4+ cycles
|
|
88
84
|
# $FD - absolute_x - 4+ cycles
|
|
89
85
|
def sbc(_addr, value)
|
|
90
|
-
|
|
91
|
-
v_inv = ~v & 0xff
|
|
86
|
+
inverted = ~value & 0xff
|
|
92
87
|
carry = status.carry? ? 0 : -1
|
|
93
88
|
|
|
94
|
-
result = a +
|
|
89
|
+
result = a + inverted + status.carry
|
|
95
90
|
status.zero = result.nobits?(0xff)
|
|
96
91
|
status.carry = result > 0xff
|
|
97
|
-
update_calculation_flags(
|
|
92
|
+
update_calculation_flags(inverted, result)
|
|
98
93
|
|
|
99
94
|
if status.decimal?
|
|
100
|
-
al = (a & 0x0f) - (
|
|
95
|
+
al = (a & 0x0f) - (value & 0x0f) + carry
|
|
101
96
|
al = ((al - 0x06) & 0x0F) - 0x10 if al.negative?
|
|
102
97
|
|
|
103
|
-
result = (a & 0xf0) - (
|
|
98
|
+
result = (a & 0xf0) - (value & 0xf0) + al
|
|
104
99
|
result -= 0x60 if result.negative?
|
|
105
100
|
end
|
|
106
101
|
|
|
@@ -15,7 +15,7 @@ module Badline
|
|
|
15
15
|
# $39 - absolute_y - 4+ cycles
|
|
16
16
|
# $3D - absolute_x - 4+ cycles
|
|
17
17
|
def and(_addr, value)
|
|
18
|
-
@a &=
|
|
18
|
+
@a &= value
|
|
19
19
|
update_number_flags(@a)
|
|
20
20
|
end
|
|
21
21
|
|
|
@@ -28,10 +28,9 @@ module Badline
|
|
|
28
28
|
# $16 - zeropage_x - 6 cycles
|
|
29
29
|
# $1E - absolute_x - 7 cycles
|
|
30
30
|
def asl(addr, value)
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
write_modified(addr, v, result)
|
|
31
|
+
result = (value << 1) & 0xff
|
|
32
|
+
status.carry = value[7]
|
|
33
|
+
write_modified(addr, result)
|
|
35
34
|
update_number_flags(result)
|
|
36
35
|
end
|
|
37
36
|
|
|
@@ -41,10 +40,9 @@ module Badline
|
|
|
41
40
|
# $24 - zeropage - 3 cycles
|
|
42
41
|
# $2C - absolute - 4 cycles
|
|
43
42
|
def bit(_addr, value)
|
|
44
|
-
v = resolve(value)
|
|
45
43
|
status.value = ((status.value & 0b00111111) +
|
|
46
|
-
(
|
|
47
|
-
status.zero = (a &
|
|
44
|
+
(value & 0b11000000)).to_i
|
|
45
|
+
status.zero = (a & value).nobits?(0xff)
|
|
48
46
|
end
|
|
49
47
|
|
|
50
48
|
# Performs bitwise XOR on accumulator with memory.
|
|
@@ -59,7 +57,7 @@ module Badline
|
|
|
59
57
|
# $59 - absolute_y - 4+ cycles
|
|
60
58
|
# $5D - absolute_x - 4+ cycles
|
|
61
59
|
def eor(_addr, value)
|
|
62
|
-
@a = (@a ^
|
|
60
|
+
@a = (@a ^ value) & 0xff
|
|
63
61
|
update_number_flags(@a)
|
|
64
62
|
end
|
|
65
63
|
|
|
@@ -72,10 +70,9 @@ module Badline
|
|
|
72
70
|
# $56 - zeropage_x - 6 cycles
|
|
73
71
|
# $5E - absolute_x - 7 cycles
|
|
74
72
|
def lsr(addr, value)
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
write_modified(addr, v, result)
|
|
73
|
+
result = (value >> 1) & 0xff
|
|
74
|
+
status.carry = value[0]
|
|
75
|
+
write_modified(addr, result)
|
|
79
76
|
update_number_flags(result)
|
|
80
77
|
end
|
|
81
78
|
|
|
@@ -91,7 +88,7 @@ module Badline
|
|
|
91
88
|
# $19 - absolute_y - 4+ cycles
|
|
92
89
|
# $1D - absolute_x - 4+ cycles
|
|
93
90
|
def ora(_addr, value)
|
|
94
|
-
@a |=
|
|
91
|
+
@a |= value
|
|
95
92
|
update_number_flags(@a)
|
|
96
93
|
end
|
|
97
94
|
|
|
@@ -104,10 +101,9 @@ module Badline
|
|
|
104
101
|
# $36 - zeropage_x - 6 cycles
|
|
105
102
|
# $3E - absolute_x - 7 cycles
|
|
106
103
|
def rol(addr, value)
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
write_modified(addr, v, result)
|
|
104
|
+
result = ((value << 1) + status.carry) & 0xff
|
|
105
|
+
status.carry = value[7]
|
|
106
|
+
write_modified(addr, result)
|
|
111
107
|
update_number_flags(result)
|
|
112
108
|
end
|
|
113
109
|
|
|
@@ -120,10 +116,9 @@ module Badline
|
|
|
120
116
|
# $76 - zeropage_x - 6 cycles
|
|
121
117
|
# $7E - absolute_x - 7 cycles
|
|
122
118
|
def ror(addr, value)
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
write_modified(addr, v, result)
|
|
119
|
+
result = ((value >> 1) + (status.carry? ? 0x80 : 0)) & 0xff
|
|
120
|
+
status.carry = value[0]
|
|
121
|
+
write_modified(addr, result)
|
|
127
122
|
update_number_flags(result)
|
|
128
123
|
end
|
|
129
124
|
end
|