mirror of
https://github.com/JetSetIlly/Gopher2600.git
synced 2025-04-02 11:02:17 -04:00
o video
- implemented trigger lists for missile/players - implemented player sprite
This commit is contained in:
parent
21dbb029f4
commit
a16ca04b12
6 changed files with 131 additions and 32 deletions
|
@ -36,6 +36,8 @@ const (
|
|||
|
||||
SubKeywordBreaks = "BREAKS"
|
||||
SubKeywordTraps = "TRAPS"
|
||||
SubKeywordVideo = "VIDEO"
|
||||
SubKeywordCPU = "CPU"
|
||||
)
|
||||
|
||||
// DebuggerCommands provides:
|
||||
|
@ -44,7 +46,7 @@ const (
|
|||
var DebuggerCommands = parser.Commands{
|
||||
KeywordInsert: parser.CommandArgs{parser.Arg{Typ: parser.ArgFile, Req: true}},
|
||||
KeywordBreak: parser.CommandArgs{parser.Arg{Typ: parser.ArgTarget, Req: true}, parser.Arg{Typ: parser.ArgNumber, Req: true}},
|
||||
KeywordTrap: parser.CommandArgs{parser.Arg{Typ: parser.ArgTarget, Req: true}, parser.Arg{Typ: parser.ArgNumber, Req: true}},
|
||||
KeywordTrap: parser.CommandArgs{parser.Arg{Typ: parser.ArgTarget, Req: true}},
|
||||
KeywordOnHalt: parser.CommandArgs{parser.Arg{Typ: parser.ArgIndeterminate, Req: false}},
|
||||
KeywordList: parser.CommandArgs{parser.Arg{Typ: parser.ArgKeyword, Req: true, Vals: parser.Keywords{SubKeywordBreaks, SubKeywordTraps}}},
|
||||
KeywordClear: parser.CommandArgs{parser.Arg{Typ: parser.ArgKeyword, Req: true, Vals: parser.Keywords{SubKeywordBreaks, SubKeywordTraps}}},
|
||||
|
@ -53,7 +55,7 @@ var DebuggerCommands = parser.Commands{
|
|||
KeywordReset: parser.CommandArgs{},
|
||||
KeywordRun: parser.CommandArgs{},
|
||||
KeywordStep: parser.CommandArgs{},
|
||||
KeywordStepMode: parser.CommandArgs{},
|
||||
KeywordStepMode: parser.CommandArgs{parser.Arg{Typ: parser.ArgKeyword, Req: false, Vals: parser.Keywords{SubKeywordCPU, SubKeywordVideo}}},
|
||||
KeywordTerse: parser.CommandArgs{},
|
||||
KeywordVerbose: parser.CommandArgs{},
|
||||
KeywordVerbosity: parser.CommandArgs{},
|
||||
|
|
|
@ -12,6 +12,7 @@ type missileSprite struct {
|
|||
size uint8
|
||||
enable bool
|
||||
futureEnable *future
|
||||
triggerList []int
|
||||
|
||||
lateStartDraw bool
|
||||
}
|
||||
|
@ -61,7 +62,7 @@ func (ms missileSprite) MachineInfo() string {
|
|||
// tick moves the counters along for the missile sprite
|
||||
func (ms *missileSprite) tick() {
|
||||
// position
|
||||
if ms.tickPosition(nil) {
|
||||
if ms.tickPosition(ms.triggerList) {
|
||||
if ms.futureReset.isScheduled() {
|
||||
ms.stopDrawing()
|
||||
ms.lateStartDraw = true
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
package video
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gopher2600/hardware/tia/colorclock"
|
||||
"math/bits"
|
||||
)
|
||||
|
||||
type playerSprite struct {
|
||||
|
@ -10,8 +12,12 @@ type playerSprite struct {
|
|||
color uint8
|
||||
gfxData uint8
|
||||
gfxDataPrev uint8
|
||||
reflection bool
|
||||
size uint8
|
||||
reflected bool
|
||||
verticalDelay bool
|
||||
triggerList []int
|
||||
|
||||
lateStartDraw bool
|
||||
}
|
||||
|
||||
func newPlayerSprite(label string, colorClock *colorclock.ColorClock) *playerSprite {
|
||||
|
@ -20,16 +26,83 @@ func newPlayerSprite(label string, colorClock *colorclock.ColorClock) *playerSpr
|
|||
return ps
|
||||
}
|
||||
|
||||
func (ps playerSprite) MachineInfoTerse() string {
|
||||
gfxData := ps.gfxData
|
||||
if ps.verticalDelay {
|
||||
gfxData = ps.gfxDataPrev
|
||||
}
|
||||
ref := " "
|
||||
if ps.reflected {
|
||||
ref = "r"
|
||||
}
|
||||
return fmt.Sprintf("%s gfx: %s %08b", ps.sprite.MachineInfoTerse(), ref, gfxData)
|
||||
}
|
||||
|
||||
// nothing to add to sprite type implementation of MachineInfo() and
|
||||
// MachineInfoTerse()
|
||||
|
||||
// tick moves the counters along for the player sprite
|
||||
func (ps *playerSprite) tick() {
|
||||
// position
|
||||
if ps.tickPosition(ps.triggerList) {
|
||||
if ps.futureReset.isScheduled() {
|
||||
ps.stopDrawing()
|
||||
ps.lateStartDraw = true
|
||||
} else {
|
||||
ps.startDrawing()
|
||||
}
|
||||
} else {
|
||||
// if player.position.tick() has not caused the position counter to
|
||||
// cycle then progress draw signal according to color clock phase and
|
||||
// nusiz_player_width. for nusiz_player_width and 0b101 and 0b111,
|
||||
// pixels are smeared over additional cycles in order to create the
|
||||
// double and quadruple sized sprites
|
||||
//
|
||||
// NOTE: the key difference between player and missile ticking is the
|
||||
// absence in the player sprite, of a filter on draw sig ticking when
|
||||
// there a position reset is pending
|
||||
if ps.size == 0x05 {
|
||||
if ps.colorClock.Phase == 0 || ps.colorClock.Phase == 2 {
|
||||
ps.tickDrawSig()
|
||||
}
|
||||
} else if ps.size == 0x07 {
|
||||
if ps.colorClock.Phase == 2 {
|
||||
ps.tickDrawSig()
|
||||
}
|
||||
} else {
|
||||
ps.tickDrawSig()
|
||||
}
|
||||
}
|
||||
|
||||
// reset
|
||||
if ps.futureReset.tick() {
|
||||
ps.resetPosition()
|
||||
if ps.lateStartDraw {
|
||||
ps.startDrawing()
|
||||
ps.lateStartDraw = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// pixel returns the color of the player at the current time. returns
|
||||
// (false, 0) if no pixel is to be seen; and (true, col) if there is
|
||||
func (ps *playerSprite) pixel() (bool, uint8) {
|
||||
// vertical delay
|
||||
gfxData := ps.gfxData
|
||||
if ps.verticalDelay {
|
||||
gfxData = ps.gfxDataPrev
|
||||
}
|
||||
|
||||
if ps.reflected {
|
||||
gfxData = bits.Reverse8(gfxData)
|
||||
}
|
||||
|
||||
if ps.drawSigCount > 0 && ps.drawSigCount <= ps.drawSigMax {
|
||||
if gfxData>>(uint8(ps.drawSigMax)-uint8(ps.drawSigCount))&0x01 == 0x01 {
|
||||
return true, ps.color
|
||||
}
|
||||
}
|
||||
|
||||
return false, 0
|
||||
}
|
||||
|
||||
|
@ -40,3 +113,6 @@ func (ps *playerSprite) scheduleReset(hblank *bool) {
|
|||
ps.futureReset.schedule(delayResetSprite, true)
|
||||
}
|
||||
}
|
||||
|
||||
func (ps *playerSprite) scheduleReflection() {
|
||||
}
|
||||
|
|
|
@ -25,15 +25,19 @@ type sprite struct {
|
|||
// position of the sprite as a polycounter value - the basic principle
|
||||
// behind VCS sprites is to begin drawing of the sprite when position
|
||||
// circulates to 0000000
|
||||
position polycounter.Polycounter
|
||||
position polycounter.Polycounter
|
||||
|
||||
// reset position of the sprite -- does not take horizonal movement into
|
||||
// account
|
||||
positionResetPixel int
|
||||
|
||||
// the draw signal controls which "bit" of the sprite is to be drawn next.
|
||||
// generally, the draw signal is activated when the position polycounter
|
||||
// matches the colorClock polycounter, but differenct sprite types handle
|
||||
// this differently in certain circumstances
|
||||
drawSigMax int
|
||||
drawSigCount int
|
||||
drawSigMax int
|
||||
drawSigOff int
|
||||
}
|
||||
|
||||
func newSprite(label string, colorClock *colorclock.ColorClock) *sprite {
|
||||
|
@ -55,7 +59,8 @@ func newSprite(label string, colorClock *colorclock.ColorClock) *sprite {
|
|||
// the direction of count and max is important - don't monkey with it
|
||||
// the value is used in Pixel*() functions to determine which pixel to check
|
||||
sp.drawSigMax = 8
|
||||
sp.drawSigCount = sp.drawSigMax + 1
|
||||
sp.drawSigOff = sp.drawSigMax + 1
|
||||
sp.drawSigCount = sp.drawSigOff
|
||||
|
||||
return sp
|
||||
}
|
||||
|
@ -120,7 +125,7 @@ func (sp *sprite) startDrawing() {
|
|||
|
||||
// stopDrawing is used to stop the draw signal prematurely
|
||||
func (sp *sprite) stopDrawing() {
|
||||
sp.drawSigCount = sp.drawSigMax + 1
|
||||
sp.drawSigCount = sp.drawSigOff
|
||||
}
|
||||
|
||||
func (sp *sprite) isDrawing() bool {
|
||||
|
|
|
@ -25,8 +25,6 @@ type Video struct {
|
|||
hmm0 uint8
|
||||
hmm1 uint8
|
||||
hmbl uint8
|
||||
|
||||
// TODO: player/missile number & spacing; trigger lists
|
||||
}
|
||||
|
||||
// New is the preferred method of initialisation for the Video structure
|
||||
|
@ -115,10 +113,10 @@ func (vd *Video) TickSpritesForHMOVE(count int) {
|
|||
func (vd Video) GetPixel() uint8 {
|
||||
if vd.Playfield.priority {
|
||||
// priority 1
|
||||
if use, c := vd.Player0.pixel(); use {
|
||||
if use, c := vd.Playfield.pixel(); use {
|
||||
return c
|
||||
}
|
||||
if use, c := vd.Missile0.pixel(); use {
|
||||
if use, c := vd.Ball.pixel(); use {
|
||||
return c
|
||||
}
|
||||
|
||||
|
@ -131,19 +129,18 @@ func (vd Video) GetPixel() uint8 {
|
|||
}
|
||||
|
||||
// priority 3
|
||||
if use, c := vd.Playfield.pixel(); use {
|
||||
if use, c := vd.Player0.pixel(); use {
|
||||
return c
|
||||
}
|
||||
if use, c := vd.Ball.pixel(); use {
|
||||
if use, c := vd.Missile0.pixel(); use {
|
||||
return c
|
||||
}
|
||||
|
||||
} else {
|
||||
// priority 1
|
||||
if use, c := vd.Playfield.pixel(); use {
|
||||
if use, c := vd.Player0.pixel(); use {
|
||||
return c
|
||||
}
|
||||
if use, c := vd.Ball.pixel(); use {
|
||||
if use, c := vd.Missile0.pixel(); use {
|
||||
return c
|
||||
}
|
||||
|
||||
|
@ -156,10 +153,10 @@ func (vd Video) GetPixel() uint8 {
|
|||
}
|
||||
|
||||
// priority 3
|
||||
if use, c := vd.Player0.pixel(); use {
|
||||
if use, c := vd.Playfield.pixel(); use {
|
||||
return c
|
||||
}
|
||||
if use, c := vd.Missile0.pixel(); use {
|
||||
if use, c := vd.Ball.pixel(); use {
|
||||
return c
|
||||
}
|
||||
}
|
||||
|
@ -168,6 +165,25 @@ func (vd Video) GetPixel() uint8 {
|
|||
return vd.Playfield.backgroundColor
|
||||
}
|
||||
|
||||
func createTriggerList(playerSize uint8) []int {
|
||||
var triggerList []int
|
||||
switch playerSize {
|
||||
case 0x0, 0x05, 0x07:
|
||||
// empty trigger list
|
||||
case 0x01:
|
||||
triggerList = []int{4} // 111100
|
||||
case 0x02:
|
||||
triggerList = []int{8} // 110111
|
||||
case 0x03:
|
||||
triggerList = []int{4, 8} // 111100, 110111
|
||||
case 0x04:
|
||||
triggerList = []int{4} // 110111
|
||||
case 0x06:
|
||||
triggerList = []int{8, 16} // 110111, 011100
|
||||
}
|
||||
return triggerList
|
||||
}
|
||||
|
||||
// ReadVideoMemory checks the TIA memory for changes to registers that are
|
||||
// interesting to the video sub-system. all changes happen immediately except
|
||||
// for those where a "schedule" function is called.
|
||||
|
@ -175,10 +191,14 @@ func (vd *Video) ReadVideoMemory(register string, value uint8) bool {
|
|||
switch register {
|
||||
case "NUSIZ0":
|
||||
vd.Missile0.size = (value & 0x30) >> 4
|
||||
// TODO: player width & trigger lists
|
||||
vd.Player0.size = value & 0x07
|
||||
vd.Player0.triggerList = createTriggerList(vd.Player0.size)
|
||||
vd.Missile0.triggerList = vd.Player0.triggerList
|
||||
case "NUSIZ1":
|
||||
vd.Missile1.size = (value & 0x30) >> 4
|
||||
// TODO: player width & trigger lists
|
||||
vd.Player1.size = value & 0x07
|
||||
vd.Player1.triggerList = createTriggerList(vd.Player1.size)
|
||||
vd.Missile1.triggerList = vd.Player1.triggerList
|
||||
case "COLUP0":
|
||||
vd.Player0.color = value & 0xfe
|
||||
vd.Missile0.color = value & 0xfe
|
||||
|
@ -196,9 +216,9 @@ func (vd *Video) ReadVideoMemory(register string, value uint8) bool {
|
|||
vd.Playfield.scoremode = value&0x02 == 0x02
|
||||
vd.Playfield.priority = value&0x04 == 0x04
|
||||
case "REFP0":
|
||||
vd.Player0.reflection = value&0x40 == 0x40
|
||||
vd.Player0.reflected = value&0x04 == 0x04
|
||||
case "REFP1":
|
||||
vd.Player1.reflection = value&0x40 == 0x40
|
||||
vd.Player1.reflected = value&0x04 == 0x04
|
||||
case "PF0":
|
||||
vd.Playfield.scheduleWrite(0, value)
|
||||
case "PF1":
|
||||
|
|
|
@ -92,28 +92,23 @@ func (vcs *VCS) Step(videoCycleCallback func(*cpu.InstructionResult) error) (int
|
|||
// TODO: not sure when in the video cycle sequence it should be run
|
||||
// TODO: is this something that can drift, thereby causing subtly different
|
||||
// results / graphical effects? is this what RSYNC is for?
|
||||
|
||||
vcs.RIOT.ReadRIOTMemory()
|
||||
vcs.RIOT.Step()
|
||||
|
||||
// three color clocks per CPU cycle so we run video cycle three times
|
||||
|
||||
vcs.MC.RdyFlg = vcs.TIA.StepVideoCycle()
|
||||
if vcs.MC.RdyFlg {
|
||||
videoCycleCallback(r)
|
||||
}
|
||||
videoCycleCallback(r)
|
||||
|
||||
vcs.MC.RdyFlg = vcs.TIA.StepVideoCycle()
|
||||
if vcs.MC.RdyFlg {
|
||||
videoCycleCallback(r)
|
||||
}
|
||||
videoCycleCallback(r)
|
||||
|
||||
// check for side effects from the CPU operation
|
||||
vcs.TIA.ReadTIAMemory()
|
||||
|
||||
vcs.MC.RdyFlg = vcs.TIA.StepVideoCycle()
|
||||
if vcs.MC.RdyFlg {
|
||||
videoCycleCallback(r)
|
||||
}
|
||||
videoCycleCallback(r)
|
||||
}
|
||||
|
||||
// TODO: full controller support -- this is emulating the rest state for the
|
||||
|
|
Loading…
Add table
Reference in a new issue