mirror of
https://github.com/giongto35/cloud-game.git
synced 2026-07-17 16:50:31 +00:00
Resize game
This commit is contained in:
parent
e56e21f5f7
commit
2171f604c4
13 changed files with 813 additions and 78 deletions
BIN
000.png
Normal file
BIN
000.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.8 KiB |
BIN
001.png
Normal file
BIN
001.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.6 KiB |
21
main.go
21
main.go
|
|
@ -17,26 +17,31 @@ import (
|
|||
)
|
||||
|
||||
var webRTC *webrtc.WebRTC
|
||||
var width = 800
|
||||
var height = 600
|
||||
var width = 256
|
||||
var height = 240
|
||||
|
||||
func init() {
|
||||
webRTC = webrtc.NewWebRTC()
|
||||
director := ui.NewDirector()
|
||||
director.Start("games/supermariobros.rom")
|
||||
// start screenshot loop, wait for connection
|
||||
go screenshotLoop(director.GetImageChannel())
|
||||
}
|
||||
|
||||
func startGame(path string, imageChannel chan *image.RGBA) {
|
||||
ui.Run([]string{path}, imageChannel)
|
||||
}
|
||||
|
||||
func main() {
|
||||
fmt.Println("http://localhost:8000")
|
||||
webRTC = webrtc.NewWebRTC()
|
||||
|
||||
router := mux.NewRouter()
|
||||
router.HandleFunc("/", getWeb).Methods("GET")
|
||||
router.HandleFunc("/session", postSession).Methods("POST")
|
||||
|
||||
http.ListenAndServe(":8000", router)
|
||||
go http.ListenAndServe(":8000", router)
|
||||
|
||||
// start screenshot loop, wait for connection
|
||||
imageChannel := make(chan *image.RGBA, 2)
|
||||
go screenshotLoop(imageChannel)
|
||||
startGame("games/supermariobros.rom", imageChannel)
|
||||
time.Sleep(time.Minute)
|
||||
}
|
||||
|
||||
func getWeb(w http.ResponseWriter, r *http.Request) {
|
||||
|
|
|
|||
|
|
@ -1,9 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/giongto35/game-online/ui"
|
||||
)
|
||||
|
||||
func startGame(path string) {
|
||||
ui.Run(path)
|
||||
}
|
||||
54
ui/audio.go
Normal file
54
ui/audio.go
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
package ui
|
||||
|
||||
import "github.com/gordonklaus/portaudio"
|
||||
|
||||
type Audio struct {
|
||||
stream *portaudio.Stream
|
||||
sampleRate float64
|
||||
outputChannels int
|
||||
channel chan float32
|
||||
}
|
||||
|
||||
func NewAudio() *Audio {
|
||||
a := Audio{}
|
||||
a.channel = make(chan float32, 44100)
|
||||
return &a
|
||||
}
|
||||
|
||||
func (a *Audio) Start() error {
|
||||
host, err := portaudio.DefaultHostApi()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
parameters := portaudio.HighLatencyParameters(nil, host.DefaultOutputDevice)
|
||||
stream, err := portaudio.OpenStream(parameters, a.Callback)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := stream.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
a.stream = stream
|
||||
a.sampleRate = parameters.SampleRate
|
||||
a.outputChannels = parameters.Output.Channels
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Audio) Stop() error {
|
||||
return a.stream.Close()
|
||||
}
|
||||
|
||||
func (a *Audio) Callback(out []float32) {
|
||||
var output float32
|
||||
for i := range out {
|
||||
if i%a.outputChannels == 0 {
|
||||
select {
|
||||
case sample := <-a.channel:
|
||||
output = sample
|
||||
default:
|
||||
output = 0
|
||||
}
|
||||
}
|
||||
out[i] = output
|
||||
}
|
||||
}
|
||||
|
|
@ -3,28 +3,39 @@ package ui
|
|||
import (
|
||||
"image"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/fogleman/nes/nes"
|
||||
"github.com/giongto35/game-online/nes"
|
||||
"github.com/go-gl/gl/v2.1/gl"
|
||||
"github.com/go-gl/glfw/v3.2/glfw"
|
||||
)
|
||||
|
||||
type View interface {
|
||||
Enter()
|
||||
Exit()
|
||||
GetImageChannel() chan *image.RGBA
|
||||
Update(t, dt float64)
|
||||
}
|
||||
|
||||
type Director struct {
|
||||
view View
|
||||
timestamp float64
|
||||
window *glfw.Window
|
||||
audio *Audio
|
||||
view View
|
||||
menuView View
|
||||
timestamp float64
|
||||
imageChannel chan *image.RGBA
|
||||
}
|
||||
|
||||
func NewDirector() *Director {
|
||||
func NewDirector(window *glfw.Window, audio *Audio, imageChannel chan *image.RGBA) *Director {
|
||||
director := Director{}
|
||||
director.window = window
|
||||
director.audio = audio
|
||||
director.imageChannel = imageChannel
|
||||
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()
|
||||
|
|
@ -33,12 +44,12 @@ func (d *Director) SetView(view View) {
|
|||
if d.view != nil {
|
||||
d.view.Enter()
|
||||
}
|
||||
d.timestamp = float64(time.Now().Unix())
|
||||
d.timestamp = glfw.GetTime()
|
||||
}
|
||||
|
||||
func (d *Director) Step() {
|
||||
//timestamp := glfw.GetTime()
|
||||
timestamp := float64(time.Now().Unix())
|
||||
gl.Clear(gl.COLOR_BUFFER_BIT)
|
||||
timestamp := glfw.GetTime()
|
||||
dt := timestamp - d.timestamp
|
||||
d.timestamp = timestamp
|
||||
if d.view != nil {
|
||||
|
|
@ -46,12 +57,22 @@ func (d *Director) Step() {
|
|||
}
|
||||
}
|
||||
|
||||
func (d *Director) Start(path string) {
|
||||
d.PlayGame(path)
|
||||
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() {
|
||||
d.Step()
|
||||
d.window.SwapBuffers()
|
||||
glfw.PollEvents()
|
||||
}
|
||||
d.SetView(nil)
|
||||
}
|
||||
|
||||
|
|
@ -64,9 +85,9 @@ func (d *Director) PlayGame(path string) {
|
|||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
d.SetView(NewGameView(d, console, path, hash))
|
||||
d.SetView(NewGameView(d, console, path, hash, d.imageChannel))
|
||||
}
|
||||
|
||||
func (d *Director) GetImageChannel() chan *image.RGBA {
|
||||
return d.view.GetImageChannel()
|
||||
func (d *Director) ShowMenu() {
|
||||
d.SetView(d.menuView)
|
||||
}
|
||||
|
|
|
|||
160
ui/font.go
Normal file
160
ui/font.go
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
package ui
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"image"
|
||||
"image/color"
|
||||
"image/draw"
|
||||
"image/png"
|
||||
"log"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var fontMask image.Image
|
||||
|
||||
func init() {
|
||||
im, err := png.Decode(bytes.NewBuffer(fontData))
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
size := im.Bounds().Size()
|
||||
mask := image.NewRGBA(im.Bounds())
|
||||
for y := 0; y < size.Y; y++ {
|
||||
for x := 0; x < size.X; x++ {
|
||||
r, _, _, _ := im.At(x, y).RGBA()
|
||||
if r > 0 {
|
||||
mask.Set(x, y, color.Opaque)
|
||||
}
|
||||
}
|
||||
}
|
||||
fontMask = mask
|
||||
}
|
||||
|
||||
func CreateGenericThumbnail(text string) image.Image {
|
||||
im := image.NewRGBA(image.Rect(0, 0, 256, 240))
|
||||
draw.Draw(im, im.Rect, &image.Uniform{color.Black}, image.ZP, draw.Src)
|
||||
DrawCenteredText(im, text, 1, 2, color.RGBA{128, 128, 128, 255})
|
||||
DrawCenteredText(im, text, 0, 0, color.White)
|
||||
return im
|
||||
}
|
||||
|
||||
func WordWrap(text string, maxLength int) []string {
|
||||
var rows []string
|
||||
words := strings.Fields(text)
|
||||
if len(words) == 0 {
|
||||
return rows
|
||||
}
|
||||
row := words[0]
|
||||
for _, word := range words[1:] {
|
||||
newRow := row + " " + word
|
||||
if len(newRow) <= maxLength {
|
||||
row = newRow
|
||||
} else {
|
||||
rows = append(rows, row)
|
||||
row = word
|
||||
}
|
||||
}
|
||||
rows = append(rows, row)
|
||||
return rows
|
||||
}
|
||||
|
||||
func DrawCenteredText(dst draw.Image, text string, dx, dy int, c color.Color) {
|
||||
rows := WordWrap(text, 15)
|
||||
for i, row := range rows {
|
||||
x := 128 - len(row)*8
|
||||
y := 120 - len(rows)*12 + i*24
|
||||
DrawText(dst, x+dx, y+dy, row, c)
|
||||
}
|
||||
}
|
||||
|
||||
func DrawCharacter(dst draw.Image, x, y int, ch byte, c color.Color) {
|
||||
if ch < 32 || ch > 128 {
|
||||
return
|
||||
}
|
||||
cx := int((ch-32)%16) * 16
|
||||
cy := int((ch-32)/16) * 16
|
||||
r := image.Rect(x, y, x+16, y+16)
|
||||
src := &image.Uniform{c}
|
||||
sp := image.Pt(cx, cy)
|
||||
draw.DrawMask(dst, r, src, sp, fontMask, sp, draw.Over)
|
||||
}
|
||||
|
||||
func DrawText(dst draw.Image, x, y int, text string, c color.Color) {
|
||||
for i := range text {
|
||||
DrawCharacter(dst, x, y, text[i], c)
|
||||
x += 16
|
||||
}
|
||||
}
|
||||
|
||||
var fontData = []byte{
|
||||
0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D,
|
||||
0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x60,
|
||||
0x02, 0x03, 0x00, 0x00, 0x00, 0x8F, 0x9F, 0x44, 0x1B, 0x00, 0x00, 0x00,
|
||||
0x06, 0x50, 0x4C, 0x54, 0x45, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFE, 0x6A,
|
||||
0x62, 0xC8, 0x2E, 0x00, 0x00, 0x00, 0x01, 0x62, 0x4B, 0x47, 0x44, 0x00,
|
||||
0x88, 0x05, 0x1D, 0x48, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73,
|
||||
0x00, 0x00, 0x0E, 0xC4, 0x00, 0x00, 0x0E, 0xC4, 0x01, 0x95, 0x2B, 0x0E,
|
||||
0x1B, 0x00, 0x00, 0x02, 0xD2, 0x49, 0x44, 0x41, 0x54, 0x78, 0xDA, 0xC5,
|
||||
0x98, 0x49, 0x62, 0x23, 0x21, 0x0C, 0x45, 0xB5, 0xE1, 0x7E, 0xDA, 0xFC,
|
||||
0xFB, 0x5F, 0xA5, 0x4B, 0xE8, 0x4B, 0x88, 0x72, 0x2A, 0x1D, 0x07, 0xB9,
|
||||
0x9B, 0xC4, 0x2E, 0x8C, 0xE1, 0x19, 0x8D, 0x0C, 0x22, 0x56, 0x86, 0x5E,
|
||||
0xFF, 0x98, 0x2F, 0x81, 0x78, 0x81, 0xB5, 0xC5, 0x77, 0xF2, 0x50, 0xE2,
|
||||
0x9B, 0x63, 0x00, 0x90, 0x80, 0x71, 0xD5, 0xAD, 0xE6, 0x9F, 0xC5, 0x81,
|
||||
0x90, 0xD9, 0x92, 0xF0, 0x2C, 0xF9, 0x6B, 0x4D, 0x00, 0x00, 0xEA, 0xFD,
|
||||
0x31, 0x45, 0x40, 0x4C, 0xD2, 0xDE, 0x50, 0xC4, 0xBB, 0x4B, 0xD0, 0x02,
|
||||
0xF0, 0xCF, 0x26, 0x82, 0xBA, 0xE2, 0xE0, 0xDF, 0x06, 0x00, 0x5E, 0xD8,
|
||||
0x1D, 0xBB, 0x04, 0xE7, 0x00, 0x4E, 0xC5, 0x44, 0x98, 0xAD, 0xF6, 0x3F,
|
||||
0x20, 0xF2, 0x17, 0x11, 0x96, 0x79, 0xBB, 0x00, 0xAE, 0x48, 0xA5, 0x11,
|
||||
0xCB, 0x34, 0xAB, 0x19, 0xB1, 0x2C, 0x08, 0x69, 0x03, 0x24, 0x87, 0xD3,
|
||||
0xB7, 0xEE, 0x48, 0xEA, 0xEE, 0xCA, 0x05, 0xA0, 0x7D, 0x80, 0xE7, 0x68,
|
||||
0x79, 0x0C, 0x9F, 0x9F, 0xB4, 0xFE, 0x10, 0x90, 0x8E, 0x33, 0x03, 0xC9,
|
||||
0x2A, 0xEE, 0x28, 0x70, 0xD7, 0x66, 0xBB, 0xBC, 0x2A, 0x5C, 0xDC, 0xE9,
|
||||
0x8E, 0x01, 0x23, 0x03, 0x67, 0xD0, 0x89, 0x90, 0x41, 0x54, 0x82, 0x7C,
|
||||
0x99, 0x70, 0x33, 0x23, 0x1A, 0x00, 0x48, 0x3B, 0x79, 0x0E, 0x63, 0x58,
|
||||
0xB3, 0x1F, 0xE4, 0x0E, 0x70, 0xAF, 0x66, 0xA5, 0x17, 0x10, 0x8A, 0xA3,
|
||||
0x00, 0xD1, 0x29, 0x95, 0x7B, 0xF3, 0xA3, 0x45, 0xEB, 0x02, 0xB8, 0x4D,
|
||||
0xE7, 0x40, 0x95, 0x32, 0x6D, 0x57, 0xEE, 0x4B, 0xE0, 0xC6, 0xE0, 0x06,
|
||||
0xC0, 0x15, 0x36, 0x9E, 0xB2, 0x62, 0x36, 0x37, 0xC5, 0x2D, 0xF3, 0x2E,
|
||||
0x33, 0xA2, 0x26, 0x97, 0x63, 0xC0, 0xB4, 0x9E, 0xA5, 0x73, 0xBA, 0xED,
|
||||
0x1C, 0x12, 0xEE, 0x6B, 0x7A, 0x02, 0x13, 0x6D, 0x00, 0x8A, 0x2B, 0x3B,
|
||||
0xE4, 0x18, 0xF0, 0x46, 0x79, 0x23, 0xEA, 0xDF, 0x00, 0xF8, 0x54, 0xB5,
|
||||
0x84, 0xB0, 0xFA, 0x12, 0xE7, 0x15, 0xF5, 0xF4, 0x36, 0xE5, 0xF4, 0x7A,
|
||||
0x09, 0xB2, 0x39, 0xF6, 0x18, 0x80, 0x4C, 0xE2, 0xF6, 0xF4, 0x5A, 0xD5,
|
||||
0xD7, 0x0A, 0xB6, 0xE8, 0x65, 0xBF, 0x67, 0x7F, 0x69, 0xEA, 0x53, 0x00,
|
||||
0xA7, 0xC6, 0x0D, 0x8E, 0xAC, 0xE7, 0xEE, 0xEA, 0x13, 0x90, 0x71, 0x67,
|
||||
0x22, 0xB6, 0x03, 0x80, 0x1C, 0x00, 0x44, 0x52, 0xA1, 0x32, 0x03, 0xB0,
|
||||
0x12, 0x8A, 0x82, 0x0A, 0x6E, 0x02, 0x00, 0x0F, 0x22, 0x70, 0x9A, 0x54,
|
||||
0x76, 0xB0, 0x30, 0x4C, 0x9C, 0x36, 0x40, 0xE9, 0xF8, 0x6A, 0xC6, 0x1B,
|
||||
0x60, 0xA0, 0xA4, 0xFB, 0x2E, 0xC0, 0x80, 0xA6, 0xE2, 0x36, 0x57, 0xF6,
|
||||
0x3D, 0x4F, 0xBA, 0xF2, 0xA0, 0x52, 0x07, 0x96, 0xD9, 0x19, 0x0B, 0x87,
|
||||
0x80, 0xFF, 0x5F, 0xC0, 0xF4, 0x05, 0xA6, 0xAE, 0x3A, 0xC5, 0x4D, 0xB9,
|
||||
0x53, 0x0C, 0x6E, 0x7F, 0x7D, 0x59, 0xEF, 0x02, 0x6C, 0x9D, 0xDD, 0x57,
|
||||
0xB7, 0xB6, 0x19, 0xB8, 0x1E, 0xC4, 0xB9, 0xC4, 0xF8, 0xC7, 0x8F, 0x00,
|
||||
0x64, 0x07, 0x0C, 0x37, 0xD5, 0x04, 0x8C, 0x72, 0x00, 0xE1, 0x36, 0xA0,
|
||||
0x1B, 0xC0, 0x85, 0x25, 0x00, 0x1A, 0x89, 0x35, 0x96, 0xFE, 0x75, 0x08,
|
||||
0x8B, 0x3E, 0x4D, 0x00, 0x33, 0xE1, 0x0C, 0xE9, 0x75, 0xC6, 0x0B, 0x37,
|
||||
0xCD, 0xE5, 0xCE, 0x25, 0xE0, 0x10, 0xEC, 0x2B, 0xCC, 0x39, 0x80, 0x5B,
|
||||
0x29, 0x0B, 0xD5, 0x1A, 0xBA, 0x73, 0xB1, 0x71, 0x41, 0x34, 0x96, 0xFF,
|
||||
0x3C, 0x04, 0xB5, 0x03, 0x2E, 0x09, 0x36, 0x25, 0xFA, 0xF3, 0x96, 0xD2,
|
||||
0xE8, 0xCA, 0x57, 0x89, 0x67, 0x13, 0xE0, 0x30, 0x92, 0xD1, 0x00, 0xC0,
|
||||
0xC3, 0x11, 0x8E, 0xD1, 0x52, 0xF5, 0x95, 0x8E, 0xF4, 0xD5, 0x01, 0xBC,
|
||||
0x1F, 0x50, 0x1F, 0x7C, 0xFB, 0x10, 0x80, 0x1B, 0x6D, 0xDF, 0xAE, 0xF8,
|
||||
0xB6, 0x46, 0xD6, 0x2A, 0xAA, 0x37, 0x11, 0x34, 0x0E, 0xE6, 0xEC, 0xDF,
|
||||
0x01, 0xE0, 0xE4, 0x06, 0x13, 0x57, 0xD6, 0x81, 0x78, 0x16, 0x11, 0x06,
|
||||
0x3D, 0xD9, 0xF6, 0x1A, 0x31, 0xE2, 0x23, 0x00, 0xC4, 0xF1, 0x87, 0xAD,
|
||||
0x09, 0xD0, 0x7F, 0x05, 0x58, 0x97, 0x0E, 0x3B, 0x20, 0x82, 0xAD, 0x1D,
|
||||
0x60, 0x99, 0xE3, 0x66, 0xC6, 0x32, 0xED, 0xF1, 0x22, 0x4E, 0x00, 0xD2,
|
||||
0x8C, 0xE7, 0x80, 0xE7, 0x78, 0xFD, 0xBA, 0xFE, 0x46, 0xC0, 0xFF, 0x02,
|
||||
0xF0, 0x5D, 0x3F, 0xE6, 0xF6, 0x99, 0x60, 0xF1, 0x74, 0x7E, 0xF9, 0x28,
|
||||
0x60, 0x9D, 0x01, 0xFC, 0xA2, 0x6A, 0x74, 0x00, 0xC2, 0x7C, 0xB6, 0x9C,
|
||||
0x4A, 0x6C, 0x75, 0x96, 0x63, 0xC1, 0x5F, 0x74, 0xEF, 0x02, 0x58, 0xF7,
|
||||
0x2F, 0xC7, 0x80, 0x0C, 0x5F, 0x8A, 0x84, 0xEA, 0xDA, 0xB6, 0xDE, 0x84,
|
||||
0xCB, 0x22, 0x77, 0x35, 0x32, 0xD6, 0xF5, 0x61, 0x23, 0x40, 0x25, 0xAF,
|
||||
0xFC, 0x36, 0x80, 0x75, 0x56, 0xD4, 0xE3, 0xBE, 0x48, 0xD9, 0xDD, 0x34,
|
||||
0x00, 0x52, 0x89, 0xB9, 0xC3, 0xF2, 0x04, 0x7E, 0xA5, 0xAF, 0x10, 0xA1,
|
||||
0xDC, 0x56, 0x7C, 0x02, 0xA0, 0x92, 0x8B, 0x06, 0xF7, 0xFF, 0xB2, 0xD2,
|
||||
0x3A, 0xAF, 0x21, 0xFD, 0x88, 0xAC, 0xD5, 0x95, 0xF3, 0x0A, 0xB5, 0x0D,
|
||||
0xF0, 0xEB, 0x7B, 0x83, 0x53, 0xC0, 0x1F, 0xEF, 0x0D, 0xA2, 0x4D, 0x77,
|
||||
0x69, 0xB8, 0xB7, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE,
|
||||
0x42, 0x60, 0x82,
|
||||
}
|
||||
118
ui/gameview.go
118
ui/gameview.go
|
|
@ -3,7 +3,9 @@ package ui
|
|||
import (
|
||||
"image"
|
||||
|
||||
"github.com/fogleman/nes/nes"
|
||||
"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
|
||||
|
|
@ -13,21 +15,23 @@ type GameView struct {
|
|||
console *nes.Console
|
||||
title string
|
||||
hash string
|
||||
texture uint32
|
||||
record bool
|
||||
frames []image.Image
|
||||
imageChannel chan *image.RGBA
|
||||
}
|
||||
|
||||
func NewGameView(director *Director, console *nes.Console, title, hash string) View {
|
||||
imageChannel := make(chan *image.RGBA, 2)
|
||||
return &GameView{director, console, title, hash, false, nil, imageChannel}
|
||||
}
|
||||
|
||||
func (view *GameView) GetImageChannel() chan *image.RGBA {
|
||||
return view.imageChannel
|
||||
func NewGameView(director *Director, console *nes.Console, title, hash string, imageChannel chan *image.RGBA) View {
|
||||
texture := createTexture()
|
||||
return &GameView{director, console, title, hash, texture, false, nil, imageChannel}
|
||||
}
|
||||
|
||||
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
|
||||
|
|
@ -44,6 +48,7 @@ func (view *GameView) Enter() {
|
|||
}
|
||||
|
||||
func (view *GameView) Exit() {
|
||||
view.director.window.SetKeyCallback(nil)
|
||||
view.console.SetAudioChannel(nil)
|
||||
view.console.SetAudioSampleRate(0)
|
||||
// save sram
|
||||
|
|
@ -59,40 +64,79 @@ func (view *GameView) Update(t, dt float64) {
|
|||
if dt > 1 {
|
||||
dt = 0
|
||||
}
|
||||
window := view.director.window
|
||||
console := view.console
|
||||
//if readKey(window, glfw.KeyEscape) {
|
||||
//view.director.ShowMenu()
|
||||
//}
|
||||
//updateControllers(window, console)
|
||||
view.imageChannel <- console.Buffer()
|
||||
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)
|
||||
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.KeyR:
|
||||
//view.console.Reset()
|
||||
//case glfw.KeyTab:
|
||||
//if view.record {
|
||||
//view.record = false
|
||||
//view.frames = nil
|
||||
//} else {
|
||||
//view.record = true
|
||||
//}
|
||||
//}
|
||||
//}
|
||||
//}
|
||||
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 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 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)
|
||||
}
|
||||
|
|
|
|||
250
ui/menuview.go
Normal file
250
ui/menuview.go
Normal file
|
|
@ -0,0 +1,250 @@
|
|||
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()
|
||||
}
|
||||
45
ui/run.go
45
ui/run.go
|
|
@ -1,7 +1,13 @@
|
|||
package ui
|
||||
|
||||
import (
|
||||
"image"
|
||||
"log"
|
||||
"runtime"
|
||||
|
||||
"github.com/go-gl/gl/v2.1/gl"
|
||||
"github.com/go-gl/glfw/v3.2/glfw"
|
||||
"github.com/gordonklaus/portaudio"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -19,16 +25,39 @@ func init() {
|
|||
runtime.LockOSThread()
|
||||
}
|
||||
|
||||
func Run(path string) {
|
||||
func Run(paths []string, imageChannel chan *image.RGBA) {
|
||||
// initialize audio
|
||||
//portaudio.Initialize()
|
||||
//defer portaudio.Terminate()
|
||||
portaudio.Initialize()
|
||||
defer portaudio.Terminate()
|
||||
|
||||
//audio := NewAudio()
|
||||
//if err := audio.Start(); err != nil {
|
||||
//log.Fatalln(err)
|
||||
//}
|
||||
//defer audio.Stop()
|
||||
audio := NewAudio()
|
||||
if err := audio.Start(); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
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)
|
||||
director.Start(paths)
|
||||
}
|
||||
|
|
|
|||
161
ui/texture.go
Normal file
161
ui/texture.go
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
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
|
||||
}
|
||||
22
ui/util.go
22
ui/util.go
|
|
@ -15,7 +15,7 @@ import (
|
|||
"os/user"
|
||||
"path"
|
||||
|
||||
"github.com/fogleman/nes/nes"
|
||||
"github.com/giongto35/game-online/nes"
|
||||
"github.com/go-gl/gl/v2.1/gl"
|
||||
"github.com/go-gl/glfw/v3.2/glfw"
|
||||
)
|
||||
|
|
@ -189,6 +189,26 @@ func saveGIF(path string, frames []image.Image) error {
|
|||
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 {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import (
|
|||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/fogleman/nes/nes"
|
||||
"github.com/giongto35/game-online/nes"
|
||||
)
|
||||
|
||||
func testRom(path string) (err error) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue