├── .gitignore ├── package.json ├── src ├── commands │ ├── camera.command.ts │ ├── card.command.ts │ ├── chess.command.ts │ ├── command.ts │ ├── orientation.command.ts │ ├── shrek.command.ts │ └── video.command.ts ├── server.ts └── socket-manager.ts ├── tsconfig.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | node_modules/ 3 | src/**/*.js 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "multiphone_backend", 3 | "version": "1.0.0", 4 | "main": "src/server.ts", 5 | "license": "MIT", 6 | "devDependencies": { 7 | "@types/node": "^14.14.19", 8 | "@types/ws": "^7.4.0" 9 | }, 10 | "dependencies": { 11 | "array-shuffle": "^2.0.0", 12 | "rxjs": "^6.6.3", 13 | "ws": "^7.4.2" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/commands/camera.command.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs"; 2 | import Command from "./command"; 3 | 4 | export default class CameraCommand extends Command { 5 | async run(args: string[]) { 6 | if (args.length < 1) { 7 | console.log("Usage: camera "); 8 | return; 9 | } 10 | 11 | const pictureName = args[0]; 12 | fs.mkdirSync(pictureName); 13 | 14 | await this.socketManager.forEachAsync((socket, i) => { 15 | socket.packets$.subscribe((packet) => { 16 | if (packet[0] == "image") { 17 | const base64 = new Buffer(packet[1], "base64"); 18 | fs.writeFileSync(`${pictureName}/${i}.jpg`, base64); 19 | } 20 | }); 21 | }); 22 | 23 | await this.socketManager.sendAndWait("camera"); 24 | await this.socketManager.send("capture"); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/commands/card.command.ts: -------------------------------------------------------------------------------- 1 | import Command from "./command"; 2 | import arrayShuffle from "array-shuffle"; 3 | 4 | const DECK: string[] = []; 5 | 6 | for (const suit of ["C", "D", "H", "S"]) { 7 | for (const value of [ 8 | "A", 9 | "2", 10 | "3", 11 | "4", 12 | "5", 13 | "6", 14 | "7", 15 | "8", 16 | "9", 17 | "10", 18 | "J", 19 | "Q", 20 | "K", 21 | ]) { 22 | DECK.push(value + suit); 23 | } 24 | } 25 | 26 | export default class CardCommand extends Command { 27 | async run() { 28 | await this.socketManager.sendAndWait("card"); 29 | 30 | const deck = arrayShuffle([...DECK]); 31 | 32 | await this.socketManager.forEachAsync((socket) => { 33 | socket.send(deck.shift()); 34 | }); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/commands/chess.command.ts: -------------------------------------------------------------------------------- 1 | import Command from "./command"; 2 | 3 | enum Piece { 4 | KING, 5 | QUEEN, 6 | ROOK, 7 | BISHOP, 8 | KNIGHT, 9 | PAWN, 10 | } 11 | 12 | enum Color { 13 | WHITE, 14 | BLACK, 15 | } 16 | 17 | class ChessPiece { 18 | static EMPTY = new ChessPiece(null, null); 19 | 20 | constructor(public piece: Piece | null, public color: Color | null) {} 21 | 22 | toString(): string { 23 | if (this.piece === null && this.color === null) { 24 | return "empty empty"; 25 | } 26 | 27 | return `${this.piece} ${this.color}`; 28 | } 29 | } 30 | 31 | const makeChessBoard = () => [ 32 | [ 33 | new ChessPiece(Piece.ROOK, Color.BLACK), 34 | new ChessPiece(Piece.KNIGHT, Color.BLACK), 35 | new ChessPiece(Piece.BISHOP, Color.BLACK), 36 | new ChessPiece(Piece.QUEEN, Color.BLACK), 37 | new ChessPiece(Piece.KING, Color.BLACK), 38 | new ChessPiece(Piece.BISHOP, Color.BLACK), 39 | new ChessPiece(Piece.KNIGHT, Color.BLACK), 40 | new ChessPiece(Piece.ROOK, Color.BLACK), 41 | ], 42 | [ 43 | new ChessPiece(Piece.PAWN, Color.BLACK), 44 | new ChessPiece(Piece.PAWN, Color.BLACK), 45 | new ChessPiece(Piece.PAWN, Color.BLACK), 46 | new ChessPiece(Piece.PAWN, Color.BLACK), 47 | new ChessPiece(Piece.PAWN, Color.BLACK), 48 | new ChessPiece(Piece.PAWN, Color.BLACK), 49 | new ChessPiece(Piece.PAWN, Color.BLACK), 50 | new ChessPiece(Piece.PAWN, Color.BLACK), 51 | ], 52 | [ 53 | ChessPiece.EMPTY, 54 | ChessPiece.EMPTY, 55 | ChessPiece.EMPTY, 56 | ChessPiece.EMPTY, 57 | ChessPiece.EMPTY, 58 | ChessPiece.EMPTY, 59 | ChessPiece.EMPTY, 60 | ChessPiece.EMPTY, 61 | ], 62 | [ 63 | ChessPiece.EMPTY, 64 | ChessPiece.EMPTY, 65 | ChessPiece.EMPTY, 66 | ChessPiece.EMPTY, 67 | ChessPiece.EMPTY, 68 | ChessPiece.EMPTY, 69 | ChessPiece.EMPTY, 70 | ChessPiece.EMPTY, 71 | ], 72 | [ 73 | ChessPiece.EMPTY, 74 | ChessPiece.EMPTY, 75 | ChessPiece.EMPTY, 76 | ChessPiece.EMPTY, 77 | ChessPiece.EMPTY, 78 | ChessPiece.EMPTY, 79 | ChessPiece.EMPTY, 80 | ChessPiece.EMPTY, 81 | ], 82 | [ 83 | ChessPiece.EMPTY, 84 | ChessPiece.EMPTY, 85 | ChessPiece.EMPTY, 86 | ChessPiece.EMPTY, 87 | ChessPiece.EMPTY, 88 | ChessPiece.EMPTY, 89 | ChessPiece.EMPTY, 90 | ChessPiece.EMPTY, 91 | ], 92 | [ 93 | new ChessPiece(Piece.PAWN, Color.WHITE), 94 | new ChessPiece(Piece.PAWN, Color.WHITE), 95 | new ChessPiece(Piece.PAWN, Color.WHITE), 96 | new ChessPiece(Piece.PAWN, Color.WHITE), 97 | new ChessPiece(Piece.PAWN, Color.WHITE), 98 | new ChessPiece(Piece.PAWN, Color.WHITE), 99 | new ChessPiece(Piece.PAWN, Color.WHITE), 100 | new ChessPiece(Piece.PAWN, Color.WHITE), 101 | ], 102 | [ 103 | new ChessPiece(Piece.ROOK, Color.WHITE), 104 | new ChessPiece(Piece.KNIGHT, Color.WHITE), 105 | new ChessPiece(Piece.BISHOP, Color.WHITE), 106 | new ChessPiece(Piece.QUEEN, Color.WHITE), 107 | new ChessPiece(Piece.KING, Color.WHITE), 108 | new ChessPiece(Piece.BISHOP, Color.WHITE), 109 | new ChessPiece(Piece.KNIGHT, Color.WHITE), 110 | new ChessPiece(Piece.ROOK, Color.WHITE), 111 | ], 112 | ]; 113 | 114 | // const makeChessBoard = () => [ 115 | // [ 116 | // new ChessPiece(Piece.PAWN, Color.BLACK), 117 | // new ChessPiece(Piece.QUEEN, Color.BLACK), 118 | // ], 119 | // [ 120 | // new ChessPiece(Piece.PAWN, Color.WHITE), 121 | // new ChessPiece(Piece.QUEEN, Color.WHITE), 122 | // ], 123 | // ]; 124 | 125 | const BOARD_WIDTH = makeChessBoard()[0].length; 126 | 127 | export default class ChessCommand extends Command { 128 | async run() { 129 | await this.socketManager.sendAndWait("chess"); 130 | 131 | const chessBoard = makeChessBoard(); 132 | let selectedRow = -1; 133 | let selectedCol = -1; 134 | 135 | await this.socketManager.forEachAsync((socket, i) => { 136 | const row = Math.floor(i / BOARD_WIDTH); 137 | const col = i % BOARD_WIDTH; 138 | 139 | socket.packets$.subscribe(() => { 140 | if (selectedRow === -1 && selectedCol === -1) { 141 | if (chessBoard[row][col] !== ChessPiece.EMPTY) { 142 | selectedRow = row; 143 | selectedCol = col; 144 | } 145 | } else { 146 | if (row === selectedRow && col === selectedCol) { 147 | selectedRow = -1; 148 | selectedCol = -1; 149 | return; 150 | } 151 | 152 | chessBoard[row][col] = chessBoard[selectedRow][selectedCol]; 153 | this.socketManager.sockets[row * BOARD_WIDTH + col].send( 154 | chessBoard[row][col].toString() 155 | ); 156 | 157 | chessBoard[selectedRow][selectedCol] = ChessPiece.EMPTY; 158 | this.socketManager.sockets[ 159 | selectedRow * BOARD_WIDTH + selectedCol 160 | ].send(chessBoard[selectedRow][selectedCol].toString()); 161 | 162 | selectedRow = -1; 163 | selectedCol = -1; 164 | } 165 | }); 166 | 167 | const chessPiece = chessBoard[row][col]; 168 | socket.send(`${chessPiece} ${row} ${col}`); 169 | }); 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /src/commands/command.ts: -------------------------------------------------------------------------------- 1 | import SocketManager from "../socket-manager"; 2 | 3 | export default abstract class Command { 4 | constructor(protected socketManager: SocketManager) {} 5 | 6 | abstract run(args: string[]): Promise; 7 | } 8 | -------------------------------------------------------------------------------- /src/commands/orientation.command.ts: -------------------------------------------------------------------------------- 1 | import Command from "./command"; 2 | 3 | export default class OrientationCommand extends Command { 4 | async run(args: string[]) { 5 | if (args.length < 1 || !["left", "right", "up"].includes(args[0])) { 6 | console.log("Usage: orientation "); 7 | return; 8 | } 9 | 10 | await this.socketManager.sendAndWait(`orientation ${args[0]}`); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/commands/shrek.command.ts: -------------------------------------------------------------------------------- 1 | import Command from "./command"; 2 | 3 | export default class ShrekCommand extends Command { 4 | async run() { 5 | await this.socketManager.sendAndWait("shrek"); 6 | 7 | await this.socketManager.forEachAsync((socket, i) => { 8 | if (i === 0) { 9 | socket.send("full"); 10 | } else { 11 | socket.send("short"); 12 | } 13 | }); 14 | 15 | await this.socketManager.allReady(); 16 | 17 | await this.socketManager.forEachAsync(async (socket, i) => { 18 | socket.send("play"); 19 | 20 | if (i === 0) { 21 | await sleep(3100); 22 | } else { 23 | await sleep(1500); 24 | } 25 | }); 26 | } 27 | } 28 | 29 | function sleep(ms: number): Promise { 30 | return new Promise((resolve) => { 31 | setTimeout(resolve, ms); 32 | }); 33 | } 34 | -------------------------------------------------------------------------------- /src/commands/video.command.ts: -------------------------------------------------------------------------------- 1 | import Command from "./command"; 2 | 3 | const videos: { [videoName: string]: { width: number; height: number } } = { 4 | noahytp: { 5 | width: 1280, 6 | height: 720, 7 | }, 8 | onealmond: { 9 | width: 1280, 10 | height: 720, 11 | }, 12 | rickroll: { 13 | width: 640, 14 | height: 360, 15 | }, 16 | noah: { 17 | width: 1280, 18 | height: 720, 19 | }, 20 | ltt: { 21 | width: 640, 22 | height: 360, 23 | }, 24 | intro: { 25 | width: 1920, 26 | height: 1080, 27 | }, 28 | luke: { 29 | width: 1280, 30 | height: 720, 31 | }, 32 | dababy: { 33 | width: 1280, 34 | height: 720, 35 | }, 36 | }; 37 | 38 | // Dimensions of landscape iPhone 4(s) in pts 39 | const PHONE_WIDTH_PTS = 733.5; 40 | const PHONE_HEIGHT_PTS = 376.53; 41 | 42 | // Bezels of an iPhone 4(s) in pts 43 | const TOP_BEZEL_PTS = 126.75; 44 | const SIDE_BEZEL_PTS = 28.265; 45 | 46 | export default class VideoCommand extends Command { 47 | async run(args: string[]) { 48 | if (args.length < 2 || !(args[0] in videos)) { 49 | console.log("Usage: video