{"version":3,"sources":["api.js","registerServiceWorker.js","index.js","pages/MenuPage.js","pages/JoinPage.js","pages/LobbyPage.js","pages/GamePage.js"],"names":["BASE_URL","process","socket","createRoom","username","fetch","headers","method","body","JSON","stringify","credentials","joinRoom","code","startGame","playAction","type","targetTiles","Boolean","window","location","hostname","match","ReactDOM","render","exact","path","component","useState","setUsername","history","useHistory","handleCreateRoom","a","res","ok","json","replace","initialPlayers","initialLeader","id","value","onChange","e","target","placeholder","onClick","push","href","setCode","useLocation","state","useEffect","handleJoinRoom","players","leader","setPlayers","setLeader","length","io","withCredentials","on","map","p","className","handConcealed","setHandConcealed","handExposed","setHandExposed","discarded","setDiscarded","turn","setTurn","winner","setWinner","pendingAction","setPendingAction","interrupts","setInterrupts","hover","setHover","Array","from","updateGameState","getAvailableInterrupts","status","canMahjongInterrupt","t","tile","Tile","winningHand","availableInterrupts","i","ti","suit","Math","abs","j","tj","action","k","tk","values","sort","b","every","splice","hiddenTileCount","handleTileClick","interrupt","getAssociatedInterrupt","t1","t2","console","log","handleMahjongClick","find","includes","src","winnerUsername","isPlaying","duration","colors","onMouseEnter","onMouseLeave","meld","document","getElementById","navigator","serviceWorker","ready","then","registration","unregister"],"mappings":"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","file":"static/js/main.ae59cf46.chunk.js","sourcesContent":["import { io } from 'socket.io-client'\n\nexport const BASE_URL = process.env.REACT_APP_API_URL\n\nexport let socket = null;\nexport const connectSocket = () => socket = io(BASE_URL, { withCredentials: true })\n\nexport const createRoom = (username) => {\n return fetch(BASE_URL + '/create_room', {\n headers: {\n 'Content-Type': 'application/json'\n },\n method: 'POST',\n body: JSON.stringify({ username }),\n credentials: 'include'\n })\n}\n\nexport const joinRoom = (username, code) => {\n return fetch(BASE_URL + '/join_room', {\n headers: {\n 'Content-Type': 'application/json'\n },\n method: 'POST',\n body: JSON.stringify({ username, code }),\n credentials: 'include'\n })\n}\n\nexport const startGame = () => {\n return fetch(BASE_URL + '/start_game', {\n headers: {\n 'Content-Type': 'application/json'\n },\n method: 'POST',\n credentials: 'include'\n })\n}\n\nexport const getGameState = () => {\n return fetch(BASE_URL + '/get_game_state', {\n headers: {\n 'Content-Type': 'application/json'\n },\n credentials: 'include'\n })\n}\n\nexport const playAction = (type, targetTiles) => {\n return fetch(BASE_URL + '/play_action', {\n headers: {\n 'Content-Type': 'application/json'\n },\n method: 'POST',\n body: JSON.stringify({ type, targetTiles }),\n credentials: 'include'\n })\n}","// In production, we register a service worker to serve assets from local cache.\n\n// This lets the app load faster on subsequent visits in production, and gives\n// it offline capabilities. However, it also means that developers (and users)\n// will only see deployed updates on the \"N+1\" visit to a page, since previously\n// cached resources are updated in the background.\n\n// To learn more about the benefits of this model, read https://goo.gl/KwvDNy.\n// This link also includes instructions on opting out of this behavior.\n\nconst isLocalhost = Boolean(\n window.location.hostname === 'localhost' ||\n // [::1] is the IPv6 localhost address.\n window.location.hostname === '[::1]' ||\n // 127.0.0.1/8 is considered localhost for IPv4.\n window.location.hostname.match(\n /^127(?:\\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/\n )\n);\n\nexport default function register() {\n if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {\n // The URL constructor is available in all browsers that support SW.\n const publicUrl = new URL(process.env.PUBLIC_URL, window.location);\n if (publicUrl.origin !== window.location.origin) {\n // Our service worker won't work if PUBLIC_URL is on a different origin\n // from what our page is served on. This might happen if a CDN is used to\n // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374\n return;\n }\n\n window.addEventListener('load', () => {\n const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;\n\n if (isLocalhost) {\n // This is running on localhost. Lets check if a service worker still exists or not.\n checkValidServiceWorker(swUrl);\n } else {\n // Is not local host. Just register service worker\n registerValidSW(swUrl);\n }\n });\n }\n}\n\nfunction registerValidSW(swUrl) {\n navigator.serviceWorker\n .register(swUrl)\n .then(registration => {\n registration.onupdatefound = () => {\n const installingWorker = registration.installing;\n installingWorker.onstatechange = () => {\n if (installingWorker.state === 'installed') {\n if (navigator.serviceWorker.controller) {\n // At this point, the old content will have been purged and\n // the fresh content will have been added to the cache.\n // It's the perfect time to display a \"New content is\n // available; please refresh.\" message in your web app.\n console.log('New content is available; please refresh.');\n } else {\n // At this point, everything has been precached.\n // It's the perfect time to display a\n // \"Content is cached for offline use.\" message.\n console.log('Content is cached for offline use.');\n }\n }\n };\n };\n })\n .catch(error => {\n console.error('Error during service worker registration:', error);\n });\n}\n\nfunction checkValidServiceWorker(swUrl) {\n // Check if the service worker can be found. If it can't reload the page.\n fetch(swUrl)\n .then(response => {\n // Ensure service worker exists, and that we really are getting a JS file.\n if (\n response.status === 404 ||\n response.headers.get('content-type').indexOf('javascript') === -1\n ) {\n // No service worker found. Probably a different app. Reload the page.\n navigator.serviceWorker.ready.then(registration => {\n registration.unregister().then(() => {\n window.location.reload();\n });\n });\n } else {\n // Service worker found. Proceed as normal.\n registerValidSW(swUrl);\n }\n })\n .catch(() => {\n console.log(\n 'No internet connection found. App is running in offline mode.'\n );\n });\n}\n\nexport function unregister() {\n if ('serviceWorker' in navigator) {\n navigator.serviceWorker.ready.then(registration => {\n registration.unregister();\n });\n }\n}\n","import React from 'react'\nimport ReactDOM from 'react-dom'\nimport { BrowserRouter as Router, Switch, Route } from 'react-router-dom'\nimport { GamePage } from './pages/GamePage'\nimport { JoinPage } from './pages/JoinPage'\nimport { LobbyPage } from './pages/LobbyPage'\nimport { MenuPage } from './pages/MenuPage'\nimport { unregister } from './registerServiceWorker'\n\nReactDOM.render(\n \n \n \n \n \n \n \n , \n document.getElementById('root')\n);\n\nunregister();\n","import { useState } from 'react'\nimport { useHistory } from 'react-router'\nimport { createRoom } from '../api'\nimport './MenuPage.scss'\n\nexport function MenuPage() {\n const [username, setUsername] = useState('')\n const history = useHistory()\n\n const handleJoinRoom = () => {\n if (!username) return\n history.push('/join', { username })\n }\n\n const handleCreateRoom = async () => {\n if (!username) return\n const res = await createRoom(username)\n if (res.ok) {\n const { code } = await res.json()\n history.replace('/lobby', { code, initialPlayers: [username], initialLeader: username, username })\n }\n }\n\n return
\n

麻将

\n setUsername(e.target.value)} placeholder=\"Username\"/>\n \n \n

Mahjong tiles by Cangjie6 is licensed under CC BY-SA 4.0

\n
\n}","import { useEffect, useState } from 'react'\nimport { useHistory, useLocation } from 'react-router'\nimport { joinRoom } from '../api'\nimport './JoinPage.scss'\n\nexport function JoinPage() {\n\n const [code, setCode] = useState('')\n const history = useHistory()\n const { username } = useLocation().state\n\n useEffect(() => {\n // Should have gotten here from main page with username set; otherwise, go back\n if (!username) {\n history.replace('/')\n return null\n }\n }, [])\n\n const handleJoinRoom = async () => {\n const res = await joinRoom(username, code)\n if (res.ok) {\n const { players, leader } = await res.json()\n history.replace('/lobby', { code, initialPlayers: players, initialLeader: leader, username })\n }\n }\n\n return
\n

Join a Room

\n setCode(e.target.value)} placeholder=\"Room Code\"/>\n \n
\n}","import { useEffect, useState } from 'react'\nimport { useHistory, useLocation } from 'react-router'\nimport { socket, connectSocket, startGame } from '../api'\nimport './LobbyPage.scss'\n\nexport function LobbyPage() {\n const history = useHistory()\n const { code, initialPlayers, initialLeader, username } = useLocation().state\n\n const [players, setPlayers] = useState(initialPlayers)\n const [leader, setLeader] = useState(initialLeader)\n\n useEffect(() => {\n // Should have gotten here from main or join page\n if (!code || !initialPlayers || initialPlayers.length === 0 || !initialLeader || !username) {\n history.replace('/')\n return null\n }\n\n // Connect to sockets, subscribe to update_players socket event\n connectSocket()\n socket.on('update_players', (players, leader) => {\n setPlayers(players)\n setLeader(leader)\n })\n\n socket.on('start_game', () => {\n history.replace('/game', { username })\n })\n\n socket.on('disconnect', () => {\n history.replace('/')\n })\n }, [])\n\n return
\n

Lobby

\n Room code: {code}\n { players.map(p =>

{p}

) }\n { leader === username && \n \n }\n
\n}","import { useEffect, useState } from 'react'\nimport { useHistory, useLocation } from 'react-router'\nimport { getGameState, playAction, socket } from '../api'\nimport { Tile } from 'mahjong'\nimport { CountdownCircleTimer } from \"react-countdown-circle-timer\";\nimport './GamePage.scss'\n\nexport function GamePage() {\n const history = useHistory()\n const { username } = useLocation().state\n\n const [handConcealed, setHandConcealed] = useState([])\n const [handExposed, setHandExposed] = useState([])\n const [discarded, setDiscarded] = useState([])\n\n // Of players, whose turn is it? Indexes the players state; 3 represents self\n const [turn, setTurn] = useState(-1)\n const [winner, setWinner] = useState(-1)\n\n const [pendingAction, setPendingAction] = useState(null)\n const [interrupts, setInterrupts] = useState([])\n const [hover, setHover] = useState(null)\n \n // List of the 3 other players, in left, top, right order\n const [players, setPlayers] = useState(Array.from({ length: 3 }, () => ({ username: '', handExposed: [], discarded: [] })))\n\n useEffect(async () => {\n // Fetch game state\n const res = await getGameState()\n if (!res.ok) {\n history.replace('/')\n return\n }\n updateGameState(await res.json())\n\n socket.on('game_state_update', (state) => {\n updateGameState(state)\n })\n\n socket.on('disconnect', () => {\n history.replace('/')\n })\n }, [])\n\n useEffect(async () => {\n setInterrupts(getAvailableInterrupts())\n }, [pendingAction])\n\n const updateGameState = (state) => {\n setHandConcealed(state.handConcealed)\n setHandExposed(state.handExposed)\n setDiscarded(state.discarded)\n setPlayers(state.players)\n setPendingAction(state.pendingAction)\n setWinner(state.winner)\n\n if (state.pendingAction) {\n setTurn(-1)\n } else {\n setTurn(state.turn)\n }\n }\n\n const canMahjongInterrupt = () => {\n // If no pending action, cannot interrupt\n if (!pendingAction) return false\n\n // If we are the pending action, we can't do anything\n if (pendingAction.username === username) return false\n\n let t = pendingAction.tile\n\n return Tile.winningHand([...handConcealed, t], 1, 4 - handExposed.length)\n }\n\n const getAvailableInterrupts = () => {\n // If no pending action, cannot interrupt\n if (!pendingAction) return []\n\n // If we are the pending action, we can't do anything\n if (pendingAction.username === username) return []\n \n let t = pendingAction.tile\n\n const availableInterrupts = []\n\n for (let i = 0; i < handConcealed.length; i++) {\n const ti = handConcealed[i];\n\n // If t and ti are not the same suit, or are more than 2 ranks removed, cannot possibly form meld\n if (ti.suit !== t.suit || Math.abs(ti.value - t.value) > 2) continue;\n\n for (let j = i + 1; j < handConcealed.length; j++) {\n const tj = handConcealed[j];\n\n // If t and tj are not the same suit, or are more than 2 ranks removed from t or ti, cannot possibly form meld\n if (tj.suit !== t.suit || Math.abs(tj.value - t.value) > 2 || Math.abs(tj.value - ti.value) > 2) continue;\n\n // Does tile t form a meld with the ones at indexes i and j in handConcealed?\n if (t.value === ti.value && ti.value === tj.value) {\n // Forms a pong; add iff pending action is discard or chow\n if (pendingAction.action < 2) {\n availableInterrupts.push([i, j])\n }\n\n // Check if forms a kong\n for (let k = j + 1; k < handConcealed.length; k++) {\n const tk = handConcealed[k];\n if (t.suit === tk.suit && t.value === tk.value) {\n // Forms a kong; add iff pending action is discard or chow\n if (pendingAction.action < 2) {\n availableInterrupts.push([i, j, k])\n }\n }\n }\n }\n\n const values = [t, ti, tj].map(tile => tile.value).sort()\n if (t.suit !== 'd' && t.suit !== 'f' && values[1] - values[0] === 1 && values[2] - values[1] === 1) {\n // Forms a chow; add iff pending action is a discard from the player immediately prior\n if (pendingAction.action < 1 && pendingAction.username === players[2].username) {\n availableInterrupts.push([i, j])\n }\n }\n }\n }\n\n // Remove any duplicate interrupts\n for (let i = 0; i < availableInterrupts.length; i++) {\n for (let j = i + 1; j < availableInterrupts.length; j++) {\n let a = availableInterrupts[i];\n let b = availableInterrupts[j];\n\n // Kongs are not duplicate w/ pongs & chows\n if (a.length !== b.length) continue;\n\n // Iterate through each tile of each actions...\n if (a.every((t, p) => handConcealed[t].suit === handConcealed[b[p]].suit && handConcealed[t].value === handConcealed[b[p]].value)) {\n // if the tileset is identical, remove action j\n availableInterrupts.splice(j, 1)\n j--\n }\n }\n }\n\n return availableInterrupts\n }\n\n const hiddenTileCount = (i) => {\n // Winner has 0 hidden tiles\n if (i === winner) return 0;\n\n const p = players[i];\n if (!p.handExposed) return 0;\n return 13 - p.handExposed.length * 3 + (1 ? turn === i : 0);\n }\n\n /**\n * Discards a tile that was clicked on, if it is this player's turn\n * @param {number} i Index of tile to discard\n * @returns void\n */\n const handleTileClick = (i) => {\n if (winner >= 0) return;\n\n // Tile click only valid in 2 scenarios...\n if (turn === 3 && !pendingAction) {\n // 1. It is our turn, and we are discarding\n playAction(0, [i])\n return\n } else if (turn !== 3 && pendingAction) {\n // 2. There is a pending discard that isn't ours\n const interrupt = getAssociatedInterrupt(i)\n if (!interrupt) return\n\n setHover(null)\n\n // Is this a chow or pong/kong?\n const t1 = handConcealed[interrupt[0]]\n const t2 = handConcealed[interrupt[1]]\n if (t1.value === t2.value) {\n console.log('play pong/kong')\n // Pong or kong\n playAction(2, interrupt)\n } else {\n console.log('play chow')\n // Chow\n playAction(1, interrupt)\n }\n }\n }\n\n const handleMahjongClick = () => {\n playAction(3)\n }\n\n const getAssociatedInterrupt = (i) => {\n return interrupts.find(interrupt => interrupt.includes(i))\n }\n\n let status;\n if (pendingAction) {\n console.log(pendingAction)\n let t = pendingAction.tile\n switch (pendingAction.action) {\n case 0:\n // DISCARD\n status =
\n

{ pendingAction.username } discarded:

\n \n { canMahjongInterrupt() && }\n
\n break;\n case 1:\n // CHOW\n status =
\n

{ pendingAction.username } to CHOW:

\n \n { canMahjongInterrupt() && }\n
\n break;\n case 2:\n // PONG\n status =
\n

{ pendingAction.username } to PONG/KONG:

\n \n { canMahjongInterrupt() && }\n
\n break;\n default:\n break;\n }\n }\n\n if (winner >= 0) {\n if (winner === 4) {\n status =
\n

Draw: out of tiles

\n
\n } else {\n const winnerUsername = winner === 3 ? 'You' : players[winner].username\n status =
\n

{ winnerUsername } won!

\n
\n }\n }\n\n // LOG STATE FOR DEBUGGING\n console.log('################################')\n console.log('Pending Action', pendingAction)\n console.log('Hand', handConcealed, handExposed)\n console.log('Hover', hover)\n console.log('Players', players)\n console.log('Turn', turn)\n console.log('Interrupts', interrupts)\n console.log('Can MJ', canMahjongInterrupt())\n\n return
\n { pendingAction && \n
\n \n \n
\n }\n {status}\n
\n {\n handConcealed.map((t, i) => {\n const interrupt = getAssociatedInterrupt(i)\n if (interrupt) {\n return setHover(i)} onMouseLeave={() => setHover(null)} onClick={() => handleTileClick(i)} src={`https://files.terranceli.com/mahjong/MJ${t.suit}${t.value}-.svg`}/>\n }\n return handleTileClick(i)} src={`https://files.terranceli.com/mahjong/MJ${t.suit}${t.value}-.svg`}/>\n })\n }\n {\n handExposed.map((meld, i) => {\n return
\n {\n meld.map((t, j) => )\n }\n
\n })\n }\n
\n
\n {\n discarded.map(t => )\n }\n
\n { turn >= 0 && winner < 0 &&\n { turn === 3 ? 'Your turn!' : `${players[turn].username}'s turn`}\n }\n
\n {\n Array.from({ length: hiddenTileCount(2) }, () => )\n }\n {\n players[2].handExposed.map((meld, i) => {\n return
\n {\n meld.map((t, j) => )\n }\n
\n })\n }\n
\n
\n {\n players[2].discarded.map(t => )\n }\n
\n {players[2].username}\n
\n {\n Array.from({ length: hiddenTileCount(1) }, () => )\n }\n {\n players[1].handExposed.map((meld, i) => {\n return
\n {\n meld.map((t, j) => )\n }\n
\n })\n }\n
\n
\n {\n players[1].discarded.map(t => )\n }\n
\n {players[1].username}\n
\n {\n Array.from({ length: hiddenTileCount(0) }, () => )\n }\n {\n players[0].handExposed.map((meld, i) => {\n return
\n {\n meld.map((t, j) => )\n }\n
\n })\n }\n
\n
\n {\n players[0].discarded.map(t => )\n }\n
\n {players[0].username}\n
\n}"],"sourceRoot":""}