diff --git a/package.json b/package.json index 5e8d898..73a4304 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "gomoku", "private": true, - "version": "0.0.1", + "version": "0.0.2", "type": "module", "scripts": { "dev": "vite", diff --git a/src/layout/Footer.jsx b/src/layout/Footer.jsx index a6fe8e4..1d92936 100644 --- a/src/layout/Footer.jsx +++ b/src/layout/Footer.jsx @@ -3,12 +3,12 @@ import {IconBrandGithubFilled, IconPower} from "@tabler/icons-react"; const Footer = () => { return <div className={'flex flex-row gap-3 mt-3'}> - <Card className={'h-12 min-w-12'} isPressable={true}> + <Card className={'h-12 min-w-12'} isPressable={true} onPress={() => { + document.location.href = import.meta.env.VITE_SSO_URL + '/settings' + }}> <CardBody> <div className={'h-full w-full flex justify-center items-center overflow-hidden'}> - <IconPower size={18} className={'text-red-600'} onClick={() => { - document.location.href = import.meta.env.VITE_SSO_URL + '/settings' - }}/> + <IconPower size={18} className={'text-red-600'}/> </div> </CardBody> </Card> @@ -19,7 +19,9 @@ const Footer = () => { </div> </CardBody> </Card> - <Card className={'h-12 min-w-12'} isPressable={true}> + <Card className={'h-12 min-w-12'} isPressable={true} onPress={() => { + window.open('http://github.com/AdonisGM', '_blank').focus(); + }}> <CardBody> <div className={'h-full w-full flex justify-center items-center overflow-hidden'}> <IconBrandGithubFilled size={18} className={'text-black'}/> diff --git a/src/pages/dashboardPage/Dashboard.jsx b/src/pages/dashboardPage/Dashboard.jsx index cf9d44e..589e20e 100644 --- a/src/pages/dashboardPage/Dashboard.jsx +++ b/src/pages/dashboardPage/Dashboard.jsx @@ -1,5 +1,6 @@ import {Card, CardBody} from "@nextui-org/react"; import PlayGame from "./items/PlayGame.jsx"; +import HistoryGame from "./items/HistoryGame.jsx"; const Dashboard = (props) => { return ( @@ -8,13 +9,24 @@ const Dashboard = (props) => { <Card shadow={'sm'} isPressable={true} - className={'h-full w-full'} + className={'h-36 w-full'} > - <CardBody> + <CardBody className={'flex justify-center items-center overflow-hidden'}> <PlayGame/> </CardBody> </Card> </div> + <div className={'col-span-3'}> + {/*<Card*/} + {/* shadow={'sm'}*/} + {/* isPressable={true}*/} + {/* className={'h-full w-full'}*/} + {/*>*/} + {/* <CardBody>*/} + <HistoryGame/> + {/* </CardBody>*/} + {/*</Card>*/} + </div> </div> ) } diff --git a/src/pages/dashboardPage/items/HistoryGame.jsx b/src/pages/dashboardPage/items/HistoryGame.jsx index e69de29..6ec257d 100644 --- a/src/pages/dashboardPage/items/HistoryGame.jsx +++ b/src/pages/dashboardPage/items/HistoryGame.jsx @@ -0,0 +1,114 @@ +import {Pagination, Table, TableBody, TableCell, TableColumn, TableHeader, TableRow} from "@nextui-org/react"; +import {useEffect, useState} from "react"; +import callApi from "../../../apis/GatewayApi.js"; +import TableLoading from "../../../layout/TableLoading.jsx"; +import TableEmpty from "../../../layout/TableEmpty.jsx"; +import {formatDate} from "../../../common/common.js"; + +const HistoryGame = (props) => { + const [page, setPage] = useState(1) + const [pages, setPages] = useState(0) + const [data, setData] = useState([]) + const [isLoading, setIsLoading] = useState(false) + + useEffect(() => { + getAllManagement() + }, [page]); + + const getAllManagement = () => { + setIsLoading(true) + callApi('pkg_gmk_game.get_all_game', { + page: page, + size_page: 10 + }, (data) => { + setData(data) + if (data.length > 0) { + setPages(Math.ceil(data[0].TB_TOTAL_ROW/10)) + } + setIsLoading(false) + }, (err) => { + console.log(err) + setIsLoading(false) + }) + } + + const renderStatus = (item) => { + if (item?.C_NEXT_PLAYER === localStorage.getItem('username')) { + return <p + className={'text-xs text-default-500 font-bold bg-clip-text text-transparent bg-gradient-to-r from-yellow-400 to-red-400 leading-normal'}>Your turn! + </p> + } else if (item?.C_NEXT_PLAYER && item?.C_NEXT_PLAYER !== localStorage.getItem('username')) { + return <p + className={'text-xs text-default-500 font-bold bg-clip-text text-transparent bg-gradient-to-r from-yellow-400 to-red-400 leading-normal'}>Your partner! + </p> + } + + if (item?.C_STATUS === 'WIN' && item?.C_WIN_PLAYER === localStorage.getItem('username')) { + return <p> + <span className={'text-default-500 font-bold bg-clip-text text-transparent bg-gradient-to-r from-green-600 to-blue-700 leading-normal'}>Your winnnn!</span> 🥳🥳🥳 + </p> + } + + if (item?.C_STATUS === 'WIN' && item?.C_WIN_PLAYER !== localStorage.getItem('username')) { + return <p> + <span className={'text-default-500 font-bold bg-clip-text text-transparent bg-gradient-to-r from-red-600 to-pink-700 leading-normal'}>Your lose! </span>ðŸ˜ðŸ˜±ðŸ¤• + </p> + } + } + + return ( + <div> + <div className={'flex flex-col gap-2.5 justify-center items-center'}> + <Table + isStriped={true} + bottomContent={ + pages > 0 ? ( + <div className="flex w-full justify-end"> + <Pagination + isCompact + showControls + showShadow + color="primary" + page={page} + total={pages} + onChange={(page) => setPage(page)} + isDisabled={isLoading} + /> + </div> + ) : null + } + > + <TableHeader> + <TableColumn>#</TableColumn> + <TableColumn>Match ID</TableColumn> + <TableColumn>Type</TableColumn> + <TableColumn>Enemy</TableColumn> + <TableColumn>Total step</TableColumn> + <TableColumn>Status</TableColumn> + <TableColumn>Created date</TableColumn> + </TableHeader> + <TableBody + items={data ?? []} + isLoading={isLoading} + loadingContent={<TableLoading/>} + emptyContent={<TableEmpty isLoading={isLoading}/>} + > + {(item) => ( + <TableRow key={item?.PK_GMK_MATCH}> + <TableCell>{item?.TB_ROW_NUM}</TableCell> + <TableCell className={'font-mono'}>{item?.C_MATCH_ID}</TableCell> + <TableCell>{item?.C_TYPE}</TableCell> + <TableCell className={'text-sm'}>{item?.C_FULLNAME} <span className={'italic text-gray-400 text-xs'}>#{item?.C_USERNAME}</span></TableCell> + <TableCell>{item?.C_TOTAL_STEP}</TableCell> + <TableCell>{renderStatus(item)}</TableCell> + <TableCell>{formatDate(item?.C_CREATED_DATE)}</TableCell> + </TableRow> + )} + </TableBody> + </Table> + </div> + </div> + ) +} + +export default HistoryGame \ No newline at end of file diff --git a/src/pages/dashboardPage/items/PlayGame.jsx b/src/pages/dashboardPage/items/PlayGame.jsx index c8dcec7..9fe8649 100644 --- a/src/pages/dashboardPage/items/PlayGame.jsx +++ b/src/pages/dashboardPage/items/PlayGame.jsx @@ -12,11 +12,8 @@ const PlayGame = (props) => { return ( <div> <div className={'flex flex-col gap-2.5 justify-center items-center'}> - <Button className={'w-1/2'} color="success" size={'sm'} variant="flat" startContent={<IconDeviceGamepad2/>} onPress={handlePlayNewGame}> - Create game! - </Button> - <Button className={'w-1/2'} color="warning" size={'sm'} variant="flat"> - Join game + <Button color="success" size={'sm'} variant="flat" startContent={<IconDeviceGamepad2/>} onPress={handlePlayNewGame}> + Ender game! </Button> </div> </div> diff --git a/src/pages/gamePage/Board.jsx b/src/pages/gamePage/Board.jsx index ab13dd1..d8ee69d 100644 --- a/src/pages/gamePage/Board.jsx +++ b/src/pages/gamePage/Board.jsx @@ -1,9 +1,7 @@ import {useEffect, useState} from "react"; import {drawTable, handleMouseDown, handleMouseMove, handleMouseUp, initBoard} from "./controller.js"; -const Board = () => { - const [listCell, setListCell] = useState([]) - +const Board = (props) => { useEffect(() => { const containerBoard = document.getElementById('containerBoard'); const canvas = document.getElementById("mainBoard"); @@ -20,59 +18,81 @@ const Board = () => { // console.log(time) // // }) - initBoard(ctx, containerHeight, containerWidth) - drawTable(listCell) + initBoard(ctx, canvas, containerHeight, containerWidth) + drawTable(props.listCell) - canvas.addEventListener("mousemove", (e) => { - e.preventDefault() - handleMouseMove({ - ctx, - canvas: canvas, - evt: e, - listCell: listCell - }) - }) + // canvas.addEventListener("mousemove", onMouseMove) - canvas.addEventListener("mouseup", (e) => { - e.preventDefault() - const location = handleMouseUp({ - canvas: canvas, - evt: e - }) - if (location) { - handleClickSquare(location) - } - }) + // canvas.addEventListener("mouseup", (e) => { + // e.preventDefault() + // const location = handleMouseUp({ + // canvas: canvas, + // evt: e + // }) + // if (location) { + // handleClickSquare(location) + // } + // }) - canvas.addEventListener("mousedown", (e) => { - e.preventDefault() - handleMouseDown({ - canvas: canvas, - evt: e - }) - }) + // canvas.addEventListener("mousedown", (e) => { + // e.preventDefault() + // handleMouseDown({ + // canvas: canvas, + // evt: e + // }) + // }) return () => { - canvas.removeEventListener("mousemove", () => {}) - canvas.removeEventListener("mouseup", () => {}) - canvas.removeEventListener("mousedown", () => {}) + // canvas.removeEventListener("mousemove", () => {}) + // canvas.removeEventListener("mouseup", () => {}) + // canvas.removeEventListener("mousedown", () => {}) } }, []); + // const onMouseMove = (e) => { + // e.preventDefault() + // handleMouseMove({ + // evt: e, + // listCell: props.listCell + // }) + // } + useEffect(() => { - }, [listCell]); + drawTable(props.listCell) + }, [props.listCell]); const handleClickSquare = (location) => { - const side = 0 - listCell.push({x: location.x, y: location.y, side: side}) - setListCell([...listCell]) - console.log(listCell) - drawTable(listCell) + props.onAddCell({x: location.x, y: location.y, side: props.side}) } return ( <div className={'w-full h-full'} id={'containerBoard'}> - <canvas id={'mainBoard'} className={'w-full h-full'}></canvas> + <canvas + id={'mainBoard'} + className={'w-full h-full'} + onMouseUp={(e) => { + e.preventDefault() + const location = handleMouseUp({ + evt: e + }) + if (location) { + handleClickSquare(location) + } + }} + onMouseDown={(e) => { + e.preventDefault() + handleMouseDown({ + evt: e + }) + }} + onMouseMove={(e) => { + e.preventDefault() + handleMouseMove({ + evt: e, + listCell: props.listCell + }) + }} + ></canvas> </div> ) } diff --git a/src/pages/gamePage/Game.jsx b/src/pages/gamePage/Game.jsx index 2be763a..9a597d6 100644 --- a/src/pages/gamePage/Game.jsx +++ b/src/pages/gamePage/Game.jsx @@ -1,17 +1,39 @@ -import {Button, Card, CardBody, CardHeader, Spacer} from "@nextui-org/react"; -import {IconCircleFilled, IconDeviceGamepad2, IconPower} from "@tabler/icons-react"; +import {Button, Card, CardBody, Spacer} from "@nextui-org/react"; +import {IconCircleFilled, IconPower} from "@tabler/icons-react"; import Board from "./Board.jsx"; import {useNavigate} from "react-router-dom"; import {socket} from "../../apis/socket.js"; import {useEffect, useState} from "react"; +import {useForm} from "react-hook-form"; +import InputText from "../../components/customInput/InputText.jsx"; const Game = () => { - const [isConnected, setIsConnected] = useState(false); + const [isConnected, setisConnected] = useState(false); const [inGame, setInGame] = useState(false); + const [isStart, setIsStart] = useState(false) + + const [matchId, setMatchId] = useState(undefined); + const [infoGame, setInfoGame] = useState(undefined) + const [listMember, setListMember] = useState([]) + const [listCell, setListCell] = useState([]) + const [side, setSide] = useState(undefined) + + const [isLoading, setIsLoading] = useState(false) + const [logs, setLogs] = useState([]) const navigate = useNavigate(); + useEffect(() => { + const fMember = listMember.find((e) => { + return e.C_USERNAME === localStorage.getItem('username') + }) + if (fMember) { + console.log(fMember) + setSide(fMember.C_ORDER) + } + }, [listMember]); + useEffect(() => { // Connect to server socket.connect(); @@ -20,7 +42,11 @@ const Game = () => { socket.on('connect', onConnect); socket.on('disconnect', onDisconnect); socket.on('info', onReceiveInfo) + socket.on('listMember', onListMember) socket.on('unauthorized', onUnauthorized) + socket.on('error', onError) + socket.on('move', onMove) + socket.on('allMove', onAllMove) // Clear up listener return () => { @@ -30,62 +56,183 @@ const Game = () => { // Handle event socket const onConnect = () => { - setIsConnected(true) + setisConnected(true) writeLog('info', 'User connected') } const onDisconnect = () => { - setIsConnected(false) + setisConnected(false) writeLog('info', 'User disconnected') } - const onReceiveInfo = (data) => { - console.log(data) + const onListMember = (listMember) => { + setListMember(listMember) + } + + const onReceiveInfo = (message) => { + console.log('info message', message) + + switch (message.action) { + case 'createGame': + onReceiveInfo_createGame(message.data); + break; + case 'joinGame': + onReceiveInfo_joinGame(message.data); + break; + case 'startGame': + setIsStart(true) + break; + case 'infoGame': + console.log(message.data) + setInfoGame(message.data[0]) + break; + default: + // + } } + const onReceiveInfo_createGame = (data) => { + setInGame(true); + setMatchId(data.matchId) + setIsLoading(false) + } + + const onReceiveInfo_joinGame = (data) => { + setInGame(true); + setIsLoading(false) + } + const onUnauthorized = () => { navigate('/') } + const onError = (err) => { + console.log(err) + } + const writeLog = (type, message) => { console.log(`${type}: ${message}`) } + const onMove = (message) => { + console.log(message) + const cell = message[0] + listCell.push({ + x: cell.C_LOCATION_X, + y: cell.C_LOCATION_Y, + side: cell.C_ORDER, + }) + setListCell([...listCell]) + } + + const onAllMove = (message) => { + console.log(message) + const mappingCells = message.map((cell) => { + return { + x: cell.C_LOCATION_X, + y: cell.C_LOCATION_Y, + side: cell.C_ORDER, + } + }) + setListCell(mappingCells) + } + // Handle event client const handleExitGame = () => { + console.log('game exit') navigate('/dashboard') } const handleClickNewGame = () => { socket.emit('createGame') + setIsLoading(true) } + const onSubmit = (data) => { + socket.emit('joinGame', { + matchId: data.matchId, + }) + setIsLoading(true) + setMatchId(data.matchId) + } + + function handleAddCell (cell) { + if ((infoGame?.C_NEXT_PLAYER !== localStorage.getItem('username')) || infoGame?.C_STATUS !== 'PLAYING') { + return + } + + listCell.push({x: cell.x, y: cell.y, side: side}) + setListCell([...listCell]) + + socket.emit('move', { + matchId: matchId, + locationX: cell.x, + locationY: cell.y + }) + + infoGame.C_NEXT_PLAYER = undefined + setInfoGame({...infoGame}) + } + + const { control, handleSubmit, reset, setValue, getValues, watch } = useForm({ + defaultValues: { + matchId: '', + } + }) + return ( <div className={'w-screen h-screen p-10'}> <div className={'w-full h-full bg-gray-50 border-1 border-gray-200 rounded-3xl overflow-hidden relative shadow-[inset_0_0px_60px_-15px_rgba(0,0,0,0.3)]'}> - {/*<Board/>*/} + {isStart && ( + <Board + side={side} + listCell={listCell} + onAddCell={handleAddCell} + /> + )} {!inGame && ( <div className={'w-full h-full flex justify-center items-center'}> <Card> - <CardBody> - <Button - color="success" - size={'sm'} - variant="flat" - startContent={<IconDeviceGamepad2/>} - onPress={handleClickNewGame} - > - Create game! - </Button> - <Spacer y={2}/> - <Button color="warning" size={'sm'} variant="flat"> - Join game - </Button> - </CardBody> - </Card> - </div> - )} + <CardBody> + <div className={'flex items-end gap-3'}> + <div className={'w-44'}> + <Button + className={'w-full'} + color="success" + size={'sm'} + variant="flat" + onPress={handleClickNewGame} + isLoading={isLoading} + isDisabled={isLoading} + > + Create game! + </Button> + </div> + <form className={'w-44'} onSubmit={handleSubmit((data) => onSubmit(data))}> + <InputText + name={'matchId'} + label={'Match Id'} + control={control} + /> + <Spacer y={2}/> + <Button + type="submit" + className={'w-full'} + color="warning" + size={'sm'} + variant="flat" + isLoading={isLoading} + isDisabled={isLoading} + > + Join game + </Button> + </form> + </div> + </CardBody> + </Card> + </div> + )} {/*<div className={'absolute bottom-3 right-3'}>*/} {/* <Card className={'max-h-24 min-w-24'}>*/} @@ -109,30 +256,36 @@ const Game = () => { </CardBody> </Card> </div> - <div className={'absolute top-3 left-3'}> - <Card className={''} isPressable={true}> + {matchId && <div className={'absolute top-3 left-3'}> + <Card className={''} isPressable={true} onPress={() => { + navigator.clipboard.writeText(matchId); + }}> <CardBody> <div> - <p className={'text-sm text-gray-500'}>ID table: <b>sdjf348y3</b></p> + <p className={'text-sm text-gray-500'}>ID table: <b>{matchId}</b></p> </div> <div> <p className={'text-sm text-gray-500'}>List players:</p> <div className={'mt-2'}> - <div className={'flex gap-3 items-center'}> - <IconCircleFilled size={18} className={'text-orange-500'}/> - <p className={'text-sm text-gray-500'}>Adonis Willer <span - className={'italic text-gray-400 text-xs'}>#adonis</span></p> - </div> - <div className={'flex gap-3 items-center'}> - <IconCircleFilled size={18} className={'text-black'}/> - <p className={'text-sm text-gray-500'}>Nguyen Manh Tung <span - className={'italic text-gray-400 text-xs'}>#tungnm</span></p> - </div> + {listMember.map((item, index) => { + return ( + <div key={index} className={'flex gap-3 items-center'}> + <IconCircleFilled size={18} className={item.C_ORDER === 2 ? 'text-gray-600' : 'text-orange-500'}/> + <p className={'text-sm text-gray-500'}>{item.C_FULLNAME} <span + className={'italic text-gray-400 text-xs'}>#{item.C_USERNAME}</span></p> + </div> + ) + })} </div> </div> + <div className={'mt-2'}> + {infoGame?.C_NEXT_PLAYER === localStorage.getItem('username') && <p className={'text-sm text-default-500 font-bold bg-clip-text text-transparent bg-gradient-to-r from-blue-400 to-purple-400 leading-normal'}>Your turn!</p>} + {infoGame?.C_STATUS === 'WIN' && infoGame?.C_WIN_PLAYER === localStorage.getItem('username') && <p><span className={'text-default-500 font-bold bg-clip-text text-transparent bg-gradient-to-r from-green-600 to-blue-700 leading-normal'}>Your winnnn!</span> 🥳🥳🥳</p>} + {infoGame?.C_STATUS === 'WIN' && infoGame?.C_WIN_PLAYER !== localStorage.getItem('username') && <p><span className={'text-default-500 font-bold bg-clip-text text-transparent bg-gradient-to-r from-red-600 to-pink-700 leading-normal'}>Your lose! </span>ðŸ˜ðŸ˜±ðŸ¤•</p>} + </div> </CardBody> </Card> - </div> + </div>} </div> </div> ) diff --git a/src/pages/gamePage/controller.js b/src/pages/gamePage/controller.js index db7c75b..87e4658 100644 --- a/src/pages/gamePage/controller.js +++ b/src/pages/gamePage/controller.js @@ -1,6 +1,7 @@ // Global let HEIGHT, WIDTH let CTX +let CANVAS let MAX_SIZE let GAP_LINE let PAN_DIRECTION = { @@ -11,18 +12,24 @@ let PAN_DIRECTION = { moveY: 0, } let offsetX = 0, offsetY = 0; +let listCell = [] -export const initBoard = (ctx, height, width) => { +export const initBoard = (ctx, canvas, height, width) => { CTX = ctx + CANVAS = canvas HEIGHT = height WIDTH = width - MAX_SIZE = 10 - GAP_LINE = 50 + MAX_SIZE = 20 + GAP_LINE = 40 + + listCell = [] } -export const drawTable = (listCell) => { +export const drawTable = (cells) => { + listCell = cells + CTX.clearRect(0, 0, WIDTH, HEIGHT) const BEGIN_X_TABLE = (MAX_SIZE / 2) * GAP_LINE * (-1) + WIDTH / 2 + offsetX @@ -92,8 +99,8 @@ export const drawTable = (listCell) => { }) } -export const handleMouseMove = ({canvas, evt, listCell}) => { - const rect = canvas.getBoundingClientRect() +export const handleMouseMove = ({evt}) => { + const rect = CANVAS.getBoundingClientRect() if (!PAN_DIRECTION.isMouseDown) { return @@ -105,16 +112,16 @@ export const handleMouseMove = ({canvas, evt, listCell}) => { drawTable(listCell) } -export const handleMouseDown = ({canvas, evt}) => { - const rect = canvas.getBoundingClientRect() +export const handleMouseDown = ({evt}) => { + const rect = CANVAS.getBoundingClientRect() PAN_DIRECTION.beginX = evt.clientX - rect.left PAN_DIRECTION.beginY = evt.clientY - rect.top PAN_DIRECTION.isMouseDown = true } -export const handleMouseUp = ({canvas, evt}) => { - const rect = canvas.getBoundingClientRect() +export const handleMouseUp = ({evt}) => { + const rect = CANVAS.getBoundingClientRect() PAN_DIRECTION.moveX = PAN_DIRECTION.moveX + evt.clientX - rect.left - PAN_DIRECTION.beginX PAN_DIRECTION.moveY = PAN_DIRECTION.moveY + evt.clientY - rect.top - PAN_DIRECTION.beginY @@ -122,7 +129,7 @@ export const handleMouseUp = ({canvas, evt}) => { PAN_DIRECTION.isMouseDown = false if (evt.clientX - rect.left - PAN_DIRECTION.beginX === 0 && evt.clientY - rect.top - PAN_DIRECTION.beginY === 0) { - const location = handleClickStep(canvas, evt) + const location = handleClickStep(evt) if (- MAX_SIZE / 2 <= location.x && - MAX_SIZE / 2 <= location.y && MAX_SIZE / 2 - 1 >= location.x && MAX_SIZE / 2 - 1 >= location.y) { return location @@ -130,8 +137,8 @@ export const handleMouseUp = ({canvas, evt}) => { } } -export const handleClickStep = (canvas, evt) => { - const rect = canvas.getBoundingClientRect() +export const handleClickStep = (evt) => { + const rect = CANVAS.getBoundingClientRect() return { x: Math.floor((evt.clientX - rect.left - WIDTH / 2 - PAN_DIRECTION.moveX) / GAP_LINE), diff --git a/src/pages/welcomePage/Welcome.jsx b/src/pages/welcomePage/Welcome.jsx index 313a217..5a472e4 100644 --- a/src/pages/welcomePage/Welcome.jsx +++ b/src/pages/welcomePage/Welcome.jsx @@ -98,7 +98,7 @@ const Welcome = () => { <Spacer y={10}/> <p className={'text-default-500 text-sm'}> - Engage in an intellectual game with friends and family. We hope you enjoy playing with us! + Challenge your friends and family to an intellectual game. We hope you have a blast! </p> <Spacer y={16}/> <Button