Support for FLASH and INVERSE. Extract text display code to the ANSI frontend

This commit is contained in:
Ivan Izaguirre 2019-02-22 00:21:17 +01:00
parent 5172919649
commit 49ea32b84d
3 changed files with 120 additions and 95 deletions

View File

@ -2,34 +2,43 @@ package apple2
import (
"bufio"
"fmt"
"os"
"strings"
"time"
)
type ansiConsoleFrontend struct {
keyChannel chan uint8
keyChannel chan uint8
extraLineFeeds chan int
}
func _stdinReader(c chan uint8) {
reader := bufio.NewReader(os.Stdin)
for {
byte, err := reader.ReadByte()
if err != nil {
panic(err)
}
c <- byte
}
}
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()
if err != nil {
panic(err)
}
c <- byte
}
}
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)
}

View File

@ -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()
}
}

View File

@ -1,13 +1,7 @@
package apple2
import (
"fmt"
"time"
)
type textPages struct {
lastDump int64
pages [4]textPage
pages [4]textPage
}
type textPage struct {
@ -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
}