package main import ( "bufio" "fmt" "image/gif" "os" "strconv" "strings" "time" "github.com/ivanizag/izapple2" "github.com/ivanizag/izapple2/screen" ) func main() { a := izapple2.MainApple() fe := &headLessFrontend{} fe.keyChannel = make(chan uint8, 200) a.SetKeyboardProvider(fe) go a.Start(true /*paused*/) inReader := bufio.NewReader(os.Stdin) done := false for !done { fmt.Print("* ") text, err := inReader.ReadString('\n') if err != nil { panic(err) } text = strings.TrimSpace(text) parts := strings.Split(text, " ") command := strings.ToLower(parts[0]) switch command { // General commands case "quit": a.SendCommand(izapple2.CommandKill) done = true case "help": fmt.Print(help) // Emulation control commands case "start": a.SendCommand(izapple2.CommandStart) spinWait(func() bool { return !a.IsPaused() }) case "pause": a.SendCommand(izapple2.CommandPause) spinWait(func() bool { return a.IsPaused() }) case "run": if len(parts) != 2 { fmt.Printf("Usage: run \n") } else if cycles, err := strconv.Atoi(parts[1]); err != nil { fmt.Printf("Usage: run \n") } else if !a.IsPaused() { fmt.Printf("Emulation is already running\n") } else { a.RequestFastMode() a.SetCycleBreakpoint(a.GetCycles() + uint64(cycles)*1000) a.SendCommand(izapple2.CommandStart) spinWait(func() bool { return a.BreakPoint() }) a.ReleaseFastMode() } case "cycle": fmt.Printf("%v\n", a.GetCycles()) case "reset": a.SendCommand(izapple2.CommandReset) // Keyboard related commands case "key": if len(parts) < 2 { fmt.Println("Usage: key ") } else { code, err := strconv.Atoi(parts[1]) if err != nil || code < 0 || code > 127 { fmt.Println("Usage: key ") } else { fe.putKey(uint8(code)) } } case "type": text := strings.Join(parts[1:], " ") for _, char := range text { fe.putKey(uint8(char)) } case "enter": fe.putKey(13) case "clearkeys": fe.clearKeyQueue() //Screen related commands case "text": fmt.Print(izapple2.DumpTextModeAnsi(a)) // Old: case "png": err := screen.SaveSnapshot(a, screen.ScreenModeNTSC, "snapshot.png") if err != nil { fmt.Printf("Error saving screen: %v.\n.", err) } else { fmt.Println("Saving screen 'snapshot.png'") } case "pngm": err := screen.SaveSnapshot(a, screen.ScreenModePlain, "snapshot.png") if err != nil { fmt.Printf("Error saving screen: %v.\n.", err) } else { fmt.Println("Saving screen 'snapshot.png'") } case "gif": SaveGif(a, "snapshot.gif") default: fmt.Println("Unknown command.") } } } var help = ` General commands: quit Quits help Prints this help Emulation control commands: start Runs the emulator stop Stops the emulator run Runs the emulator for thousand cycles at full speed. Waits until completed. cycle Prints the current cycle count reset Sends a reset to the emulator Keyboard related commands: key Queues the key to the emulator. is a decimal number from 0 to 127. type Queues the string characters to the emulator. No quotes for the argument, it can have spaces. enter Queues the enter key to the emulator. Alias for "key 13". clearkeys Clears the key queue. Screen related commands: text Prints the text mode screen. * png Stores the active screen to in PNG format as NTSC color. * pngm Same as "png" in monochrome. * gif Stores the running screen to in GIF format during with a per frame in 100ths of a second as NTSC color. If the emulators is stopped. It is run at full speed during and the stopped again. * gifm Same as "gif" in monochrome. ` /* TODO: floppy related commands: load disk.... joystick related commands: set paddle and button state, dump state */ func spinWait(f func() bool) { for !f() { time.Sleep(time.Millisecond * 1) } } func SaveGif(a *izapple2.Apple2, filename string) error { animation := gif.GIF{} delay := 50 * time.Millisecond delayHundredsS := 5 frames := 20 // 1 second planned := time.Now() for i := 0; i < frames; i++ { lapse := planned.Sub(time.Now()) fmt.Printf("%v\n", lapse) if lapse > 0 { time.Sleep(lapse) } fmt.Printf("%v\n", time.Now()) img := screen.SnapshotPaletted(a, screen.ScreenModeNTSC) animation.Image = append(animation.Image, img) animation.Delay = append(animation.Delay, delayHundredsS) planned = planned.Add(delay) } f, err := os.Create(filename) if err != nil { return err } defer f.Close() gif.EncodeAll(f, &animation) return nil } /* Uses the console to send commands and queries to an emulated machine. */ type headLessFrontend struct { keyChannel chan uint8 } func (fe *headLessFrontend) GetKey(strobed bool) (key uint8, ok bool) { 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 (fe *headLessFrontend) putKey(key uint8) { fe.keyChannel <- key } func (fe *headLessFrontend) clearKeyQueue() { empty := false for !empty { select { case <-fe.keyChannel: default: empty = true } } }