{"version":3,"sources":["connect-four-game.tsx","game-over-modal.tsx","connect-four-game-controller.tsx","models/game-state.ts","theme.ts","connect-four-page.tsx","registerServiceWorker.js","index.tsx"],"names":["BoardContainer","styled","div","position","transform","top","left","marginLeft","marginRight","BoardGrid","display","gridTemplateRows","gridTemplateColumns","GridCell","props","gridRow","row","gridColumn","column","padding","BoardPiece","pieceState","backgroundColor","borderStyle","borderWidth","borderColor","borderRadius","width","height","maxWidth","maxHeight","DownArrow","borderLeft","borderRight","borderTop","margin","Exclamation","ConnectFourGame","gameState","onHover","onClick","gridCells","forcedColumns","forEach","forcedColumn","columnHover","gridCell","key","push","undefined","r","board","length","c","onMouseOver","data-column","onMouseLeave","GameOverModal","isOpen","winner","onNewGame","Modal","show","onHide","Header","closeButton","Title","Body","Footer","Button","variant","initialGameState","Object","freeze","turn","ConnectFourGameController","useState","setGameState","isGameOverModalOpen","setIsGameOverModalOpen","GetWinner","isLegalMove","isValidMove","includes","addPieceToBoard","map","lastIndexOf","newBoard","cloneDeep","isDraw","e","currentTarget","dataset","parseInt","newGameState","opponentTurn","getForcedColumns","makeMove","Theme","MOBILE","DESKTOP","Container","minHeight","Spacing","GameContainer","h1","fontFamily","textAlign","fontSize","fontWeight","lineHeight","marginBottom","PageBody","color","whiteSpace","paddingBottom","marginTop","minWidth","ConnectFourPage","Boolean","window","location","hostname","match","rootElement","document","getElementById","ReactDOM","render","navigator","serviceWorker","ready","then","registration","unregister"],"mappings":"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","file":"static/js/main.642a6c34.chunk.js","sourcesContent":["import React, { MouseEvent } from 'react';\r\nimport styled from 'styled-components';\r\nimport {GameState} from \"./models/game-state\";\r\n\r\nconst tokenWidth = (100 * 0.50) / 7;\r\nconst BoardContainer = styled.div({\r\n position: 'absolute',\r\n transform: 'translate(-50%, calc(-50%))',\r\n top: '50%',\r\n left: '50%',\r\n marginLeft: 'auto',\r\n marginRight: 'auto',\r\n});\r\n\r\nconst BoardGrid = styled.div({\r\n display: 'grid',\r\n gridTemplateRows: `min(${0.4 * tokenWidth}vw, 40px) repeat(6, 1fr)`,\r\n gridTemplateColumns: 'repeat(7, 1fr)',\r\n});\r\ntype GridCellProps = {\r\n row: number,\r\n column: number,\r\n}\r\n\r\nconst GridCell = styled.div((props:GridCellProps) => ({\r\n gridRow: `${props.row + 1}`,\r\n gridColumn: `${props.column + 1}`,\r\n padding: '0.5vw 1vw',\r\n}));\r\n\r\ntype BoardPieceProps = {\r\n pieceState: string,\r\n};\r\n\r\n// @ts-ignore\r\nconst BoardPiece = styled.div(({pieceState}:BoardPieceProps) => ({\r\n backgroundColor: pieceState === 'r' ? 'maroon' : pieceState === 'b' ? 'black' : 'white',\r\n borderStyle: pieceState === '' ? 'solid' : 'solid',\r\n borderWidth: '1px',\r\n borderColor: pieceState === 'r' ? 'maroon' : pieceState === 'b' ? 'black' : 'white',\r\n borderRadius: '50%',\r\n position: 'relative',\r\n width: `${tokenWidth}vw`,\r\n height: `${tokenWidth}vw`,\r\n maxWidth: '75px',\r\n maxHeight: '75px',\r\n}));\r\n\r\nconst DownArrow = styled.div({\r\n width: 0,\r\n height: 0,\r\n borderLeft: `min(${0.3 * tokenWidth}vw, 20px) solid transparent`,\r\n borderRight: `min(${0.3 * tokenWidth}vw, 20px) solid transparent`,\r\n borderTop: `min(${0.2 * tokenWidth}vw, 15px) solid gold`,\r\n margin: 'auto auto'\r\n})\r\n\r\nconst Exclamation = styled(DownArrow)({\r\n borderTop: `min(${0.2 * tokenWidth}vw, 15px) solid red`,\r\n})\r\n\r\ntype Props = {\r\n gameState: GameState;\r\n onHover: (e: MouseEvent) => void,\r\n onClick: (e: MouseEvent) => void,\r\n}\r\n\r\nexport function ConnectFourGame({gameState, onHover, onClick}: Props) {\r\n let gridCells = [];\r\n \r\n gameState.forcedColumns.forEach(forcedColumn => {\r\n if (forcedColumn === gameState.columnHover) return;\r\n const gridCell = (\r\n \r\n \r\n \r\n );\r\n gridCells.push(gridCell);\r\n });\r\n \r\n if (gameState.columnHover !== undefined) {\r\n const gridCell = (\r\n \r\n \r\n \r\n );\r\n gridCells.push(gridCell);\r\n }\r\n \r\n for(let r = 0; r < gameState.board.length; r++) {\r\n for(let c = 0; c < gameState.board[r].length; c++) {\r\n const key = r * 13 + c;\r\n const pieceState = gameState.board[r][c];\r\n const gridCell = (\r\n \r\n {/*@ts-ignore*/}\r\n \r\n \r\n \r\n );\r\n gridCells.push(gridCell);\r\n }\r\n }\r\n return (\r\n \r\n \r\n {gridCells}\r\n \r\n \r\n );\r\n}","import React from 'react';\r\nimport {Button, Modal} from \"react-bootstrap\";\r\n\r\ntype Props = {\r\n isOpen: boolean,\r\n winner: string,\r\n onNewGame: () => void,\r\n}\r\n\r\nexport function GameOverModal({isOpen, winner, onNewGame}: Props) {\r\n return (\r\n \r\n \r\n Game Over\r\n \r\n {winner} wins!\r\n \r\n \r\n \r\n \r\n );\r\n}","import React, {MouseEvent, useState} from 'react';\r\nimport {Board, GameState, NewBoard} from \"./models/game-state\";\r\nimport {ConnectFourGame} from \"./connect-four-game\";\r\nimport {cloneDeep} from 'lodash'\r\nimport {GameOverModal} from \"./game-over-modal\";\r\n\r\n\r\nconst initialGameState: GameState = Object.freeze({\r\n board: NewBoard(),\r\n turn: \"r\",\r\n forcedColumns: [],\r\n});\r\n\r\nexport function ConnectFourGameController() {\r\n const [gameState, setGameState] = useState(initialGameState);\r\n const [isGameOverModalOpen, setIsGameOverModalOpen] = useState(false);\r\n const winner = GetWinner(gameState.board);\r\n \r\n if (winner && !isGameOverModalOpen) {\r\n setIsGameOverModalOpen(true);\r\n }\r\n\r\n function onNewGame() {\r\n setGameState(initialGameState);\r\n setIsGameOverModalOpen(false);\r\n }\r\n \r\n function onHover(e: MouseEvent) {\r\n const column = e.currentTarget.dataset['column'] !== undefined\r\n ? parseInt(e.currentTarget.dataset['column'])\r\n : undefined;\r\n \r\n const newGameState = cloneDeep(gameState);\r\n newGameState.columnHover = isLegalMove(gameState, column) ? column : undefined;\r\n setGameState(newGameState);\r\n }\r\n \r\n function onClick(e: MouseEvent) {\r\n if (e.currentTarget.dataset['column'] === undefined) return;\r\n const column = parseInt(e.currentTarget.dataset['column']);\r\n makeMove(column);\r\n }\r\n \r\n function isLegalMove(gameState: GameState, column?: number) {\r\n if (column === undefined) return false;\r\n if (!isValidMove(gameState.board, column)) return false;\r\n return gameState.forcedColumns.length === 0 || gameState.forcedColumns.includes(column);\r\n }\r\n \r\n function isValidMove(board: Board, column?: number) {\r\n if (column === undefined) return false;\r\n return board[0][column] === '';\r\n }\r\n \r\n function makeMove(column: number) {\r\n if (!isLegalMove(gameState, column)) return;\r\n const newGameState = cloneDeep(gameState);\r\n newGameState.board = addPieceToBoard(newGameState.board, column, newGameState.turn);\r\n newGameState.turn = newGameState.turn === 'r' ? 'b' : 'r';\r\n newGameState.forcedColumns = getForcedColumns(newGameState.board, newGameState.turn);\r\n newGameState.columnHover = isLegalMove(newGameState, newGameState.columnHover) ? newGameState.columnHover : undefined;\r\n setGameState(newGameState);\r\n }\r\n \r\n function addPieceToBoard(board: Board, column: number, turn: string): Board {\r\n if (!isValidMove(board, column)) return board;\r\n const row = [0, 1, 2, 3, 4, 5]\r\n .map(r => board[r][column])\r\n .lastIndexOf('');\r\n \r\n const newBoard = cloneDeep(board);\r\n newBoard[row][column] = turn;\r\n return newBoard;\r\n }\r\n \r\n function getForcedColumns(board: Board, turn: string): number[] {\r\n if (GetWinner(board)) return [];\r\n let forcedColumns = [];\r\n let opponentTurn = turn === 'r' ? 'b' : 'r';\r\n \r\n // If you can win, you must win!\r\n for(let c = 0; c < board[0].length; c++) {\r\n const nextBoardState = addPieceToBoard(board, c, turn);\r\n const winner = GetWinner(nextBoardState);\r\n if (winner === turn) {\r\n forcedColumns.push(c);\r\n }\r\n }\r\n \r\n // If you can block, you must block!\r\n if (forcedColumns.length === 0) {\r\n for(let c = 0; c < board[0].length; c++) {\r\n const nextBoardState = addPieceToBoard(board, c, opponentTurn);\r\n const winner = GetWinner(nextBoardState);\r\n if (winner === opponentTurn) {\r\n forcedColumns.push(c);\r\n }\r\n }\r\n }\r\n \r\n return forcedColumns;\r\n }\r\n \r\n function GetWinner(board: Board): string | undefined {\r\n let isDraw = true;\r\n for(let r = 0; r < board.length; r++) {\r\n for(let c = 0; c < board[r].length; c++) {\r\n // check for draw\r\n if (board[r][c] === '') {\r\n isDraw = false;\r\n }\r\n \r\n // check down\r\n if (r + 3 < board.length) {\r\n if (board[r][c] !== '' &&\r\n board[r][c] === board[r+1][c] &&\r\n board[r][c] === board[r+2][c] &&\r\n board[r][c] === board[r+3][c]) {\r\n return board[r][c];\r\n }\r\n }\r\n \r\n // check right\r\n if (c + 3 < board[r].length) {\r\n if (board[r][c] !== '' && \r\n board[r][c] === board[r][c+1] &&\r\n board[r][c] === board[r][c+2] &&\r\n board[r][c] === board[r][c+3]) {\r\n return board[r][c];\r\n }\r\n }\r\n \r\n // check down-right diagonal\r\n if (r + 3 < board.length && c + 3 < board[r].length) {\r\n if (board[r][c] !== '' &&\r\n board[r][c] === board[r+1][c+1] &&\r\n board[r][c] === board[r+2][c+2] &&\r\n board[r][c] === board[r+3][c+3]) {\r\n return board[r][c];\r\n }\r\n }\r\n \r\n // check up-right diagonal\r\n if (r - 3 >= 0 && c + 3 < board[r].length) {\r\n if (board[r][c] !== '' &&\r\n board[r][c] === board[r-1][c+1] &&\r\n board[r][c] === board[r-2][c+2] &&\r\n board[r][c] === board[r-3][c+3]) {\r\n return board[r][c];\r\n }\r\n }\r\n }\r\n }\r\n \r\n return isDraw ? 'draw' : undefined;\r\n }\r\n \r\n return (\r\n <>\r\n \r\n \r\n \r\n );\r\n}","\r\nexport type Board = string[][];\r\nexport type GameState = {\r\n turn: string,\r\n board: Board,\r\n columnHover?: number,\r\n forcedColumns: number[],\r\n}\r\n\r\nexport function NewBoard(): Board {\r\n return [\r\n ['', '', '', '', '', '', ''],\r\n ['', '', '', '', '', '', ''],\r\n ['', '', '', '', '', '', ''],\r\n ['', '', '', '', '', '', ''],\r\n ['', '', '', '', '', '', ''],\r\n ['', '', '', '', '', '', ''],\r\n ];\r\n}","// inspiration from:\r\n// https://pimpmytype.com/review-tech-blog/\r\n\r\nexport const Theme = {\r\n subtitleColor: '#5d6266',\r\n titleFontFamily: 'basic-sans,Helvetica,Arial,sans-serif',\r\n storyFontFamily: 'bressay,Georgia,serif',\r\n}\r\n\r\nexport function px(size: number): string {\r\n return `${size}px`;\r\n}\r\n\r\n// device constants\r\nexport const MIN_PAGE_WIDTH = '320px';\r\nexport const MIN_DESKTOP_PAGE_WIDTH = '600px';\r\nexport const MOBILE = `@media all and (max-width: ${MIN_DESKTOP_PAGE_WIDTH})`;\r\nexport const DESKTOP = `@media all and (min-width: ${MIN_DESKTOP_PAGE_WIDTH})`;\r\nexport const HOVERABLE = `@media (hover: hover)`;\r\nexport const DEFAULT = \"@media all\";","import React from 'react';\r\nimport {ConnectFourGameController} from \"./connect-four-game-controller\";\r\nimport styled from \"styled-components\";\r\nimport {DESKTOP, MOBILE, Theme} from \"./theme\";\r\n\r\nconst Container = styled.div({\r\n width: '100%',\r\n minHeight: '100%',\r\n});\r\n\r\nconst Spacing = styled.div({\r\n height: '45px',\r\n});\r\n\r\nconst GameContainer = styled.div({\r\n width: '100%',\r\n height: '60vw',\r\n maxHeight: '80vh',\r\n position: 'relative',\r\n});\r\n\r\nconst Title = styled.h1({\r\n fontFamily: Theme.titleFontFamily,\r\n textAlign: 'center',\r\n fontSize: '2.6em',\r\n fontWeight: 'bold',\r\n lineHeight: '3rem',\r\n marginBottom: '3rem',\r\n});\r\n\r\nconst Subtitle = styled.div({\r\n color: Theme.subtitleColor,\r\n fontFamily: Theme.titleFontFamily,\r\n textAlign: 'center',\r\n fontSize: '1em',\r\n lineHeight: '2rem',\r\n});\r\n\r\nconst PageBody = styled.div({\r\n color: 'black',\r\n fontFamily: 'bressay,Georgia,serif',\r\n fontSize: '1.2em',\r\n lineHeight: '2rem',\r\n whiteSpace: 'pre-wrap',\r\n paddingBottom: '3rem',\r\n\r\n [DESKTOP]: {\r\n marginTop: '4rem',\r\n width: '35%',\r\n minWidth: '500px',\r\n marginLeft: 'auto',\r\n marginRight: 'auto',\r\n },\r\n [MOBILE]: {\r\n marginLeft: '30px',\r\n marginRight: '30px',\r\n marginTop: '2rem',\r\n },\r\n})\r\n\r\nexport function ConnectFourPage() {\r\n return (\r\n \r\n \r\n \r\n \r\n \r\n \r\n Connect Four\r\n

\r\n I'm pretty good at connect four. It's not difficult to play a winning strategy. \r\n This is in fact a \"solved\" game. In perfect play, the first player will always win. \r\n However, I've come across embarassing situations where I've lost a match,\r\n even after making such hubristic claims! \r\n

\r\n

\r\n Most embarassingly, I've lost on silly mistakes where I simply didn't realize a threat.\r\n I wasn't aware that my opponent had lined up three tokens and would win on the next move \r\n unless I blocked. So rather than improve my concentration in the game, I decided to build \r\n this version of Connect-Four, where the rules are modified to my favor 🙂\r\n

\r\n

\r\n I borrowed some ideas from Chess:\r\n

\r\n
    \r\n
  • This version of Connect-Four has \"forced\" moves
  • \r\n
  • If you are about to lose unless you block you opponent, you are in \"Check\"
  • \r\n
  • If you are in \"Check\", you must block your opponent
  • \r\n
  • If you have a winning move, you must make the winning move
  • \r\n
\r\n

Enjoy!

\r\n
\r\n
\r\n );\r\n}","// In production, we register a service worker to serve assets from local cache.\r\n\r\n// This lets the app load faster on subsequent visits in production, and gives\r\n// it offline capabilities. However, it also means that developers (and users)\r\n// will only see deployed updates on the \"N+1\" visit to a page, since previously\r\n// cached resources are updated in the background.\r\n\r\n// To learn more about the benefits of this model, read https://goo.gl/KwvDNy.\r\n// This link also includes instructions on opting out of this behavior.\r\n\r\nconst isLocalhost = Boolean(\r\n window.location.hostname === 'localhost' ||\r\n // [::1] is the IPv6 localhost address.\r\n window.location.hostname === '[::1]' ||\r\n // 127.0.0.1/8 is considered localhost for IPv4.\r\n window.location.hostname.match(\r\n /^127(?:\\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/\r\n )\r\n);\r\n\r\nexport default function register () {\r\n if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {\r\n // The URL constructor is available in all browsers that support SW.\r\n const publicUrl = new URL(process.env.PUBLIC_URL, window.location);\r\n if (publicUrl.origin !== window.location.origin) {\r\n // Our service worker won't work if PUBLIC_URL is on a different origin\r\n // from what our page is served on. This might happen if a CDN is used to\r\n // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374\r\n return;\r\n }\r\n\r\n window.addEventListener('load', () => {\r\n const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;\r\n\r\n if (isLocalhost) {\r\n // This is running on localhost. Lets check if a service worker still exists or not.\r\n checkValidServiceWorker(swUrl);\r\n } else {\r\n // Is not local host. Just register service worker\r\n registerValidSW(swUrl);\r\n }\r\n });\r\n }\r\n}\r\n\r\nfunction registerValidSW (swUrl) {\r\n navigator.serviceWorker\r\n .register(swUrl)\r\n .then(registration => {\r\n registration.onupdatefound = () => {\r\n const installingWorker = registration.installing;\r\n installingWorker.onstatechange = () => {\r\n if (installingWorker.state === 'installed') {\r\n if (navigator.serviceWorker.controller) {\r\n // At this point, the old content will have been purged and\r\n // the fresh content will have been added to the cache.\r\n // It's the perfect time to display a \"New content is\r\n // available; please refresh.\" message in your web app.\r\n console.log('New content is available; please refresh.');\r\n } else {\r\n // At this point, everything has been precached.\r\n // It's the perfect time to display a\r\n // \"Content is cached for offline use.\" message.\r\n console.log('Content is cached for offline use.');\r\n }\r\n }\r\n };\r\n };\r\n })\r\n .catch(error => {\r\n console.error('Error during service worker registration:', error);\r\n });\r\n}\r\n\r\nfunction checkValidServiceWorker (swUrl) {\r\n // Check if the service worker can be found. If it can't reload the page.\r\n fetch(swUrl)\r\n .then(response => {\r\n // Ensure service worker exists, and that we really are getting a JS file.\r\n if (\r\n response.status === 404 ||\r\n response.headers.get('content-type').indexOf('javascript') === -1\r\n ) {\r\n // No service worker found. Probably a different app. Reload the page.\r\n navigator.serviceWorker.ready.then(registration => {\r\n registration.unregister().then(() => {\r\n window.location.reload();\r\n });\r\n });\r\n } else {\r\n // Service worker found. Proceed as normal.\r\n registerValidSW(swUrl);\r\n }\r\n })\r\n .catch(() => {\r\n console.log(\r\n 'No internet connection found. App is running in offline mode.'\r\n );\r\n });\r\n}\r\n\r\nexport function unregister () {\r\n if ('serviceWorker' in navigator) {\r\n navigator.serviceWorker.ready.then(registration => {\r\n registration.unregister();\r\n });\r\n }\r\n}\r\n","import 'bootstrap/dist/css/bootstrap.css';\r\nimport React from 'react';\r\nimport ReactDOM from 'react-dom';\r\nimport {ConnectFourPage} from \"./connect-four-page\";\r\nimport {unregister} from \"./registerServiceWorker\";\r\n\r\nconst rootElement = document.getElementById('root');\r\nReactDOM.render(, rootElement);\r\nunregister();\r\n\r\n"],"sourceRoot":""}