mirror of
https://github.com/zellyn/goapple2.git
synced 2024-11-30 00:49:24 +00:00
Working on first quick and dirty SDL version
This commit is contained in:
parent
95c9b2c3ee
commit
1d5e822fe8
41
goapple2.go
41
goapple2.go
@ -13,24 +13,39 @@ type Apple2 struct {
|
||||
keys chan byte
|
||||
plotter videoscan.Plotter
|
||||
scanner *videoscan.Scanner
|
||||
done bool
|
||||
tw TickWaiter
|
||||
}
|
||||
|
||||
// Cycle counter. Satisfies the cpu.Ticker interface.
|
||||
type CycleCount uint64
|
||||
|
||||
func (c *CycleCount) Tick() {
|
||||
*c += 1
|
||||
type TickWaiter struct {
|
||||
Wait chan byte
|
||||
}
|
||||
|
||||
func NewApple2(p videoscan.Plotter, rom []byte) *Apple2 {
|
||||
var cc CycleCount
|
||||
func (t TickWaiter) Tick() {
|
||||
<-t.Wait
|
||||
}
|
||||
|
||||
func NewTickWaiter() TickWaiter {
|
||||
return TickWaiter{Wait: make(chan byte)}
|
||||
}
|
||||
|
||||
func NewApple2(p videoscan.Plotter, rom []byte, charRom [2048]byte) *Apple2 {
|
||||
tw := NewTickWaiter()
|
||||
a2 := Apple2{
|
||||
keys: make(chan byte, 16),
|
||||
tw: tw,
|
||||
}
|
||||
copy(a2.mem[len(a2.mem)-len(rom):len(a2.mem)], rom)
|
||||
a2.scanner = videoscan.NewScanner(&a2, p, [2048]byte{})
|
||||
a2.cpu = cpu.NewCPU(&a2, &cc, cpu.VERSION_6502)
|
||||
a2.scanner = videoscan.NewScanner(&a2, p, charRom)
|
||||
a2.cpu = cpu.NewCPU(&a2, tw, cpu.VERSION_6502)
|
||||
a2.cpu.Reset()
|
||||
go func() {
|
||||
tw.Tick()
|
||||
for !a2.done {
|
||||
a2.cpu.Step()
|
||||
}
|
||||
}()
|
||||
return &a2
|
||||
}
|
||||
|
||||
@ -65,13 +80,15 @@ func (a2 *Apple2) Write(address uint16, value byte) {
|
||||
}
|
||||
|
||||
func (a2 *Apple2) Keypress(key byte) {
|
||||
a2.keys <- key
|
||||
a2.keys <- key | 0x80
|
||||
}
|
||||
|
||||
func (a2 *Apple2) Step() error {
|
||||
if err := a2.cpu.Step(); err != nil {
|
||||
return err
|
||||
}
|
||||
a2.tw.Wait <- 0
|
||||
a2.scanner.Scan1()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a2 *Apple2) Quit() {
|
||||
a2.done = true
|
||||
}
|
||||
|
1
sdl/run.sh
Executable file
1
sdl/run.sh
Executable file
@ -0,0 +1 @@
|
||||
SDL_VIDEODRIVER=x11 go run sdl.go
|
272
sdl/sdl.go
Normal file
272
sdl/sdl.go
Normal file
@ -0,0 +1,272 @@
|
||||
// Simplest possible Apple II that will possibly boot, in SDL. ~ (tilde) to quit.
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/0xe2-0x9a-0x9b/Go-SDL/sdl"
|
||||
"github.com/zellyn/goapple2"
|
||||
"github.com/zellyn/goapple2/util"
|
||||
"github.com/zellyn/goapple2/videoscan"
|
||||
)
|
||||
|
||||
const (
|
||||
BORDER_H = 20
|
||||
BORDER_W = 20
|
||||
SCREEN_WIDTH = 560 + 2*BORDER_W
|
||||
SCREEN_HEIGHT = 384 + 2*BORDER_H
|
||||
SCREEN_BPP = 32
|
||||
)
|
||||
|
||||
func plot(x, y uint, color uint32, screen *sdl.Surface) {
|
||||
x = x + BORDER_W
|
||||
y = y + BORDER_H
|
||||
screen.Lock()
|
||||
pixels := uintptr(screen.Pixels)
|
||||
offset := uintptr(y*uint(screen.Pitch) + x*(SCREEN_BPP/8))
|
||||
addr := pixels + offset
|
||||
*(*uint32)(unsafe.Pointer(addr)) = color
|
||||
screen.Unlock()
|
||||
}
|
||||
|
||||
func Init() (screen *sdl.Surface, err error) {
|
||||
|
||||
// Intialise the SDL subsystems
|
||||
if ok := sdl.Init(sdl.INIT_EVERYTHING); ok != 0 {
|
||||
return nil, fmt.Errorf("%s", sdl.GetError())
|
||||
}
|
||||
|
||||
// Set up the screen
|
||||
screen = sdl.SetVideoMode(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_BPP, sdl.SWSURFACE)
|
||||
|
||||
// If couldn't get the screen, return false
|
||||
if screen == nil {
|
||||
return nil, fmt.Errorf("%s", sdl.GetError())
|
||||
|
||||
}
|
||||
|
||||
// Set the window caption (window title)
|
||||
sdl.WM_SetCaption("Apple ][", "")
|
||||
|
||||
// Everything went fine
|
||||
return screen, nil
|
||||
}
|
||||
|
||||
const (
|
||||
M_NONE = iota
|
||||
M_SHIFT_OR_NONE
|
||||
M_SHIFT
|
||||
M_CTRL
|
||||
M_ANY
|
||||
)
|
||||
|
||||
type Key struct {
|
||||
sym uint32
|
||||
mod uint32
|
||||
}
|
||||
|
||||
var KeyToApple = map[Key]byte{
|
||||
Key{sdl.K_a, M_SHIFT_OR_NONE}: 'A',
|
||||
Key{sdl.K_b, M_SHIFT_OR_NONE}: 'B',
|
||||
Key{sdl.K_c, M_SHIFT_OR_NONE}: 'C',
|
||||
Key{sdl.K_d, M_SHIFT_OR_NONE}: 'D',
|
||||
Key{sdl.K_e, M_SHIFT_OR_NONE}: 'E',
|
||||
Key{sdl.K_f, M_SHIFT_OR_NONE}: 'F',
|
||||
Key{sdl.K_g, M_SHIFT_OR_NONE}: 'G',
|
||||
Key{sdl.K_h, M_SHIFT_OR_NONE}: 'H',
|
||||
Key{sdl.K_i, M_SHIFT_OR_NONE}: 'I',
|
||||
Key{sdl.K_j, M_SHIFT_OR_NONE}: 'J',
|
||||
Key{sdl.K_k, M_SHIFT_OR_NONE}: 'K',
|
||||
Key{sdl.K_l, M_SHIFT_OR_NONE}: 'L',
|
||||
Key{sdl.K_m, M_SHIFT_OR_NONE}: 'M',
|
||||
Key{sdl.K_n, M_SHIFT_OR_NONE}: 'N',
|
||||
Key{sdl.K_o, M_SHIFT_OR_NONE}: 'O',
|
||||
Key{sdl.K_p, M_SHIFT_OR_NONE}: 'P',
|
||||
Key{sdl.K_q, M_SHIFT_OR_NONE}: 'Q',
|
||||
Key{sdl.K_r, M_SHIFT_OR_NONE}: 'R',
|
||||
Key{sdl.K_s, M_SHIFT_OR_NONE}: 'S',
|
||||
Key{sdl.K_t, M_SHIFT_OR_NONE}: 'T',
|
||||
Key{sdl.K_u, M_SHIFT_OR_NONE}: 'U',
|
||||
Key{sdl.K_v, M_SHIFT_OR_NONE}: 'V',
|
||||
Key{sdl.K_w, M_SHIFT_OR_NONE}: 'W',
|
||||
Key{sdl.K_x, M_SHIFT_OR_NONE}: 'X',
|
||||
Key{sdl.K_y, M_SHIFT_OR_NONE}: 'Y',
|
||||
Key{sdl.K_z, M_SHIFT_OR_NONE}: 'Z',
|
||||
|
||||
Key{sdl.K_a, M_CTRL}: 1,
|
||||
Key{sdl.K_b, M_CTRL}: 2,
|
||||
Key{sdl.K_c, M_CTRL}: 3,
|
||||
Key{sdl.K_d, M_CTRL}: 4,
|
||||
Key{sdl.K_e, M_CTRL}: 5,
|
||||
Key{sdl.K_f, M_CTRL}: 6,
|
||||
Key{sdl.K_g, M_CTRL}: 7,
|
||||
Key{sdl.K_h, M_CTRL}: 8,
|
||||
Key{sdl.K_i, M_CTRL}: 9,
|
||||
Key{sdl.K_j, M_CTRL}: 10,
|
||||
Key{sdl.K_k, M_CTRL}: 11,
|
||||
Key{sdl.K_l, M_CTRL}: 12,
|
||||
Key{sdl.K_m, M_CTRL}: 13,
|
||||
Key{sdl.K_n, M_CTRL}: 14,
|
||||
Key{sdl.K_o, M_CTRL}: 15,
|
||||
Key{sdl.K_p, M_CTRL}: 16,
|
||||
Key{sdl.K_q, M_CTRL}: 17,
|
||||
Key{sdl.K_r, M_CTRL}: 18,
|
||||
Key{sdl.K_s, M_CTRL}: 19,
|
||||
Key{sdl.K_t, M_CTRL}: 20,
|
||||
Key{sdl.K_u, M_CTRL}: 21,
|
||||
Key{sdl.K_v, M_CTRL}: 22,
|
||||
Key{sdl.K_w, M_CTRL}: 23,
|
||||
Key{sdl.K_x, M_CTRL}: 24,
|
||||
Key{sdl.K_y, M_CTRL}: 25,
|
||||
Key{sdl.K_z, M_CTRL}: 26,
|
||||
|
||||
Key{sdl.K_0, M_NONE}: '0',
|
||||
Key{sdl.K_1, M_NONE}: '1',
|
||||
Key{sdl.K_2, M_NONE}: '2',
|
||||
Key{sdl.K_3, M_NONE}: '3',
|
||||
Key{sdl.K_4, M_NONE}: '4',
|
||||
Key{sdl.K_5, M_NONE}: '5',
|
||||
Key{sdl.K_6, M_NONE}: '6',
|
||||
Key{sdl.K_7, M_NONE}: '7',
|
||||
Key{sdl.K_8, M_NONE}: '8',
|
||||
Key{sdl.K_9, M_NONE}: '9',
|
||||
|
||||
Key{sdl.K_0, M_SHIFT}: '!',
|
||||
Key{sdl.K_1, M_SHIFT}: '@',
|
||||
Key{sdl.K_2, M_SHIFT}: '#',
|
||||
Key{sdl.K_3, M_SHIFT}: '$',
|
||||
Key{sdl.K_4, M_SHIFT}: '%',
|
||||
Key{sdl.K_5, M_SHIFT}: '^',
|
||||
Key{sdl.K_6, M_SHIFT}: '&',
|
||||
Key{sdl.K_7, M_SHIFT}: '*',
|
||||
Key{sdl.K_8, M_SHIFT}: '(',
|
||||
Key{sdl.K_9, M_SHIFT}: ')',
|
||||
|
||||
Key{sdl.K_MINUS, M_NONE}: '-',
|
||||
Key{sdl.K_MINUS, M_SHIFT}: '_',
|
||||
Key{sdl.K_EQUALS, M_NONE}: '=',
|
||||
Key{sdl.K_EQUALS, M_SHIFT}: '+',
|
||||
Key{sdl.K_LEFTBRACKET, M_NONE}: '[',
|
||||
Key{sdl.K_RIGHTBRACKET, M_NONE}: ']',
|
||||
Key{sdl.K_SEMICOLON, M_NONE}: ';',
|
||||
Key{sdl.K_SEMICOLON, M_SHIFT}: ':',
|
||||
Key{sdl.K_QUOTE, M_NONE}: '\'',
|
||||
Key{sdl.K_QUOTE, M_SHIFT}: '"',
|
||||
Key{sdl.K_COMMA, M_NONE}: ',',
|
||||
Key{sdl.K_COMMA, M_SHIFT}: '<',
|
||||
Key{sdl.K_PERIOD, M_NONE}: '.',
|
||||
Key{sdl.K_PERIOD, M_SHIFT}: '>',
|
||||
Key{sdl.K_SLASH, M_NONE}: '/',
|
||||
Key{sdl.K_SLASH, M_SHIFT}: '?',
|
||||
Key{sdl.K_BACKSLASH, M_NONE}: '\\',
|
||||
|
||||
Key{sdl.K_SPACE, M_SHIFT_OR_NONE}: ' ',
|
||||
Key{sdl.K_RETURN, M_ANY}: 13,
|
||||
|
||||
Key{sdl.K_BACKSPACE, M_ANY}: 8,
|
||||
Key{sdl.K_LEFT, M_ANY}: 8,
|
||||
Key{sdl.K_RIGHT, M_ANY}: 21,
|
||||
}
|
||||
|
||||
func sdlToAppleKeyboard(k sdl.Keysym) (key byte, err error) {
|
||||
if b, ok := KeyToApple[Key{k.Sym, M_ANY}]; ok {
|
||||
return b, nil
|
||||
}
|
||||
switch k.Mod {
|
||||
case sdl.KMOD_NONE:
|
||||
if b, ok := KeyToApple[Key{k.Sym, M_NONE}]; ok {
|
||||
return b, nil
|
||||
}
|
||||
if b, ok := KeyToApple[Key{k.Sym, M_SHIFT_OR_NONE}]; ok {
|
||||
return b, nil
|
||||
}
|
||||
case sdl.KMOD_LSHIFT, sdl.KMOD_RSHIFT, sdl.KMOD_LSHIFT | sdl.KMOD_RSHIFT:
|
||||
if b, ok := KeyToApple[Key{k.Sym, M_SHIFT}]; ok {
|
||||
return b, nil
|
||||
}
|
||||
if b, ok := KeyToApple[Key{k.Sym, M_SHIFT_OR_NONE}]; ok {
|
||||
return b, nil
|
||||
}
|
||||
case sdl.KMOD_LCTRL, sdl.KMOD_RCTRL, sdl.KMOD_LCTRL | sdl.KMOD_RCTRL:
|
||||
if b, ok := KeyToApple[Key{k.Sym, M_CTRL}]; ok {
|
||||
return b, nil
|
||||
}
|
||||
}
|
||||
return 0, fmt.Errorf("hi")
|
||||
}
|
||||
|
||||
func ProcessEvents(events <-chan interface{}, a2 *goapple2.Apple2) (done bool) {
|
||||
select {
|
||||
case _event := <-events:
|
||||
switch e := _event.(type) {
|
||||
case sdl.QuitEvent:
|
||||
return true
|
||||
case sdl.KeyboardEvent:
|
||||
if e.Type == sdl.KEYDOWN {
|
||||
if e.Keysym.Sym == sdl.K_F1 {
|
||||
return true
|
||||
}
|
||||
if e.Keysym.Mod == sdl.KMOD_LCTRL && e.Keysym.Sym == sdl.K_LEFTBRACKET {
|
||||
return true
|
||||
}
|
||||
if key, err := sdlToAppleKeyboard(e.Keysym); err == nil {
|
||||
a2.Keypress(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
// Nothing to do here
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type SdlPlotter struct {
|
||||
screen *sdl.Surface
|
||||
}
|
||||
|
||||
func (s SdlPlotter) Plot(pd videoscan.PlotData) {
|
||||
y := uint(pd.Row)
|
||||
x := uint(pd.Column) * 14
|
||||
data := pd.Data
|
||||
for i := uint(0); i < 14; i++ {
|
||||
color1 := uint32(0)
|
||||
color2 := uint32(0)
|
||||
if data&1 > 0 {
|
||||
color1 = 0x00ff00
|
||||
color2 = 0x008800
|
||||
}
|
||||
plot(x+i, y*2+0, color1, s.screen)
|
||||
plot(x+i, y*2+1, color2, s.screen)
|
||||
data >>= 1
|
||||
}
|
||||
if pd.Column == 39 && y == 191 {
|
||||
s.screen.Flip()
|
||||
}
|
||||
}
|
||||
|
||||
// Run the emulator
|
||||
func RunEmulator() {
|
||||
rom := util.ReadRomOrDie("../data/roms/apple2+.rom")
|
||||
charRom := util.ReadSmallCharacterRomOrDie("../data/roms/apple2-chars.rom")
|
||||
screen, err := Init()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
plotter := SdlPlotter{screen}
|
||||
a2 := goapple2.NewApple2(plotter, rom, charRom)
|
||||
for !ProcessEvents(sdl.Events, a2) {
|
||||
err := a2.Step()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
break
|
||||
}
|
||||
time.Sleep(1 * time.Nanosecond) // So the keyboard-reading goroutines can run
|
||||
}
|
||||
sdl.Quit()
|
||||
}
|
||||
|
||||
func main() {
|
||||
RunEmulator()
|
||||
}
|
@ -45,23 +45,6 @@ func termboxToAppleKeyboard(ev termbox.Event) (key byte, err error) {
|
||||
return 0, fmt.Errorf("hi")
|
||||
}
|
||||
|
||||
// func Write(address uint16, value byte) {
|
||||
// a2.mem[address] = value
|
||||
// if 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 ProcessEvents(events chan termbox.Event, a2 *goapple2.Apple2) bool {
|
||||
select {
|
||||
case ev := <-events:
|
||||
@ -70,7 +53,7 @@ func ProcessEvents(events chan termbox.Event, a2 *goapple2.Apple2) bool {
|
||||
}
|
||||
if ev.Type == termbox.EventKey {
|
||||
if key, err := termboxToAppleKeyboard(ev); err == nil {
|
||||
a2.Keypress(key | 0x80)
|
||||
a2.Keypress(key)
|
||||
}
|
||||
}
|
||||
default:
|
||||
@ -96,7 +79,8 @@ func (p TextPlotter) Plot(data videoscan.PlotData) {
|
||||
func RunEmulator() {
|
||||
rom := util.ReadRomOrDie("../data/roms/apple2+.rom")
|
||||
plotter := TextPlotter(0)
|
||||
a2 := goapple2.NewApple2(plotter, rom)
|
||||
var charRom [2048]byte
|
||||
a2 := goapple2.NewApple2(plotter, rom, charRom)
|
||||
if err := termbox.Init(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
16
util/util.go
16
util/util.go
@ -1,6 +1,7 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
@ -11,3 +12,18 @@ func ReadRomOrDie(filename string) []byte {
|
||||
}
|
||||
return bytes
|
||||
}
|
||||
|
||||
func ReadSmallCharacterRomOrDie(filename string) [2048]byte {
|
||||
bytes := ReadRomOrDie(filename)
|
||||
if len(bytes) != 512 {
|
||||
panic(fmt.Sprintf("Got %d bytes (not 512) from file '%s'", len(bytes), filename))
|
||||
}
|
||||
var value [2048]byte
|
||||
for i, b := range bytes {
|
||||
value[i] = (b ^ 0xff) & 0x7f
|
||||
value[i+512] = b | 0x80
|
||||
value[i+1024] = b & 0x7f
|
||||
value[i+1536] = b & 0x7f
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user