2013-02-28 07:01:51 +00:00
|
|
|
// Simplest possible Apple II that will possibly boot. ~ (tilde) to quit.
|
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2016-09-10 00:49:21 +00:00
|
|
|
"flag"
|
2013-02-28 07:01:51 +00:00
|
|
|
"fmt"
|
2016-09-10 00:49:21 +00:00
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
2013-02-28 07:01:51 +00:00
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/nsf/termbox-go"
|
2013-03-28 04:49:38 +00:00
|
|
|
"github.com/zellyn/goapple2"
|
|
|
|
"github.com/zellyn/goapple2/util"
|
|
|
|
"github.com/zellyn/goapple2/videoscan"
|
2013-02-28 07:01:51 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// Mapping of screen bytes to character values
|
|
|
|
var AppleChars = "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_ !\"#$%&'()*+,-./0123456789:;<=>?"
|
|
|
|
|
2016-09-10 00:49:21 +00:00
|
|
|
var ColorFG = termbox.ColorGreen
|
|
|
|
var ColorBG = termbox.ColorBlack
|
|
|
|
|
2013-02-28 07:01:51 +00:00
|
|
|
// 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 {
|
2016-09-10 00:49:21 +00:00
|
|
|
return ch, ColorFG, ColorBG
|
2013-02-28 07:01:51 +00:00
|
|
|
}
|
2016-09-10 00:49:21 +00:00
|
|
|
return ch, ColorBG, ColorFG
|
2013-02-28 07:01:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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")
|
|
|
|
}
|
|
|
|
|
2013-03-28 04:49:38 +00:00
|
|
|
func ProcessEvents(events chan termbox.Event, a2 *goapple2.Apple2) bool {
|
2013-02-28 07:01:51 +00:00
|
|
|
select {
|
2013-03-28 04:49:38 +00:00
|
|
|
case ev := <-events:
|
2013-02-28 07:01:51 +00:00
|
|
|
if ev.Type == termbox.EventKey && ev.Ch == '~' {
|
2013-03-28 04:49:38 +00:00
|
|
|
return true
|
2013-02-28 07:01:51 +00:00
|
|
|
}
|
|
|
|
if ev.Type == termbox.EventKey {
|
|
|
|
if key, err := termboxToAppleKeyboard(ev); err == nil {
|
2013-03-30 05:43:15 +00:00
|
|
|
a2.Keypress(key)
|
2013-02-28 07:01:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
}
|
|
|
|
|
2013-03-28 04:49:38 +00:00
|
|
|
return false
|
2013-02-28 07:01:51 +00:00
|
|
|
}
|
|
|
|
|
2013-03-28 04:49:38 +00:00
|
|
|
type TextPlotter int
|
2013-02-28 07:01:51 +00:00
|
|
|
|
2013-03-28 04:49:38 +00:00
|
|
|
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)
|
2013-04-08 01:31:14 +00:00
|
|
|
}
|
|
|
|
func (p TextPlotter) OncePerFrame() {
|
|
|
|
termbox.Flush()
|
2013-02-28 07:01:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Run the emulator
|
2016-09-10 00:49:21 +00:00
|
|
|
func RunEmulator(file string) 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)
|
2013-04-08 01:31:14 +00:00
|
|
|
charRom := util.ReadSmallCharacterRomOrDie("../data/roms/apple2-chars.rom")
|
2013-03-28 04:49:38 +00:00
|
|
|
plotter := TextPlotter(0)
|
2016-09-10 00:49:21 +00:00
|
|
|
a2 := goapple2.NewApple2(plotter, rom, charRom, options...)
|
2013-03-28 04:49:38 +00:00
|
|
|
if err := termbox.Init(); err != nil {
|
2016-09-10 00:49:21 +00:00
|
|
|
return err
|
2013-02-28 07:01:51 +00:00
|
|
|
}
|
2013-03-28 04:49:38 +00:00
|
|
|
events := make(chan termbox.Event)
|
|
|
|
go func() {
|
2016-09-10 00:49:21 +00:00
|
|
|
if file != "" {
|
|
|
|
for _, ch := range "CALL 24576" {
|
|
|
|
a2.Keypress(byte(ch))
|
|
|
|
}
|
|
|
|
a2.Keypress(13)
|
|
|
|
}
|
2013-03-28 04:49:38 +00:00
|
|
|
for {
|
|
|
|
events <- termbox.PollEvent()
|
2013-02-28 07:01:51 +00:00
|
|
|
}
|
2013-03-28 04:49:38 +00:00
|
|
|
}()
|
|
|
|
for !ProcessEvents(events, a2) {
|
|
|
|
err := a2.Step()
|
2013-02-28 07:01:51 +00:00
|
|
|
if err != nil {
|
|
|
|
fmt.Println(err)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
time.Sleep(1 * time.Nanosecond) // So the keyboard-reading goroutines can run
|
|
|
|
}
|
2013-03-28 04:49:38 +00:00
|
|
|
termbox.Close()
|
2016-09-10 00:49:21 +00:00
|
|
|
return nil
|
2013-02-28 07:01:51 +00:00
|
|
|
}
|
|
|
|
|
2016-09-10 00:49:21 +00:00
|
|
|
var binfile = flag.String("binfile", "", "binary file to load at $6000 and CALL")
|
|
|
|
|
2013-02-28 07:01:51 +00:00
|
|
|
func main() {
|
2016-09-10 00:49:21 +00:00
|
|
|
flag.Parse()
|
|
|
|
if err := RunEmulator(*binfile); err != nil {
|
|
|
|
fmt.Fprintln(os.Stderr, err)
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
2013-02-28 07:01:51 +00:00
|
|
|
}
|