remove candidate, add limit frame, add multi game

This commit is contained in:
trichimtrich 2019-04-06 05:21:16 +08:00
parent 81444a405b
commit 1048846266
22 changed files with 1214 additions and 1164 deletions

View file

@ -1,87 +1,87 @@
package ui
import (
"image"
"log"
"time"
"github.com/giongto35/game-online/nes"
"github.com/giongto35/game-online/webrtc"
)
type View interface {
Enter()
Exit()
Update(t, dt float64)
}
type Director struct {
// audio *Audio
view View
timestamp float64
imageChannel chan *image.RGBA
inputChannel chan int
webRTC *webrtc.WebRTC
}
func NewDirector(imageChannel chan *image.RGBA, inputChannel chan int, webRTC *webrtc.WebRTC) *Director {
// func NewDirector(audio *Audio, imageChannel chan *image.RGBA, inputChannel chan int) *Director {
director := Director{}
// director.audio = audio
director.imageChannel = imageChannel
director.inputChannel = inputChannel
director.webRTC = webRTC
return &director
}
func (d *Director) SetView(view View) {
if d.view != nil {
d.view.Exit()
}
d.view = view
if d.view != nil {
d.view.Enter()
}
d.timestamp = float64(time.Now().Nanosecond()) / float64(time.Second)
}
func (d *Director) Step() {
//gl.Clear(gl.COLOR_BUFFER_BIT)
timestamp := float64(time.Now().Nanosecond()) / float64(time.Second)
dt := timestamp - d.timestamp
d.timestamp = timestamp
if d.view != nil {
d.view.Update(timestamp, dt)
}
}
func (d *Director) Start(paths []string) {
if len(paths) == 1 {
d.PlayGame(paths[0])
}
d.Run()
}
func (d *Director) Run() {
for {
// quit game
if d.webRTC.IsClosed() {
break
}
d.Step()
}
d.SetView(nil)
}
func (d *Director) PlayGame(path string) {
hash, err := hashFile(path)
if err != nil {
log.Fatalln(err)
}
console, err := nes.NewConsole(path)
if err != nil {
log.Fatalln(err)
}
d.SetView(NewGameView(d, console, path, hash, d.imageChannel, d.inputChannel))
}
package ui
import (
"image"
"log"
"time"
"github.com/giongto35/game-online/nes"
"github.com/giongto35/game-online/webrtc"
)
type View interface {
Enter()
Exit()
Update(t, dt float64)
}
type Director struct {
// audio *Audio
view View
timestamp float64
imageChannel chan *image.RGBA
inputChannel chan int
webRTC *webrtc.WebRTC
}
func NewDirector(imageChannel chan *image.RGBA, inputChannel chan int, webRTC *webrtc.WebRTC) *Director {
// func NewDirector(audio *Audio, imageChannel chan *image.RGBA, inputChannel chan int) *Director {
director := Director{}
// director.audio = audio
director.imageChannel = imageChannel
director.inputChannel = inputChannel
director.webRTC = webRTC
return &director
}
func (d *Director) SetView(view View) {
if d.view != nil {
d.view.Exit()
}
d.view = view
if d.view != nil {
d.view.Enter()
}
d.timestamp = float64(time.Now().Nanosecond()) / float64(time.Second)
}
func (d *Director) Step() {
//gl.Clear(gl.COLOR_BUFFER_BIT)
timestamp := float64(time.Now().Nanosecond()) / float64(time.Second)
dt := timestamp - d.timestamp
d.timestamp = timestamp
if d.view != nil {
d.view.Update(timestamp, dt)
}
}
func (d *Director) Start(paths []string) {
if len(paths) == 1 {
d.PlayGame(paths[0])
}
d.Run()
}
func (d *Director) Run() {
for {
// quit game
if d.webRTC.IsClosed() {
break
}
d.Step()
}
d.SetView(nil)
}
func (d *Director) PlayGame(path string) {
hash, err := hashFile(path)
if err != nil {
log.Fatalln(err)
}
console, err := nes.NewConsole(path)
if err != nil {
log.Fatalln(err)
}
d.SetView(NewGameView(d, console, path, hash, d.imageChannel, d.inputChannel))
}

View file

