Project reorg. Separate executables for the SDL and console versions
This commit is contained in:
parent
cf4a7e2115
commit
f9d213a806
|
@ -4,10 +4,11 @@ import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
"go6502/core6502"
|
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/ivanizag/apple2/core6502"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Apple2 represents all the components and state of the emulated machine
|
// Apple2 represents all the components and state of the emulated machine
|
|
@ -1,173 +0,0 @@
|
||||||
package apple2
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
|
||||||
Uses the console standard input and output to interface with the machine.
|
|
||||||
Input is buffered until the next CR. This avoids working in place, a line
|
|
||||||
for input is added at the end.
|
|
||||||
Outut is done in place using ANSI escape sequences.
|
|
||||||
|
|
||||||
Those tricks do not work with the Apple2e ROM
|
|
||||||
*/
|
|
||||||
|
|
||||||
type ansiConsoleFrontend struct {
|
|
||||||
apple2 *Apple2
|
|
||||||
keyChannel chan uint8
|
|
||||||
extraLineFeeds chan int
|
|
||||||
stdinKeyboard bool
|
|
||||||
lastContent string
|
|
||||||
}
|
|
||||||
|
|
||||||
func newAnsiConsoleFrontend(a *Apple2, stdinKeyboard bool) *ansiConsoleFrontend {
|
|
||||||
var fe ansiConsoleFrontend
|
|
||||||
fe.apple2 = a
|
|
||||||
fe.stdinKeyboard = stdinKeyboard
|
|
||||||
return &fe
|
|
||||||
}
|
|
||||||
|
|
||||||
const refreshDelayMs = 100
|
|
||||||
|
|
||||||
func (fe *ansiConsoleFrontend) GetKey(strobed bool) (key uint8, ok bool) {
|
|
||||||
|
|
||||||
// Init the first time
|
|
||||||
if fe.keyChannel == nil {
|
|
||||||
stdinReader := func(c chan uint8) {
|
|
||||||
reader := bufio.NewReader(os.Stdin)
|
|
||||||
for {
|
|
||||||
key, err := reader.ReadByte()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c <- key
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fe.keyChannel = make(chan uint8, 100)
|
|
||||||
go stdinReader(fe.keyChannel)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !strobed {
|
|
||||||
// We must use the strobe to control the flow from stdin
|
|
||||||
ok = false
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case key = <-fe.keyChannel:
|
|
||||||
if key == 10 {
|
|
||||||
key = 13
|
|
||||||
if fe.extraLineFeeds != nil {
|
|
||||||
fe.extraLineFeeds <- 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ok = true
|
|
||||||
default:
|
|
||||||
ok = false
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func ansiCursorUp(steps int) string {
|
|
||||||
return fmt.Sprintf("\033[%vA", steps)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fe *ansiConsoleFrontend) textModeGoRoutine() {
|
|
||||||
fe.extraLineFeeds = make(chan int, 100)
|
|
||||||
|
|
||||||
fmt.Printf(strings.Repeat("\n", textLines+3))
|
|
||||||
for {
|
|
||||||
// Go up
|
|
||||||
content := ansiCursorUp(textLines + 3)
|
|
||||||
done := false
|
|
||||||
for !done {
|
|
||||||
select {
|
|
||||||
case lineFeeds := <-fe.extraLineFeeds:
|
|
||||||
content += ansiCursorUp(lineFeeds)
|
|
||||||
default:
|
|
||||||
done = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
content += "\n"
|
|
||||||
content += fmt.Sprintln(strings.Repeat("#", textColumns+4))
|
|
||||||
|
|
||||||
pageIndex := 0
|
|
||||||
if fe.apple2.io.isSoftSwitchActive(ioFlagSecondPage) {
|
|
||||||
pageIndex = 1
|
|
||||||
}
|
|
||||||
isAltText := fe.apple2.isApple2e && fe.apple2.io.isSoftSwitchActive(ioFlagAltChar)
|
|
||||||
|
|
||||||
for l := 0; l < textLines; l++ {
|
|
||||||
line := ""
|
|
||||||
for c := 0; c < textColumns; c++ {
|
|
||||||
char := getTextChar(fe.apple2, c, l, pageIndex)
|
|
||||||
line += textMemoryByteToString(char, isAltText)
|
|
||||||
}
|
|
||||||
content += fmt.Sprintf("# %v #\n", line)
|
|
||||||
}
|
|
||||||
|
|
||||||
content += fmt.Sprintln(strings.Repeat("#", textColumns+4))
|
|
||||||
if fe.stdinKeyboard {
|
|
||||||
content += "\033[KLine: "
|
|
||||||
}
|
|
||||||
|
|
||||||
if content != fe.lastContent {
|
|
||||||
fmt.Print(content)
|
|
||||||
fe.lastContent = content
|
|
||||||
}
|
|
||||||
time.Sleep(refreshDelayMs * time.Millisecond)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func textMemoryByteToString(value uint8, isAltCharSet bool) string {
|
|
||||||
// See https://en.wikipedia.org/wiki/Apple_II_character_set
|
|
||||||
// Supports the new lowercase characters in the Apple2e
|
|
||||||
// Only ascii from 0x20 to 0x5F is visible
|
|
||||||
topBits := value >> 6
|
|
||||||
isInverse := topBits == 0
|
|
||||||
isFlash := topBits == 1
|
|
||||||
if isFlash && isAltCharSet {
|
|
||||||
// On the Apple2e with lowercase chars there is not flash mode.
|
|
||||||
isFlash = false
|
|
||||||
isInverse = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if isAltCharSet {
|
|
||||||
value = value & 0x7F
|
|
||||||
} else {
|
|
||||||
value = value & 0x3F
|
|
||||||
}
|
|
||||||
|
|
||||||
if value < 0x20 {
|
|
||||||
value += 0x40
|
|
||||||
}
|
|
||||||
|
|
||||||
if value == 0x7f {
|
|
||||||
// DEL is full box
|
|
||||||
value = '_'
|
|
||||||
}
|
|
||||||
|
|
||||||
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, _ bool) string {
|
|
||||||
return fmt.Sprintf("%02x ", value)
|
|
||||||
}
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1,8 +1,6 @@
|
||||||
package apple2
|
package apple2
|
||||||
|
|
||||||
import (
|
import "github.com/ivanizag/apple2/core6502"
|
||||||
"go6502/core6502"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewApple2 instantiates an apple2
|
// NewApple2 instantiates an apple2
|
||||||
func NewApple2(romFile string, charRomFile string, clockMhz float64,
|
func NewApple2(romFile string, charRomFile string, clockMhz float64,
|
||||||
|
@ -70,22 +68,6 @@ func (a *Apple2) AddCardInOut(slot int) {
|
||||||
a.insertCard(&cardInOut{}, slot)
|
a.insertCard(&cardInOut{}, slot)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConfigureStdConsole uses stdin and stdout to interface with the Apple2
|
|
||||||
func (a *Apple2) ConfigureStdConsole(stdinKeyboard bool, stdoutScreen bool) {
|
|
||||||
if !stdinKeyboard && !stdoutScreen {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Init frontend
|
|
||||||
fe := newAnsiConsoleFrontend(a, stdinKeyboard)
|
|
||||||
if stdinKeyboard {
|
|
||||||
a.io.setKeyboardProvider(fe)
|
|
||||||
}
|
|
||||||
if stdoutScreen {
|
|
||||||
go fe.textModeGoRoutine()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetKeyboardProvider attaches an external keyboard provider
|
// SetKeyboardProvider attaches an external keyboard provider
|
||||||
func (a *Apple2) SetKeyboardProvider(kb KeyboardProvider) {
|
func (a *Apple2) SetKeyboardProvider(kb KeyboardProvider) {
|
||||||
a.io.setKeyboardProvider(kb)
|
a.io.setKeyboardProvider(kb)
|
Binary file not shown.
|
@ -0,0 +1,112 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
apple2 "github.com/ivanizag/apple2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
a := apple2.MainApple()
|
||||||
|
fe := &ansiConsoleFrontend{}
|
||||||
|
a.SetKeyboardProvider(fe)
|
||||||
|
go fe.textModeGoRoutine(a)
|
||||||
|
|
||||||
|
a.Run(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Uses the console standard input and output to interface with the machine.
|
||||||
|
Input is buffered until the next CR. This avoids working in place, a line
|
||||||
|
for input is added at the end.
|
||||||
|
Outut is done in place using ANSI escape sequences.
|
||||||
|
|
||||||
|
Those tricks do not work with the Apple2e ROM
|
||||||
|
*/
|
||||||
|
|
||||||
|
type ansiConsoleFrontend struct {
|
||||||
|
keyChannel chan uint8
|
||||||
|
extraLineFeeds chan int
|
||||||
|
lastContent string
|
||||||
|
}
|
||||||
|
|
||||||
|
const refreshDelayMs = 100
|
||||||
|
|
||||||
|
func (fe *ansiConsoleFrontend) GetKey(strobed bool) (key uint8, ok bool) {
|
||||||
|
|
||||||
|
// Init the first time
|
||||||
|
if fe.keyChannel == nil {
|
||||||
|
stdinReader := func(c chan uint8) {
|
||||||
|
reader := bufio.NewReader(os.Stdin)
|
||||||
|
for {
|
||||||
|
key, err := reader.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if key == 10 {
|
||||||
|
key = 13
|
||||||
|
if fe.extraLineFeeds != nil {
|
||||||
|
fe.extraLineFeeds <- 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c <- key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fe.keyChannel = make(chan uint8, 100)
|
||||||
|
go stdinReader(fe.keyChannel)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strobed {
|
||||||
|
// We must use the strobe to control the flow from stdin
|
||||||
|
ok = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case key = <-fe.keyChannel:
|
||||||
|
ok = true
|
||||||
|
default:
|
||||||
|
ok = false
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func ansiCursorUp(steps int) string {
|
||||||
|
return fmt.Sprintf("\033[%vA", steps)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fe *ansiConsoleFrontend) textModeGoRoutine(a *apple2.Apple2) {
|
||||||
|
fe.extraLineFeeds = make(chan int, 100)
|
||||||
|
|
||||||
|
fmt.Printf(strings.Repeat("\n", 24+3))
|
||||||
|
for {
|
||||||
|
// Go up
|
||||||
|
content := ansiCursorUp(24 + 3)
|
||||||
|
done := false
|
||||||
|
for !done {
|
||||||
|
select {
|
||||||
|
case lineFeeds := <-fe.extraLineFeeds:
|
||||||
|
content += ansiCursorUp(lineFeeds)
|
||||||
|
default:
|
||||||
|
done = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
content += apple2.DumpTextModeAnsi(a)
|
||||||
|
content += "\033[KLine: "
|
||||||
|
|
||||||
|
if content != fe.lastContent {
|
||||||
|
fmt.Print(content)
|
||||||
|
fe.lastContent = content
|
||||||
|
}
|
||||||
|
time.Sleep(refreshDelayMs * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,19 +1,19 @@
|
||||||
package main
|
package apple2
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
"go6502/apple2"
|
"os"
|
||||||
"go6502/apple2sdl"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
// MainApple is a device independant main. Video, keyboard and speaker won't be defined
|
||||||
|
func MainApple() *Apple2 {
|
||||||
romFile := flag.String(
|
romFile := flag.String(
|
||||||
"rom",
|
"rom",
|
||||||
"apple2/romdumps/Apple2_Plus.rom",
|
"../romdumps/Apple2_Plus.rom",
|
||||||
"main rom file")
|
"main rom file")
|
||||||
disk2RomFile := flag.String(
|
disk2RomFile := flag.String(
|
||||||
"diskRom",
|
"diskRom",
|
||||||
"apple2/romdumps/DISK2.rom",
|
"../romdumps/DISK2.rom",
|
||||||
"rom file for the disk drive controller")
|
"rom file for the disk drive controller")
|
||||||
disk2Slot := flag.Int(
|
disk2Slot := flag.Int(
|
||||||
"disk2Slot",
|
"disk2Slot",
|
||||||
|
@ -21,15 +21,15 @@ func main() {
|
||||||
"slot for the disk driver. -1 for none.")
|
"slot for the disk driver. -1 for none.")
|
||||||
diskImage := flag.String(
|
diskImage := flag.String(
|
||||||
"disk",
|
"disk",
|
||||||
"../dos33.dsk",
|
"../romdumps/dos33.dsk",
|
||||||
"file to load on the first disk drive")
|
"file to load on the first disk drive")
|
||||||
cpuClock := flag.Float64(
|
cpuClock := flag.Float64(
|
||||||
"mhz",
|
"mhz",
|
||||||
apple2.CpuClockMhz,
|
CpuClockMhz,
|
||||||
"cpu speed in Mhz, use 0 for full speed. Use F5 to toggle.")
|
"cpu speed in Mhz, use 0 for full speed. Use F5 to toggle.")
|
||||||
charRomFile := flag.String(
|
charRomFile := flag.String(
|
||||||
"charRom",
|
"charRom",
|
||||||
"apple2/romdumps/Apple2rev7CharGen.rom",
|
"../romdumps/Apple2rev7CharGen.rom",
|
||||||
"rom file for the disk drive controller")
|
"rom file for the disk drive controller")
|
||||||
languageCardSlot := flag.Int(
|
languageCardSlot := flag.Int(
|
||||||
"languageCardSlot",
|
"languageCardSlot",
|
||||||
|
@ -39,15 +39,6 @@ func main() {
|
||||||
"saturnCardSlot",
|
"saturnCardSlot",
|
||||||
-1,
|
-1,
|
||||||
"slot for the 256kb Saturn card. -1 for none")
|
"slot for the 256kb Saturn card. -1 for none")
|
||||||
|
|
||||||
useSdl := flag.Bool(
|
|
||||||
"sdl",
|
|
||||||
true,
|
|
||||||
"use SDL")
|
|
||||||
stdoutScreen := flag.Bool(
|
|
||||||
"stdout",
|
|
||||||
false,
|
|
||||||
"show the text screen on the standard output")
|
|
||||||
mono := flag.Bool(
|
mono := flag.Bool(
|
||||||
"mono",
|
"mono",
|
||||||
false,
|
false,
|
||||||
|
@ -71,13 +62,13 @@ func main() {
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
if *dumpChars {
|
if *dumpChars {
|
||||||
cg := apple2.NewCharacterGenerator(*charRomFile)
|
cg := NewCharacterGenerator(*charRomFile)
|
||||||
cg.Dump()
|
cg.Dump()
|
||||||
return
|
os.Exit(0)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
log := false
|
a := NewApple2(*romFile, *charRomFile, *cpuClock, !*mono, *fastDisk, *panicSS)
|
||||||
a := apple2.NewApple2(*romFile, *charRomFile, *cpuClock, !*mono, *fastDisk, *panicSS)
|
|
||||||
if *languageCardSlot >= 0 {
|
if *languageCardSlot >= 0 {
|
||||||
a.AddLanguageCard(*languageCardSlot)
|
a.AddLanguageCard(*languageCardSlot)
|
||||||
}
|
}
|
||||||
|
@ -91,11 +82,13 @@ func main() {
|
||||||
//a.AddCardInOut(2)
|
//a.AddCardInOut(2)
|
||||||
//a.AddCardLogger(4)
|
//a.AddCardLogger(4)
|
||||||
|
|
||||||
if *useSdl {
|
return a
|
||||||
a.ConfigureStdConsole(false, *stdoutScreen)
|
/* if *useSDL {
|
||||||
apple2sdl.SDLRun(a)
|
a.ConfigureStdConsole(false, *stdoutScreen)
|
||||||
} else {
|
apple2sdl.SDLRun(a)
|
||||||
a.ConfigureStdConsole(true, true)
|
} else {
|
||||||
a.Run(log)
|
a.ConfigureStdConsole(true, true)
|
||||||
}
|
a.Run(log)
|
||||||
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
Binary file not shown.
|
@ -1,16 +1,20 @@
|
||||||
package apple2sdl
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/ivanizag/apple2"
|
||||||
"github.com/veandco/go-sdl2/sdl"
|
"github.com/veandco/go-sdl2/sdl"
|
||||||
|
|
||||||
"go6502/apple2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
a := apple2.MainApple()
|
||||||
|
SDLRun(a)
|
||||||
|
}
|
||||||
|
|
||||||
// SDLRun starts the Apple2 emulator on SDL
|
// SDLRun starts the Apple2 emulator on SDL
|
||||||
func SDLRun(a *apple2.Apple2) {
|
func SDLRun(a *apple2.Apple2) {
|
||||||
s := newSdlSpeaker()
|
s := newSDLSpeaker()
|
||||||
s.start()
|
s.start()
|
||||||
|
|
||||||
window, renderer, err := sdl.CreateWindowAndRenderer(4*40*7, 4*24*8,
|
window, renderer, err := sdl.CreateWindowAndRenderer(4*40*7, 4*24*8,
|
|
@ -1,9 +1,9 @@
|
||||||
package apple2sdl
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"go6502/apple2"
|
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
|
"github.com/ivanizag/apple2"
|
||||||
"github.com/veandco/go-sdl2/sdl"
|
"github.com/veandco/go-sdl2/sdl"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package apple2sdl
|
package main
|
||||||
|
|
||||||
/*
|
/*
|
||||||
typedef unsigned char Uint8;
|
typedef unsigned char Uint8;
|
||||||
|
@ -7,10 +7,10 @@ void SpeakerCallback(void *userdata, Uint8 *stream, int len);
|
||||||
import "C"
|
import "C"
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"go6502/apple2"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/ivanizag/apple2"
|
||||||
"github.com/veandco/go-sdl2/sdl"
|
"github.com/veandco/go-sdl2/sdl"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -34,9 +34,9 @@ type sdlSpeaker struct {
|
||||||
I have not found a way to encode the pointer to sdlSpeaker on the userdata of
|
I have not found a way to encode the pointer to sdlSpeaker on the userdata of
|
||||||
the call to SpeakerCallback(). I use a global as workaround...
|
the call to SpeakerCallback(). I use a global as workaround...
|
||||||
*/
|
*/
|
||||||
var theSdlSpeaker *sdlSpeaker
|
var theSDLSpeaker *sdlSpeaker
|
||||||
|
|
||||||
func newSdlSpeaker() *sdlSpeaker {
|
func newSDLSpeaker() *sdlSpeaker {
|
||||||
var s sdlSpeaker
|
var s sdlSpeaker
|
||||||
s.clickChannel = make(chan uint64, bufferSize)
|
s.clickChannel = make(chan uint64, bufferSize)
|
||||||
s.pendingClicks = make([]uint64, 0, bufferSize)
|
s.pendingClicks = make([]uint64, 0, bufferSize)
|
||||||
|
@ -57,7 +57,7 @@ func stateToLevel(state bool) C.Uint8 {
|
||||||
|
|
||||||
//export SpeakerCallback
|
//export SpeakerCallback
|
||||||
func SpeakerCallback(userdata unsafe.Pointer, stream *C.Uint8, length C.int) {
|
func SpeakerCallback(userdata unsafe.Pointer, stream *C.Uint8, length C.int) {
|
||||||
s := theSdlSpeaker
|
s := theSDLSpeaker
|
||||||
if s == nil {
|
if s == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -152,7 +152,7 @@ func (s *sdlSpeaker) start() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
sdl.PauseAudio(false)
|
sdl.PauseAudio(false)
|
||||||
theSdlSpeaker = s
|
theSDLSpeaker = s
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *sdlSpeaker) close() {
|
func (s *sdlSpeaker) close() {
|
||||||
|
|
|
@ -15,7 +15,7 @@ be used in slot 0 anyway.
|
||||||
Note also that language cards for the Apple ][ had ROM on
|
Note also that language cards for the Apple ][ had ROM on
|
||||||
board to replace the main board F8 ROM with Autostart. That
|
board to replace the main board F8 ROM with Autostart. That
|
||||||
was not used/needed on the Apple ][+. As this emulates the
|
was not used/needed on the Apple ][+. As this emulates the
|
||||||
Apple ][+, it is not considered. For the PLus it is often
|
Apple ][+, it is not considered. For the Plus it is often
|
||||||
refered as Language card but it is really a 16 KB Ram card,
|
refered as Language card but it is really a 16 KB Ram card,
|
||||||
|
|
||||||
|
|
Binary file not shown.
|
@ -1,8 +1,10 @@
|
||||||
package apple2
|
package apple2
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
"image/color"
|
"image/color"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -73,3 +75,69 @@ func snapshotTextMode(a *Apple2, page int, mixMode bool, light color.Color) *ima
|
||||||
|
|
||||||
return img
|
return img
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DumpTextModeAnsi returns the text mode contents using ANSI escape codes
|
||||||
|
// for reverse and flash
|
||||||
|
func DumpTextModeAnsi(a *Apple2) string {
|
||||||
|
content := "\n"
|
||||||
|
content += fmt.Sprintln(strings.Repeat("#", textColumns+4))
|
||||||
|
|
||||||
|
pageIndex := 0
|
||||||
|
if a.io.isSoftSwitchActive(ioFlagSecondPage) {
|
||||||
|
pageIndex = 1
|
||||||
|
}
|
||||||
|
isAltText := a.isApple2e && a.io.isSoftSwitchActive(ioFlagAltChar)
|
||||||
|
|
||||||
|
for l := 0; l < textLines; l++ {
|
||||||
|
line := ""
|
||||||
|
for c := 0; c < textColumns; c++ {
|
||||||
|
char := getTextChar(a, c, l, pageIndex)
|
||||||
|
line += textMemoryByteToString(char, isAltText)
|
||||||
|
}
|
||||||
|
content += fmt.Sprintf("# %v #\n", line)
|
||||||
|
}
|
||||||
|
|
||||||
|
content += fmt.Sprintln(strings.Repeat("#", textColumns+4))
|
||||||
|
return content
|
||||||
|
}
|
||||||
|
|
||||||
|
func textMemoryByteToString(value uint8, isAltCharSet bool) string {
|
||||||
|
// See https://en.wikipedia.org/wiki/Apple_II_character_set
|
||||||
|
// Supports the new lowercase characters in the Apple2e
|
||||||
|
// Only ascii from 0x20 to 0x5F is visible
|
||||||
|
topBits := value >> 6
|
||||||
|
isInverse := topBits == 0
|
||||||
|
isFlash := topBits == 1
|
||||||
|
if isFlash && isAltCharSet {
|
||||||
|
// On the Apple2e with lowercase chars there is not flash mode.
|
||||||
|
isFlash = false
|
||||||
|
isInverse = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if isAltCharSet {
|
||||||
|
value = value & 0x7F
|
||||||
|
} else {
|
||||||
|
value = value & 0x3F
|
||||||
|
}
|
||||||
|
|
||||||
|
if value < 0x20 {
|
||||||
|
value += 0x40
|
||||||
|
}
|
||||||
|
|
||||||
|
if value == 0x7f {
|
||||||
|
// DEL is full box
|
||||||
|
value = '_'
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue