fuzzyworld

dao-game / react-multiplayer / files / src / game / remote-game-2.ts

Branch react-multiplayer · default branch · all branches

remote-game-2.ts

5.2 KB · history · blame

1import Peer, { PeerJSOption } from 'peerjs';
2import { Game, Move, MoveResult } from './game';
3import { Coord } from './coord';
4import { generateHash } from '../utils/hashing';
5import { delay } from '../utils/delay';
6
7export interface Pos {
8 x: number;
9 y: number;
10}
11interface PeerData {
12 mousePos?: Pos;
13 move?: Move;
14}
15
16const peerOptions: PeerJSOption = { debug: 0 };
17const minString = (a: string, b: string): string => (a < b ? a : b);
18const maxString = (a: string, b: string): string => (a > b ? a : b);
19const getFirstPlayerId = (playerId1: string, playerId2: string) => {
20 const hash1 = generateHash(playerId1);
21 const hash2 = generateHash(playerId2);
22 const prod = hash1 * hash2;
23 const isEven = prod % 2 == 0;
24 const id = isEven ? minString(playerId1, playerId2) : maxString(playerId1, playerId2);
25 return id;
26};
27
28export class MousePosEventEmitter {
29 private _cbs: ((pos: Pos) => void)[] = [];
30 public onData(callback: (pos: Pos) => void): void {
31 this._cbs.push(callback);
32 }
33 public sendData(pos: Pos): void {
34 this._cbs.forEach((cb) => cb(pos));
35 }
36}
37
38export interface PeerGameSettings {
39 id?: string;
40 peerId?: string;
41 onError: (type: string, error: any) => void;
42 onSetup: (selfId: string) => void;
43 onStart: (selfId: string, peerId: string) => void;
44 onPeerMouseMove: (unitPos: Pos) => void;
45 mouseEventEmitter: MousePosEventEmitter;
46}
47
48export class PeerGame2 extends Game {
49 public conn: Peer.DataConnection;
50 public selfId: string;
51 public onMouseMoveCallback: (pos: Coord) => void = () => { };
52 public peerMousePositionCallback: (pos: Coord) => void = () => { };
53
54 public constructor(player1Id: string, player1Name: string, player2Id: string, player2Name: string, selfId: string, conn: Peer.DataConnection) {
55 super(player1Id, player1Name, player2Id, player2Name);
56 this.selfId = selfId;
57 this.conn = conn;
58 }
59 public override makeMove(source: Coord, dest: Coord): Promise<MoveResult> {
60 const result = super.makeMove(source, dest);
61 const peerData: PeerData = { move: { source, dest, playerId: this.currentPlayerId } };
62 this.conn.send(JSON.stringify(peerData));
63 return result;
64 }
65 public static async createPeerGame(settings: PeerGameSettings): Promise<PeerGame2> {
66 let id: string = settings.id ? settings.id : '';
67 let peerId: string = settings.peerId ? settings.peerId : '';
68 let game: PeerGame2 | undefined = undefined;
69
70 const handleConn = async (conn: Peer.DataConnection) => {
71 // wait for connection to 'open'
72 await new Promise<void>((resolve) => conn.on('open', resolve));
73
74 id = peer.id;
75 peerId = conn.peer;
76 settings.onStart(id, peerId);
77
78 const isLocalP1 = getFirstPlayerId(id, peerId) === id;
79 game = isLocalP1 ?
80 new PeerGame2(id, "Local", peerId, "Remote", id, conn) :
81 new PeerGame2(peerId, "Remote", id, "Local", id, conn);
82
83 conn.on('data', (data: string) => {
84 if (!game) {
85 console.warn({ warning: 'Receiving data, but there is no game', data });
86 return;
87 }
88
89 const peerData = JSON.parse(data) as PeerData;
90 const { mousePos, move } = peerData;
91
92 if (mousePos) {
93 settings.onPeerMouseMove(mousePos);
94 }
95
96 const isWaitingForRemoteMove = peerId === game.currentPlayerId;
97 if (isWaitingForRemoteMove && move) {
98 const isMoveLegal = game.getPiece(move.source) === game.currentPlayerId && game.getPiece(move.dest) === null;
99 if (isMoveLegal) {
100 game.makeMove(move.source, move.dest);
101 } else {
102 console.log({ isMoveLegal, move });
103 }
104 }
105 });
106 conn.on('error', (err) => {
107 settings.onError(`Connection error`, err);
108 });
109 conn.on('close', () => {
110 settings.onError(`Connection closed`, 'Connection closed for some reason')
111 });
112 settings.mouseEventEmitter.onData((pos) => {
113 const peerData: PeerData = { mousePos: pos };
114 conn.send(JSON.stringify(peerData));
115 });
116 };
117
118 const peer = id === undefined ? new Peer(peerOptions) : new Peer(id, peerOptions);
119 peer.on('connection', (conn) => handleConn(conn));
120 peer.on('error', async (err) => {
121 settings.onError('Peer error encountered', err);
122 });
123 peer.on('open', (trueId) => {
124 id = trueId;
125 settings.onSetup(id);
126
127 if (peerId) {
128 console.log(`${id} connecting to peer "${peerId}"`);
129 const conn = peer.connect(peerId);
130 handleConn(conn);
131 }
132 });
133 peer.on('disconnected', async () => {
134 settings.onError('Peer disconnected', 'For some reason')
135 });
136 while (game === undefined) {
137 console.log('Waiting to connect to peer');
138 await delay(500);
139 }
140 return game;
141 }
142}