@ -1,106 +1,117 @@
package ui
import (
"image"
"fmt"
// "strconv"
"github.com/giongto35/game-online/nes"
)
const padding = 0
type GameView struct {
director *Director
console *nes.Console
title string
hash string
record bool
frames []image.Image
keyPressed [8]bool
imageChannel chan *image.RGBA
inputChannel chan int
}
func NewGameView(director *Director, console *nes.Console, title, hash string, imageChannel chan *image.RGBA, inputChannel chan int) View {
gameview := &GameView{director, console, title, hash, false, nil, [8]bool{false}, imageChannel, inputChannel}
go gameview.ListenToInputChannel()
return gameview
}
func (view *GameView) ListenToInputChannel() {
for {
key := <-view.inputChannel
s := fmt.Sprintf("%.8b", key)
for i := 0; i < len(s); i++ {
if s[i] == '1' {
view.keyPressed[i] = true
} else {
view.keyPressed[i] = false
}
}
}
}
func (view *GameView) Enter() {
// view.console.SetAudioChannel(view.director.audio.channel)
// view.console.SetAudioSampleRate(view.director.audio.sampleRate)
// load state
if err := view.console.LoadState(savePath(view.hash)); err == nil {
return
} else {
view.console.Reset()
}
// load sram
cartridge := view.console.Cartridge
if cartridge.Battery != 0 {
if sram, err := readSRAM(sramPath(view.hash)); err == nil {
cartridge.SRAM = sram
}
}
}
func (view *GameView) Exit() {
// view.console.SetAudioChannel(nil)
// view.console.SetAudioSampleRate(0)
// save sram
cartridge := view.console.Cartridge
if cartridge.Battery != 0 {
writeSRAM(sramPath(view.hash), cartridge.SRAM)
}
// save state
view.console.SaveState(savePath(view.hash))
}
func (view *GameView) Update(t, dt float64) {
if dt > 1 {
dt = 0
}
console := view.console
//updateControllers(window, console)
view.updateControllers()
//fmt.Println(console.Buffer())
console.StepSeconds(dt)
view.imageChannel <- console.Buffer()
if view.record {
view.frames = append(view.frames, copyImage(console.Buffer()))
}
}
func (view *GameView) updateControllers() {
// TODO: switch case
// var buttons [8]bool
// buttons[nes.ButtonLeft] = view.keyPressed[37]
// buttons[nes.ButtonUp] = view.keyPressed[38]
// buttons[nes.ButtonRight] = view.keyPressed[39]
// buttons[nes.ButtonDown] = view.keyPressed[40]
// buttons[nes.ButtonA] = view.keyPressed[32]
// buttons[nes.ButtonB] = view.keyPressed[17]
// buttons[nes.ButtonStart] = view.keyPressed[13]
// buttons[nes.ButtonSelect] = view.keyPressed[16]
// view.console.Controller1.SetButtons(buttons)
view.console.Controller1.SetButtons(view.keyPressed)
}
package ui
import (
"image"
"fmt"
"time"
"github.com/giongto35/game-online/nes"
)
const padding = 0
type GameView struct {
director *Director
console *nes.Console
title string
hash string
record bool
frames []image.Image
keyPressed [8]bool
imageChannel chan *image.RGBA
inputChannel chan int
nanotime int64
}
func NewGameView(director *Director, console *nes.Console, title, hash string, imageChannel chan *image.RGBA, inputChannel chan int) View {
gameview := &GameView{director, console, title, hash, false, nil, [8]bool{false}, imageChannel, inputChannel, time.Now().UnixNano()}
go gameview.ListenToInputChannel()
return gameview
}
func (view *GameView) ListenToInputChannel() {
for {
key := <-view.inputChannel
s := fmt.Sprintf("%.8b", key)
for i := 0; i < len(s); i++ {
if s[i] == '1' {
view.keyPressed[i] = true
} else {
view.keyPressed[i] = false
}
}
}
}
func (view *GameView) Enter() {
// view.console.SetAudioChannel(view.director.audio.channel)
// view.console.SetAudioSampleRate(view.director.audio.sampleRate)
// load state
if err := view.console.LoadState(savePath(view.hash)); err == nil {
return
} else {
view.console.Reset()
}
// load sram
cartridge := view.console.Cartridge
if cartridge.Battery != 0 {
if sram, err := readSRAM(sramPath(view.hash)); err == nil {
cartridge.SRAM = sram
}
}
}
func (view *GameView) Exit() {
// view.console.SetAudioChannel(nil)
// view.console.SetAudioSampleRate(0)
// save sram
cartridge := view.console.Cartridge
if cartridge.Battery != 0 {
writeSRAM(sramPath(view.hash), cartridge.SRAM)
}
// save state
view.console.SaveState(savePath(view.hash))
}
func (view *GameView) Update(t, dt float64) {
if dt > 1 {
dt = 0
}
console := view.console
//updateControllers(window, console)
view.updateControllers()
//fmt.Println(console.Buffer())
console.StepSeconds(dt)
// fps to set frame
n := time.Now().UnixNano()
if n - view.nanotime > 1000000000 / 100000 {
view.nanotime = n
view.imageChannel <- console.Buffer()
}
if view.record {
view.frames = append(view.frames, copyImage(console.Buffer()))
}
}
func (view *GameView) updateControllers() {
// TODO: switch case
// var buttons [8]bool
// buttons[nes.ButtonLeft] = view.keyPressed[37]
// buttons[nes.ButtonUp] = view.keyPressed[38]
// buttons[nes.ButtonRight] = view.keyPressed[39]
// buttons[nes.ButtonDown] = view.keyPressed[40]
// buttons[nes.ButtonA] = view.keyPressed[32]
// buttons[nes.ButtonB] = view.keyPressed[17]
// buttons[nes.ButtonStart] = view.keyPressed[13]
// buttons[nes.ButtonSelect] = view.keyPressed[16]
// view.console.Controller1.SetButtons(buttons)
view.console.Controller1.SetButtons(view.keyPressed)
}

