This commit is contained in:
giongto35 2019-04-03 22:44:58 +08:00
parent 44fb0c3e7e
commit 8e9bb9687c
9 changed files with 15 additions and 617 deletions

BIN
002.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

BIN
003.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

BIN
game-online Executable file

Binary file not shown.

View file

@ -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)
}

View file

@ -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

View file

@ -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()
}

View file

@ -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)
}

View file

@ -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
}

View file

@ -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)