Project reorg. Separate executables for the SDL and console versions

This commit is contained in:
Ivan Izaguirre 2019-06-01 17:11:25 +02:00
parent cf4a7e2115
commit f9d213a806
40 changed files with 222 additions and 235 deletions

View File

@ -4,10 +4,11 @@ import (
"bufio"
"encoding/binary"
"fmt"
"go6502/core6502"
"io"
"os"
"time"
"github.com/ivanizag/apple2/core6502"
)
// Apple2 represents all the components and state of the emulated machine

View File

@ -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.

View File

@ -1,8 +1,6 @@
package apple2
import (
"go6502/core6502"
)
import "github.com/ivanizag/apple2/core6502"
// NewApple2 instantiates an apple2
func NewApple2(romFile string, charRomFile string, clockMhz float64,
@ -70,22 +68,6 @@ func (a *Apple2) AddCardInOut(slot int) {
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
func (a *Apple2) SetKeyboardProvider(kb KeyboardProvider) {
a.io.setKeyboardProvider(kb)

BIN
apple2console/apple2console Executable file

Binary file not shown.

112
apple2console/main.go Normal file
View File

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

View File

@ -1,19 +1,19 @@
package main
package apple2
import (
"flag"
"go6502/apple2"
"go6502/apple2sdl"
"os"
)
func main() {
// MainApple is a device independant main. Video, keyboard and speaker won't be defined
func MainApple() *Apple2 {
romFile := flag.String(
"rom",
"apple2/romdumps/Apple2_Plus.rom",
"../romdumps/Apple2_Plus.rom",
"main rom file")
disk2RomFile := flag.String(
"diskRom",
"apple2/romdumps/DISK2.rom",
"../romdumps/DISK2.rom",
"rom file for the disk drive controller")
disk2Slot := flag.Int(
"disk2Slot",
@ -21,15 +21,15 @@ func main() {
"slot for the disk driver. -1 for none.")
diskImage := flag.String(
"disk",
"../dos33.dsk",
"../romdumps/dos33.dsk",
"file to load on the first disk drive")
cpuClock := flag.Float64(
"mhz",
apple2.CpuClockMhz,
CpuClockMhz,
"cpu speed in Mhz, use 0 for full speed. Use F5 to toggle.")
charRomFile := flag.String(
"charRom",
"apple2/romdumps/Apple2rev7CharGen.rom",
"../romdumps/Apple2rev7CharGen.rom",
"rom file for the disk drive controller")
languageCardSlot := flag.Int(
"languageCardSlot",
@ -39,15 +39,6 @@ func main() {
"saturnCardSlot",
-1,
"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",
false,
@ -71,13 +62,13 @@ func main() {
flag.Parse()
if *dumpChars {
cg := apple2.NewCharacterGenerator(*charRomFile)
cg := NewCharacterGenerator(*charRomFile)
cg.Dump()
return
os.Exit(0)
return nil
}
log := false
a := apple2.NewApple2(*romFile, *charRomFile, *cpuClock, !*mono, *fastDisk, *panicSS)
a := NewApple2(*romFile, *charRomFile, *cpuClock, !*mono, *fastDisk, *panicSS)
if *languageCardSlot >= 0 {
a.AddLanguageCard(*languageCardSlot)
}
@ -91,11 +82,13 @@ func main() {
//a.AddCardInOut(2)
//a.AddCardLogger(4)
if *useSdl {
a.ConfigureStdConsole(false, *stdoutScreen)
apple2sdl.SDLRun(a)
} else {
a.ConfigureStdConsole(true, true)
a.Run(log)
}
return a
/* if *useSDL {
a.ConfigureStdConsole(false, *stdoutScreen)
apple2sdl.SDLRun(a)
} else {
a.ConfigureStdConsole(true, true)
a.Run(log)
}
*/
}

BIN
apple2sdl/apple2sdl Executable file

Binary file not shown.

View File

@ -1,16 +1,20 @@
package apple2sdl
package main
import (
"unsafe"
"github.com/ivanizag/apple2"
"github.com/veandco/go-sdl2/sdl"
"go6502/apple2"
)
func main() {
a := apple2.MainApple()
SDLRun(a)
}
// SDLRun starts the Apple2 emulator on SDL
func SDLRun(a *apple2.Apple2) {
s := newSdlSpeaker()
s := newSDLSpeaker()
s.start()
window, renderer, err := sdl.CreateWindowAndRenderer(4*40*7, 4*24*8,

View File

@ -1,9 +1,9 @@
package apple2sdl
package main
import (
"go6502/apple2"
"unicode/utf8"
"github.com/ivanizag/apple2"
"github.com/veandco/go-sdl2/sdl"
)

View File

@ -1,4 +1,4 @@
package apple2sdl
package main
/*
typedef unsigned char Uint8;
@ -7,10 +7,10 @@ void SpeakerCallback(void *userdata, Uint8 *stream, int len);
import "C"
import (
"fmt"
"go6502/apple2"
"reflect"
"unsafe"
"github.com/ivanizag/apple2"
"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
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
s.clickChannel = make(chan uint64, bufferSize)
s.pendingClicks = make([]uint64, 0, bufferSize)
@ -57,7 +57,7 @@ func stateToLevel(state bool) C.Uint8 {
//export SpeakerCallback
func SpeakerCallback(userdata unsafe.Pointer, stream *C.Uint8, length C.int) {
s := theSdlSpeaker
s := theSDLSpeaker
if s == nil {
return
}
@ -152,7 +152,7 @@ func (s *sdlSpeaker) start() {
return
}
sdl.PauseAudio(false)
theSdlSpeaker = s
theSDLSpeaker = s
}
func (s *sdlSpeaker) close() {

View File

@ -15,7 +15,7 @@ be used in slot 0 anyway.
Note also that language cards for the Apple ][ had ROM on
board to replace the main board F8 ROM with Autostart. That
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,

BIN
romdumps/dos33.dsk Normal file

Binary file not shown.

View File

@ -1,8 +1,10 @@
package apple2
import (
"fmt"
"image"
"image/color"
"strings"
"time"
)
@ -73,3 +75,69 @@ func snapshotTextMode(a *Apple2, page int, mixMode bool, light color.Color) *ima
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)
}
}