mirror of
https://github.com/JetSetIlly/Gopher2600.git
synced 2025-04-02 11:02:17 -04:00
o contollers
- generalised controller support - opened the way for different stick implementations, including scripted playback
This commit is contained in:
parent
b03b64e527
commit
c5edff64e7
9 changed files with 236 additions and 124 deletions
|
@ -878,12 +878,20 @@ func (dbg *Debugger) enactCommand(tokens *commandline.Tokens) (parseCommandResul
|
|||
}
|
||||
|
||||
case cmdStick:
|
||||
var err error
|
||||
|
||||
stick, _ := tokens.Get()
|
||||
action, _ := tokens.Get()
|
||||
|
||||
stickN, _ := strconv.Atoi(stick)
|
||||
|
||||
err := dbg.vcs.Controller.HandleStick(stickN, action)
|
||||
switch stickN {
|
||||
case 0:
|
||||
err = dbg.vcs.Player0.Handle(action)
|
||||
case 1:
|
||||
err = dbg.vcs.Player1.Handle(action)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return doNothing, err
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"gopher2600/hardware"
|
||||
"gopher2600/hardware/cpu/definitions"
|
||||
"gopher2600/hardware/cpu/result"
|
||||
"gopher2600/hardware/peripherals/digitalsticks"
|
||||
"gopher2600/symbols"
|
||||
"os"
|
||||
"os/signal"
|
||||
|
@ -137,6 +138,18 @@ func NewDebugger(tv gui.GUI) (*Debugger, error) {
|
|||
return nil, fmt.Errorf("error preparing VCS: %s", err)
|
||||
}
|
||||
|
||||
// create a controller
|
||||
dst, err := digitalsticks.NewSplaceStick(0, dbg.vcs.Mem.TIA, dbg.vcs.Mem.RIOT, dbg.vcs.Panel)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error preparing VCS: %s", err)
|
||||
}
|
||||
|
||||
// attach a controller
|
||||
err = dbg.vcs.AttachController(0, dst)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error preparing VCS: %s", err)
|
||||
}
|
||||
|
||||
// create instance of disassembly -- the same base structure is used
|
||||
// for disassemblies subseuquent to the first one.
|
||||
dbg.disasm = &disassembly.Disassembly{}
|
||||
|
|
|
@ -52,13 +52,11 @@ const (
|
|||
ImageTV
|
||||
DigestTV
|
||||
|
||||
// Controllers
|
||||
StickDisconnected
|
||||
|
||||
// GUI
|
||||
UnknownGUIRequest
|
||||
SDL
|
||||
|
||||
// Peripherals
|
||||
NoControllersFound
|
||||
NoControllerHardware
|
||||
NoPlayerPort
|
||||
)
|
||||
|
|
|
@ -49,13 +49,11 @@ var messages = map[Errno]string{
|
|||
ImageTV: "ImageTV: %s",
|
||||
DigestTV: "DigestTV: %s",
|
||||
|
||||
// Controllers
|
||||
StickDisconnected: "Stick for player %d is not connected",
|
||||
|
||||
// GUI
|
||||
UnknownGUIRequest: "GUI does not support %v request",
|
||||
SDL: "SDL: %s",
|
||||
|
||||
// Peripherals
|
||||
NoControllersFound: "no controllers found",
|
||||
NoControllerHardware: "no hardware controllers found",
|
||||
NoPlayerPort: "VCS only supports two players (0 and 1)",
|
||||
}
|
||||
|
|
19
hardware/peripherals/controller.go
Normal file
19
hardware/peripherals/controller.go
Normal file
|
@ -0,0 +1,19 @@
|
|||
package peripherals
|
||||
|
||||
// Controller defines the operations required for VCS controllers
|
||||
type Controller interface {
|
||||
// make sure the most recent input is ready for the emulation
|
||||
Strobe() error
|
||||
|
||||
// handle interprets the supplied action and updates the emulation
|
||||
Handle(action string) error
|
||||
|
||||
// add InputTranscriber implementation for consideration by the controller
|
||||
RegisterTranscriber(Transcriber)
|
||||
}
|
||||
|
||||
// Transcriber defines the operation required for a transcriber (observer) of
|
||||
// VCS controller input
|
||||
type Transcriber interface {
|
||||
Transcribe(action string)
|
||||
}
|
101
hardware/peripherals/digitalsticks/splace.go
Normal file
101
hardware/peripherals/digitalsticks/splace.go
Normal file
|
@ -0,0 +1,101 @@
|
|||
package digitalsticks
|
||||
|
||||
import (
|
||||
"gopher2600/errors"
|
||||
"gopher2600/hardware/memory"
|
||||
"gopher2600/hardware/peripherals"
|
||||
|
||||
"github.com/splace/joysticks"
|
||||
)
|
||||
|
||||
// SplaceStick emulaes the digital VCS joystick
|
||||
type SplaceStick struct {
|
||||
*peripherals.DigitalStick
|
||||
|
||||
device *joysticks.HID
|
||||
err error
|
||||
}
|
||||
|
||||
// NewSplaceStick is the preferred method of initialisation for the Stick type
|
||||
func NewSplaceStick(player int, tia memory.PeriphBus, riot memory.PeriphBus, panel *peripherals.Panel) (*SplaceStick, error) {
|
||||
var err error
|
||||
|
||||
sps := new(SplaceStick)
|
||||
sps.DigitalStick, err = peripherals.NewDigitalStick(player, riot, tia)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// there is a flaw (either in splace/joysticks or somewehere else lower
|
||||
// down in the kernel driver) which means that Connect() will not return
|
||||
// until it recieves some input from the controller. to get around this,
|
||||
// we've put the main body of the NewStick() function in a go routine.
|
||||
go func() {
|
||||
// try connecting to specific controller.
|
||||
// system assigned index: typically increments on each new controller added.
|
||||
sps.device = joysticks.Connect(1)
|
||||
if sps.device == nil {
|
||||
sps.err = errors.NewFormattedError(errors.NoControllerHardware, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// get/assign channels for specific events
|
||||
stickMove := sps.device.OnMove(1)
|
||||
|
||||
buttonPress := sps.device.OnClose(1)
|
||||
buttonRelease := sps.device.OnOpen(1)
|
||||
|
||||
// on xbox controller, button 8 is the start button
|
||||
resetPress := sps.device.OnClose(8)
|
||||
resetRelease := sps.device.OnOpen(8)
|
||||
|
||||
// on xbox controller, button 9 is the back button
|
||||
selectPress := sps.device.OnClose(7)
|
||||
selectRelease := sps.device.OnOpen(7)
|
||||
|
||||
// start feeding OS events onto the event channels.
|
||||
go sps.device.ParcelOutEvents()
|
||||
|
||||
// handle event channels
|
||||
for {
|
||||
select {
|
||||
case <-resetPress:
|
||||
panel.SetGameReset(true)
|
||||
case <-resetRelease:
|
||||
panel.SetGameReset(false)
|
||||
|
||||
case <-selectPress:
|
||||
panel.SetGameSelect(true)
|
||||
case <-selectRelease:
|
||||
panel.SetGameSelect(false)
|
||||
|
||||
case <-buttonPress:
|
||||
sps.DigitalStick.Handle("FIRE")
|
||||
case <-buttonRelease:
|
||||
sps.DigitalStick.Handle("NOFIRE")
|
||||
|
||||
case ev := <-stickMove:
|
||||
x := ev.(joysticks.CoordsEvent).X
|
||||
y := ev.(joysticks.CoordsEvent).Y
|
||||
if x < -0.5 {
|
||||
sps.DigitalStick.Handle("LEFT")
|
||||
} else if x > 0.5 {
|
||||
sps.DigitalStick.Handle("RIGHT")
|
||||
} else if y < -0.5 {
|
||||
sps.DigitalStick.Handle("UP")
|
||||
} else if y > 0.5 {
|
||||
sps.DigitalStick.Handle("DOWN")
|
||||
} else {
|
||||
sps.DigitalStick.Handle("CENTRE")
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return sps, nil
|
||||
}
|
||||
|
||||
// Strobe implements the Controller interface
|
||||
func (sps *SplaceStick) Strobe() error {
|
||||
return nil
|
||||
}
|
|
@ -1,138 +1,77 @@
|
|||
package peripherals
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gopher2600/errors"
|
||||
"gopher2600/hardware/memory"
|
||||
"gopher2600/hardware/memory/vcssymbols"
|
||||
"strings"
|
||||
|
||||
"github.com/splace/joysticks"
|
||||
)
|
||||
|
||||
// Stick emulaes the digital VCS joystick
|
||||
type Stick struct {
|
||||
device *joysticks.HID
|
||||
err error
|
||||
|
||||
tia memory.PeriphBus
|
||||
riot memory.PeriphBus
|
||||
// DigitalStick is the minimal implementation for the VCS joystick
|
||||
type DigitalStick struct {
|
||||
riot memory.PeriphBus
|
||||
tia memory.PeriphBus
|
||||
stickAddress uint16
|
||||
fireAddress uint16
|
||||
transcriber Transcriber
|
||||
}
|
||||
|
||||
// NewStick is the preferred method of initialisation for the Stick type
|
||||
func NewStick(tia memory.PeriphBus, riot memory.PeriphBus, panel *Panel) *Stick {
|
||||
stk := new(Stick)
|
||||
stk.tia = tia
|
||||
stk.riot = riot
|
||||
// NewDigitalStick is the preferred method of initialisation the DigitalStick
|
||||
// type
|
||||
func NewDigitalStick(player int, riot memory.PeriphBus, tia memory.PeriphBus) (*DigitalStick, error) {
|
||||
dst := &DigitalStick{riot: riot, tia: tia}
|
||||
|
||||
// TODO: make all this work with a second contoller. for now, initialise
|
||||
// and asssume that there is just one controller for player 0
|
||||
stk.riot.PeriphWrite(vcssymbols.SWCHA, 0xff)
|
||||
stk.tia.PeriphWrite(vcssymbols.INPT4, 0x80)
|
||||
stk.tia.PeriphWrite(vcssymbols.INPT5, 0x80)
|
||||
|
||||
// there is a flaw (either in splace/joysticks or somewehere else lower
|
||||
// down in the kernel driver) which means that Connect() will not return
|
||||
// until it recieves some input from the controller. to get around this,
|
||||
// we've put the main body of the NewStick() function in a go routine.
|
||||
go func() {
|
||||
// try connecting to specific controller.
|
||||
// system assigned index: typically increments on each new controller added.
|
||||
stk.device = joysticks.Connect(1)
|
||||
if stk.device == nil {
|
||||
stk.err = errors.NewFormattedError(errors.NoControllersFound, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// get/assign channels for specific events
|
||||
stickMove := stk.device.OnMove(1)
|
||||
|
||||
buttonPress := stk.device.OnClose(1)
|
||||
buttonRelease := stk.device.OnOpen(1)
|
||||
|
||||
// on xbox controller, button 8 is the start button
|
||||
resetPress := stk.device.OnClose(8)
|
||||
resetRelease := stk.device.OnOpen(8)
|
||||
|
||||
// on xbox controller, button 9 is the back button
|
||||
selectPress := stk.device.OnClose(7)
|
||||
selectRelease := stk.device.OnOpen(7)
|
||||
|
||||
// start feeding OS events onto the event channels.
|
||||
go stk.device.ParcelOutEvents()
|
||||
|
||||
// handle event channels
|
||||
for {
|
||||
select {
|
||||
case <-resetPress:
|
||||
panel.SetGameReset(true)
|
||||
case <-resetRelease:
|
||||
panel.SetGameReset(false)
|
||||
|
||||
case <-selectPress:
|
||||
panel.SetGameSelect(true)
|
||||
case <-selectRelease:
|
||||
panel.SetGameSelect(false)
|
||||
|
||||
case <-buttonPress:
|
||||
stk.HandleStick(0, "FIRE")
|
||||
case <-buttonRelease:
|
||||
stk.HandleStick(0, "NOFIRE")
|
||||
|
||||
case ev := <-stickMove:
|
||||
x := ev.(joysticks.CoordsEvent).X
|
||||
y := ev.(joysticks.CoordsEvent).Y
|
||||
if x < -0.5 {
|
||||
stk.HandleStick(0, "LEFT")
|
||||
} else if x > 0.5 {
|
||||
stk.HandleStick(0, "RIGHT")
|
||||
} else if y < -0.5 {
|
||||
stk.HandleStick(0, "UP")
|
||||
} else if y > 0.5 {
|
||||
stk.HandleStick(0, "DOWN")
|
||||
} else {
|
||||
stk.HandleStick(0, "CENTRE")
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return stk
|
||||
}
|
||||
|
||||
// HandleStick parses the action and writes to the correct memory location
|
||||
func (stk *Stick) HandleStick(player int, action string) error {
|
||||
var stickAddress uint16
|
||||
var fireAddress uint16
|
||||
dst.riot.PeriphWrite(vcssymbols.SWCHA, 0xff)
|
||||
dst.tia.PeriphWrite(vcssymbols.INPT4, 0x80)
|
||||
dst.tia.PeriphWrite(vcssymbols.INPT5, 0x80)
|
||||
|
||||
if player == 0 {
|
||||
stickAddress = vcssymbols.SWCHA
|
||||
fireAddress = vcssymbols.INPT4
|
||||
dst.stickAddress = vcssymbols.SWCHA
|
||||
dst.fireAddress = vcssymbols.INPT4
|
||||
} else if player == 1 {
|
||||
stickAddress = vcssymbols.SWCHB
|
||||
fireAddress = vcssymbols.INPT5
|
||||
dst.stickAddress = vcssymbols.SWCHB
|
||||
dst.fireAddress = vcssymbols.INPT5
|
||||
} else {
|
||||
panic(fmt.Sprintf("there is no player %d with a joystick to handle", player))
|
||||
return nil, errors.NewFormattedError(errors.NoPlayerPort)
|
||||
}
|
||||
|
||||
return dst, nil
|
||||
}
|
||||
|
||||
// Handle implements the Controller interface
|
||||
func (dst DigitalStick) Handle(action string) error {
|
||||
switch strings.ToUpper(action) {
|
||||
case "LEFT":
|
||||
stk.riot.PeriphWrite(stickAddress, 0xbf)
|
||||
dst.riot.PeriphWrite(dst.stickAddress, 0xbf)
|
||||
case "RIGHT":
|
||||
stk.riot.PeriphWrite(stickAddress, 0x7f)
|
||||
dst.riot.PeriphWrite(dst.stickAddress, 0x7f)
|
||||
case "UP":
|
||||
stk.riot.PeriphWrite(stickAddress, 0xef)
|
||||
dst.riot.PeriphWrite(dst.stickAddress, 0xef)
|
||||
case "DOWN":
|
||||
stk.riot.PeriphWrite(stickAddress, 0xdf)
|
||||
case "CENTER":
|
||||
fallthrough
|
||||
case "CENTRE":
|
||||
stk.riot.PeriphWrite(stickAddress, 0xff)
|
||||
dst.riot.PeriphWrite(dst.stickAddress, 0xdf)
|
||||
case "CENTRE", "CENTER":
|
||||
dst.riot.PeriphWrite(dst.stickAddress, 0xff)
|
||||
case "FIRE":
|
||||
stk.tia.PeriphWrite(fireAddress, 0x00)
|
||||
dst.tia.PeriphWrite(dst.fireAddress, 0x00)
|
||||
case "NOFIRE":
|
||||
stk.tia.PeriphWrite(fireAddress, 0x80)
|
||||
dst.tia.PeriphWrite(dst.fireAddress, 0x80)
|
||||
}
|
||||
|
||||
if dst.transcriber != nil {
|
||||
dst.transcriber.Transcribe(action)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RegisterTranscriber implements the Controller interface
|
||||
func (dst *DigitalStick) RegisterTranscriber(trans Transcriber) {
|
||||
dst.transcriber = trans
|
||||
}
|
||||
|
||||
// Strobe implements the Controller interface
|
||||
func (dst *DigitalStick) Strobe() error {
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package hardware
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"gopher2600/errors"
|
||||
"gopher2600/hardware/cpu"
|
||||
"gopher2600/hardware/cpu/result"
|
||||
"gopher2600/hardware/memory"
|
||||
|
@ -22,8 +23,10 @@ type VCS struct {
|
|||
// tv is not part of the VCS but is attached to it
|
||||
TV television.Television
|
||||
|
||||
Panel *peripherals.Panel
|
||||
Controller *peripherals.Stick
|
||||
Panel *peripherals.Panel
|
||||
|
||||
Player0 peripherals.Controller
|
||||
Player1 peripherals.Controller
|
||||
}
|
||||
|
||||
// NewVCS creates a new VCS and everything associated with the hardware. It is
|
||||
|
@ -59,15 +62,23 @@ func NewVCS(tv television.Television) (*VCS, error) {
|
|||
return nil, fmt.Errorf("can't create console control panel")
|
||||
}
|
||||
|
||||
// TODO: better contoller support
|
||||
vcs.Controller = peripherals.NewStick(vcs.Mem.TIA, vcs.Mem.RIOT, vcs.Panel)
|
||||
if vcs.Controller == nil {
|
||||
return nil, fmt.Errorf("can't create stick controller")
|
||||
}
|
||||
|
||||
return vcs, nil
|
||||
}
|
||||
|
||||
// AttachController allows control of the emulation with the Contoller
|
||||
// implementation
|
||||
func (vcs *VCS) AttachController(player int, controller peripherals.Controller) error {
|
||||
switch player {
|
||||
case 0:
|
||||
vcs.Player0 = controller
|
||||
case 1:
|
||||
vcs.Player1 = controller
|
||||
default:
|
||||
return errors.NewFormattedError(errors.NoPlayerPort)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AttachCartridge loads a cartridge (given by filename) into the emulators memory
|
||||
func (vcs *VCS) AttachCartridge(filename string) error {
|
||||
if filename == "" {
|
||||
|
@ -116,6 +127,15 @@ func (vcs *VCS) Reset() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (vcs *VCS) strobeControllers() {
|
||||
if vcs.Player0 != nil {
|
||||
vcs.Player0.Strobe()
|
||||
}
|
||||
if vcs.Player1 != nil {
|
||||
vcs.Player1.Strobe()
|
||||
}
|
||||
}
|
||||
|
||||
// Step the emulator state one CPU instruction
|
||||
// -- we can put this function in a loop for an effective debugging loop
|
||||
// ths videoCycleCallback function for an additional callback point in the
|
||||
|
@ -135,6 +155,9 @@ func (vcs *VCS) Step(videoCycleCallback func(*result.Instruction) error) (int, *
|
|||
cycleVCS := func(r *result.Instruction) {
|
||||
cpuCycles++
|
||||
|
||||
// ensure controllers have updated their input
|
||||
vcs.strobeControllers()
|
||||
|
||||
// run riot only once per CPU cycle
|
||||
// TODO: not sure when in the video cycle sequence it should be run
|
||||
// TODO: is this something that can drift, thereby causing subtly different
|
||||
|
@ -208,6 +231,7 @@ func (vcs *VCS) RunConcurrent(running *atomic.Value) error {
|
|||
}()
|
||||
|
||||
cycleVCS := func(r *result.Instruction) {
|
||||
vcs.strobeControllers()
|
||||
triggerTIA <- true
|
||||
triggerRIOT <- true
|
||||
<-triggerTIA
|
||||
|
@ -231,6 +255,7 @@ func (vcs *VCS) Run(continueCheck func() bool) error {
|
|||
var err error
|
||||
|
||||
cycleVCS := func(r *result.Instruction) {
|
||||
vcs.strobeControllers()
|
||||
vcs.RIOT.ReadRIOTMemory()
|
||||
vcs.RIOT.Step()
|
||||
vcs.TIA.ReadTIAMemory()
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"gopher2600/gui"
|
||||
"gopher2600/gui/sdl"
|
||||
"gopher2600/hardware"
|
||||
"gopher2600/hardware/peripherals/digitalsticks"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
|
@ -20,6 +21,16 @@ func Play(cartridgeFile, tvMode string, scaling float32, stable bool) error {
|
|||
return fmt.Errorf("error preparing VCS: %s", err)
|
||||
}
|
||||
|
||||
dst, err := digitalsticks.NewSplaceStick(0, vcs.Mem.TIA, vcs.Mem.RIOT, vcs.Panel)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = vcs.AttachController(0, dst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = vcs.AttachCartridge(cartridgeFile)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
Loading…
Add table
Reference in a new issue