mirror of
https://github.com/giongto35/cloud-game.git
synced 2026-07-21 17:18:47 +00:00
reconstruct debug frontend, reduce input in 8 bit, save and load in websocket
This commit is contained in:
parent
eb8b465acd
commit
768baf02ba
13 changed files with 427 additions and 629 deletions
|
|
@ -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
84
main.go
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
56
static/js/global.js
Normal 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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))
|
||||
// }
|
||||
}
|
||||
|
|
|
|||
38
ui/run.go
38
ui/run.go
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue