From 49ea32b84d34cf511a3d84cc68723f1193803b26 Mon Sep 17 00:00:00 2001 From: Ivan Izaguirre Date: Fri, 22 Feb 2019 00:21:17 +0100 Subject: [PATCH] Support for FLASH and INVERSE. Extract text display code to the ANSI frontend --- apple2/ansiConsoleFrontend.go | 112 ++++++++++++++++++++++++++++++---- apple2/apple2.go | 3 +- apple2/textPages.go | 100 ++++++------------------------ 3 files changed, 120 insertions(+), 95 deletions(-) diff --git a/apple2/ansiConsoleFrontend.go b/apple2/ansiConsoleFrontend.go index 5ed98c2..4f85d1d 100644 --- a/apple2/ansiConsoleFrontend.go +++ b/apple2/ansiConsoleFrontend.go @@ -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) +} diff --git a/apple2/apple2.go b/apple2/apple2.go index 50895a3..6c191da 100644 --- a/apple2/apple2.go +++ b/apple2/apple2.go @@ -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() } } diff --git a/apple2/textPages.go b/apple2/textPages.go index ab38adc..a730448 100644 --- a/apple2/textPages.go +++ b/apple2/textPages.go @@ -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 +}