Support for FLASH and INVERSE. Extract text display code to the ANSI frontend
This commit is contained in:
parent
5172919649
commit
49ea32b84d
|
@ -2,14 +2,21 @@ package apple2
|
|||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ansiConsoleFrontend struct {
|
||||
keyChannel chan uint8
|
||||
extraLineFeeds chan int
|
||||
}
|
||||
|
||||
func _stdinReader(c chan uint8) {
|
||||
const refreshDelayMs = 100
|
||||
|
||||
func (fe *ansiConsoleFrontend) getKey() (key uint8, ok bool) {
|
||||
stdinReader := func(c chan uint8) {
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
for {
|
||||
byte, err := reader.ReadByte()
|
||||
|
@ -20,16 +27,18 @@ func _stdinReader(c chan uint8) {
|
|||
}
|
||||
}
|
||||
|
||||
func (fe *ansiConsoleFrontend) getKey() (key uint8, ok bool) {
|
||||
if fe.keyChannel == nil {
|
||||
fe.keyChannel = make(chan uint8, 100)
|
||||
go _stdinReader(fe.keyChannel)
|
||||
go stdinReader(fe.keyChannel)
|
||||
}
|
||||
|
||||
select {
|
||||
case key = <-fe.keyChannel:
|
||||
if key == 10 {
|
||||
key = 13
|
||||
if fe.extraLineFeeds != nil {
|
||||
fe.extraLineFeeds <- 1
|
||||
}
|
||||
}
|
||||
ok = true
|
||||
default:
|
||||
|
@ -37,3 +46,82 @@ func (fe *ansiConsoleFrontend) getKey() (key uint8, ok bool) {
|
|||
}
|
||||
return
|
||||
}
|
||||
|
||||
func ansiCursorUp(steps int) {
|
||||
fmt.Printf("\033[%vA", steps)
|
||||
}
|
||||
|
||||
func (fe *ansiConsoleFrontend) textModeGoRoutine(tp *textPages) {
|
||||
fe.extraLineFeeds = make(chan int, 100)
|
||||
|
||||
fmt.Printf(strings.Repeat("\n", 26))
|
||||
for {
|
||||
if tp.strobe() {
|
||||
// Go up
|
||||
ansiCursorUp(26)
|
||||
done := false
|
||||
for !done {
|
||||
select {
|
||||
case lineFeeds := <-fe.extraLineFeeds:
|
||||
ansiCursorUp(lineFeeds)
|
||||
default:
|
||||
done = true
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println(strings.Repeat("#", 44))
|
||||
|
||||
// See "Understand the Apple II", page 5-10
|
||||
// http://www.applelogic.org/files/UNDERSTANDINGTHEAII.pdf
|
||||
var i, j, h uint8
|
||||
// Top, middle and botton screen
|
||||
for i = 0; i < 120; i = i + 40 {
|
||||
// Memory pages
|
||||
for _, p := range tp.pages {
|
||||
// The two half pages
|
||||
for _, h = range []uint8{0, 128} {
|
||||
line := ""
|
||||
for j = i + h; j < i+h+40; j++ {
|
||||
line += textMemoryByteToString(p.Peek(j))
|
||||
}
|
||||
fmt.Printf("# %v #\n", line)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println(strings.Repeat("#", 44))
|
||||
fmt.Print("\033[KLine: ")
|
||||
|
||||
}
|
||||
time.Sleep(refreshDelayMs * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
func textMemoryByteToString(value uint8) string {
|
||||
// See https://en.wikipedia.org/wiki/Apple_II_character_set
|
||||
// Only ascii from 0x20 to 0x5F is visible
|
||||
topBits := value >> 6
|
||||
isInverse := topBits == 0
|
||||
isFlash := topBits == 1
|
||||
|
||||
value = (value & 0x3F)
|
||||
if value < 0x20 {
|
||||
value += 0x40
|
||||
}
|
||||
|
||||
if isFlash {
|
||||
if value == ' ' {
|
||||
// Flashing space in Apple is the full box. It can't be done with ANSI codes
|
||||
value = '_'
|
||||
}
|
||||
return fmt.Sprintf("\033[5m%v\033[0m", string(value))
|
||||
} else if isInverse {
|
||||
return fmt.Sprintf("\033[7m%v\033[0m", string(value))
|
||||
} else {
|
||||
return string(value)
|
||||
}
|
||||
}
|
||||
|
||||
func textMemoryByteToStringHex(value uint8) string {
|
||||
return fmt.Sprintf("%02x ", value)
|
||||
}
|
||||
|
|
|
@ -18,12 +18,11 @@ func Run(romFile string, log bool) {
|
|||
|
||||
var fe ansiConsoleFrontend
|
||||
io.setKeyboardProvider(&fe)
|
||||
go fe.textModeGoRoutine(&t)
|
||||
|
||||
// Start the processor
|
||||
core6502.Reset(&s)
|
||||
t.prepare()
|
||||
for {
|
||||
core6502.ExecuteInstruction(&s, log)
|
||||
t.dumpIfDirty()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,6 @@
|
|||
package apple2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
type textPages struct {
|
||||
lastDump int64
|
||||
pages [4]textPage
|
||||
}
|
||||
|
||||
|
@ -25,80 +19,6 @@ func (p *textPage) Poke(address uint8, value uint8) {
|
|||
p.dirty = true
|
||||
}
|
||||
|
||||
func textMemoryByteToString(value uint8) string {
|
||||
value = value & 0x7F
|
||||
if value < ' ' {
|
||||
return "@"
|
||||
}
|
||||
|
||||
return string(value)
|
||||
}
|
||||
|
||||
func textMemoryByteToStringHex(value uint8) string {
|
||||
return fmt.Sprintf("%02x ", value)
|
||||
}
|
||||
|
||||
func (tp *textPages) prepare() {
|
||||
fmt.Printf("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n")
|
||||
}
|
||||
|
||||
func (tp *textPages) dump() {
|
||||
// See "Understand the Apple II", page 5-10
|
||||
// http://www.applelogic.org/files/UNDERSTANDINGTHEAII.pdf
|
||||
|
||||
fmt.Print("\033[26A")
|
||||
|
||||
fmt.Println("------------------------------------------")
|
||||
|
||||
var i, j, h uint8
|
||||
// Top, middle and botton screen
|
||||
for i = 0; i < 120; i = i + 40 {
|
||||
// Memory pages
|
||||
for _, p := range tp.pages {
|
||||
// The two half pages
|
||||
for _, h = range []uint8{0, 128} {
|
||||
line := ""
|
||||
for j = i + h; j < i+h+40; j++ {
|
||||
line += textMemoryByteToString(p.Peek(j))
|
||||
}
|
||||
fmt.Printf("| %v |\n", line)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("------------------------------------------")
|
||||
tp.lastDump = time.Now().UnixNano()
|
||||
|
||||
}
|
||||
|
||||
const refreshDelayMs = 100
|
||||
|
||||
func (tp *textPages) dumpIfDirty() {
|
||||
if time.Now().UnixNano()-tp.lastDump < refreshDelayMs*1000*1000 {
|
||||
// Wait more the next refresh
|
||||
return
|
||||
}
|
||||
|
||||
dirty := false
|
||||
for i := 0; i < 4; i++ {
|
||||
if tp.pages[i].dirty {
|
||||
dirty = true
|
||||
tp.pages[i].dirty = false
|
||||
}
|
||||
}
|
||||
|
||||
if !dirty {
|
||||
return
|
||||
}
|
||||
tp.dump()
|
||||
}
|
||||
|
||||
func (tp *textPages) charAddress(column uint8, line uint8) (page uint8, address uint8) {
|
||||
page = (line % 8) / 2
|
||||
address = column + (line/8)*40 + (line%2)*128
|
||||
return
|
||||
}
|
||||
|
||||
func (tp *textPages) read(column uint8, line uint8) uint8 {
|
||||
page, address := tp.charAddress(column, line)
|
||||
return tp.pages[page].Peek(address)
|
||||
|
@ -108,3 +28,21 @@ func (tp *textPages) write(column uint8, line uint8, value uint8) {
|
|||
page, address := tp.charAddress(column, line)
|
||||
tp.pages[page].Poke(address, value)
|
||||
}
|
||||
|
||||
func (tp *textPages) charAddress(column uint8, line uint8) (page uint8, address uint8) {
|
||||
page = (line % 8) / 2
|
||||
address = column + (line/8)*40 + (line%2)*128
|
||||
return
|
||||
}
|
||||
|
||||
func (tp *textPages) strobe() bool {
|
||||
// Thread safe. May just mark more dirties than needed.
|
||||
dirty := false
|
||||
for i := 0; i < 4; i++ {
|
||||
if tp.pages[i].dirty {
|
||||
dirty = true
|
||||
tp.pages[i].dirty = false
|
||||
}
|
||||
}
|
||||
return dirty
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue