From 576556afe718a2814cf5975a53abfa9f83de4c74 Mon Sep 17 00:00:00 2001 From: djmil Date: Thu, 16 Nov 2023 09:17:07 +0100 Subject: [PATCH] Game: surrender --- ...ateController.java => GameController.java} | 22 ++++++++++++++--- webapp/src/api/games.js | 8 ++++++- webapp/src/container/Games.css | 14 +++++++---- webapp/src/container/Games.jsx | 8 +++++-- .../src/container/games/action/Surrender.jsx | 24 +++++++++++++++---- .../src/container/games/view/GameSelector.jsx | 2 +- webapp/src/reducer/games.js | 1 + 7 files changed, 64 insertions(+), 15 deletions(-) rename backend/src/main/java/djmil/cordacheckers/api/{GameStateController.java => GameController.java} (60%) diff --git a/backend/src/main/java/djmil/cordacheckers/api/GameStateController.java b/backend/src/main/java/djmil/cordacheckers/api/GameController.java similarity index 60% rename from backend/src/main/java/djmil/cordacheckers/api/GameStateController.java rename to backend/src/main/java/djmil/cordacheckers/api/GameController.java index 4b2432b..301e012 100644 --- a/backend/src/main/java/djmil/cordacheckers/api/GameStateController.java +++ b/backend/src/main/java/djmil/cordacheckers/api/GameController.java @@ -1,11 +1,14 @@ package djmil.cordacheckers.api; import java.util.List; +import java.util.UUID; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -16,8 +19,8 @@ import djmil.cordacheckers.user.User; @RestController -@RequestMapping("api/games") -public class GameStateController { +@RequestMapping("api/game") +public class GameController { @Autowired CordaClient cordaClient; @@ -26,7 +29,7 @@ public class GameStateController { HoldingIdentityResolver holdingIdentityResolver; @GetMapping - public ResponseEntity> gameStateList( + public ResponseEntity> list( @AuthenticationPrincipal User player ) { final List gsList = cordaClient.gameStateList(player.getHoldingIdentity()); @@ -34,4 +37,17 @@ public class GameStateController { return ResponseEntity.ok(gsList); } + @PutMapping("/{uuid}/surrender") + public ResponseEntity surrender( + @AuthenticationPrincipal User issuer, + @PathVariable UUID uuid + ) { + final GameView finishedGame = cordaClient.gameBoardSurrender( + issuer.getHoldingIdentity(), + uuid + ); + + return ResponseEntity + .ok(finishedGame); + } } diff --git a/webapp/src/api/games.js b/webapp/src/api/games.js index 00dd76b..41d4712 100644 --- a/webapp/src/api/games.js +++ b/webapp/src/api/games.js @@ -9,7 +9,7 @@ export default function useGamesApi(gamesReducer, config) { dispatchGames({ type: 'next', gamesList }); } - const isPollingGamesList = usePolling('/api/games', onSuccess, config.intervalMode(30)); + const isPollingGamesList = usePolling('/api/game', onSuccess, config.intervalMode(30)); if (games.isPollingGamesList !== isPollingGamesList) { dispatchGames({ type: 'next', isPollingGamesList }); } @@ -43,6 +43,12 @@ export default function useGamesApi(gamesReducer, config) { onPushing: (isPushingGameProposalAccept) => dispatchGames({ type: 'next', isPushingGameProposalAccept }), onSuccess: (acceptedGame) => dispatchGames({ type: 'next', gamesList: games.nextGameList(acceptedGame), proposal: gamesInitialState.proposal }) }), + + pushGameSurrender: ({ uuid }) => ifNot(games.isPushingActiveGameSurrender) && + doPushing(`/api/game/${uuid}/surrender`, 'PUT', null, { + onPushing: (isPushingActiveGameSurrender) => dispatchGames({ type: 'next', isPushingActiveGameSurrender }), + onSuccess: (finishedGame) => dispatchGames({ type: 'next', gamesList: games.nextGameList(finishedGame), proposal: gamesInitialState.active }) + }), } } diff --git a/webapp/src/container/Games.css b/webapp/src/container/Games.css index 674ec27..dba6872 100644 --- a/webapp/src/container/Games.css +++ b/webapp/src/container/Games.css @@ -102,7 +102,7 @@ margin: 2px; } -.ActionPanel .Create.ready { /* , */ /* OR .game-action.busy */ +.ActionPanel .Create.ready { background-color:#00b0ff30; } @@ -115,17 +115,23 @@ } .ActionPanel .Cancel.ready, -.ActionPanel .Reject.ready { +.ActionPanel .Reject.ready, +.ActionPanel .Surrender.ready +{ background-color:#ffaaaa60 } .ActionPanel .Cancel.ready:hover, -.ActionPanel .Reject.ready:hover { +.ActionPanel .Reject.ready:hover, +.ActionPanel .Surrender.ready:hover +{ background-color:#ff000060 } .ActionPanel .Cancel.ready:active, -.ActionPanel .Reject.ready:active { +.ActionPanel .Reject.ready:active, +.ActionPanel .Surrender.ready:active + { background-color:#ff0000a0 } diff --git a/webapp/src/container/Games.jsx b/webapp/src/container/Games.jsx index 635ebce..da3e41c 100644 --- a/webapp/src/container/Games.jsx +++ b/webapp/src/container/Games.jsx @@ -96,11 +96,15 @@ function ActionPanel({ players, gamesApi }) { /> } /> gamesApi.pushGameProposalAccept({ uuid })}/>, + gamesApi.pushGameProposalAccept({ uuid })} />, gamesApi.pushGameProposalReject({ uuid })} />, gamesApi.pushGameProposalCancel({ uuid })} /> ]} /> - , , ]} /> + , + , + gamesApi.pushGameSurrender({ uuid })}/> + ]} /> , ]} /> diff --git a/webapp/src/container/games/action/Surrender.jsx b/webapp/src/container/games/action/Surrender.jsx index 3a9303d..9cedf0d 100644 --- a/webapp/src/container/games/action/Surrender.jsx +++ b/webapp/src/container/games/action/Surrender.jsx @@ -1,6 +1,22 @@ -import React from 'react'; +import React, { useContext } from 'react'; +import Wobler from '../../../components/Wobler'; +import { GamesContext } from '../../../context/games'; -export default function Surrender() { +export default function Surrender({ onClick }) { + const games = useContext(GamesContext); - return -} + const selectedGame = games.findGame({ uuid: games.active.selectedUUID }); + const gameStatus = selectedGame?.status; + const isReady = (gameStatus === 'GAME_BOARD_WAIT_FOR_OPPONENT' || gameStatus === 'GAME_BOARD_WAIT_FOR_YOU') ? true : ''; + + if (gameStatus === 'DRAW_REQUEST_WAIT_FOR_YOU' || gameStatus === 'DRAW_REQUEST_WAIT_FOR_OPPONENT') + return; // You shall not surrender if there is an active tie negotiations + + return ( + + ) +} \ No newline at end of file diff --git a/webapp/src/container/games/view/GameSelector.jsx b/webapp/src/container/games/view/GameSelector.jsx index 7821f7d..43dc176 100644 --- a/webapp/src/container/games/view/GameSelector.jsx +++ b/webapp/src/container/games/view/GameSelector.jsx @@ -40,7 +40,7 @@ export function ActiveGameSelector({ onSelect }) { if (games.gamesList === null) return - const isSelected = (uuid) => uuid === games.proposal.selectedUUID; + const isSelected = (uuid) => uuid === games.active.selectedUUID; const onClick = (uuid) => { if (isSelected(uuid)) diff --git a/webapp/src/reducer/games.js b/webapp/src/reducer/games.js index c5b0353..528beae 100644 --- a/webapp/src/reducer/games.js +++ b/webapp/src/reducer/games.js @@ -30,6 +30,7 @@ export const gamesInitialState = { isPushingGameProposalCancel: false, isPushingGameProposalReject: false, isPushingGameProposalAccept: false, + isPushingActiveGameSurrender: false, findGame, nextGameList,