goapple2/texty/texty.go

171 lines
3.9 KiB
Go

// Simplest possible Apple II that will possibly boot. ~ (tilde) to quit.
package main
import (
"flag"
"fmt"
"io/ioutil"
"os"
"strings"
"github.com/nsf/termbox-go"
"github.com/zellyn/goapple2"
"github.com/zellyn/goapple2/util"
"github.com/zellyn/goapple2/videoscan"
)
// Mapping of screen bytes to character values
var AppleChars = "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_ !\"#$%&'()*+,-./0123456789:;<=>?"
var ColorFG = termbox.ColorGreen
var ColorBG = termbox.ColorBlack
// Translate to termbox
func translateToTermbox(value byte) (char rune, fg, bg termbox.Attribute) {
// BUG(zellyn): change this to return char, MODE_ENUM.
ch := rune(AppleChars[value&0x3F])
if value&0x80 > 0 {
return ch, ColorFG, ColorBG
}
return ch, ColorBG, ColorFG
}
func termboxToAppleKeyboard(ev termbox.Event) (key byte, err error) {
if ev.Key > 0 && ev.Key <= 32 {
return byte(ev.Key), nil
}
if ev.Ch >= '!' && ev.Ch <= 'Z' || ev.Ch == '^' {
return byte(ev.Ch), nil
}
if ev.Ch >= 'a' && ev.Ch <= 'z' {
return byte(ev.Ch - 'a' + 'A'), nil
}
switch ev.Key {
case termbox.KeyBackspace2:
return 8, nil // backspace
case termbox.KeyArrowLeft:
return 8, nil // left arrow
case termbox.KeyArrowRight:
return 21, nil // right arrow
}
return 0, fmt.Errorf("hi")
}
func ProcessEvents(events chan termbox.Event, a2 *goapple2.Apple2) bool {
select {
case ev := <-events:
if ev.Type == termbox.EventKey && ev.Ch == '~' {
return true
}
if ev.Type == termbox.EventKey {
if key, err := termboxToAppleKeyboard(ev); err == nil {
a2.Keypress(key)
}
}
default:
}
return false
}
type TextPlotter int
func (p TextPlotter) Plot(data videoscan.PlotData) {
y := int(data.Row / 8)
x := int(data.Column)
value := data.RawData
ch, fg, bg := translateToTermbox(value)
termbox.SetCell(x+1, y+1, ch, fg, bg)
}
func (p TextPlotter) OncePerFrame() {
termbox.Flush()
}
// Run the emulator. If file is not empty, load it at address 0x6000,
// add a PCAction to quit if address 0 is called, clear the screen,
// call 0x6000, call 0, and dump the screen contents (minus trailing
// whitespace).
func RunEmulator(file string, quit bool) error {
var options []goapple2.Option
if file != "" {
ColorFG = termbox.ColorDefault
ColorBG = termbox.ColorDefault
bytes, err := ioutil.ReadFile(file)
if err != nil {
return err
}
options = append(options, goapple2.WithRAM(0x6000, bytes))
}
rom := util.ReadRomOrDie("../data/roms/apple2+.rom", 12288)
charRom := util.ReadSmallCharacterRomOrDie("../data/roms/apple2-chars.rom")
plotter := TextPlotter(0)
a2 := goapple2.NewApple2(plotter, rom, charRom, options...)
if err := termbox.Init(); err != nil {
return err
}
events := make(chan termbox.Event)
done := false
if file != "" {
a2.AddPCAction(0, goapple2.PCAction{
Type: goapple2.ActionCallback,
Callback: func() { done = true },
})
}
go func() {
if file != "" {
for _, ch := range "HOME:CALL 24576" {
a2.Keypress(byte(ch))
}
if quit {
for _, ch := range ":CALL 0" {
a2.Keypress(byte(ch))
}
}
a2.Keypress(13)
}
for {
events <- termbox.PollEvent()
}
}()
for !ProcessEvents(events, a2) && !done {
err := a2.Step()
if err != nil {
fmt.Println(err)
break
}
}
termbox.Close()
if file != "" {
dumpscreen(a2)
}
return nil
}
func dumpscreen(a2 *goapple2.Apple2) {
chars := []byte{}
for third := 0x400; third <= 0x450; third += 0x28 {
for base := third; base <= third+0x380; base += 0x80 {
for x := 0; x < 40; x++ {
ch := a2.RamRead(uint16(base + x))
chars = append(chars, ch&0x7f)
}
chars = append(chars, '\n')
}
}
screen := string(chars)
screen = strings.TrimRight(screen, "\r\n\t ")
fmt.Println(screen)
}
var binfile = flag.String("binfile", "", "binary file to load at $6000 and CALL")
var quit = flag.Bool("quit", false, "quit after running binary")
func main() {
flag.Parse()
if err := RunEmulator(*binfile, *quit); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}