View file

@ -1,42 +1,42 @@
package ui
import (
"image"
// "log"
"runtime"
// "github.com/gordonklaus/portaudio"
"github.com/giongto35/game-online/webrtc"
)
const (
width = 256
height = 240
scale = 3
title = "NES"
)
func init() {
// we need a parallel OS thread to avoid audio stuttering
runtime.GOMAXPROCS(2)
// we need to keep OpenGL calls on a single thread
runtime.LockOSThread()
}
func Run(paths []string, imageChannel chan *image.RGBA, inputChannel chan int, webRTC *webrtc.WebRTC) {
// initialize audio
// portaudio.Initialize()
// defer portaudio.Terminate()
// audio := NewAudio()
// if err := audio.Start(); err != nil {
// log.Fatalln(err)
// }
// defer audio.Stop()
// run director
director := NewDirector(imageChannel, inputChannel, webRTC)
// director := NewDirector(audio, imageChannel, inputChannel)
director.Start(paths)
}
package ui
import (
"image"
// "log"
"runtime"
// "github.com/gordonklaus/portaudio"
"github.com/giongto35/game-online/webrtc"
)
const (
width = 256
height = 240
scale = 3
title = "NES"
)
func init() {
// we need a parallel OS thread to avoid audio stuttering
// runtime.GOMAXPROCS(2)
// we need to keep OpenGL calls on a single thread
runtime.LockOSThread()
}
func Run(paths []string, imageChannel chan *image.RGBA, inputChannel chan int, webRTC *webrtc.WebRTC) {
// initialize audio
// portaudio.Initialize()
// defer portaudio.Terminate()
// audio := NewAudio()
// if err := audio.Start(); err != nil {
// log.Fatalln(err)
// }
// defer audio.Stop()
// run director
director := NewDirector(imageChannel, inputChannel, webRTC)
// director := NewDirector(audio, imageChannel, inputChannel)
director.Start(paths)
}

View file

