fuzzyworld

dao-game / peerjs-browser-bug / files / src / remote-game.ts

Branch peerjs-browser-bug · default branch · all branches

remote-game.ts

Blame: the commit that last changed each line · file · history

c3c52daf 2022-03-18 Pavel Safronov1import Peer, { PeerJSOption } from 'peerjs';
1e41e9b4 2022-03-18 Pavel Safronov2import { LocalGame } from './local-game';
b3c7b964 2022-03-22 Pavel Safronov3import { showModal } from './modal';
47dfe48a 2022-03-20 Pavel Safronov4import { useSessionStorage } from './storage';
5import { Coord, Move, MoveResult } from './types';
e46456c2 2022-03-22 Pavel Safronov6import { copyToClipboard, delay, generateHash } from './utils';
5648264d 2022-03-22 Pavel Safronov7import { showSnackbar } from "./snackbar";
f2837cac 2022-03-22 Pavel Safronov8import { detect } from 'detect-browser';
1e41e9b4 2022-03-18 Pavel Safronov9
10interface LocalData {
f2c94061 2022-03-21 Pavel Safronov11 id: string;
12 peerId: string;
1e41e9b4 2022-03-18 Pavel Safronov13}
7e7f0490 2022-03-18 Pavel Safronov14interface Pos {
f2c94061 2022-03-21 Pavel Safronov15 x: number;
16 y: number;
7e7f0490 2022-03-18 Pavel Safronov17}
18interface PeerData {
f2c94061 2022-03-21 Pavel Safronov19 mousePos?: Pos;
20 move?: Move;
7e7f0490 2022-03-18 Pavel Safronov21}
f2837cac 2022-03-22 Pavel Safronov22const peerOptions: PeerJSOption = { debug: 3 };
47dfe48a 2022-03-20 Pavel Safronov23const { getter: getData, setter: setData } = useSessionStorage<LocalData>('dao-game', { id: undefined, peerId: undefined });
24const otherMouseElement = document.getElementById('other-mouse');
b3c7b964 2022-03-22 Pavel Safronov25const shareContainer = document.getElementById('share-container');
26const shareLinkButton = document.getElementById('share-link');
47dfe48a 2022-03-20 Pavel Safronov27const minString = (a: string, b: string): string => (a < b ? a : b);
28const maxString = (a: string, b: string): string => (a > b ? a : b);
29const getFirstPlayerId = (playerId1: string, playerId2: string) => {
f2c94061 2022-03-21 Pavel Safronov30 const hash1 = generateHash(playerId1);
31 const hash2 = generateHash(playerId2);
32 const prod = hash1 * hash2;
33 const isEven = prod % 2 == 0;
34 const id = isEven ? minString(playerId1, playerId2) : maxString(playerId1, playerId2);
35 return id;
47dfe48a 2022-03-20 Pavel Safronov36};
5805825c 2022-03-21 Pavel Safronov37const dropFileExtension = (path: string) => {
38 const lastSlashIndex = path.lastIndexOf('/');
39 const dirName = path.substring(0, lastSlashIndex);
40 return dirName;
41}
f2837cac 2022-03-22 Pavel Safronov42const showMessageAndLeave = async (title, text) => {
43 await showModal({ title, text, willBackgroundCancel: false });
44 sessionStorage.clear();
45 const { protocol, host, pathname } = location;
46 const dir = dropFileExtension(pathname);
47 const newUrl = `${protocol}//${host}${dir}`
48 document.location = newUrl;
49}
1e41e9b4 2022-03-18 Pavel Safronov50
51export class PeerGame extends LocalGame {
f2c94061 2022-03-21 Pavel Safronov52 public conn: Peer.DataConnection;
53 public selfId: string;
5805825c 2022-03-21 Pavel Safronov54 public constructor(player1Id: string, player1Name: string, player2Id: string, player2Name: string, selfId: string, conn: Peer.DataConnection) {
55 super(player1Id, player1Name, player2Id, player2Name);
f2c94061 2022-03-21 Pavel Safronov56 this.selfId = selfId;
57 this.conn = conn;
47dfe48a 2022-03-20 Pavel Safronov58 }
f2c94061 2022-03-21 Pavel Safronov59 public override setActiveUi(): void {
60 const { currentPlayerId, selfId } = this;
61
62 const isLocalTurn = currentPlayerId === selfId;
63 if (isLocalTurn) {
64 super.setActiveUi();
47dfe48a 2022-03-20 Pavel Safronov65 }
f2c94061 2022-03-21 Pavel Safronov66 }
67 public override makeMove(source: Coord, dest: Coord): Promise<MoveResult> {
68 const result = super.makeMove(source, dest);
69 const peerData: PeerData = { move: { source, dest, playerId: this.currentPlayerId } };
70 this.conn.send(JSON.stringify(peerData));
71 return result;
72 }
5805825c 2022-03-21 Pavel Safronov73 public override async handleMoveResult(result: MoveResult): Promise<void> {
b3c7b964 2022-03-22 Pavel Safronov74 const { isTerminal, message } = result;
f2837cac 2022-03-22 Pavel Safronov75 if (isTerminal) {
76 showMessageAndLeave('Game Over', message)
5805825c 2022-03-21 Pavel Safronov77 } else {
78 this.resetUi();
79 this.setActiveUi();
80 }
81 }
47dfe48a 2022-03-20 Pavel Safronov82
f2837cac 2022-03-22 Pavel Safronov83
f2c94061 2022-03-21 Pavel Safronov84 public static async createPeerGame(): Promise<PeerGame> {
85 let { id, peerId } = getData();
86 const main = document.getElementsByTagName('main')[0];
87 let game: PeerGame = undefined;
88
f2837cac 2022-03-22 Pavel Safronov89 const handleConn = (source: string, conn: Peer.DataConnection) => {
90 if (source === 'peer-on-connection') {
91 conn.open
f2c94061 2022-03-21 Pavel Safronov92 }
93
b3c7b964 2022-03-22 Pavel Safronov94 shareContainer.classList.add('invisible');
5805825c 2022-03-21 Pavel Safronov95
f2c94061 2022-03-21 Pavel Safronov96 id = peer.id;
97 peerId = conn.peer;
98 setData({ id, peerId });
99
100 const isLocalP1 = getFirstPlayerId(id, peerId) === id;
5805825c 2022-03-21 Pavel Safronov101 game = isLocalP1 ?
102 new PeerGame(id, "Local", peerId, "Remote", id, conn) :
103 new PeerGame(peerId, "Remote", id, "Local", id, conn);
f2c94061 2022-03-21 Pavel Safronov104 game.resetUi();
105 game.setActiveUi();
106 main.classList.remove('invisible');
107
5805825c 2022-03-21 Pavel Safronov108 const table = document.getElementById('table');
f2837cac 2022-03-22 Pavel Safronov109 table.addEventListener('click', (ev: MouseEvent) => {
5805825c 2022-03-21 Pavel Safronov110 const xTable = ev.clientX - table.offsetLeft;
111 const yTable = ev.clientY - table.offsetTop;
112 const xRatio = xTable / table.clientWidth;
113 const yRatio = yTable / table.clientHeight;
114 const peerData: PeerData = { mousePos: { x: xRatio, y: yRatio } };
f2837cac 2022-03-22 Pavel Safronov115 const data = JSON.stringify(peerData);
116 console.table({ on: 'mousemove', from: id, to: peerId, data });
117 conn.send(data);
f2c94061 2022-03-21 Pavel Safronov118 });
f2837cac 2022-03-22 Pavel Safronov119 // table.addEventListener('mouseleave', () => {
120 // const peerData: PeerData = { mousePos: { x: -1, y: -1 } };
121 // const text = JSON.stringify(peerData);
122 // console.table({ on: 'mouseleave', from: id, to: peerId, text });
123 // conn.send(text);
124 // });
f2c94061 2022-03-21 Pavel Safronov125
5805825c 2022-03-21 Pavel Safronov126 let lastTimerId = undefined;
f2c94061 2022-03-21 Pavel Safronov127 conn.on('data', (data: string) => {
f2837cac 2022-03-22 Pavel Safronov128 console.table({ on: 'data', to: id, from: peerId, data });
f2c94061 2022-03-21 Pavel Safronov129 const peerData = JSON.parse(data) as PeerData;
130 const { mousePos, move } = peerData;
131
132 if (mousePos) {
5805825c 2022-03-21 Pavel Safronov133 const isValidPos = mousePos.x !== -1 && mousePos.y !== -1;
134 if (isValidPos) {
135 const xRatio = mousePos.x;
136 const yRatio = mousePos.y;
137 const xTable = xRatio * table.clientWidth;
138 const yTable = yRatio * table.clientHeight;
139 const x = table.offsetLeft + xTable;
140 const y = table.offsetTop + yTable;
141 const style = `left:${x - otherMouseElement.clientWidth / 2}px;top:${y}px;display:block;`;
142 otherMouseElement.setAttribute('style', style);
143 if (lastTimerId !== undefined) {
144 clearTimeout(lastTimerId);
145 }
146 lastTimerId = setTimeout(() => {
147 otherMouseElement.setAttribute('style', '');
148 lastTimerId = undefined;
149 }, 3000);
150 } else if (lastTimerId !== undefined) {
151 otherMouseElement.setAttribute('style', '');
152 }
f2c94061 2022-03-21 Pavel Safronov153 }
154
155 const isWaitingForRemoteMove = peerId === game.currentPlayerId;
156 if (isWaitingForRemoteMove && move) {
157 const isMoveLegal = game.getPiece(move.source) === game.currentPlayerId && game.getPiece(move.dest) === null;
158 if (isMoveLegal) {
159 game.makeMove(move.source, move.dest);
160 } else {
161 console.log({ isMoveLegal, move });
162 }
163 }
164 });
f2837cac 2022-03-22 Pavel Safronov165 conn.on('error', (err) => {
166 showMessageAndLeave(`Connection error: ${source}`, err);
167 });
168 conn.on('close', () => {
169 showMessageAndLeave(`Connection closed: ${source}`, 'Connection closed for some reason')
170 });
171 conn.on('open', () => {
172 showModal({ title: 'conn-on-open', text: 'connected!' });
173 });
f2c94061 2022-03-21 Pavel Safronov174 };
175
176 const { host, protocol, pathname, search } = location;
177 const queryParams = new URLSearchParams(search);
178
179 const peer = id === undefined ? new Peer(peerOptions) : new Peer(id, peerOptions);
180 window.onunload = () => peer.destroy();
f2837cac 2022-03-22 Pavel Safronov181 peer.on('connection', (conn) => handleConn('peer-on-connection', conn));
e46456c2 2022-03-22 Pavel Safronov182 peer.on('error', async (err) => {
f2837cac 2022-03-22 Pavel Safronov183 // await showModal({
184 // text: err,
185 // title: 'Error encountered:',
186 // willBackgroundCancel: false
187 // });
188 // sessionStorage.clear();
189 await showMessageAndLeave('Error encountered', err);
e46456c2 2022-03-22 Pavel Safronov190 });
f2c94061 2022-03-21 Pavel Safronov191 peer.on('open', (trueId) => {
192 id = trueId;
193 peerId = queryParams.get('peerId') ?? peerId;
194 setData({ id, peerId });
195
196 if (peerId) {
197 console.log(`${id} connecting to peer "${peerId}"`);
198 const conn = peer.connect(peerId);
f2837cac 2022-03-22 Pavel Safronov199 handleConn('peer-connect', conn);
f2c94061 2022-03-21 Pavel Safronov200 } else {
201 queryParams.set('peerId', id);
202 const newSearch = queryParams.toString();
b3c7b964 2022-03-22 Pavel Safronov203 const newUrl = `${protocol}//${host}${pathname}?${newSearch}`;
f2837cac 2022-03-22 Pavel Safronov204 // const newUrl = `microsoft-edge:${protocol}//${host}${pathname}?${newSearch}`;
b3c7b964 2022-03-22 Pavel Safronov205 shareLinkButton.onclick = () => {
206 showSnackbar('Url copied to clipboard');
e46456c2 2022-03-22 Pavel Safronov207 copyToClipboard(newUrl);
b3c7b964 2022-03-22 Pavel Safronov208 };
209 shareContainer.classList.remove('invisible');
f2c94061 2022-03-21 Pavel Safronov210 }
211 });
f2837cac 2022-03-22 Pavel Safronov212 peer.on('disconnected', async () => {
213 await showMessageAndLeave('Peer disconnected', 'For some reason')
214 });
f2c94061 2022-03-21 Pavel Safronov215
216 while (game === undefined) {
217 console.log('Waiting to connect to peer');
218 await delay(500);
1e41e9b4 2022-03-18 Pavel Safronov219 }
f2c94061 2022-03-21 Pavel Safronov220 return game;
47dfe48a 2022-03-20 Pavel Safronov221 }
7e7f0490 2022-03-18 Pavel Safronov222}
223
f2837cac 2022-03-22 Pavel Safronov224const isBrowserSupported = () => {
225 const browser = detect();
226 switch (browser && browser.name) {
227 case 'chrome':
228 case 'firefox':
229 return true;
230 default:
231 return false;
232 }
233}
234
47dfe48a 2022-03-20 Pavel Safronov235export const playGame = async () => {
f2837cac 2022-03-22 Pavel Safronov236 if (isBrowserSupported()) {
237 await PeerGame.createPeerGame();
238 } else {
239 showMessageAndLeave(
240 'Unsupported browser',
241 '<p>Your current browser is not yet supported.</p>' +
242 '<p>This PeerJs issue is tracking support in Microsoft Edge and Safari: </p>' +
243 '<p><a href="https://github.com/peers/peerjs/issues/899" target="_blank">https://github.com/peers/peerjs/issues/899</a></p>')
244 }
c3c52daf 2022-03-18 Pavel Safronov245};