Skip to content

Commit

Permalink
Merge pull request #69 from SkyLightQP/main
Browse files Browse the repository at this point in the history
feat: close action modal automatically and add background music
  • Loading branch information
SkyLightQP authored Mar 24, 2024
2 parents fbb66af + 31e84c2 commit 6c539d1
Show file tree
Hide file tree
Showing 20 changed files with 756 additions and 839 deletions.
4 changes: 2 additions & 2 deletions apps/backend/src/app/game/game.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { StartGameHandler } from '@/app/game/handlers/start-game.handler';
import { ArrivedCityListener } from '@/app/game/listeners/arrived-city.listener';
import { EndGameWhenDropoutListener } from '@/app/game/listeners/end-game-when-dropout.listener';
import { EndedGameListener } from '@/app/game/listeners/ended-game.listener';
import { CalculatePenaltyService } from '@/app/game/services/calculate-penalty.service';
import { ProcessCityPenaltyService } from '@/app/game/services/process-city-penalty.service';
import { SocketModule } from '@/app/socket/socket.module';
import { DatabaseModule } from '@/infrastructure/database/database.module';
import { RedisModule } from '@/infrastructure/redis/redis.module';
Expand All @@ -18,7 +18,7 @@ const commands = [StartGameHandler, DropoutGameHandler, RollDiceHandler, BuyCity
const queries = [GetGameHandler];
const gateways = [GameGateway];
const listeners = [ArrivedCityListener, EndGameWhenDropoutListener, EndedGameListener];
const services = [CalculatePenaltyService];
const services = [ProcessCityPenaltyService];

@Module({
imports: [DatabaseModule, RedisModule, SocketModule],
Expand Down
53 changes: 3 additions & 50 deletions apps/backend/src/app/game/listeners/arrived-city.listener.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,9 @@ import { RedisClientType } from 'redis';
import { GetCityByPositionReturn } from '@/app/city/handlers/get-city-by-position.handler';
import { GetCityByPositionQuery } from '@/app/city/queries/get-city-by-position.query';
import { SPECIAL_CARD_POSITIONS } from '@/app/game/constants/game-board.constant';
import { EndedGameEvent } from '@/app/game/events/ended-game.event';
import { EndedTurnEvent } from '@/app/game/events/ended-turn.event';
import { RolledDiceEvent } from '@/app/game/events/rolled-dice.event';
import { CalculatePenaltyService } from '@/app/game/services/calculate-penalty.service';
import { GetRoomReturn } from '@/app/room/handlers/get-room.handler';
import { GetRoomQuery } from '@/app/room/queries/get-room.query';
import { ProcessCityPenaltyService } from '@/app/game/services/process-city-penalty.service';
import { SocketGateway } from '@/app/socket/socket.gateway';

@EventsHandler(RolledDiceEvent)
Expand All @@ -19,7 +16,7 @@ export class ArrivedCityListener implements IEventHandler<RolledDiceEvent> {
private readonly eventBus: EventBus,
private readonly socketGateway: SocketGateway,
@Inject('REDIS_CLIENT') private readonly redis: RedisClientType,
private readonly calculatePenaltyService: CalculatePenaltyService
private readonly processCityPenaltyService: ProcessCityPenaltyService
) {}

async handle({ args: { game, position, executePlayer } }: RolledDiceEvent) {
Expand Down Expand Up @@ -50,51 +47,7 @@ export class ArrivedCityListener implements IEventHandler<RolledDiceEvent> {

const isCityOwnerOtherPlayer = cityOwnerId !== undefined && cityOwnerId !== executePlayer.userId;
if (isCityOwnerOtherPlayer) {
const ownerHaveCities = game.getPlayerStatus(cityOwnerId).haveCities;
const penalty = this.calculatePenaltyService.calculate(ownerHaveCities[city.id], {
land: city.cityPrices[0].landPrice,
house: city.cityPrices[0].housePrice,
building: city.cityPrices[0].buildingPrice,
hotel: city.cityPrices[0].hotelPrice
});

this.socketGateway.server.to(socketId).emit('penalty', {
city,
ownerNickname: game.getPlayerStatus(cityOwnerId).nickname,
penalty
});

if (playerStatus.money < penalty) {
const currentPlayer = game.playerOrder[game.currentOrderPlayerIndex];
currentPlayer.isDisable = true;

game.removeCitiesWhoHavePlayer(executePlayer.userId);

playerStatus.money = -1;
playerStatus.land = 0;
playerStatus.house = 0;
playerStatus.building = 0;
playerStatus.hotel = 0;

await game.syncRedis(this.redis);

Logger.log({ message: '플레이어를 파산 처리합니다.', target: executePlayer.userId, penalty });

if (game.getPlayersNotDisable().length <= 1) {
const room = await this.queryBus.execute<GetRoomQuery, GetRoomReturn>(
new GetRoomQuery({ roomId: game.roomId })
);
this.eventBus.publish(new EndedGameEvent({ game, room }));
}

return;
}

game.takeMoney(executePlayer.userId, penalty);
game.giveMoney(cityOwnerId, penalty);
await game.syncRedis(this.redis);

Logger.log({ message: '벌금 부과를 했습니다.', executor: executePlayer.userId });
await this.processCityPenaltyService.process(game, city, executePlayer.userId, cityOwnerId);
return;
}

Expand Down
37 changes: 0 additions & 37 deletions apps/backend/src/app/game/services/calculate-penalty.service.ts

This file was deleted.

107 changes: 107 additions & 0 deletions apps/backend/src/app/game/services/process-city-penalty.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { City, CityPrice } from '@marble/database';
import { Inject, Injectable, Logger } from '@nestjs/common';
import { EventBus, QueryBus } from '@nestjs/cqrs';
import { RedisClientType } from 'redis';
import { Game } from '@/app/game/domain/game';
import { EndedGameEvent } from '@/app/game/events/ended-game.event';
import { GetRoomReturn } from '@/app/room/handlers/get-room.handler';
import { GetRoomQuery } from '@/app/room/queries/get-room.query';
import { SocketGateway } from '@/app/socket/socket.gateway';
import { CityType } from '@/infrastructure/common/types/city-type.type';

const LAND_PENALTY_RATIO = 0.7;
const HOUSE_PENALTY_RATIO = 1.5;
const BUILDING_PENALTY_RATIO = 2;
const HOTEL_PENALTY_RATIO = 2.7;

@Injectable()
export class ProcessCityPenaltyService {
constructor(
private readonly socketGateway: SocketGateway,
private readonly queryBus: QueryBus,
private readonly eventBus: EventBus,
@Inject('REDIS_CLIENT') private readonly redis: RedisClientType
) {}

async process(game: Game, city: City & { cityPrices: Array<CityPrice> }, userId: string, ownerId: string) {
const playerStatus = game.getPlayerStatus(userId);
const socketId = game.playerOrder[game.currentOrderPlayerIndex].socketClientId;
const ownerHaveCities = game.getPlayerStatus(ownerId).haveCities;
const penalty = this.calculatePenalty(ownerHaveCities[city.id], {
land: city.cityPrices[0].landPrice,
house: city.cityPrices[0].housePrice,
building: city.cityPrices[0].buildingPrice,
hotel: city.cityPrices[0].hotelPrice
});

this.socketGateway.server.to(socketId).emit('penalty', {
city,
ownerNickname: game.getPlayerStatus(ownerId).nickname,
penalty
});

if (playerStatus.money < penalty) {
const currentPlayer = game.playerOrder[game.currentOrderPlayerIndex];
currentPlayer.isDisable = true;

game.removeCitiesWhoHavePlayer(userId);

playerStatus.money = -1;
playerStatus.land = 0;
playerStatus.house = 0;
playerStatus.building = 0;
playerStatus.hotel = 0;

await game.syncRedis(this.redis);

Logger.log({ message: '플레이어를 파산 처리합니다.', target: userId, penalty });

if (game.getPlayersNotDisable().length <= 1) {
const room = await this.queryBus.execute<GetRoomQuery, GetRoomReturn>(
new GetRoomQuery({ roomId: game.roomId })
);
this.eventBus.publish(new EndedGameEvent({ game, room }));
}

return;
}

game.takeMoney(userId, penalty);
game.giveMoney(ownerId, penalty);
await game.syncRedis(this.redis);

Logger.log({ message: '벌금 부과를 했습니다.', executor: userId });
}

private calculatePenalty(
haveCityTypes: CityType[],
price: {
land: number;
house: number;
building: number;
hotel: number;
}
): number {
let penalty = 0;

haveCityTypes.forEach((cityType) => {
switch (cityType) {
case 'land':
penalty += Math.floor(price.land * LAND_PENALTY_RATIO);
break;
case 'house':
penalty += Math.floor(price.house * HOUSE_PENALTY_RATIO);
break;
case 'building':
penalty += Math.floor(price.building * BUILDING_PENALTY_RATIO);
break;
case 'hotel':
penalty += Math.floor(price.hotel * HOTEL_PENALTY_RATIO);
break;
default:
}
});

return penalty;
}
}
2 changes: 2 additions & 0 deletions apps/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"clsx": "^2.0.0",
"framer-motion": "^11.0.3",
"history": "^5.3.0",
"howler": "^2.2.4",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hook-form": "^7.49.2",
Expand All @@ -29,6 +30,7 @@
},
"devDependencies": {
"@marble/tsconfig": "*",
"@types/howler": "^2.2.11",
"@types/node": "^16.18.59",
"@types/react": "^18.2.33",
"@types/react-dom": "^18.2.14",
Expand Down
Binary file added apps/frontend/public/assets/bgm/ingame.mp3
Binary file not shown.
Binary file added apps/frontend/public/assets/bgm/lobby.mp3
Binary file not shown.
13 changes: 11 additions & 2 deletions apps/frontend/src/components/Game/CityBuyModal/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { FC } from 'react';
import { Button } from '@/components/Button';
import { Modal } from '@/components/Modal';
import { useAutoEndAction } from '@/services/useAutoEndAction';
import { cn } from '@/utils/cn';

interface CityBuyModalProps {
Expand Down Expand Up @@ -36,9 +37,17 @@ export const CityBuyModal: FC<CityBuyModalProps> = ({
onClose
}) => {
const disableStyle = 'cursor-no-drop bg-gray-500 hover:bg-gray-500';
const { time, onOverrideClose } = useAutoEndAction(30, isOpen, close, onClose);

return (
<Modal isOpen={isOpen} close={close} title={`${cityName} 구입하기`} width="700px" height="330px" onClose={onClose}>
<Modal
isOpen={isOpen}
close={close}
title={`${cityName} 구입하기`}
width="700px"
height="330px"
onClose={onOverrideClose}
>
<h2 className="text-lg">땅만 구입하거나 건물(별장, 빌딩, 호텔)을 함께 구입할 수 있습니다.</h2>
<div className="mt-6 space-y-2">
<p className="text-lg">보유 중인 현금: {money.toLocaleString('ko-KR')}</p>
Expand Down Expand Up @@ -69,7 +78,7 @@ export const CityBuyModal: FC<CityBuyModalProps> = ({
</div>
<div>
<p className="text-gray-500">* 건물을 구입하려면 먼저 땅을 구입해야 합니다.</p>
<p className="text-gray-500">* 창을 닫으면 내 턴을 끝냅니다.</p>
<p className="text-gray-500">* 창을 닫아 끝내거나 {time}초 뒤 자동으로 턴을 끝냅니다.</p>
</div>
</div>
</Modal>
Expand Down
7 changes: 5 additions & 2 deletions apps/frontend/src/components/Game/PenaltyModal/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { FC } from 'react';
import { Modal } from '@/components/Modal';
import { useAutoEndAction } from '@/services/useAutoEndAction';

interface PenaltyModalProps {
readonly isOpen: boolean;
Expand All @@ -11,14 +12,16 @@ interface PenaltyModalProps {
}

export const PenaltyModal: FC<PenaltyModalProps> = ({ isOpen, close, ownerNickname, penalty, money, onClose }) => {
const { time, onOverrideClose } = useAutoEndAction(15, isOpen, close, onClose);

return (
<Modal
isOpen={isOpen}
close={close}
title={`${ownerNickname}의 땅입니다!`}
width="700px"
height="250px"
onClose={onClose}
onClose={onOverrideClose}
>
<h2 className="text-lg">벌금을 내야합니다. 보유 중인 금액이 부족하면 파산합니다.</h2>
<div className="mt-6 space-y-2">
Expand All @@ -32,7 +35,7 @@ export const PenaltyModal: FC<PenaltyModalProps> = ({ isOpen, close, ownerNickna
</p>
</div>
<div>
<p className="text-gray-500">* 창을 닫으면 내 턴을 끝냅니다.</p>
<p className="text-gray-500">* 창을 닫아 끝내거나 {time}초 뒤 자동으로 턴을 끝냅니다.</p>
</div>
</div>
</Modal>
Expand Down
Loading

0 comments on commit 6c539d1

Please sign in to comment.