@ -1,153 +1,153 @@
package ui
import (
"crypto/md5"
"encoding/binary"
"fmt"
"image"
"image/color"
"image/draw"
"image/gif"
"image/png"
"io/ioutil"
"log"
"os"
"os/user"
"path"
"github.com/giongto35/game-online/nes"
)
var homeDir string
func init() {
u, err := user.Current()
if err != nil {
log.Fatalln(err)
}
homeDir = u.HomeDir
}
func thumbnailURL(hash string) string {
return "http://www.michaelfogleman.com/static/nes/" + hash + ".png"
}
func thumbnailPath(hash string) string {
return homeDir + "/.nes/thumbnail/" + hash + ".png"
}
func sramPath(hash string) string {
return homeDir + "/.nes/sram/" + hash + ".dat"
}
func savePath(hash string) string {
return homeDir + "/.nes/save/" + hash + ".dat"
}
func combineButtons(a, b [8]bool) [8]bool {
var result [8]bool
for i := 0; i < 8; i++ {
result[i] = a[i] || b[i]
}
return result
}
func hashFile(path string) (string, error) {
data, err := ioutil.ReadFile(path)
if err != nil {
return "", err
}
return fmt.Sprintf("%x", md5.Sum(data)), nil
}
func copyImage(src image.Image) *image.RGBA {
dst := image.NewRGBA(src.Bounds())
draw.Draw(dst, dst.Rect, src, image.ZP, draw.Src)
return dst
}
func loadPNG(path string) (image.Image, error) {
file, err := os.Open(path)
if err != nil {
return nil, err
}
defer file.Close()
return png.Decode(file)
}
func savePNG(path string, im image.Image) error {
file, err := os.Create(path)
if err != nil {
return err
}
defer file.Close()
return png.Encode(file, im)
}
func saveGIF(path string, frames []image.Image) error {
var palette []color.Color
for _, c := range nes.Palette {
palette = append(palette, c)
}
g := gif.GIF{}
for i, src := range frames {
if i%3 != 0 {
continue
}
dst := image.NewPaletted(src.Bounds(), palette)
draw.Draw(dst, dst.Rect, src, image.ZP, draw.Src)
g.Image = append(g.Image, dst)
g.Delay = append(g.Delay, 5)
}
file, err := os.Create(path)
if err != nil {
return err
}
defer file.Close()
return gif.EncodeAll(file, &g)
}
func screenshot(im image.Image) {
for i := 0; i < 1000; i++ {
path := fmt.Sprintf("%03d.png", i)
if _, err := os.Stat(path); os.IsNotExist(err) {
savePNG(path, im)
return
}
}
}
func animation(frames []image.Image) {
for i := 0; i < 1000; i++ {
path := fmt.Sprintf("%03d.gif", i)
if _, err := os.Stat(path); os.IsNotExist(err) {
saveGIF(path, frames)
return
}
}
}
func writeSRAM(filename string, sram []byte) error {
dir, _ := path.Split(filename)
if err := os.MkdirAll(dir, 0755); err != nil {
return err
}
file, err := os.Create(filename)
if err != nil {
return err
}
defer file.Close()
return binary.Write(file, binary.LittleEndian, sram)
}
func readSRAM(filename string) ([]byte, error) {
file, err := os.Open(filename)
if err != nil {
return nil, err
}
defer file.Close()
sram := make([]byte, 0x2000)
if err := binary.Read(file, binary.LittleEndian, sram); err != nil {
return nil, err
}
return sram, nil
}
package ui
import (
"crypto/md5"
"encoding/binary"
"fmt"
"image"
"image/color"
"image/draw"
"image/gif"
"image/png"
"io/ioutil"
"log"
"os"
"os/user"
"path"
"github.com/giongto35/game-online/nes"
)
var homeDir string
func init() {
u, err := user.Current()
if err != nil {
log.Fatalln(err)
}
homeDir = u.HomeDir
}
func thumbnailURL(hash string) string {
return "http://www.michaelfogleman.com/static/nes/" + hash + ".png"
}
func thumbnailPath(hash string) string {
return homeDir + "/.nes/thumbnail/" + hash + ".png"
}
func sramPath(hash string) string {
return homeDir + "/.nes/sram/" + hash + ".dat"
}
func savePath(hash string) string {
return homeDir + "/.nes/save/" + hash + ".dat"
}
func combineButtons(a, b [8]bool) [8]bool {
var result [8]bool
for i := 0; i < 8; i++ {
result[i] = a[i] || b[i]
}
return result
}
func hashFile(path string) (string, error) {
data, err := ioutil.ReadFile(path)
if err != nil {
return "", err
}
return fmt.Sprintf("%x", md5.Sum(data)), nil
}
func copyImage(src image.Image) *image.RGBA {
dst := image.NewRGBA(src.Bounds())
draw.Draw(dst, dst.Rect, src, image.ZP, draw.Src)
return dst
}
func loadPNG(path string) (image.Image, error) {
file, err := os.Open(path)
if err != nil {
return nil, err
}
defer file.Close()
return png.Decode(file)
}
func savePNG(path string, im image.Image) error {
file, err := os.Create(path)
if err != nil {
return err
}
defer file.Close()
return png.Encode(file, im)
}
func saveGIF(path string, frames []image.Image) error {
var palette []color.Color
for _, c := range nes.Palette {
palette = append(palette, c)
}
g := gif.GIF{}
for i, src := range frames {
if i%3 != 0 {
continue
}
dst := image.NewPaletted(src.Bounds(), palette)
draw.Draw(dst, dst.Rect, src, image.ZP, draw.Src)
g.Image = append(g.Image, dst)
g.Delay = append(g.Delay, 5)
}
file, err := os.Create(path)
if err != nil {
return err
}
defer file.Close()
return gif.EncodeAll(file, &g)
}
func screenshot(im image.Image) {
for i := 0; i < 1000; i++ {
path := fmt.Sprintf("%03d.png", i)
if _, err := os.Stat(path); os.IsNotExist(err) {
savePNG(path, im)
return
}
}
}
func animation(frames []image.Image) {
for i := 0; i < 1000; i++ {
path := fmt.Sprintf("%03d.gif", i)
if _, err := os.Stat(path); os.IsNotExist(err) {
saveGIF(path, frames)
return
}
}
}
func writeSRAM(filename string, sram []byte) error {
dir, _ := path.Split(filename)
if err := os.MkdirAll(dir, 0755); err != nil {
return err
}
file, err := os.Create(filename)
if err != nil {
return err
}
defer file.Close()
return binary.Write(file, binary.LittleEndian, sram)
}
func readSRAM(filename string) ([]byte, error) {
file, err := os.Open(filename)
if err != nil {
return nil, err
}
defer file.Close()
sram := make([]byte, 0x2000)
if err := binary.Read(file, binary.LittleEndian, sram); err != nil {
return nil, err
}
return sram, nil
}