Skip to content

Commit ef4a554

Browse files
committed
Initial commit
0 parents  commit ef4a554

File tree

5 files changed

+188
-0
lines changed

5 files changed

+188
-0
lines changed

LICENSE

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Copyright 2023 Zeus WPI
2+
3+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4+
5+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6+
7+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Single-keyboard, multi-player Flash games; now over the network!
2+
3+
Remember, way back, when Flash games were all the hype, and you had a friend come over to play a game together? You'd share the same keyboard, with one of you controlling a character with the arrow keys, and the other controlling their character with the WASD keys. Now, thanks to [Ruffle](https://ruffle.rs/) (a Flash emulator) and modern browser technology, you can play those games over the internet!
4+
5+
## How does it work?
6+
7+
One player, Alice, is the host: she will have the game running in Ruffle, and will stream a videostream of the game to Bob (the guest). Bob will send his keyboard events to Alice; these are then injected back into the game. Thanks to WebRTC, this can be entirely done in a peer-to-peer fashion: we don't need a game server. This demo runs entirely in the browser.
8+
9+
## Future features
10+
11+
At the moment, the game is playable, but some features aren't implemented yet.
12+
13+
- [ ] The layout looks pretty bad; I'm not a frontend developer
14+
- [ ] Selecting games: at the moment, "Tank Trouble" is hardcoded, but this can be swapped out easily.
15+
- [ ] Lobby & matchmaking (we'll need a small backend for this)
16+
- At the moment, the guest must send their ID out-of-band to the host
17+
- A lobby system could be implemented, where guests send their ID to a backend server, and hosts request a guest ID
18+
- Risk with this approach: this lobby-server approach would make it easy for an attacker to obtain guest IDs, with which they could stream arbitrary video to guests.
19+
- [ ] Optimize video for latency
20+
- [ ] Compensate lag: at the moment, the host has an advantage over the host with regards to latency: they immediately see the game, and their input arrives faster. To make it fair, the host view and input can be artificially delayed, until the guest and host have the same video and input latency.
21+
- [ ] Sound: the game sound is not transmitted to the guest yet

index.html

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8"/>
5+
<title>Networked ruffle</title>
6+
<script src="main.js"></script>
7+
<script src="https://unpkg.com/[email protected]/dist/peerjs.min.js"></script>
8+
</head>
9+
<body>
10+
<div id="hostguestchoice">
11+
<p>One player needs to be the host, one player needs to be the guest. Only the host will have control over the mouse.
12+
<form>
13+
<button onclick="click_host()">HOST</button>
14+
<button onclick="click_guest()">GUEST</button>
15+
</form>
16+
</div>
17+
<div id="connectiondetails"></div>
18+
<div id="container"></div>
19+
<video id="receiving-video"></video>
20+
<script src="https://unpkg.com/@ruffle-rs/ruffle"></script>
21+
</body>
22+
</html>

main.js

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
"use strict";
2+
3+
let game_keyup = null;
4+
let game_keydown = null;
5+
6+
7+
window.RufflePlayer = window.RufflePlayer || {};
8+
9+
var callIntervalId = null;
10+
11+
var guest_video_id = null;
12+
var guest_data_id = null;
13+
14+
function on_host_load() {
15+
const ruffle = window.RufflePlayer.newest();
16+
const player = ruffle.createPlayer();
17+
const container = document.getElementById("container");
18+
player.style.width = "100%";
19+
player.style.height = "800px";
20+
container.appendChild(player);
21+
player.load("tank-trouble.swf");
22+
const peer = new Peer();
23+
console.log("peer=", peer);
24+
peer.on('open', function(id) {
25+
console.log('My peer ID is: ' + id);
26+
let conn = peer.connect(guest_data_id);
27+
conn.on('open', function() {
28+
console.log("Keyboard connection established");
29+
// Receive messages
30+
conn.on('data', function(data) {
31+
console.log("received data", data);
32+
window.dispatchEvent(new KeyboardEvent(data["type"], {
33+
code: data['code'],
34+
}));
35+
});
36+
});
37+
});
38+
39+
40+
const videopeer = new Peer();
41+
callIntervalId = setInterval(function(p) {
42+
const canvasElt = document.querySelector("ruffle-player")?.shadowRoot.querySelector("canvas");
43+
if (canvasElt != null) {
44+
console.log("Canvas exists, setting up call now");
45+
const stream = canvasElt.captureStream(25); // 25 FPS
46+
var call = p.call(guest_video_id, stream);
47+
console.log("stream=", stream);
48+
// Disabled, we'll re-enable this for lag-compensation
49+
// document.getElementById("receiving-video").srcObject = stream;
50+
// document.getElementById("receiving-video").play();
51+
clearInterval(callIntervalId);
52+
} else {
53+
console.log("canvas still null");
54+
}
55+
}, 1000, videopeer);
56+
}
57+
58+
function transmitKeystroke(conn, type, event) {
59+
console.log("transmitting ", type, event);
60+
conn.send({type: type, code: event.code});
61+
}
62+
63+
var displayPeerIdIntervalId = null;
64+
65+
function on_guest_load() {
66+
const peer = new Peer();
67+
68+
console.log("peer=", peer);
69+
peer.on('open', function(id) {
70+
console.log('Opened, data peer ID is: ' + id);
71+
guest_data_id = id;
72+
});
73+
peer.on('connection', function(conn) {
74+
document.getElementById("connectiondetails").innerHTML = "";
75+
conn.on('open', function() {
76+
console.log("Keyboard connection established");
77+
document.addEventListener("keyup", function(ev) {transmitKeystroke(conn, "keyup", ev)});
78+
document.addEventListener("keydown", function(ev) {transmitKeystroke(conn, "keydown", ev)});
79+
});
80+
});
81+
82+
83+
const videopeer = new Peer();
84+
videopeer.on('open', function(id) {
85+
console.log('Opened, video peer ID is: ' + id);
86+
guest_video_id = id;
87+
})
88+
videopeer.on('call', function(call) {
89+
console.log("received call");
90+
call.on('stream', function(stream) {
91+
console.log("On stream, setting video element to ", stream);
92+
document.getElementById("receiving-video").srcObject = stream;
93+
document.getElementById("receiving-video").play();
94+
});
95+
call.answer();
96+
});
97+
98+
displayPeerIdIntervalId = setInterval(function() {
99+
if (guest_data_id != null && guest_video_id != null) {
100+
let combinedID = `${guest_data_id}/${guest_video_id}`
101+
document.getElementById("connectiondetails").innerHTML =
102+
`Please pass your connection ID <input readonly size="${combinedID.length}" value="${combinedID}"></input> to the host.
103+
The game will automatically start when the host clicks the 'Start game' button`
104+
clearInterval(displayPeerIdIntervalId);
105+
} else {
106+
console.log("still null");
107+
}
108+
}, 200);
109+
110+
111+
}
112+
113+
function submit_host_id() {
114+
let guest_combined_id = document.getElementById("guest_combined_id").value.trim();
115+
if (guest_combined_id.length == 73) {
116+
guest_data_id = guest_combined_id.split('/')[0];
117+
guest_video_id = guest_combined_id.split('/')[1];
118+
on_host_load();
119+
document.getElementById("connectiondetails").innerHTML = '';
120+
} else {
121+
document.getElementById("error-connectiondetails").innerText = "That does not look right";
122+
}
123+
}
124+
125+
function click_host() {
126+
document.getElementById("hostguestchoice").remove();
127+
document.getElementById("connectiondetails").innerHTML = `
128+
<p>Please paste the ID you received from the guest</p>
129+
<input id="guest_combined_id" size="73">
130+
<button onclick="submit_host_id()">Start game</button>
131+
<div id="error-connectiondetails"></div>
132+
`
133+
}
134+
135+
function click_guest() {
136+
document.getElementById("hostguestchoice").remove();
137+
on_guest_load();
138+
}

tank-trouble.swf

154 KB
Binary file not shown.

0 commit comments

Comments
 (0)