reconstruct debug frontend, reduce input in 8 bit, save and load in websocket

This commit is contained in:
hasagi 2019-04-12 07:16:46 +07:00
parent eb8b465acd
commit 768baf02ba
13 changed files with 427 additions and 629 deletions

View file

@ -6,11 +6,12 @@ WORKDIR /go/src/github.com/giongto35/cloud-game
# Install server dependencies
RUN apt-get update
#RUN apt-get install portaudio19-dev -y
RUN apt-get install libvpx-dev -y
RUN go get github.com/pion/webrtc
#RUN go get github.com/gordonklaus/portaudio
RUN go get github.com/gorilla/mux
#RUN apt-get install portaudio19-dev -y
RUN apt-get install pkg-config libvpx-dev -y
RUN go get github.com/pion/webrtc
RUN go get github.com/gorilla/websocket
RUN go install github.com/giongto35/cloud-game

84
main.go
View file

@ -1,6 +1,7 @@
package main
import (
"os"
"encoding/json"
"fmt"
"image"
@ -19,22 +20,21 @@ import (
pionRTC "github.com/pion/webrtc"
)
const gameboyIndex = "./static/gameboy.html"
const wsIndex = "./static/index_ws.html"
const (
width = 256
height = 240
scale = 3
title = "NES"
gameboyIndex = "./static/gameboy.html"
debugIndex = "./static/index_ws.html"
)
var width = 256
var height = 240
var indexFN string = gameboyIndex
var service string = "ws"
var indexFN = gameboyIndex
// Time allowed to write a message to the peer.
var readWait = 30 * time.Second
var writeWait = 30 * time.Second
type IndexPageData struct {
Service string
}
var upgrader = websocket.Upgrader{}
type WSPacket struct {
@ -53,11 +53,20 @@ type Room struct {
closedChannel chan bool
rtcSessions []*webrtc.WebRTC
director *ui.Director
}
var rooms map[string]*Room
func main() {
fmt.Println("Usage: ./game [debug]")
if len(os.Args) > 1 {
// debug
indexFN = debugIndex
fmt.Println("Use debug version")
}
rand.Seed(time.Now().UTC().UnixNano())
fmt.Println("http://localhost:8000")
rooms = map[string]*Room{}
@ -71,9 +80,6 @@ func main() {
http.ListenAndServe(":8000", nil)
}
func startGame(path string, roomID string, imageChannel chan *image.RGBA, inputChannel chan int, closedChannel chan bool) {
ui.Run([]string{path}, roomID, imageChannel, inputChannel, closedChannel)
}
func getWeb(w http.ResponseWriter, r *http.Request) {
bs, err := ioutil.ReadFile(indexFN)
@ -92,14 +98,20 @@ func initRoom(roomID, gameName string) string {
imageChannel := make(chan *image.RGBA, 100)
inputChannel := make(chan int, 100)
closedChannel := make(chan bool)
// create director
director := ui.NewDirector(roomID, imageChannel, inputChannel, closedChannel)
rooms[roomID] = &Room{
imageChannel: imageChannel,
inputChannel: inputChannel,
closedChannel: closedChannel,
rtcSessions: []*webrtc.WebRTC{},
director: director,
}
go fanoutScreen(imageChannel, roomID)
go startGame("games/"+gameName, roomID, imageChannel, inputChannel, closedChannel)
go director.Start([]string{"games/" + gameName})
return roomID
}
@ -140,12 +152,12 @@ func startSession(webRTC *webrtc.WebRTC, gameName string, roomID string, playerI
func ws(w http.ResponseWriter, r *http.Request) {
c, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Print("upgrade:", err)
log.Print("[!] WS upgrade:", err)
return
}
defer c.Close()
log.Println("New Connection")
log.Println("New ws connection")
webRTC := webrtc.NewWebRTC()
// streaming game
@ -207,10 +219,40 @@ func ws(w http.ResponseWriter, r *http.Request) {
case "start":
log.Println("Starting game")
roomID = startSession(webRTC, gameName, roomID, playerIndex)
res.ID = "start"
res.RoomID = startSession(webRTC, gameName, roomID, playerIndex)
res.RoomID = roomID
isDone = true
// maybe we wont close websocket
// isDone = true
case "save":
log.Println("Saving game state")
res.ID = "save"
res.Data = "ok"
if roomID != "" {
err = rooms[roomID].director.SaveGame()
if err != nil {
log.Println("[!] Cannot save game state: ", err)
res.Data = "error"
}
} else {
res.Data = "error"
}
case "load":
log.Println("Loading game state")
res.ID = "load"
res.Data = "ok"
if roomID != "" {
err = rooms[roomID].director.LoadGame()
if err != nil {
log.Println("[!] Cannot load game state: ", err)
res.Data = "error"
}
} else {
res.Data = "error"
}
}
stRes, err := json.Marshal(res)
@ -274,9 +316,9 @@ func faninInput(inputChannel chan int, webRTC *webrtc.WebRTC, playerIndex int) {
// encode frame
if webRTC.IsConnected() {
input := <-webRTC.InputChannel
// the first 10 bits belong to player 1
// the next 10 belongs to player 2 ...
// We standardize and put it to inputChannel (20 bits)
// the first 8 bits belong to player 1
// the next 8 belongs to player 2 ...
// We standardize and put it to inputChannel (16 bits)
input = input << ((uint(playerIndex) - 1) * ui.NumKeys)
inputChannel <- input
}

View file

@ -6,7 +6,7 @@ body {
background-repeat: no-repeat; /*Prevent showing multiple background images*/
}
#loading-screen {
#game-screen {
width: 100%;
height: 100%;
display: none;
@ -58,9 +58,9 @@ body {
-webkit-transform:translateX(50%);
transform:translateX(50%);
/* Apply animation to this element */
-moz-animation: horizontally 1s linear infinite alternate;
-webkit-animation: horizontally 1s linear infinite alternate;
animation: horizontally 1s linear infinite alternate;
-moz-animation: horizontally 5s linear infinite alternate;
-webkit-animation: horizontally 5s linear infinite alternate;
animation: horizontally 5s linear infinite alternate;
}
/* Move it (define the animation) */
@-moz-keyframes horizontally {
@ -154,11 +154,6 @@ body {
#colors {
position: fixed;
bottom: 0px;
}
@import url(https://fonts.googleapis.com/css?family=Asap:400italic,700italic);
@import url(https://fonts.googleapis.com/css?family=Oxygen:700,300);
* {
@ -176,7 +171,7 @@ body {
width: 340px;
height: 570px;
/*overflow: hidden;*/
top: 25%;
top: 17%;
margin: auto;
line-height: 1;
@ -283,8 +278,8 @@ body {
right: 0;
border-radius: 15px 15px 170px 170px / 15px 15px 35px 35px;
background-color: #b0b0b0;
-webkit-animation: canvas 1s linear;
animation: canvas 1s linear;
-webkit-animation: canvas 8s linear;
animation: canvas 8s linear;
z-index: 1;
}
@-moz-keyframes border {
@ -425,8 +420,8 @@ body {
border: 4px #808080 solid;
border-radius: 15px 15px 170px 170px / 15px 15px 35px 35px;
z-index: 2;
-webkit-animation: border 1s linear;
animation: border 1s linear;
-webkit-animation: border 8s linear;
animation: border 8s linear;
}
#border-top,
#border-bottom {
@ -497,8 +492,8 @@ body {
#border-top {
top: 20px;
right: 11px;
-webkit-animation: border-top 1s linear;
animation: border-top 1s linear;
-webkit-animation: border-top 8s linear;
animation: border-top 8s linear;
}
@-moz-keyframes border-left {
0% {
@ -552,8 +547,8 @@ body {
left: 11px;
top: 20px;
width: 20px;
-webkit-animation: border-left 1s linear;
animation: border-left 1s linear;
-webkit-animation: border-left 8s linear;
animation: border-left 8s linear;
}
@-moz-keyframes border-bottom {
0% {
@ -607,8 +602,8 @@ body {
top: 516px;
left: 11px;
height: 30px;
-webkit-animation: border-bottom 1s linear;
animation: border-bottom 1s linear;
-webkit-animation: border-bottom 8s linear;
animation: border-bottom 8s linear;
}
@-moz-keyframes border-right {
0% {
@ -680,8 +675,8 @@ body {
right: 11px;
top: 26px;
width: 8px;
-webkit-animation: border-right 1s linear;
animation: border-right 1s linear;
-webkit-animation: border-right 8s linear;
animation: border-right 8s linear;
}
.screw {
border-radius: 100%;
@ -761,8 +756,8 @@ body {
}
#screw-small-right {
right: 25px;
-webkit-animation: screw-small-right 1s linear;
animation: screw-small-right 1s linear;
-webkit-animation: screw-small-right 8s linear;
animation: screw-small-right 8s linear;
}
@-moz-keyframes screw-small-left {
0% {
@ -826,8 +821,8 @@ body {
}
#screw-small-left {
left: 25px;
-webkit-animation: screw-small-left 1s linear;
animation: screw-small-left 1s linear;
-webkit-animation: screw-small-left 8s linear;
animation: screw-small-left 8s linear;
}
@-moz-keyframes screw-large-right {
0% {
@ -891,8 +886,8 @@ body {
}
#screw-large-right {
right: 3px;
-webkit-animation: screw-large-right 1s linear;
animation: screw-large-right 1s linear;
-webkit-animation: screw-large-right 8s linear;
animation: screw-large-right 8s linear;
}
@-moz-keyframes screw-large-left {
0% {
@ -956,8 +951,8 @@ body {
}
#screw-large-left {
left: 3px;
-webkit-animation: screw-large-left 1s linear;
animation: screw-large-left 1s linear;
-webkit-animation: screw-large-left 8s linear;
animation: screw-large-left 8s linear;
}
@-moz-keyframes backboard {
0% {
@ -1051,8 +1046,8 @@ body {
left: 60px;
bottom: 340px;
background-color: #222222;
-webkit-animation: backboard 1s linear;
animation: backboard 1s linear;
-webkit-animation: backboard 8s linear;
animation: backboard 8s linear;
}
@-moz-keyframes motherboard {
0% {
@ -1231,8 +1226,8 @@ body {
top: 75px;
overflow: hidden;
background-color: #4ca879;
-webkit-animation: motherboard 1s linear;
animation: motherboard 1s linear;
-webkit-animation: motherboard 8s linear;
animation: motherboard 8s linear;
}
#motherboard::before {
content: "";
@ -1242,8 +1237,8 @@ body {
height: 100%;
background-color: #66947d;
box-shadow: -6px 0 #66947d, 9px 0 #66947d;
-webkit-animation: motherboard-before 1s linear;
animation: motherboard-before 1s linear;
-webkit-animation: motherboard-before 8s linear;
animation: motherboard-before 8s linear;
}
.chip {
z-index: 4;
@ -1360,8 +1355,8 @@ body {
height: 400px;
top: 75px;
right: 35px;
-webkit-animation: chip-tall 1s linear;
animation: chip-tall 1s linear;
-webkit-animation: chip-tall 8s linear;
animation: chip-tall 8s linear;
}
#chip-tall::before {
content: "";
@ -1371,8 +1366,8 @@ body {
width: 4px;
height: 100%;
background-color: #267146;
-webkit-animation: chip-tall-before 1s linear;
animation: chip-tall-before 1s linear;
-webkit-animation: chip-tall-before 8s linear;
animation: chip-tall-before 8s linear;
}
#chip-tall::after {
content: "";
@ -1382,8 +1377,8 @@ body {
width: 4px;
height: 100%;
background-color: #267146;
-webkit-animation: chip-tall-after 1s linear;
animation: chip-tall-after 1s linear;
-webkit-animation: chip-tall-after 8s linear;
animation: chip-tall-after 8s linear;
}
@-moz-keyframes chip-short {
0% {
@ -1552,8 +1547,8 @@ body {
height: 140px;
top: 75px;
right: 35px;
-webkit-animation: chip-short 1s linear;
animation: chip-short 1s linear;
-webkit-animation: chip-short 8s linear;
animation: chip-short 8s linear;
}
#chip-short::before {
content: "";
@ -1564,8 +1559,8 @@ body {
position: absolute;
left: 36px;
box-shadow: 6px 0 #66947d;
-webkit-animation: chip-details 1s linear;
animation: chip-details 1s linear;
-webkit-animation: chip-details 8s linear;
animation: chip-details 8s linear;
}
#chip-short::after {
content: "";
@ -1575,8 +1570,8 @@ body {
position: absolute;
left: -14px;
box-shadow: 6px 0 #66947d;
-webkit-animation: chip-details 1s linear;
animation: chip-details 1s linear;
-webkit-animation: chip-details 8s linear;
animation: chip-details 8s linear;
}
@-moz-keyframes chip-diagonal {
0% {
@ -1630,8 +1625,8 @@ body {
transform-origin: 0 100%;
-webkit-transform: rotateZ(-40deg);
transform: rotateZ(-40deg);
-webkit-animation: chip-diagonal 1s linear;
animation: chip-diagonal 1s linear;
-webkit-animation: chip-diagonal 8s linear;
animation: chip-diagonal 8s linear;
}
#chip-diagonal::before {
content: "";
@ -1642,8 +1637,8 @@ body {
left: -11px;
top: -9px;
box-shadow: 5px 4px #66947d;
-webkit-animation: chip-details-diagonal 1s linear;
animation: chip-details-diagonal 1s linear;
-webkit-animation: chip-details-diagonal 8s linear;
animation: chip-details-diagonal 8s linear;
}
.capacitors {
position: absolute;
@ -1717,8 +1712,8 @@ body {
#chip-capacitors {
left: 240px;
top: 130px;
-webkit-animation: chip-capacitors 1s linear;
animation: chip-capacitors 1s linear;
-webkit-animation: chip-capacitors 8s linear;
animation: chip-capacitors 8s linear;
}
@-moz-keyframes motherboard-capacitors {
0% {
@ -1785,8 +1780,8 @@ body {
top: 265px;
-webkit-transform: rotateZ(-90deg);
transform: rotateZ(-90deg);
-webkit-animation: motherboard-capacitors 1s linear;
animation: motherboard-capacitors 1s linear;
-webkit-animation: motherboard-capacitors 8s linear;
animation: motherboard-capacitors 8s linear;
}
@-moz-keyframes contrast-knob {
0% {
@ -1934,8 +1929,8 @@ body {
width: 28px;
height: 28px;
background-color: #808080;
-webkit-animation: contrast-knob 1s linear;
animation: contrast-knob 1s linear;
-webkit-animation: contrast-knob 8s linear;
animation: contrast-knob 8s linear;
}
#contrast-knob:before {
content: '';
@ -1946,8 +1941,8 @@ body {
height: 32px;
border-radius: 50%;
background-color: #303030;
-webkit-animation: contrast-knob-before 1s linear;
animation: contrast-knob-before 1s linear;
-webkit-animation: contrast-knob-before 8s linear;
animation: contrast-knob-before 8s linear;
}
@-moz-keyframes link-port {
0% {
@ -2026,8 +2021,8 @@ body {
width: 34px;
height: 34px;
background-color: #ffffff;
-webkit-animation: link-port 1s linear;
animation: link-port 1s linear;
-webkit-animation: link-port 8s linear;
animation: link-port 8s linear;
}
.circuit {
z-index: 4;
@ -2160,16 +2155,16 @@ body {
left: 155px;
-webkit-transform: rotateZ(-90deg);
transform: rotateZ(-90deg);
-webkit-animation: circuit-bottom 1s linear;
animation: circuit-bottom 1s linear;
-webkit-animation: circuit-bottom 8s linear;
animation: circuit-bottom 8s linear;
}
#circuit-bottom::before {
-webkit-animation: circuit-bottom-before 1s linear;
animation: circuit-bottom-before 1s linear;
-webkit-animation: circuit-bottom-before 8s linear;
animation: circuit-bottom-before 8s linear;
}
#circuit-bottom::after {
-webkit-animation: circuit-bottom-after 1s linear;
animation: circuit-bottom-after 1s linear;
-webkit-animation: circuit-bottom-after 8s linear;
animation: circuit-bottom-after 8s linear;
}
@-moz-keyframes circuit-top {
0% {
@ -2275,16 +2270,16 @@ body {
height: 80px;
top: 100px;
left: 70px;
-webkit-animation: circuit-top 1s linear;
animation: circuit-top 1s linear;
-webkit-animation: circuit-top 8s linear;
animation: circuit-top 8s linear;
}
#circuit-top::before {
-webkit-animation: circuit-top-before 1s linear;
animation: circuit-top-before 1s linear;
-webkit-animation: circuit-top-before 8s linear;
animation: circuit-top-before 8s linear;
}
#circuit-top::after {
-webkit-animation: circuit-top-after 1s linear;
animation: circuit-top-after 1s linear;
-webkit-animation: circuit-top-after 8s linear;
animation: circuit-top-after 8s linear;
}
@-moz-keyframes transistors {
0% {
@ -2356,8 +2351,8 @@ body {
border-radius: 50%;
background-color: #303030;
box-shadow: 16px 0, 32px 0, 48px 0;
-webkit-animation: transistors 1s linear;
animation: transistors 1s linear;
-webkit-animation: transistors 8s linear;
animation: transistors 8s linear;
}
@-moz-keyframes processor {
0% {
@ -2498,8 +2493,8 @@ body {
letter-spacing: 3px;
font-weight: 700;
font-family: 'Oxygen', Helvetica, arial, sans-serif;
-webkit-animation: processor 1s linear;
animation: processor 1s linear;
-webkit-animation: processor 8s linear;
animation: processor 8s linear;
}
#processor::before {
content: "llllll";
@ -2513,8 +2508,8 @@ body {
transform-origin: 0 0;
left: 48px;
bottom: -9px;
-webkit-animation: processor-before 1s linear;
animation: processor-before 1s linear;
-webkit-animation: processor-before 8s linear;
animation: processor-before 8s linear;
}
#processor::after {
content: "llllll";
@ -2528,8 +2523,8 @@ body {
transform-origin: 0 0;
left: 7px;
top: 50px;
-webkit-animation: processor-after 1s linear;
animation: processor-after 1s linear;
-webkit-animation: processor-after 8s linear;
animation: processor-after 8s linear;
}
@-moz-keyframes component {
0% {
@ -2626,8 +2621,8 @@ body {
border-bottom-right-radius: 3px;
width: 14px;
height: 55px;
-webkit-animation: component 1s linear;
animation: component 1s linear;
-webkit-animation: component 8s linear;
animation: component 8s linear;
}
@-moz-keyframes controller {
0% {
@ -2805,8 +2800,8 @@ body {
top: 390px;
left: 68px;
background-color: #9d9d9d;
-webkit-animation: controller 1s linear;
animation: controller 1s linear;
-webkit-animation: controller 8s linear;
animation: controller 8s linear;
}
#controller::before {
content: "";
@ -2816,8 +2811,8 @@ body {
height: 6px;
left: -18px;
bottom: 25px;
-webkit-animation: controller-before 1s linear;
animation: controller-before 1s linear;
-webkit-animation: controller-before 8s linear;
animation: controller-before 8s linear;
}
#controller::after {
content: "";
@ -2827,8 +2822,8 @@ body {
bottom: -24px;
left: 45px;
background-color: #303030;
-webkit-animation: controller-after 1s linear;
animation: controller-after 1s linear;
-webkit-animation: controller-after 8s linear;
animation: controller-after 8s linear;
}
@-moz-keyframes speaker {
0% {
@ -2917,8 +2912,8 @@ body {
bottom: 24px;
border-radius: 100%;
background-color: #222222;
-webkit-animation: speaker 1s linear;
animation: speaker 1s linear;
-webkit-animation: speaker 8s linear;
animation: speaker 8s linear;
}
#speaker::after {
content: "";
@ -3029,8 +3024,8 @@ body {
left: 76px;
background-color: #ffffff;
border: 5px #808080 solid;
-webkit-animation: whitescreen 1s linear;
animation: whitescreen 1s linear;
-webkit-animation: whitescreen 8s linear;
animation: whitescreen 8s linear;
}
@-moz-keyframes screen {
0% {
@ -3158,8 +3153,8 @@ body {
bottom: 278px;
border-radius: 10px 10px 50% 50% / 10px 10px 20px 20px;
background-color: #222222;
-webkit-animation: screen 1s linear;
animation: screen 1s linear;
-webkit-animation: screen 8s linear;
animation: screen 8s linear;
}
@-moz-keyframes glass-gameboy-text {
0% {
@ -3210,8 +3205,8 @@ body {
white-space: nowrap;
-webkit-transform: skew(-5deg);
transform: skew(-5deg);
-webkit-animation: glass-gameboy-text 1s linear;
animation: glass-gameboy-text 1s linear;
-webkit-animation: glass-gameboy-text 8s linear;
animation: glass-gameboy-text 8s linear;
}
@-moz-keyframes glass-color-text {
0% {
@ -3352,8 +3347,8 @@ body {
font-size: 23px;
letter-spacing: -2px;
font-weight: 700;
-webkit-animation: glass-color-text 1s linear;
animation: glass-color-text 1s linear;
-webkit-animation: glass-color-text 8s linear;
animation: glass-color-text 8s linear;
}
#glass-color-text::before {
content: "L";
@ -3367,8 +3362,8 @@ body {
border-radius: 50%;
width: 7px;
height: 7px;
-webkit-animation: glass-color-text-before 1s linear;
animation: glass-color-text-before 1s linear;
-webkit-animation: glass-color-text-before 8s linear;
animation: glass-color-text-before 8s linear;
}
#glass-color-text::after {
content: "R";
@ -3382,8 +3377,8 @@ body {
border-radius: 50%;
width: 7px;
height: 7px;
-webkit-animation: glass-color-text-after 1s linear;
animation: glass-color-text-after 1s linear;
-webkit-animation: glass-color-text-after 8s linear;
animation: glass-color-text-after 8s linear;
}
@-moz-keyframes glass {
0% {
@ -3498,8 +3493,8 @@ body {
left: 80px;
top: 60px;
background-color: #f0f0f0;
-webkit-animation: glass 1s linear;
animation: glass 1s linear;
-webkit-animation: glass 8s linear;
animation: glass 8s linear;
}
@-moz-keyframes screen-gameboy-text {
0% {
@ -3667,8 +3662,8 @@ body {
background-position: 130px;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
-webkit-animation: screen-gameboy-text 1s linear;
animation: screen-gameboy-text 1s linear;
-webkit-animation: screen-gameboy-text 8s linear;
animation: screen-gameboy-text 8s linear;
}
@-moz-keyframes screen-nintendo-text {
0% {
@ -3730,8 +3725,8 @@ body {
font-family: Arial Black, sans-serif;
font-weight: 700;
font-size: 13px;
-webkit-animation: screen-nintendo-text 1s linear;
animation: screen-nintendo-text 1s linear;
-webkit-animation: screen-nintendo-text 8s linear;
animation: screen-nintendo-text 8s linear;
}
@-moz-keyframes joystick-pad {
0% {
@ -3861,8 +3856,8 @@ body {
background-color: #f0f0f0;
left: 11px;
bottom: 130px;
-webkit-animation: joystick-pad 1s linear;
animation: joystick-pad 1s linear;
-webkit-animation: joystick-pad 8s linear;
animation: joystick-pad 8s linear;
}
@-moz-keyframes joystick {
0% {
@ -4060,8 +4055,8 @@ body {
line-height: 44px;
text-indent: 43px;
color: #222222;
-webkit-animation: joystick 1s linear;
animation: joystick 1s linear;
-webkit-animation: joystick 8s linear;
animation: joystick 8s linear;
}
#joystick::before,
#joystick::after {
@ -4083,8 +4078,8 @@ body {
text-align: center;
font-size: 16px;
color: #222222;
-webkit-animation: joystick-control 1s linear;
animation: joystick-control 1s linear;
-webkit-animation: joystick-control 8s linear;
animation: joystick-control 8s linear;
}
#joystick::after {
-webkit-transform: rotateZ(-90deg);
@ -4235,8 +4230,8 @@ body {
box-shadow: 0 3px;
-webkit-transform: rotate(-18deg);
transform: rotate(-18deg);
-webkit-animation: control 1s linear;
animation: control 1s linear;
-webkit-animation: control 8s linear;
animation: control 8s linear;
}
.control-button {
position: absolute;
@ -4332,8 +4327,8 @@ body {
color: white;
right: 97px;
bottom: 156px;
-webkit-animation: control-b 1s linear;
animation: control-b 1s linear;
-webkit-animation: control-b 8s linear;
animation: control-b 8s linear;
}
@-moz-keyframes control-a {
0% {
@ -4414,8 +4409,8 @@ body {
color: white;
right: 34px;
bottom: 176px;
-webkit-animation: control-a 1s linear;
animation: control-a 1s linear;
-webkit-animation: control-a 8s linear;
animation: control-a 8s linear;
}
@-moz-keyframes start-select-box {
0% {
@ -4550,8 +4545,8 @@ body {
bottom: 85px;
left: 120px;
background-color: #f0f0f0;
-webkit-animation: start-select-box 1s linear;
animation: start-select-box 1s linear;
-webkit-animation: start-select-box 8s linear;
animation: start-select-box 8s linear;
}
#start-select-box::before {
content: "";
@ -4564,8 +4559,8 @@ body {
background-color: #9d9d9d;
left: 5px;
box-shadow: 50px 0 #9d9d9d;
-webkit-animation: start-select-pad 1s linear;
animation: start-select-pad 1s linear;
-webkit-animation: start-select-pad 8s linear;
animation: start-select-pad 8s linear;
}
@-moz-keyframes start-select-button {
0% {
@ -4637,8 +4632,8 @@ body {
background-color: #303030;
border-radius: 80% / 100%;
box-shadow: 50px 0 #303030;
-webkit-animation: start-select-button 1s linear;
animation: start-select-button 1s linear;
-webkit-animation: start-select-button 8s linear;
animation: start-select-button 8s linear;
}
#cover-vertical {
position: absolute;
@ -4787,8 +4782,8 @@ body {
}
#gameboy.transparent #cover-vertical {
background-color: rgba(140, 46, 217, 0.125);
-webkit-animation: cover-vertical-transparent 1s linear;
animation: cover-vertical-transparent 1s linear;
-webkit-animation: cover-vertical-transparent 8s linear;
animation: cover-vertical-transparent 8s linear;
box-shadow: 34px 0 rgba(140, 46, 217, 0.125), 68px 0 rgba(140, 46, 217, 0.125), 102px 0 rgba(140, 46, 217, 0.125), 136px 0 rgba(140, 46, 217, 0.125), 170px 0 rgba(140, 46, 217, 0.125), 204px 0 rgba(140, 46, 217, 0.125), 238px 0 rgba(140, 46, 217, 0.125), 272px 0 rgba(140, 46, 217, 0.125), 306px 0 rgba(140, 46, 217, 0.125);
}
@-moz-keyframes cover-vertical-teal {
@ -4931,8 +4926,8 @@ body {
}
#gameboy.teal #cover-vertical {
background-color: #01b4dd;
-webkit-animation: cover-vertical-teal 1s linear;
animation: cover-vertical-teal 1s linear;
-webkit-animation: cover-vertical-teal 8s linear;
animation: cover-vertical-teal 8s linear;
box-shadow: 34px 0 #01b4dd, 68px 0 #01b4dd, 102px 0 #01b4dd, 136px 0 #01b4dd, 170px 0 #01b4dd, 204px 0 #01b4dd, 238px 0 #01b4dd, 272px 0 #01b4dd, 306px 0 #01b4dd;
}
@-moz-keyframes cover-vertical-yellow {
@ -5075,8 +5070,8 @@ body {
}
#gameboy.yellow #cover-vertical {
background-color: #f9e52e;
-webkit-animation: cover-vertical-yellow 1s linear;
animation: cover-vertical-yellow 1s linear;
-webkit-animation: cover-vertical-yellow 8s linear;
animation: cover-vertical-yellow 8s linear;
box-shadow: 34px 0 #f9e52e, 68px 0 #f9e52e, 102px 0 #f9e52e, 136px 0 #f9e52e, 170px 0 #f9e52e, 204px 0 #f9e52e, 238px 0 #f9e52e, 272px 0 #f9e52e, 306px 0 #f9e52e;
}
@-moz-keyframes cover-vertical-green {
@ -5219,8 +5214,8 @@ body {
}
#gameboy.green #cover-vertical {
background-color: #85e367;
-webkit-animation: cover-vertical-green 1s linear;
animation: cover-vertical-green 1s linear;
-webkit-animation: cover-vertical-green 8s linear;
animation: cover-vertical-green 8s linear;
box-shadow: 34px 0 #85e367, 68px 0 #85e367, 102px 0 #85e367, 136px 0 #85e367, 170px 0 #85e367, 204px 0 #85e367, 238px 0 #85e367, 272px 0 #85e367, 306px 0 #85e367;
}
@-moz-keyframes cover-vertical-purple {
@ -5363,8 +5358,8 @@ body {
}
#gameboy.purple #cover-vertical {
background-color: #5151dd;
-webkit-animation: cover-vertical-purple 1s linear;
animation: cover-vertical-purple 1s linear;
-webkit-animation: cover-vertical-purple 8s linear;
animation: cover-vertical-purple 8s linear;
box-shadow: 34px 0 #5151dd, 68px 0 #5151dd, 102px 0 #5151dd, 136px 0 #5151dd, 170px 0 #5151dd, 204px 0 #5151dd, 238px 0 #5151dd, 272px 0 #5151dd, 306px 0 #5151dd;
}
@-moz-keyframes cover-vertical-red {
@ -5507,8 +5502,8 @@ body {
}
#gameboy.red #cover-vertical {
background-color: #ff0151;
-webkit-animation: cover-vertical-red 1s linear;
animation: cover-vertical-red 1s linear;
-webkit-animation: cover-vertical-red 8s linear;
animation: cover-vertical-red 8s linear;
box-shadow: 34px 0 #ff0151, 68px 0 #ff0151, 102px 0 #ff0151, 136px 0 #ff0151, 170px 0 #ff0151, 204px 0 #ff0151, 238px 0 #ff0151, 272px 0 #ff0151, 306px 0 #ff0151;
}
#cover-horizontal {
@ -5704,8 +5699,8 @@ body {
}
#gameboy.transparent #cover-horizontal {
background-color: rgba(140, 46, 217, 0.125);
-webkit-animation: cover-horizontal-transparent 1s linear;
animation: cover-horizontal-transparent 1s linear;
-webkit-animation: cover-horizontal-transparent 8s linear;
animation: cover-horizontal-transparent 8s linear;
box-shadow: 0 -38px rgba(140, 46, 217, 0.125), 0 -76px rgba(140, 46, 217, 0.125), 0 -114px rgba(140, 46, 217, 0.125), 0 -152px rgba(140, 46, 217, 0.125), 0 -190px rgba(140, 46, 217, 0.125), 0 -228px rgba(140, 46, 217, 0.125), 0 -266px rgba(140, 46, 217, 0.125), 0 -304px rgba(140, 46, 217, 0.125), 0 -342px rgba(140, 46, 217, 0.125), 0 -380px rgba(140, 46, 217, 0.125), 0 -418px rgba(140, 46, 217, 0.125), 0 -456px rgba(140, 46, 217, 0.125), 0 -494px rgba(140, 46, 217, 0.125), 0 -532px rgba(140, 46, 217, 0.125);
}
@-moz-keyframes cover-horizontal-teal {
@ -5893,8 +5888,8 @@ body {
}
#gameboy.teal #cover-horizontal {
background-color: #01b4dd;
-webkit-animation: cover-horizontal-teal 1s linear;
animation: cover-horizontal-teal 1s linear;
-webkit-animation: cover-horizontal-teal 8s linear;
animation: cover-horizontal-teal 8s linear;
box-shadow: 0 -38px #01b4dd, 0 -76px #01b4dd, 0 -114px #01b4dd, 0 -152px #01b4dd, 0 -190px #01b4dd, 0 -228px #01b4dd, 0 -266px #01b4dd, 0 -304px #01b4dd, 0 -342px #01b4dd, 0 -380px #01b4dd, 0 -418px #01b4dd, 0 -456px #01b4dd, 0 -494px #01b4dd, 0 -532px #01b4dd;
}
@-moz-keyframes cover-horizontal-yellow {
@ -6082,8 +6077,8 @@ body {
}
#gameboy.yellow #cover-horizontal {
background-color: #f9e52e;
-webkit-animation: cover-horizontal-yellow 1s linear;
animation: cover-horizontal-yellow 1s linear;
-webkit-animation: cover-horizontal-yellow 8s linear;
animation: cover-horizontal-yellow 8s linear;
box-shadow: 0 -38px #f9e52e, 0 -76px #f9e52e, 0 -114px #f9e52e, 0 -152px #f9e52e, 0 -190px #f9e52e, 0 -228px #f9e52e, 0 -266px #f9e52e, 0 -304px #f9e52e, 0 -342px #f9e52e, 0 -380px #f9e52e, 0 -418px #f9e52e, 0 -456px #f9e52e, 0 -494px #f9e52e, 0 -532px #f9e52e;
}
@-moz-keyframes cover-horizontal-green {
@ -6271,8 +6266,8 @@ body {
}
#gameboy.green #cover-horizontal {
background-color: #85e367;
-webkit-animation: cover-horizontal-green 1s linear;
animation: cover-horizontal-green 1s linear;
-webkit-animation: cover-horizontal-green 8s linear;
animation: cover-horizontal-green 8s linear;
box-shadow: 0 -38px #85e367, 0 -76px #85e367, 0 -114px #85e367, 0 -152px #85e367, 0 -190px #85e367, 0 -228px #85e367, 0 -266px #85e367, 0 -304px #85e367, 0 -342px #85e367, 0 -380px #85e367, 0 -418px #85e367, 0 -456px #85e367, 0 -494px #85e367, 0 -532px #85e367;
}
@-moz-keyframes cover-horizontal-purple {
@ -6460,8 +6455,8 @@ body {
}
#gameboy.purple #cover-horizontal {
background-color: #5151dd;
-webkit-animation: cover-horizontal-purple 1s linear;
animation: cover-horizontal-purple 1s linear;
-webkit-animation: cover-horizontal-purple 8s linear;
animation: cover-horizontal-purple 8s linear;
box-shadow: 0 -38px #5151dd, 0 -76px #5151dd, 0 -114px #5151dd, 0 -152px #5151dd, 0 -190px #5151dd, 0 -228px #5151dd, 0 -266px #5151dd, 0 -304px #5151dd, 0 -342px #5151dd, 0 -380px #5151dd, 0 -418px #5151dd, 0 -456px #5151dd, 0 -494px #5151dd, 0 -532px #5151dd;
}
@-moz-keyframes cover-horizontal-red {
@ -6649,8 +6644,8 @@ body {
}
#gameboy.red #cover-horizontal {
background-color: #ff0151;
-webkit-animation: cover-horizontal-red 1s linear;
animation: cover-horizontal-red 1s linear;
-webkit-animation: cover-horizontal-red 8s linear;
animation: cover-horizontal-red 8s linear;
box-shadow: 0 -38px #ff0151, 0 -76px #ff0151, 0 -114px #ff0151, 0 -152px #ff0151, 0 -190px #ff0151, 0 -228px #ff0151, 0 -266px #ff0151, 0 -304px #ff0151, 0 -342px #ff0151, 0 -380px #ff0151, 0 -418px #ff0151, 0 -456px #ff0151, 0 -494px #ff0151, 0 -532px #ff0151;
}
@-moz-keyframes gloss {
@ -6713,8 +6708,8 @@ body {
left: 54px;
border-bottom-left-radius: 116px 20px;
border-bottom-right-radius: 116px 20px;
-webkit-animation: gloss 1s linear;
animation: gloss 1s linear;
-webkit-animation: gloss 8s linear;
animation: gloss 8s linear;
}
#gameboy.transparent #gloss {
background-color: rgba(242, 232, 251, 0.43125);
@ -6777,8 +6772,8 @@ body {
z-index: 14;
border-radius: 50%;
box-shadow: 9.5px 8px #222222, 19px 16px #222222, 0px -19px #222222, 9.5px -11px #222222, 19px -3px #222222, 28.5px 5px #222222, 38px 13px #222222, 0px -38px #222222, 9.5px -30px #222222, 19px -22px #222222, 28.5px -14px #222222, 38px -6px #222222, 47.5px 2px #222222, 57px 10px #222222, 9.5px -49px #222222, 19px -41px #222222, 28.5px -33px #222222, 38px -25px #222222, 47.5px -17px #222222, 57px -9px #222222, 66.5px -1px #222222, 28px -52px #222222, 37.5px -44px #222222, 47px -36px #222222, 56.5px -28px #222222, 66px -20px #222222, 47px -56px #222222, 56.5px -48px #222222, 66px -40px #222222;
-webkit-animation: speaker-holes 1s linear;
animation: speaker-holes 1s linear;
-webkit-animation: speaker-holes 8s linear;
animation: speaker-holes 8s linear;
}
@-moz-keyframes power {
0% {
@ -6865,14 +6860,18 @@ body {
border-radius: 50%;
background-color: #ff0151;
box-shadow: 5px 0 #222222, 8px 0 #b0b0b0, 12px 0 #222222, 15px 0 #b0b0b0, 19px 0 #222222, 22px 0 #b0b0b0;
-webkit-animation: power 1s linear;
animation: power 1s linear;
-webkit-animation: power 8s linear;
animation: power 8s linear;
}
#colors {
position: fixed;
bottom: 0px;
right: 20px;
font-family: 'Oxygen', Helvetica, arial, sans-serif;
font-weight: 300;
width: 100%;
margin: 0 auto 40px;
/* width: 100%; */
margin: 0 auto 20px;
text-align: center;
}
#colors span {
@ -6919,4 +6918,4 @@ body {
a {
color: lightblue;
}
}

View file

@ -37,7 +37,7 @@
<div id="glass-gameboy-text">GAME BOY</div>
<div id="glass-color-text">C</div>
<div id="screen">
<video id="loading-screen" autoplay=true poster="https://orig00.deviantart.net/cdcd/f/2017/276/a/a/october_2nd___gameboy_poltergeist_by_wanyo-dbpdmnd.gif">
<video id="game-screen" autoplay=true poster="https://orig00.deviantart.net/cdcd/f/2017/276/a/a/october_2nd___gameboy_poltergeist_by_wanyo-dbpdmnd.gif">
</video>
<div id="menu-screen">
@ -78,7 +78,7 @@
<div>Rocked by <a href="https://github.com/giongto35">giongto35</a> <i class="far fa-handshake"></i> <a href="https://trich.im">trichimtrich</a></div>
</div>
<div style="position:absolute; top:30%; left: 5%;">
<div style="position:absolute; top:17%; left: 5%;">
<h3>Instruction</h3>
<div>
Menu:
@ -119,61 +119,7 @@
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<script src="/static/js/const.js"></script>
<script>
/*
GLOBAL VARS
*/
// Game state
screenState = "loader";
gameIdx = 0;
// Input vars
keyState = {
// controllers
a: false,
b: false,
start: false,
select: false,
// navigators
up: false,
down: false,
left: false,
right: false,
// game meta keys
save: false,
load: false,
// unofficial
quit: false
}
stateUnchange = true;
unchangePacket = INPUT_STATE_PACKET;
inputTimer = null;
// Connection vars
var pc, inputChannel;
var localSessionDescription = "";
var remoteSessionDescription = "";
var conn;
/*
FUNCTIONS
*/
// miscs
function log(msg) {
if (DEBUG) {
document.getElementById('div').innerHTML += msg + '<br>'
console.log(msg);
}
}
</script>
<script src="/static/js/global.js"></script>
<script src="/static/js/gameboy_loader.js"></script>
<script src="/static/js/gameboy_controller.js"></script>
<script src="/static/js/ws.js"></script>

View file

@ -8,20 +8,11 @@ textarea {
</style>
<select id="gameOp">
<option value="Contra.nes">Contra.nes</option>
<option value="Kirby's Adventure.nes">Kirby's Adventure.nes</option>
<option value="Mega Man 2.nes">Mega Man 2.nes</option>
<option value="Metal Gear.nes">Metal Gear.nes</option>
<option value="Mortal Kombat 4.nes">Mortal Kombat 4.nes</option>
<option value="Super Mario Bros 2.nes">Super Mario Bros 2.nes</option>
<option value="Super Mario Bros 3.nes">Super Mario Bros 3.nes</option>
<option value="Super Mario Bros.nes">Super Mario Bros.nes</option>
<option value="Teenage Mutant Ninja Turtles 3.nes">Teenage Mutant Ninja Turtles 3.nes</option>
<option value="VS Super Mario Bros.nes">VS Super Mario Bros.nes</option>
<option value="supermariobros.rom">supermariobros.rom</option>
<option value="zelda.rom">zelda.rom</option>
</select>
<button id="play" onclick="window.startGame()">Play</button>
<button id="play" onclick="window.startGame()">Play</button><br/><br/>
Your current room: <b><label id="currentRoomID" style="color:white"></b> <br />
You can join a remote game by roomID.<br />
Room ID: <input type="text" id="roomID">
Play as player(1,2): <select id="playerIndex">
<option value="1">1</option>
@ -31,7 +22,9 @@ Play as player(1,2): <select id="playerIndex">
<br/><br/>
<body scroll="no" style="overflow: hidden">
<div id="remoteVideos"></div> <br />
<div id="remoteVideos">
<video id="game-screen" autoplay=true width=400 height=300 poster="https://orig00.deviantart.net/cdcd/f/2017/276/a/a/october_2nd___gameboy_poltergeist_by_wanyo-dbpdmnd.gif" style="display: none">
</div> <br />
<h3>Instruction</h3>
<div>
@ -53,248 +46,28 @@ Play as player(1,2): <select id="playerIndex">
<div>
🎮<u><i>Refresh to retry when checking is too long</i></u>
</div>
<script>
// miscs
DEBUG = true;
let log = msg => {
if (DEBUG) {
document.getElementById('div').innerHTML += msg + '<br>'
console.log(msg);
}
}
// web socket
window.startGame = () => {
// clear
endInput();
document.getElementById('div').innerHTML = "";
aa = document.getElementsByTagName("video");
for (i = 0; i < aa.length ; ++i) {
aa[i].remove();
}
// end clear
conn = new WebSocket(`ws://${location.host}/ws`);
conn.onopen = () => {
log("WebSocket is opened. Send ping");
conn.send(JSON.stringify({"id": "ping", "data": gameOp.value, "room_id": roomID.value, "player_index": parseInt(playerIndex.value, 10)}));
}
conn.onerror = error => {
log(`Websocket error: ${error}`);
}
conn.onclose = () => {
log("Websocket closed");
// pc.close();
}
conn.onmessage = e => {
d = JSON.parse(e.data);
switch (d["id"]) {
case "pong":
log("Recv pong. Start webrtc");
startWebRTC();
break;
case "sdp":
log("Got remote sdp");
pc.setRemoteDescription(new RTCSessionDescription(JSON.parse(atob(d["data"]))));
break;
case "start":
log("Got start");
roomID.value = d["room_id"]
break;
}
}
// webrtc
pc = new RTCPeerConnection({iceServers: [{urls: 'stun:stun.l.google.com:19302'}]})
// input channel
inputChannel = pc.createDataChannel('foo')
inputChannel.onclose = () => {
log('inputChannel has closed');
}
inputChannel.onopen = () => {
log('inputChannel has opened');
}
inputChannel.onmessage = e => {
log(`Message from DataChannel '${inputChannel.label}' payload '${e.data}'`);
}
pc.oniceconnectionstatechange = e => {
log(`iceConnectionState: ${pc.iceConnectionState}`);
if (pc.iceConnectionState === "connected") {
conn.send(JSON.stringify({"id": "start", "data": ""}));
startInput();
}
else if (pc.iceConnectionState === "disconnected") {
// else { // not sure about this =]
endInput();
}
}
// stream channel
pc.ontrack = function (event) {
var el = document.createElement(event.track.kind);
// console.log(event.streams);
el.srcObject = event.streams[0];
el.autoplay = true;
el.width = 800;
el.height = 600;
el.poster = new URL("https://orig00.deviantart.net/cdcd/f/2017/276/a/a/october_2nd___gameboy_poltergeist_by_wanyo-dbpdmnd.gif");
document.getElementById('remoteVideos').appendChild(el)
}
// candidate packet from STUN
pc.onicecandidate = event => {
if (event.candidate === null) {
// send to ws
session = btoa(JSON.stringify(pc.localDescription));
localSessionDescription = session;
log("Send SDP to remote peer");
conn.send(JSON.stringify({"id": "sdp", "data": session}));
}
else {
console.log(JSON.stringify(event.candidate));
// conn.send(JSON.stringify({"id": "candidate", "data": JSON.stringify(event.candidate)}));
}
}
}
// webrtc
var pc, inputChannel;
var localSessionDescription = "";
var remoteSessionDescription = "";
var conn;
// Input handler
keyState = {
// controllers
a: false,
b: false,
start: false,
select: false,
// navigators
up: false,
down: false,
left: false,
right: false,
// game meta keys
save: false,
load: false,
}
keyMap = {
37: "left",
38: "up",
39: "right",
40: "down",
90: "a",
88: "b",
67: "start",
86: "select",
83: "save",
76: "load",
}
INPUT_FPS = 100;
INPUT_STATE_PACKET = 5;
stateUnchange = true;
unchangePacket = INPUT_STATE_PACKET;
function setState(e, bo) {
if (e.keyCode in keyMap) {
keyState[keyMap[e.keyCode]] = bo;
stateUnchange = false;
unchangePacket = INPUT_STATE_PACKET;
}
}
document.body.onkeydown = function(e){
setState(e, true);
};
document.body.onkeyup = function(e){
setState(e, false);
};
var timer = null;
function sendInput() {
// prepare key
/*
const (
ButtonA = iota
ButtonB
ButtonSelect
ButtonStart
ButtonUp
ButtonDown
ButtonLeft
ButtonRight
)
*/
if (stateUnchange || unchangePacket > 0) {
st = "";
["a", "b", "select", "start", "up", "down", "left", "right", "save", "load"].reverse().forEach(elem => {
st += keyState[elem]?1:0;
});
ss = parseInt(st, 2);
console.log(`Key state string: ${st} ==> ${ss}`);
// send
inputChannel.send(ss);
stateUnchange = false;
unchangePacket--;
}
}
function startInput() {
if (timer == null) {
timer = setInterval(sendInput, 1000 / INPUT_FPS)
}
}
function endInput() {
clearInterval(timer);
timer = null;
}
function startWebRTC() {
// receiver only tracks
pc.addTransceiver('video', {'direction': 'recvonly'});
// create SDP
pc.createOffer({offerToReceiveVideo: true, offerToReceiveAudio: false}).then(d => {
pc.setLocalDescription(d).catch(log);
})
}
DEBUG = true;
</script>
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<script src="/static/js/const.js"></script>
<script src="/static/js/global.js"></script>
<script src="/static/js/gameboy_controller.js"></script>
<script src="/static/js/ws.js"></script>
<script>
GAME_LIST.forEach(e => {
ee = document.createElement("option");
ee.value = e.nes;
ee.innerHTML = e.name;
gameOp.append(ee);
});
$("#gameOp").on("change", function() {
gameIdx = gameOp.selectedIndex;
});
</script>
</html>

View file

@ -1,6 +1,3 @@
// miscs
DEBUG = true;
// list game
GAME_LIST = [
{
@ -78,9 +75,11 @@ KEY_MAP = {
67: "start", // c
86: "select", // v
// non-game
81: "quit", // q
83: "save", // s
76: "load" // l
76: "load", // l
70: "full", // f
}
/*
@ -95,7 +94,8 @@ KEY_MAP = {
ButtonRight
)
*/
KEY_BIT = ["a", "b", "select", "start", "up", "down", "left", "right", "save", "load"];
// KEY_BIT = ["a", "b", "select", "start", "up", "down", "left", "right", "save", "load"];
KEY_BIT = ["a", "b", "select", "start", "up", "down", "left", "right"];
INPUT_FPS = 100;

View file

@ -21,25 +21,27 @@ function showMenuScreen() {
log(`> [Warning] Websocket connection: ${err}`);
}
$("#loading-screen").hide();
$("#menu-screen").hide();
// show
$("#loading-screen").show().delay(1000).fadeOut(400, () => {
log("Loading menu screen");
$("#menu-screen").fadeIn(400, () => {
chooseGame(7);
screenState = "menu";
$("#game-screen").hide();
if (!DEBUG) {
$("#menu-screen").hide();
// show
$("#game-screen").show().delay(1000).fadeOut(400, () => {
log("Loading menu screen");
$("#menu-screen").fadeIn(400, () => {
chooseGame(gameIdx, true);
screenState = "menu";
});
});
});
} else {
screenState = "debug";
}
}
// game menu
function chooseGame(idx) {
if (idx < 0 || idx == gameIdx || idx >= GAME_LIST.length) return false;
function chooseGame(idx, force=false) {
if (idx < 0 || (idx == gameIdx && !force) || idx >= GAME_LIST.length) return false;
$("#menu-screen #box-art").fadeOut(400, function () {
$(this).attr("src", `/static/img/boxarts/${GAME_LIST[idx].art}`);
@ -75,23 +77,43 @@ function setState(e, bo) {
document.body.onkeyup = function (e) {
if (screenState === "menu") {
switch (KEY_MAP[e.keyCode]) {
case "left":
chooseGame(gameIdx - 1);
break;
case "left":
chooseGame(gameIdx - 1);
break;
case "right":
chooseGame(gameIdx + 1);
break;
case "right":
chooseGame(gameIdx + 1);
break;
case "select":
startGame();
case "select":
startGame();
}
} else if (screenState === "game") {
setState(e, false);
switch (KEY_MAP[e.keyCode]) {
case "save":
conn.send(JSON.stringify({"id": "save", "data": ""}));
break;
case "load":
conn.send(JSON.stringify({"id": "load", "data": ""}));
break;
case "full":
// Fullscreen
screen = document.getElementById("game-screen");
console.log(screen.height, window.innerHeight);
if (screen.height === window.innerHeight) {
closeFullscreen();
} else {
openFullscreen(screen);
}
break;
}
}
// global reset
if (KEY_MAP[e.keyCode] == "quit") {
if (KEY_MAP[e.keyCode] === "quit") {
endInput();
showMenuScreen();
}
@ -125,19 +147,6 @@ function closeFullscreen() {
document.body.onkeydown = function (e) {
if (screenState === "game") {
// Meta key not related to Game
if (e.keyCode === 70) {
// Fullscreen
screen = document.getElementById("loading-screen")
console.log(screen.height, window.innerHeight)
if (screen.height === window.innerHeight) {
closeFullscreen()
} else {
openFullscreen(screen)
}
}
// game keys
setState(e, true);
}
@ -155,7 +164,9 @@ function sendInput() {
console.log(`Key state string: ${st} ==> ${ss}`);
// send
inputChannel.send(ss);
a = new Uint8Array(1);
a[0] = ss;
inputChannel.send(a);
stateUnchange = false;
unchangePacket--;

56
static/js/global.js Normal file
View file

@ -0,0 +1,56 @@
/*
GLOBAL VARS
*/
LOG = true;
if (!("DEBUG" in window)) {
window.DEBUG = false;
}
// Game state
screenState = "loader";
gameIdx = 0;
// Input vars
keyState = {
// controllers
a: false,
b: false,
start: false,
select: false,
// navigators
up: false,
down: false,
left: false,
right: false,
// game meta keys
save: false,
load: false,
// unofficial
quit: false
}
stateUnchange = true;
unchangePacket = INPUT_STATE_PACKET;
inputTimer = null;
// Connection vars
var pc, inputChannel;
var localSessionDescription = "";
var remoteSessionDescription = "";
var conn;
/*
FUNCTIONS
*/
// miscs
function log(msg) {
if (LOG) {
document.getElementById('div').innerHTML += msg + '<br>'
console.log(msg);
}
}

View file

@ -6,8 +6,11 @@ function startGame() {
// clear
endInput();
document.getElementById('div').innerHTML = "";
$("#loading-screen").show();
$("#menu-screen").fadeOut();
if (!DEBUG) {
$("#menu-screen").fadeOut(400, function() {
$("#game-screen").show();
});
}
// end clear
conn = new WebSocket(`ws://${location.host}/ws`);
@ -42,6 +45,12 @@ function startGame() {
roomID.value = ""
currentRoomID.innerText = d["room_id"]
break;
case "save":
log(`Got save response: ${d["data"]}`);
break;
case "load":
log(`Got load response: ${d["data"]}`);
break;
}
}
@ -78,7 +87,8 @@ function startGame() {
// stream channel
pc.ontrack = function (event) {
log("New stream, yay!");
document.getElementById("loading-screen").srcObject = event.streams[0];
document.getElementById("game-screen").srcObject = event.streams[0];
$("#game-screen").show();
}

View file

@ -8,20 +8,15 @@ import (
"github.com/giongto35/cloud-game/nes"
)
type View interface {
Enter()
Exit()
Update(t, dt float64)
}
type Director struct {
// audio *Audio
view View
view *GameView
timestamp float64
imageChannel chan *image.RGBA
inputChannel chan int
closedChannel chan bool
roomID string
hash string
}
const FPS = 60
@ -33,10 +28,11 @@ func NewDirector(roomID string, imageChannel chan *image.RGBA, inputChannel chan
director.inputChannel = inputChannel
director.closedChannel = closedChannel
director.roomID = roomID
director.hash = ""
return &director
}
func (d *Director) SetView(view View) {
func (d *Director) SetView(view *GameView) {
if d.view != nil {
d.view.Exit()
}
@ -87,10 +83,27 @@ func (d *Director) PlayGame(path string) {
if err != nil {
log.Fatalln(err)
}
d.hash = hash
console, err := nes.NewConsole(path)
if err != nil {
log.Fatalln(err)
}
// Set GameView as current view
d.SetView(NewGameView(d, console, path, hash, d.imageChannel, d.inputChannel))
d.SetView(NewGameView(console, path, hash, d.imageChannel, d.inputChannel))
}
func (d *Director) SaveGame() error {
if d.hash != "" {
return d.view.console.SaveState(savePath(d.hash))
} else {
return nil
}
}
func (d *Director) LoadGame() error {
if d.hash != "" {
return d.view.console.LoadState(savePath(d.hash))
} else {
return nil
}
}

View file

@ -3,14 +3,9 @@ package ui
import (
"image"
// "strconv"
"github.com/giongto35/cloud-game/nes"
)
const padding = 0
// List key pressed
const (
a1 = iota
@ -21,8 +16,7 @@ const (
down1
left1
right1
save1
load1
a2
b2
select2
@ -31,13 +25,10 @@ const (
down2
left2
right2
save2
load2
)
const NumKeys = 10
const NumKeys = 8
type GameView struct {
director *Director
console *nes.Console
title string
hash string
@ -49,9 +40,8 @@ type GameView struct {
inputChannel chan int
}
func NewGameView(director *Director, console *nes.Console, title, hash string, imageChannel chan *image.RGBA, inputChannel chan int) View {
func NewGameView(console *nes.Console, title, hash string, imageChannel chan *image.RGBA, inputChannel chan int) *GameView {
gameview := &GameView{
director: director,
console: console,
title: title,
hash: hash,
@ -126,19 +116,19 @@ func (view *GameView) Update(t, dt float64) {
func (view *GameView) updateControllers() {
// Divide keyPressed to player 1 and player 2
// First 10 keys are player 1
// First 8 keys are player 1
var player1Keys [8]bool
copy(player1Keys[:], view.keyPressed[:8])
var player2Keys [8]bool
copy(player2Keys[:], view.keyPressed[10:18])
copy(player2Keys[:], view.keyPressed[8:])
view.console.Controller1.SetButtons(player1Keys)
view.console.Controller2.SetButtons(player2Keys)
if view.keyPressed[save1] || view.keyPressed[save2] {
view.console.SaveState(savePath(view.hash))
}
if view.keyPressed[load1] || view.keyPressed[load2] {
view.console.LoadState(savePath(view.hash))
}
// if view.keyPressed[save1] || view.keyPressed[save2] {
// view.console.SaveState(savePath(view.hash))
// }
// if view.keyPressed[load1] || view.keyPressed[load2] {
// view.console.LoadState(savePath(view.hash))
// }
}

View file

@ -1,38 +0,0 @@
// credit to https://github.com/fogleman/nes
package ui
import (
"image"
// "log"
"runtime"
// "github.com/gordonklaus/portaudio"
)
const (
width = 256
height = 240
scale = 3
title = "NES"
)
func init() {
// we need to keep OpenGL calls on a single thread
runtime.LockOSThread()
}
func Run(paths []string, roomID string, imageChannel chan *image.RGBA, inputChannel chan int, closedChannel chan bool) {
// TODO: Add these code back when we support stream audio
// 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(roomID, imageChannel, inputChannel, closedChannel)
director.Start(paths)
}

View file

@ -6,10 +6,9 @@ import (
"compress/gzip"
"encoding/base64"
"encoding/json"
"fmt"
"log"
"io/ioutil"
"math/rand"
"strconv"
"time"
vpxEncoder "github.com/giongto35/cloud-game/vpx-encoder"
@ -114,7 +113,7 @@ type WebRTC struct {
func (w *WebRTC) StartClient(remoteSession string, width, height int) (string, error) {
defer func() {
if err := recover(); err != nil {
fmt.Println(err)
log.Println(err)
w.StopClient()
}
}()
@ -131,7 +130,7 @@ func (w *WebRTC) StartClient(remoteSession string, width, height int) (string, e
}
w.encoder = encoder
fmt.Println("=== StartClient ===")
log.Println("=== StartClient ===")
w.connection, err = webrtc.NewPeerConnection(config)
if err != nil {
@ -149,11 +148,11 @@ func (w *WebRTC) StartClient(remoteSession string, width, height int) (string, e
// WebRTC state callback
w.connection.OnICEConnectionStateChange(func(connectionState webrtc.ICEConnectionState) {
fmt.Printf("ICE Connection State has changed: %s\n", connectionState.String())
log.Printf("ICE Connection State has changed: %s\n", connectionState.String())
if connectionState == webrtc.ICEConnectionStateConnected {
go func() {
w.isConnected = true
fmt.Println("ConnectionStateConnected")
log.Println("ConnectionStateConnected")
w.startStreaming(vp8Track)
}()
@ -165,25 +164,21 @@ func (w *WebRTC) StartClient(remoteSession string, width, height int) (string, e
// TODO: take a look at this
w.connection.OnICECandidate(func(iceCandidate *webrtc.ICECandidate) {
fmt.Println(iceCandidate)
log.Println(iceCandidate)
})
// Data channel callback
w.connection.OnDataChannel(func(d *webrtc.DataChannel) {
fmt.Printf("New DataChannel %s %d\n", d.Label(), d.ID())
log.Printf("New DataChannel %s %d\n", d.Label(), d.ID())
// Register channel opening handling
d.OnOpen(func() {
fmt.Printf("Data channel '%s'-'%d' open.\n", d.Label(), d.ID())
log.Printf("Data channel '%s'-'%d' open.\n", d.Label(), d.ID())
})
// Register text message handling
d.OnMessage(func(msg webrtc.DataChannelMessage) {
//fmt.Printf("Message from DataChannel '%s': '%s' byte '%b'\n", d.Label(), string(msg.Data), msg.Data)
i, _ := strconv.Atoi(string(msg.Data))
// TODO: check if we can send int directly
// Input is key state, represented as binary string, 1001011. We compress it to binary number
w.InputChannel <- i
w.InputChannel <- int(msg.Data[0])
})
d.OnClose(func() {
@ -213,13 +208,13 @@ func (w *WebRTC) StartClient(remoteSession string, width, height int) (string, e
func (w *WebRTC) AddCandidate(candidate webrtc.ICECandidateInit) {
err := w.connection.AddICECandidate(candidate)
if err != nil {
fmt.Println("Cannot add candidate: ", err)
log.Println("Cannot add candidate: ", err)
}
}
// StopClient disconnect
func (w *WebRTC) StopClient() {
fmt.Println("===StopClient===")
log.Println("===StopClient===")
w.isConnected = false
if w.encoder != nil {
w.encoder.Release()
@ -241,7 +236,7 @@ func (w *WebRTC) IsClosed() bool {
}
func (w *WebRTC) startStreaming(vp8Track *webrtc.Track) {
fmt.Println("Start streaming")
log.Println("Start streaming")
// send screenshot
go func() {
for w.isConnected {