mirror of
https://github.com/zellyn/goapple2.git
synced 2024-12-13 06:29:02 +00:00
Simplest possible text-mode only no-disk Apple II
This commit is contained in:
parent
3370c3b1f6
commit
b9603add19
192
texty/texty.go
Normal file
192
texty/texty.go
Normal file
@ -0,0 +1,192 @@
|
||||
// Simplest possible Apple II that will possibly boot. ~ (tilde) to quit.
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"time"
|
||||
|
||||
"github.com/nsf/termbox-go"
|
||||
"github.com/zellyn/go6502/asm"
|
||||
"github.com/zellyn/go6502/cpu"
|
||||
)
|
||||
|
||||
// Memory for the tests. Satisfies the cpu.Memory interface.
|
||||
type Apple2 struct {
|
||||
mem [65536]byte
|
||||
events chan termbox.Event
|
||||
done bool
|
||||
key byte // BUG(zellyn): make reads/writes atomic
|
||||
keys chan byte
|
||||
debug bool // Set true and close termbox to start tracing out instructions
|
||||
}
|
||||
|
||||
// Mapping of screen bytes to character values
|
||||
var AppleChars = "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_ !\"#$%&'()*+,-./0123456789:;<=>?"
|
||||
|
||||
// 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, termbox.ColorGreen, termbox.ColorBlack
|
||||
}
|
||||
return ch, termbox.ColorGreen, termbox.ColorBlack + termbox.AttrReverse
|
||||
}
|
||||
|
||||
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 (a2 *Apple2) Read(address uint16) byte {
|
||||
// Keyboard read
|
||||
if address == 0xC000 {
|
||||
return a2.key
|
||||
}
|
||||
if address == 0xC010 {
|
||||
a2.key &= 0x7F
|
||||
return 0 // BUG(zellyn): return proper value (keydown on IIe, not sure on II+)
|
||||
}
|
||||
return a2.mem[address]
|
||||
}
|
||||
|
||||
func (a2 *Apple2) Write(address uint16, value byte) {
|
||||
if address >= 0xD000 {
|
||||
termbox.Close()
|
||||
panic(fmt.Sprintln("Write to ROM at address $%04X", address))
|
||||
}
|
||||
if address == 0xC010 {
|
||||
// Clear keyboard strobe
|
||||
a2.key &= 0x7F
|
||||
}
|
||||
a2.mem[address] = value
|
||||
if !a2.debug && address >= 0x0400 && address < 0x0800 {
|
||||
offset := int(address - 0x0400)
|
||||
count := offset & 0x7f
|
||||
if count <= 119 {
|
||||
x := count % 40
|
||||
segment := offset / 128
|
||||
which40 := count / 40
|
||||
y := which40*8 + segment
|
||||
ch, fg, bg := translateToTermbox(value)
|
||||
termbox.SetCell(x+1, y+1, ch, fg, bg)
|
||||
termbox.Flush()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (a2 *Apple2) Init() error {
|
||||
if err := termbox.Init(); err != nil {
|
||||
return err
|
||||
}
|
||||
a2.events = make(chan termbox.Event)
|
||||
a2.keys = make(chan byte, 16)
|
||||
go func() {
|
||||
for {
|
||||
a2.events <- termbox.PollEvent()
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
func (a2 *Apple2) Close() {
|
||||
termbox.Close()
|
||||
}
|
||||
|
||||
func (a2 *Apple2) ProcessEvents() {
|
||||
select {
|
||||
case ev := <-a2.events:
|
||||
if ev.Type == termbox.EventKey && ev.Ch == '~' {
|
||||
a2.done = true
|
||||
}
|
||||
if ev.Type == termbox.EventKey {
|
||||
if key, err := termboxToAppleKeyboard(ev); err == nil {
|
||||
a2.keys <- key | 0x80
|
||||
}
|
||||
}
|
||||
default:
|
||||
}
|
||||
|
||||
if a2.key&0x80 == 0 {
|
||||
select {
|
||||
case key := <-a2.keys:
|
||||
a2.key = key
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cycle counter for the tests. Satisfies the cpu.Ticker interface.
|
||||
type CycleCount uint64
|
||||
|
||||
func (c *CycleCount) Tick() {
|
||||
*c += 1
|
||||
}
|
||||
|
||||
// printStatus prints out the current CPU instruction and register status.
|
||||
func printStatus(c cpu.Cpu, m *[65536]byte) {
|
||||
bytes, text, _ := asm.Disasm(c.PC(), m[c.PC()], m[c.PC()+1], m[c.PC()+2])
|
||||
fmt.Printf("$%04X: %s %s A=$%02X X=$%02X Y=$%02X SP=$%02X P=$%08b\n",
|
||||
c.PC(), bytes, text, c.A(), c.X(), c.Y(), c.SP(), c.P())
|
||||
}
|
||||
|
||||
// Run the emulator
|
||||
func RunEmulator() {
|
||||
bytes, err := ioutil.ReadFile("../data/roms/apple2+.rom")
|
||||
if err != nil {
|
||||
panic("Cannot read ROM file")
|
||||
}
|
||||
var a2 Apple2
|
||||
ROM_OFFSET := 0xD000
|
||||
copy(a2.mem[ROM_OFFSET:ROM_OFFSET+len(bytes)], bytes)
|
||||
a2.Init()
|
||||
var cc CycleCount
|
||||
c := cpu.NewCPU(&a2, &cc, cpu.VERSION_6502)
|
||||
c.Reset()
|
||||
for !a2.done {
|
||||
// // LIST
|
||||
// if c.PC() == 0xD6A5 {
|
||||
// termbox.Close()
|
||||
// a2.debug = true
|
||||
// }
|
||||
// // End of LIST
|
||||
// if c.PC() == 0xD729 {
|
||||
// break
|
||||
// }
|
||||
if !a2.debug {
|
||||
a2.ProcessEvents()
|
||||
}
|
||||
if a2.debug {
|
||||
printStatus(c, &a2.mem)
|
||||
}
|
||||
err := c.Step()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
break
|
||||
}
|
||||
time.Sleep(1 * time.Nanosecond) // So the keyboard-reading goroutines can run
|
||||
}
|
||||
if !a2.debug {
|
||||
a2.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
RunEmulator()
|
||||
}
|
Loading…
Reference in New Issue
Block a user