diff --git a/002.png b/002.png new file mode 100644 index 00000000..d21b59d7 Binary files /dev/null and b/002.png differ diff --git a/003.png b/003.png new file mode 100644 index 00000000..803e2260 Binary files /dev/null and b/003.png differ diff --git a/game-online b/game-online new file mode 100755 index 00000000..421669a2 Binary files /dev/null and b/game-online differ diff --git a/ui/director.go b/ui/director.go index 8aafeb46..fbbbe0de 100644 --- a/ui/director.go +++ b/ui/director.go @@ -1,12 +1,13 @@ package ui import ( + "fmt" "image" "log" + "time" "github.com/giongto35/game-online/nes" - "github.com/go-gl/gl/v2.1/gl" - "github.com/go-gl/glfw/v3.2/glfw" + "github.com/go-gl/glfw/v3.0/glfw" ) type View interface { @@ -16,28 +17,21 @@ type View interface { } type Director struct { - window *glfw.Window audio *Audio view View - menuView View timestamp float64 imageChannel chan *image.RGBA inputChannel chan int } -func NewDirector(window *glfw.Window, audio *Audio, imageChannel chan *image.RGBA, inputChannel chan int) *Director { +func NewDirector(audio *Audio, imageChannel chan *image.RGBA, inputChannel chan int) *Director { director := Director{} - director.window = window director.audio = audio director.imageChannel = imageChannel director.inputChannel = inputChannel return &director } -func (d *Director) SetTitle(title string) { - d.window.SetTitle(title) -} - func (d *Director) SetView(view View) { if d.view != nil { d.view.Exit() @@ -46,33 +40,33 @@ func (d *Director) SetView(view View) { if d.view != nil { d.view.Enter() } - d.timestamp = glfw.GetTime() + //d.timestamp = glfw.GetTime() + d.timestamp = float64(time.Now().Unix()) } func (d *Director) Step() { - gl.Clear(gl.COLOR_BUFFER_BIT) - timestamp := glfw.GetTime() + //gl.Clear(gl.COLOR_BUFFER_BIT) + timestamp := float64(time.Now().Unix()) + fmt.Println("Time stamp", timestamp) dt := timestamp - d.timestamp + fmt.Println("dt", dt) d.timestamp = timestamp + fmt.Println("view", d.view) if d.view != nil { d.view.Update(timestamp, dt) } } func (d *Director) Start(paths []string) { - d.menuView = NewMenuView(d, paths) if len(paths) == 1 { d.PlayGame(paths[0]) - } else { - d.ShowMenu() } d.Run() } func (d *Director) Run() { - for !d.window.ShouldClose() { + for { d.Step() - d.window.SwapBuffers() glfw.PollEvents() } d.SetView(nil) @@ -89,7 +83,3 @@ func (d *Director) PlayGame(path string) { } d.SetView(NewGameView(d, console, path, hash, d.imageChannel, d.inputChannel)) } - -func (d *Director) ShowMenu() { - d.SetView(d.menuView) -} diff --git a/ui/gameview.go b/ui/gameview.go index 2c1150e5..5c9d7b75 100644 --- a/ui/gameview.go +++ b/ui/gameview.go @@ -4,8 +4,6 @@ import ( "image" "github.com/giongto35/game-online/nes" - "github.com/go-gl/gl/v2.1/gl" - "github.com/go-gl/glfw/v3.2/glfw" ) const padding = 0 @@ -15,7 +13,6 @@ type GameView struct { console *nes.Console title string hash string - texture uint32 record bool frames []image.Image @@ -26,8 +23,7 @@ type GameView struct { } func NewGameView(director *Director, console *nes.Console, title, hash string, imageChannel chan *image.RGBA, inputChannel chan int) View { - texture := createTexture() - gameview := &GameView{director, console, title, hash, texture, false, nil, make([]bool, 255), imageChannel, inputChannel} + gameview := &GameView{director, console, title, hash, false, nil, make([]bool, 255), imageChannel, inputChannel} go gameview.ListenToInputChannel() return gameview } @@ -45,11 +41,8 @@ func (view *GameView) ListenToInputChannel() { } func (view *GameView) Enter() { - gl.ClearColor(0, 0, 0, 1) - view.director.SetTitle(view.title) view.console.SetAudioChannel(view.director.audio.channel) view.console.SetAudioSampleRate(view.director.audio.sampleRate) - view.director.window.SetKeyCallback(view.onKey) // load state if err := view.console.LoadState(savePath(view.hash)); err == nil { return @@ -66,7 +59,6 @@ func (view *GameView) Enter() { } func (view *GameView) Exit() { - view.director.window.SetKeyCallback(nil) view.console.SetAudioChannel(nil) view.console.SetAudioSampleRate(0) // save sram @@ -82,84 +74,17 @@ func (view *GameView) Update(t, dt float64) { if dt > 1 { dt = 0 } - window := view.director.window console := view.console - if joystickReset(glfw.Joystick1) { - view.director.ShowMenu() - } - if joystickReset(glfw.Joystick2) { - view.director.ShowMenu() - } - if readKey(window, glfw.KeyEscape) { - view.director.ShowMenu() - } //updateControllers(window, console) view.updateControllers() + //fmt.Println(console.Buffer()) console.StepSeconds(dt) - gl.BindTexture(gl.TEXTURE_2D, view.texture) - setTexture(console.Buffer()) view.imageChannel <- console.Buffer() - drawBuffer(view.director.window) - gl.BindTexture(gl.TEXTURE_2D, 0) if view.record { view.frames = append(view.frames, copyImage(console.Buffer())) } } -func (view *GameView) onKey(window *glfw.Window, - key glfw.Key, scancode int, action glfw.Action, mods glfw.ModifierKey) { - if action == glfw.Press { - switch key { - case glfw.KeySpace: - screenshot(view.console.Buffer()) - case glfw.KeyR: - view.console.Reset() - case glfw.KeyTab: - if view.record { - view.record = false - animation(view.frames) - view.frames = nil - } else { - view.record = true - } - } - } -} - -func drawBuffer(window *glfw.Window) { - w, h := window.GetFramebufferSize() - s1 := float32(w) / 256 - s2 := float32(h) / 240 - f := float32(1 - padding) - var x, y float32 - if s1 >= s2 { - x = f * s2 / s1 - y = f - } else { - x = f - y = f * s1 / s2 - } - gl.Begin(gl.QUADS) - gl.TexCoord2f(0, 1) - gl.Vertex2f(-x, -y) - gl.TexCoord2f(1, 1) - gl.Vertex2f(x, -y) - gl.TexCoord2f(1, 0) - gl.Vertex2f(x, y) - gl.TexCoord2f(0, 0) - gl.Vertex2f(-x, y) - gl.End() -} - -func updateControllers(window *glfw.Window, console *nes.Console) { - turbo := console.PPU.Frame%6 < 3 - k1 := readKeys(window, turbo) - j1 := readJoystick(glfw.Joystick1, turbo) - j2 := readJoystick(glfw.Joystick2, turbo) - console.SetButtons1(combineButtons(k1, j1)) - console.SetButtons2(j2) -} - func (view *GameView) updateControllers() { // TODO: switch case var buttons [8]bool diff --git a/ui/menuview.go b/ui/menuview.go deleted file mode 100644 index 1685d1b0..00000000 --- a/ui/menuview.go +++ /dev/null @@ -1,250 +0,0 @@ -package ui - -import ( - "path" - "strings" - - "github.com/giongto35/game-online/nes" - "github.com/go-gl/gl/v2.1/gl" - "github.com/go-gl/glfw/v3.2/glfw" -) - -const ( - border = 10 - margin = 10 - initialDelay = 0.3 - repeatDelay = 0.1 - typeDelay = 0.5 -) - -type MenuView struct { - director *Director - paths []string - texture *Texture - nx, ny, i, j int - scroll int - t float64 - buttons [8]bool - times [8]float64 - typeBuffer string - typeTime float64 -} - -func NewMenuView(director *Director, paths []string) View { - view := MenuView{} - view.director = director - view.paths = paths - view.texture = NewTexture() - return &view -} - -func (view *MenuView) checkButtons() { - window := view.director.window - k1 := readKeys(window, false) - j1 := readJoystick(glfw.Joystick1, false) - j2 := readJoystick(glfw.Joystick2, false) - buttons := combineButtons(combineButtons(j1, j2), k1) - now := glfw.GetTime() - for i := range buttons { - if buttons[i] && !view.buttons[i] { - view.times[i] = now + initialDelay - view.onPress(i) - } else if !buttons[i] && view.buttons[i] { - view.onRelease(i) - } else if buttons[i] && now >= view.times[i] { - view.times[i] = now + repeatDelay - view.onPress(i) - } - } - view.buttons = buttons -} - -func (view *MenuView) onPress(index int) { - switch index { - case nes.ButtonUp: - view.j-- - case nes.ButtonDown: - view.j++ - case nes.ButtonLeft: - view.i-- - case nes.ButtonRight: - view.i++ - default: - return - } - view.t = glfw.GetTime() -} - -func (view *MenuView) onRelease(index int) { - switch index { - case nes.ButtonStart: - view.onSelect() - } -} - -func (view *MenuView) onSelect() { - index := view.nx*(view.j+view.scroll) + view.i - if index >= len(view.paths) { - return - } - view.director.PlayGame(view.paths[index]) -} - -func (view *MenuView) onChar(window *glfw.Window, char rune) { - now := glfw.GetTime() - if now > view.typeTime { - view.typeBuffer = "" - } - view.typeTime = now + typeDelay - view.typeBuffer = strings.ToLower(view.typeBuffer + string(char)) - for index, p := range view.paths { - _, p = path.Split(strings.ToLower(p)) - if p >= view.typeBuffer { - view.highlight(index) - return - } - } -} - -func (view *MenuView) highlight(index int) { - view.scroll = index/view.nx - (view.ny-1)/2 - view.clampScroll(false) - view.i = index % view.nx - view.j = (index-view.i)/view.nx - view.scroll -} - -func (view *MenuView) Enter() { - gl.ClearColor(0.333, 0.333, 0.333, 1) - view.director.SetTitle("Select Game") - view.director.window.SetCharCallback(view.onChar) -} - -func (view *MenuView) Exit() { - view.director.window.SetCharCallback(nil) -} - -func (view *MenuView) Update(t, dt float64) { - view.checkButtons() - view.texture.Purge() - window := view.director.window - w, h := window.GetFramebufferSize() - sx := 256 + margin*2 - sy := 240 + margin*2 - nx := (w - border*2) / sx - ny := (h - border*2) / sy - ox := (w-nx*sx)/2 + margin - oy := (h-ny*sy)/2 + margin - if nx < 1 { - nx = 1 - } - if ny < 1 { - ny = 1 - } - view.nx = nx - view.ny = ny - view.clampSelection() - gl.PushMatrix() - gl.Ortho(0, float64(w), float64(h), 0, -1, 1) - view.texture.Bind() - for j := 0; j < ny; j++ { - for i := 0; i < nx; i++ { - x := float32(ox + i*sx) - y := float32(oy + j*sy) - index := nx*(j+view.scroll) + i - if index >= len(view.paths) || index < 0 { - continue - } - path := view.paths[index] - tx, ty, tw, th := view.texture.Lookup(path) - drawThumbnail(x, y, tx, ty, tw, th) - } - } - view.texture.Unbind() - if int((t-view.t)*4)%2 == 0 { - x := float32(ox + view.i*sx) - y := float32(oy + view.j*sy) - drawSelection(x, y, 8, 4) - } - gl.PopMatrix() -} - -func (view *MenuView) clampSelection() { - if view.i < 0 { - view.i = view.nx - 1 - } - if view.i >= view.nx { - view.i = 0 - } - if view.j < 0 { - view.j = 0 - view.scroll-- - } - if view.j >= view.ny { - view.j = view.ny - 1 - view.scroll++ - } - view.clampScroll(true) -} - -func (view *MenuView) clampScroll(wrap bool) { - n := len(view.paths) - rows := n / view.nx - if n%view.nx > 0 { - rows++ - } - maxScroll := rows - view.ny - if view.scroll < 0 { - if wrap { - view.scroll = maxScroll - view.j = view.ny - 1 - } else { - view.scroll = 0 - view.j = 0 - } - } - if view.scroll > maxScroll { - if wrap { - view.scroll = 0 - view.j = 0 - } else { - view.scroll = maxScroll - view.j = view.ny - 1 - } - } -} - -func drawThumbnail(x, y, tx, ty, tw, th float32) { - sx := x + 4 - sy := y + 4 - gl.Disable(gl.TEXTURE_2D) - gl.Color3f(0.2, 0.2, 0.2) - gl.Begin(gl.QUADS) - gl.Vertex2f(sx, sy) - gl.Vertex2f(sx+256, sy) - gl.Vertex2f(sx+256, sy+240) - gl.Vertex2f(sx, sy+240) - gl.End() - gl.Enable(gl.TEXTURE_2D) - gl.Color3f(1, 1, 1) - gl.Begin(gl.QUADS) - gl.TexCoord2f(tx, ty) - gl.Vertex2f(x, y) - gl.TexCoord2f(tx+tw, ty) - gl.Vertex2f(x+256, y) - gl.TexCoord2f(tx+tw, ty+th) - gl.Vertex2f(x+256, y+240) - gl.TexCoord2f(tx, ty+th) - gl.Vertex2f(x, y+240) - gl.End() -} - -func drawSelection(x, y, p, w float32) { - gl.LineWidth(w) - gl.Begin(gl.LINE_STRIP) - gl.Vertex2f(x-p, y-p) - gl.Vertex2f(x+256+p, y-p) - gl.Vertex2f(x+256+p, y+240+p) - gl.Vertex2f(x-p, y+240+p) - gl.Vertex2f(x-p, y-p) - gl.End() -} diff --git a/ui/run.go b/ui/run.go index af9c382a..5b5e342b 100644 --- a/ui/run.go +++ b/ui/run.go @@ -5,8 +5,6 @@ import ( "log" "runtime" - "github.com/go-gl/gl/v2.1/gl" - "github.com/go-gl/glfw/v3.2/glfw" "github.com/gordonklaus/portaudio" ) @@ -36,28 +34,7 @@ func Run(paths []string, imageChannel chan *image.RGBA, inputChannel chan int) { } defer audio.Stop() - // initialize glfw - if err := glfw.Init(); err != nil { - log.Fatalln(err) - } - defer glfw.Terminate() - - // create window - glfw.WindowHint(glfw.ContextVersionMajor, 2) - glfw.WindowHint(glfw.ContextVersionMinor, 1) - window, err := glfw.CreateWindow(width*scale, height*scale, title, nil, nil) - if err != nil { - log.Fatalln(err) - } - window.MakeContextCurrent() - - // initialize gl - if err := gl.Init(); err != nil { - log.Fatalln(err) - } - gl.Enable(gl.TEXTURE_2D) - // run director - director := NewDirector(window, audio, imageChannel, inputChannel) + director := NewDirector(audio, imageChannel, inputChannel) director.Start(paths) } diff --git a/ui/texture.go b/ui/texture.go deleted file mode 100644 index 48704d4f..00000000 --- a/ui/texture.go +++ /dev/null @@ -1,161 +0,0 @@ -package ui - -import ( - "image" - "io" - "net/http" - "os" - "path" - "strings" - - "github.com/go-gl/gl/v2.1/gl" -) - -const textureSize = 4096 -const textureDim = textureSize / 256 -const textureCount = textureDim * textureDim - -type Texture struct { - texture uint32 - lookup map[string]int - reverse [textureCount]string - access [textureCount]int - counter int - ch chan string -} - -func NewTexture() *Texture { - texture := createTexture() - gl.BindTexture(gl.TEXTURE_2D, texture) - gl.TexImage2D( - gl.TEXTURE_2D, 0, gl.RGBA, - textureSize, textureSize, - 0, gl.RGBA, gl.UNSIGNED_BYTE, nil) - gl.BindTexture(gl.TEXTURE_2D, 0) - t := Texture{} - t.texture = texture - t.lookup = make(map[string]int) - t.ch = make(chan string, 1024) - return &t -} - -func (t *Texture) Purge() { - for { - select { - case path := <-t.ch: - delete(t.lookup, path) - default: - return - } - } -} - -func (t *Texture) Bind() { - gl.BindTexture(gl.TEXTURE_2D, t.texture) -} - -func (t *Texture) Unbind() { - gl.BindTexture(gl.TEXTURE_2D, 0) -} - -func (t *Texture) Lookup(path string) (x, y, dx, dy float32) { - if index, ok := t.lookup[path]; ok { - return t.coord(index) - } else { - return t.coord(t.load(path)) - } -} - -func (t *Texture) mark(index int) { - t.counter++ - t.access[index] = t.counter -} - -func (t *Texture) lru() int { - minIndex := 0 - minValue := t.counter + 1 - for i, n := range t.access { - if n < minValue { - minIndex = i - minValue = n - } - } - return minIndex -} - -func (t *Texture) coord(index int) (x, y, dx, dy float32) { - x = float32(index%textureDim) / textureDim - y = float32(index/textureDim) / textureDim - dx = 1.0 / textureDim - dy = dx * 240 / 256 - return -} - -func (t *Texture) load(path string) int { - index := t.lru() - delete(t.lookup, t.reverse[index]) - t.mark(index) - t.lookup[path] = index - t.reverse[index] = path - x := int32((index % textureDim) * 256) - y := int32((index / textureDim) * 256) - im := copyImage(t.loadThumbnail(path)) - size := im.Rect.Size() - gl.TexSubImage2D( - gl.TEXTURE_2D, 0, x, y, int32(size.X), int32(size.Y), - gl.RGBA, gl.UNSIGNED_BYTE, gl.Ptr(im.Pix)) - return index -} - -func (t *Texture) loadThumbnail(romPath string) image.Image { - _, name := path.Split(romPath) - name = strings.TrimSuffix(name, ".nes") - name = strings.Replace(name, "_", " ", -1) - name = strings.Title(name) - im := CreateGenericThumbnail(name) - hash, err := hashFile(romPath) - if err != nil { - return im - } - filename := thumbnailPath(hash) - if _, err := os.Stat(filename); os.IsNotExist(err) { - go t.downloadThumbnail(romPath, hash) - return im - } else { - thumbnail, err := loadPNG(filename) - if err != nil { - return im - } - return thumbnail - } -} - -func (t *Texture) downloadThumbnail(romPath, hash string) error { - url := thumbnailURL(hash) - filename := thumbnailPath(hash) - dir, _ := path.Split(filename) - - resp, err := http.Get(url) - if err != nil { - return err - } - defer resp.Body.Close() - - if err := os.MkdirAll(dir, 0755); err != nil { - return err - } - - file, err := os.Create(filename) - if err != nil { - return err - } - defer file.Close() - - if _, err := io.Copy(file, resp.Body); err != nil { - return err - } - - t.ch <- romPath - - return nil -} diff --git a/ui/util.go b/ui/util.go index 91ef925d..dc0e20eb 100644 --- a/ui/util.go +++ b/ui/util.go @@ -16,8 +16,6 @@ import ( "path" "github.com/giongto35/game-online/nes" - "github.com/go-gl/gl/v2.1/gl" - "github.com/go-gl/glfw/v3.2/glfw" ) var homeDir string @@ -46,67 +44,6 @@ func savePath(hash string) string { return homeDir + "/.nes/save/" + hash + ".dat" } -func readKey(window *glfw.Window, key glfw.Key) bool { - return window.GetKey(key) == glfw.Press -} - -func readKeys(window *glfw.Window, turbo bool) [8]bool { - var result [8]bool - result[nes.ButtonA] = readKey(window, glfw.KeyZ) || (turbo && readKey(window, glfw.KeyA)) - result[nes.ButtonB] = readKey(window, glfw.KeyX) || (turbo && readKey(window, glfw.KeyS)) - result[nes.ButtonSelect] = readKey(window, glfw.KeyRightShift) - result[nes.ButtonStart] = readKey(window, glfw.KeyEnter) - result[nes.ButtonUp] = readKey(window, glfw.KeyUp) - result[nes.ButtonDown] = readKey(window, glfw.KeyDown) - result[nes.ButtonLeft] = readKey(window, glfw.KeyLeft) - result[nes.ButtonRight] = readKey(window, glfw.KeyRight) - return result -} - -func readJoystick(joy glfw.Joystick, turbo bool) [8]bool { - var result [8]bool - if !glfw.JoystickPresent(joy) { - return result - } - joyname := glfw.GetJoystickName(joy) - axes := glfw.GetJoystickAxes(joy) - buttons := glfw.GetJoystickButtons(joy) - if joyname == "PLAYSTATION(R)3 Controller" { - result[nes.ButtonA] = buttons[14] == 1 || (turbo && buttons[2] == 1) - result[nes.ButtonB] = buttons[13] == 1 || (turbo && buttons[3] == 1) - result[nes.ButtonSelect] = buttons[0] == 1 - result[nes.ButtonStart] = buttons[3] == 1 - result[nes.ButtonUp] = buttons[4] == 1 || axes[1] < -0.5 - result[nes.ButtonDown] = buttons[6] == 1 || axes[1] > 0.5 - result[nes.ButtonLeft] = buttons[7] == 1 || axes[0] < -0.5 - result[nes.ButtonRight] = buttons[5] == 1 || axes[0] > 0.5 - return result - } - if len(buttons) < 8 { - return result - } - result[nes.ButtonA] = buttons[0] == 1 || (turbo && buttons[2] == 1) - result[nes.ButtonB] = buttons[1] == 1 || (turbo && buttons[3] == 1) - result[nes.ButtonSelect] = buttons[6] == 1 - result[nes.ButtonStart] = buttons[7] == 1 - result[nes.ButtonUp] = axes[1] < -0.5 - result[nes.ButtonDown] = axes[1] > 0.5 - result[nes.ButtonLeft] = axes[0] < -0.5 - result[nes.ButtonRight] = axes[0] > 0.5 - return result -} - -func joystickReset(joy glfw.Joystick) bool { - if !glfw.JoystickPresent(joy) { - return false - } - buttons := glfw.GetJoystickButtons(joy) - if len(buttons) < 6 { - return false - } - return buttons[4] == 1 && buttons[5] == 1 -} - func combineButtons(a, b [8]bool) [8]bool { var result [8]bool for i := 0; i < 8; i++ { @@ -122,26 +59,6 @@ func hashFile(path string) (string, error) { } return fmt.Sprintf("%x", md5.Sum(data)), nil } - -func createTexture() uint32 { - var texture uint32 - gl.GenTextures(1, &texture) - gl.BindTexture(gl.TEXTURE_2D, texture) - gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST) - gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST) - gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE) - gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE) - gl.BindTexture(gl.TEXTURE_2D, 0) - return texture -} - -func setTexture(im *image.RGBA) { - size := im.Rect.Size() - gl.TexImage2D( - gl.TEXTURE_2D, 0, gl.RGBA, int32(size.X), int32(size.Y), - 0, gl.RGBA, gl.UNSIGNED_BYTE, gl.Ptr(im.Pix)) -} - func copyImage(src image.Image) *image.RGBA { dst := image.NewRGBA(src.Bounds()) draw.Draw(dst, dst.Rect, src, image.ZP, draw.Src)