Game: surrender

This commit is contained in:
djmil 2023-11-16 09:17:07 +01:00
parent cacc8c99d8
commit 576556afe7
7 changed files with 64 additions and 15 deletions

View File

@ -1,11 +1,14 @@
package djmil.cordacheckers.api; package djmil.cordacheckers.api;
import java.util.List; import java.util.List;
import java.util.UUID;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.GetMapping; 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.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
@ -16,8 +19,8 @@ import djmil.cordacheckers.user.User;
@RestController @RestController
@RequestMapping("api/games") @RequestMapping("api/game")
public class GameStateController { public class GameController {
@Autowired @Autowired
CordaClient cordaClient; CordaClient cordaClient;
@ -26,7 +29,7 @@ public class GameStateController {
HoldingIdentityResolver holdingIdentityResolver; HoldingIdentityResolver holdingIdentityResolver;
@GetMapping @GetMapping
public ResponseEntity<List<GameView>> gameStateList( public ResponseEntity<List<GameView>> list(
@AuthenticationPrincipal User player @AuthenticationPrincipal User player
) { ) {
final List<GameView> gsList = cordaClient.gameStateList(player.getHoldingIdentity()); final List<GameView> gsList = cordaClient.gameStateList(player.getHoldingIdentity());
@ -34,4 +37,17 @@ public class GameStateController {
return ResponseEntity.ok(gsList); return ResponseEntity.ok(gsList);
} }
@PutMapping("/{uuid}/surrender")
public ResponseEntity<GameView> surrender(
@AuthenticationPrincipal User issuer,
@PathVariable UUID uuid
) {
final GameView finishedGame = cordaClient.gameBoardSurrender(
issuer.getHoldingIdentity(),
uuid
);
return ResponseEntity
.ok(finishedGame);
}
} }

View File

@ -9,7 +9,7 @@ export default function useGamesApi(gamesReducer, config) {
dispatchGames({ type: 'next', gamesList }); 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) { if (games.isPollingGamesList !== isPollingGamesList) {
dispatchGames({ type: 'next', isPollingGamesList }); dispatchGames({ type: 'next', isPollingGamesList });
} }
@ -43,6 +43,12 @@ export default function useGamesApi(gamesReducer, config) {
onPushing: (isPushingGameProposalAccept) => dispatchGames({ type: 'next', isPushingGameProposalAccept }), onPushing: (isPushingGameProposalAccept) => dispatchGames({ type: 'next', isPushingGameProposalAccept }),
onSuccess: (acceptedGame) => dispatchGames({ type: 'next', gamesList: games.nextGameList(acceptedGame), proposal: gamesInitialState.proposal }) 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 })
}),
} }
} }

View File

@ -102,7 +102,7 @@
margin: 2px; margin: 2px;
} }
.ActionPanel .Create.ready { /* , */ /* OR .game-action.busy */ .ActionPanel .Create.ready {
background-color:#00b0ff30; background-color:#00b0ff30;
} }
@ -115,17 +115,23 @@
} }
.ActionPanel .Cancel.ready, .ActionPanel .Cancel.ready,
.ActionPanel .Reject.ready { .ActionPanel .Reject.ready,
.ActionPanel .Surrender.ready
{
background-color:#ffaaaa60 background-color:#ffaaaa60
} }
.ActionPanel .Cancel.ready:hover, .ActionPanel .Cancel.ready:hover,
.ActionPanel .Reject.ready:hover { .ActionPanel .Reject.ready:hover,
.ActionPanel .Surrender.ready:hover
{
background-color:#ff000060 background-color:#ff000060
} }
.ActionPanel .Cancel.ready:active, .ActionPanel .Cancel.ready:active,
.ActionPanel .Reject.ready:active { .ActionPanel .Reject.ready:active,
.ActionPanel .Surrender.ready:active
{
background-color:#ff0000a0 background-color:#ff0000a0
} }

View File

@ -96,11 +96,15 @@ function ActionPanel({ players, gamesApi }) {
/> />
} /> } />
<Route path='proposal' element={[ <Route path='proposal' element={[
<Accept key={1} onClick={(uuid) => gamesApi.pushGameProposalAccept({ uuid })}/>, <Accept key={1} onClick={(uuid) => gamesApi.pushGameProposalAccept({ uuid })} />,
<Reject key={2} onClick={(uuid) => gamesApi.pushGameProposalReject({ uuid })} />, <Reject key={2} onClick={(uuid) => gamesApi.pushGameProposalReject({ uuid })} />,
<Cancel key={3} onClick={(uuid) => gamesApi.pushGameProposalCancel({ uuid })} /> <Cancel key={3} onClick={(uuid) => gamesApi.pushGameProposalCancel({ uuid })} />
]} /> ]} />
<Route path='active' element={[<DrawReq key={1} />, <DrawAcq key={2} />, <Surrender key={3} />]} /> <Route path='active' element={[
<DrawReq key={1} />,
<DrawAcq key={2} />,
<Surrender key={3} onClick={(uuid) => gamesApi.pushGameSurrender({ uuid })}/>
]} />
<Route path='archive' element={[<Backward key={1} />, <Forward key={2} />]} /> <Route path='archive' element={[<Backward key={1} />, <Forward key={2} />]} />
</Routes> </Routes>
</div> </div>

View File

@ -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 <button className='Surrender'>Surrender</button> 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 (
<button className={'Surrender' + (isReady && ' ready')}
onClick={() => isReady ? onClick(selectedGame.uuid) : alert('You have to select some game')}
>
<Wobler text="Surrender" dance={games.isPushingActiveGameSurrender} />
</button>
)
} }

View File

@ -40,7 +40,7 @@ export function ActiveGameSelector({ onSelect }) {
if (games.gamesList === null) if (games.gamesList === null)
return <Loading /> return <Loading />
const isSelected = (uuid) => uuid === games.proposal.selectedUUID; const isSelected = (uuid) => uuid === games.active.selectedUUID;
const onClick = (uuid) => { const onClick = (uuid) => {
if (isSelected(uuid)) if (isSelected(uuid))

View File

@ -30,6 +30,7 @@ export const gamesInitialState = {
isPushingGameProposalCancel: false, isPushingGameProposalCancel: false,
isPushingGameProposalReject: false, isPushingGameProposalReject: false,
isPushingGameProposalAccept: false, isPushingGameProposalAccept: false,
isPushingActiveGameSurrender: false,
findGame, findGame,
nextGameList, nextGameList,