mirror of
https://github.com/giongto35/cloud-game.git
synced 2026-07-23 01:57:22 +00:00
Add 2 players
This commit is contained in:
parent
ee2dfa726e
commit
387cfc91b0
5 changed files with 74 additions and 49 deletions
28
main.go
28
main.go
|
|
@ -41,9 +41,10 @@ type IndexPageData struct {
|
|||
var upgrader = websocket.Upgrader{}
|
||||
|
||||
type WSPacket struct {
|
||||
ID string `json:"id"`
|
||||
Data string `json:"data"`
|
||||
RoomID string `json:"room_id"`
|
||||
ID string `json:"id"`
|
||||
Data string `json:"data"`
|
||||
RoomID string `json:"room_id"`
|
||||
PlayerIndex int `json:"player_index"`
|
||||
}
|
||||
|
||||
type SessionPacket struct {
|
||||
|
|
@ -123,13 +124,14 @@ func initRoom(roomID, gameName string) string {
|
|||
return roomID
|
||||
}
|
||||
|
||||
func startSession(webRTC *webrtc.WebRTC, gameName string, roomID string) string {
|
||||
func startSession(webRTC *webrtc.WebRTC, gameName string, roomID string, playerIndex int) string {
|
||||
if roomID == "" {
|
||||
roomID = initRoom(roomID, gameName)
|
||||
}
|
||||
|
||||
// TODO: Might have race condition
|
||||
rooms[roomID].rtcSessions = append(rooms[roomID].rtcSessions, webRTC)
|
||||
faninInput(rooms[roomID].inputChannel, webRTC)
|
||||
faninInput(rooms[roomID].inputChannel, webRTC, playerIndex)
|
||||
|
||||
return roomID
|
||||
}
|
||||
|
|
@ -152,6 +154,7 @@ func ws(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
var gameName string
|
||||
var roomID string
|
||||
var playerIndex int
|
||||
|
||||
for !isDone {
|
||||
mt, message, err := c.ReadMessage()
|
||||
|
|
@ -174,6 +177,7 @@ func ws(w http.ResponseWriter, r *http.Request) {
|
|||
case "ping":
|
||||
gameName = req.Data
|
||||
roomID = req.RoomID
|
||||
playerIndex = req.PlayerIndex
|
||||
log.Println("Ping from server with game:", gameName)
|
||||
res.ID = "pong"
|
||||
|
||||
|
|
@ -200,7 +204,7 @@ func ws(w http.ResponseWriter, r *http.Request) {
|
|||
case "start":
|
||||
log.Println("Starting game")
|
||||
res.ID = "start"
|
||||
res.RoomID = startSession(webRTC, gameName, roomID)
|
||||
res.RoomID = startSession(webRTC, gameName, roomID, playerIndex)
|
||||
|
||||
isDone = true
|
||||
}
|
||||
|
|
@ -262,7 +266,7 @@ func postSession(w http.ResponseWriter, r *http.Request) {
|
|||
// if there is room, reuse image channel, add webRTC session
|
||||
}
|
||||
rooms[roomID].rtcSessions = append(rooms[roomID].rtcSessions, webRTC)
|
||||
faninInput(rooms[roomID].inputChannel, webRTC)
|
||||
faninInput(rooms[roomID].inputChannel, webRTC, 1)
|
||||
|
||||
res := SessionPacket{
|
||||
SDP: localSession,
|
||||
|
|
@ -283,7 +287,7 @@ func generateRoomID() string {
|
|||
return roomID
|
||||
}
|
||||
|
||||
// func fanoutScreen(imageChannel chan *image.RGBA) {
|
||||
// fanoutScreen fanout outputs to all webrtc in the same room
|
||||
func fanoutScreen(imageChannel chan *image.RGBA, roomID string) {
|
||||
for image := range imageChannel {
|
||||
yuv := util.RgbaToYuv(image)
|
||||
|
|
@ -302,8 +306,8 @@ func fanoutScreen(imageChannel chan *image.RGBA, roomID string) {
|
|||
}
|
||||
}
|
||||
|
||||
// func faninInput(input chan int) {
|
||||
func faninInput(inputChannel chan int, webRTC *webrtc.WebRTC) {
|
||||
// faninInput fan-in of the same room to inputChannel
|
||||
func faninInput(inputChannel chan int, webRTC *webrtc.WebRTC, playerIndex int) {
|
||||
go func() {
|
||||
for {
|
||||
// Client stopped
|
||||
|
|
@ -314,6 +318,10 @@ func faninInput(inputChannel chan int, webRTC *webrtc.WebRTC) {
|
|||
// 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 bytes)
|
||||
input = input << ((uint(playerIndex) - 1) * ui.NumKeys / 2)
|
||||
inputChannel <- input
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,10 @@ textarea {
|
|||
</select>
|
||||
<button id="play" onclick="window.startGame()">Play</button>
|
||||
Room ID: <input type="text" id="roomID"><br>
|
||||
Play as player(1,2): <select id="playerIndex">
|
||||
<option value="1">1</option>
|
||||
<option value="2">2</option>
|
||||
</select>
|
||||
|
||||
<br/><br/>
|
||||
|
||||
|
|
@ -31,14 +35,15 @@ Room ID: <input type="text" id="roomID"><br>
|
|||
|
||||
<h3>Instruction</h3>
|
||||
<div>
|
||||
C is start button. Use it to start game<br />
|
||||
V is select button <br />
|
||||
|
||||
Player 1
|
||||
Use Up, Down, Left, Right to Move <br />
|
||||
Z to jump (A) <br />
|
||||
X to sprint (B) <br />
|
||||
C is start button <br />
|
||||
V is select button <br />
|
||||
S to save <br />
|
||||
L to load <br />
|
||||
|
||||
<!--Fullscreen media for better gaming experience<br /-->
|
||||
</div><br>
|
||||
|
||||
|
|
@ -77,8 +82,7 @@ window.startGame = () => {
|
|||
|
||||
conn.onopen = () => {
|
||||
log("WebSocket is opened. Send ping");
|
||||
roomID = document.getElementById('roomID').value
|
||||
conn.send(JSON.stringify({"id": "ping", "data": gameOp.value, "room_id": roomID}));
|
||||
conn.send(JSON.stringify({"id": "ping", "data": gameOp.value, "room_id": roomID.value, "player_index": parseInt(playerIndex.value, 10)}));
|
||||
}
|
||||
|
||||
conn.onerror = error => {
|
||||
|
|
@ -103,7 +107,7 @@ window.startGame = () => {
|
|||
break;
|
||||
case "start":
|
||||
log("Got start");
|
||||
document.getElementById('roomID').value = d["room_id"]
|
||||
roomID.value = d["room_id"]
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -247,7 +251,7 @@ function sendInput() {
|
|||
|
||||
if (stateUnchange || unchangePacket > 0) {
|
||||
st = "";
|
||||
["a", "b", "select", "start", "up", "down", "left", "right", "save", "load"].forEach(elem => {
|
||||
["a", "b", "select", "start", "up", "down", "left", "right", "save", "load"].reverse().forEach(elem => {
|
||||
st += keyState[elem]?1:0;
|
||||
});
|
||||
ss = parseInt(st, 2);
|
||||
|
|
|
|||
|
|
@ -45,7 +45,6 @@ func (d *Director) SetView(view View) {
|
|||
}
|
||||
|
||||
func (d *Director) Step() {
|
||||
//gl.Clear(gl.COLOR_BUFFER_BIT)
|
||||
timestamp := float64(time.Now().Nanosecond()) / float64(time.Second)
|
||||
dt := timestamp - d.timestamp
|
||||
d.timestamp = timestamp
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ package ui
|
|||
import (
|
||||
"image"
|
||||
|
||||
"fmt"
|
||||
// "strconv"
|
||||
|
||||
"github.com/giongto35/cloud-game/nes"
|
||||
|
|
@ -12,6 +11,31 @@ import (
|
|||
|
||||
const padding = 0
|
||||
|
||||
// List key pressed
|
||||
const (
|
||||
a1 = iota
|
||||
b1
|
||||
select1
|
||||
start1
|
||||
up1
|
||||
down1
|
||||
left1
|
||||
right1
|
||||
save1
|
||||
load1
|
||||
a2
|
||||
b2
|
||||
select2
|
||||
start2
|
||||
up2
|
||||
down2
|
||||
left2
|
||||
right2
|
||||
save2
|
||||
load2
|
||||
)
|
||||
const NumKeys = 20
|
||||
|
||||
type GameView struct {
|
||||
director *Director
|
||||
console *nes.Console
|
||||
|
|
@ -20,29 +44,25 @@ type GameView struct {
|
|||
record bool
|
||||
frames []image.Image
|
||||
|
||||
keyPressed [10]bool
|
||||
// equivalent to the list key pressed const above
|
||||
keyPressed [20]bool
|
||||
|
||||
imageChannel chan *image.RGBA
|
||||
inputChannel chan int
|
||||
}
|
||||
|
||||
func NewGameView(director *Director, console *nes.Console, title, hash string, imageChannel chan *image.RGBA, inputChannel chan int) View {
|
||||
gameview := &GameView{director, console, title, hash, false, nil, [10]bool{false}, imageChannel, inputChannel}
|
||||
gameview := &GameView{director, console, title, hash, false, nil, [NumKeys]bool{false}, imageChannel, inputChannel}
|
||||
go gameview.ListenToInputChannel()
|
||||
return gameview
|
||||
}
|
||||
|
||||
func (view *GameView) ListenToInputChannel() {
|
||||
for {
|
||||
key := <-view.inputChannel
|
||||
s := fmt.Sprintf("%.10b", key)
|
||||
fmt.Println(s)
|
||||
for i := 0; i < len(s); i++ {
|
||||
if s[i] == '1' {
|
||||
view.keyPressed[i] = true
|
||||
} else {
|
||||
view.keyPressed[i] = false
|
||||
}
|
||||
keysInBinary := <-view.inputChannel
|
||||
for i := 0; i < NumKeys; i++ {
|
||||
view.keyPressed[i] = ((keysInBinary & 1) == 1)
|
||||
keysInBinary = keysInBinary >> 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -85,7 +105,6 @@ func (view *GameView) Update(t, dt float64) {
|
|||
console := view.console
|
||||
//updateControllers(window, console)
|
||||
view.updateControllers()
|
||||
//fmt.Println(console.Buffer())
|
||||
console.StepSeconds(dt)
|
||||
|
||||
// fps to set frame
|
||||
|
|
@ -98,27 +117,20 @@ func (view *GameView) Update(t, dt float64) {
|
|||
|
||||
func (view *GameView) updateControllers() {
|
||||
// TODO: switch case
|
||||
// var buttons [8]bool
|
||||
// buttons[nes.ButtonLeft] = view.keyPressed[37]
|
||||
// buttons[nes.ButtonUp] = view.keyPressed[38]
|
||||
// buttons[nes.ButtonRight] = view.keyPressed[39]
|
||||
// buttons[nes.ButtonDown] = view.keyPressed[40]
|
||||
// buttons[nes.ButtonA] = view.keyPressed[32]
|
||||
// buttons[nes.ButtonB] = view.keyPressed[17]
|
||||
// buttons[nes.ButtonStart] = view.keyPressed[13]
|
||||
// buttons[nes.ButtonSelect] = view.keyPressed[16]
|
||||
// view.console.Controller1.SetButtons(buttons)
|
||||
var gameKeys [8]bool
|
||||
copy(gameKeys[:], view.keyPressed[:8])
|
||||
// Divide keyPressed to player 1 and player 2
|
||||
// First 10 keys are player 1
|
||||
var player1Keys [8]bool
|
||||
copy(player1Keys[:], view.keyPressed[:8])
|
||||
var player2Keys [8]bool
|
||||
copy(player2Keys[:], view.keyPressed[10:18])
|
||||
|
||||
if view.keyPressed[8] {
|
||||
fmt.Println("saving", view.hash)
|
||||
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[9] {
|
||||
fmt.Println("loading", view.hash)
|
||||
if view.keyPressed[load1] || view.keyPressed[load2] {
|
||||
view.console.LoadState(savePath(view.hash))
|
||||
}
|
||||
|
||||
view.console.Controller1.SetButtons(gameKeys)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -181,6 +181,8 @@ func (w *WebRTC) StartClient(remoteSession string, width, height int) (string, e
|
|||
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
|
||||
})
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue