Skip to content

Commit

Permalink
Co-authored-by: 賴鴻德 <[email protected]>
Browse files Browse the repository at this point in the history
Co-authored-by: Pikacnu  <[email protected]>
Co-authored-by: Tuhacrt <[email protected]>
  • Loading branch information
Dlutermade committed Nov 26, 2023
1 parent f21033b commit 2723a3f
Show file tree
Hide file tree
Showing 11 changed files with 221 additions and 3 deletions.
2 changes: 1 addition & 1 deletion packages/domain_layer/src/card/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export enum Status {
}

export type Card = {
id?: number;
id?: string;
status: Status;
creature: Creature;
};
17 changes: 17 additions & 0 deletions packages/domain_layer/src/game/deck/createDeck.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { always } from 'utils/fp';
import A from 'fp-ts/Array';
import O from 'fp-ts/Option';
import { pipe } from 'fp-ts/function';
import { Game } from 'game';

type NoIdCards = Omit<Card, 'id'>[];

Expand Down Expand Up @@ -39,3 +40,19 @@ export const createDeck: CreateDeck = () =>
concatCardOfDeck(Creature.StickBug),
concatCardOfDeck(Creature.Toad),
);

export const createIdToCardOfDeck = (game: Game): Game =>
pipe(
//
game,
(game) => ({
...game,
deck: pipe(
game.deck,
A.mapWithIndex<Card, Card>((idx, card) => ({
...card,
id: idx.toString(),
})),
),
}),
);
1 change: 1 addition & 0 deletions packages/domain_layer/src/game/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './createGame';
export * from './type';
export * as deck from './deck';
export * from './dealCards';
7 changes: 7 additions & 0 deletions packages/domain_layer/src/lib/lookupWithNegativeNumber.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import A from 'fp-ts/Array';
import { Option } from 'fp-ts/Option';

export const lookupWithNegativeIndex =
(index: number) =>
<T>(as: Array<T>): Option<T> =>
index >= 0 ? A.lookup(index, as) : A.lookup(as.length + index, as);
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ describe('checkPlayerHasUnrevealedCardOfHands', () => {
createIdToPlayer,
O.map((player) => {
player.hands = [
{ id: 1, status: Status.Unrevealed, creature: Creature.Bat },
{ id: '1', status: Status.Unrevealed, creature: Creature.Bat },
];
return player;
}),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import O from 'fp-ts/Option';
import { Game } from 'game';
import { Round } from 'round';
import { Card, Creature, Status } from 'card';

import { Player } from './type';
import { currentPlayerSelectUnrevealedCard } from './currentPlayerSelectUnrevealedCard';

describe('CurrentPlayerSelectUnrevealedCard', () => {
it(`given a game
contain player A B
and
player A hand [
{ id: 9999, status: 'UNREVEALED', creature: 'BAT' }
]
when player A selected { id: 9999, status: 'UNREVEALED', creature: 'BAT' }
then return round
`, () => {
// Arrange
const playerA: Player = {
id: '1',
uid: '1',
name: 'A',
hands: [],
pastReceivedCards: [],
};
const playerB: Player = {
id: '2',
uid: '2',
name: 'B',
hands: [],
pastReceivedCards: [],
};

const game: Game = {
id: '1',
players: [playerA, playerB],
rounds: [],
deck: [],
};

const card: Card = {
id: '9999',
status: Status.Unrevealed,
creature: Creature.Bat,
};

playerA.hands = [card];
game.deck = [card];

const round: Round = {
id: '1',
currentPlayer: playerA,
selectedCard: undefined,
state: undefined,
};

game.rounds = [round];

// Act
const result = currentPlayerSelectUnrevealedCard(game)(playerA)(card);

// Assert
expect(result).toMatchObject(O.some({ ...round, selectedCard: card }));
});
});
113 changes: 113 additions & 0 deletions packages/domain_layer/src/player/currentPlayerSelectUnrevealedCard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { flow, pipe } from 'fp-ts/function';
import A from 'fp-ts/Array';
import O from 'fp-ts/Option';
import L from 'monocle-ts/Lens';
import { Card } from 'card';
import { Game } from 'game';
import { allPass, anyPass, prop } from 'remeda';
import { Round, SelectedPlayerState } from 'round';
import { P, match } from 'ts-pattern';
import { always } from 'utils/fp';
import { lookupWithNegativeIndex } from 'lib/lookupWithNegativeNumber';

import { Player } from './type';

interface CurrentPlayerSelectUnrevealedCard {
(game: Game): (player: Player) => (selectedCard: Card) => O.Option<Round>;
}

const checkPlayerIsCurrentPlayer = (player: Player) => (game: Game) =>
pipe(
game,
prop('rounds'),
A.last,
O.map(prop('currentPlayer')),
O.map(prop('id')),
O.map((id) => id === player.id),
O.getOrElse(always(false)),
);

const isFirstRound = (game: Game) =>
pipe(
//
game,
prop('rounds'),
A.size,
(size) => size === 1,
);

const checkPlayerHasTheCard = (player: Player) => (selectedCard: Card) =>
pipe(
//
player,
prop('hands'),
A.some((card) => card.id === selectedCard.id),
);

const checkSelectCardIsAllowed = (selectedCard: Card) => (game: Game) =>
pipe(
//
game,
prop('rounds'),
lookupWithNegativeIndex(-2),
O.map((round) =>
match(round)
.with(
P.union(
{
state: SelectedPlayerState.GUESS_CARD_WIN,
},
{
state: SelectedPlayerState.GUESS_CARD_LOSS,
},
{
state: SelectedPlayerState.EVADE_NOT_LOOK_CARD,
selectedCard: {
id: selectedCard.id,
},
},
{
state: SelectedPlayerState.EVADE_LOOK_CARD,
selectedCard: {
id: selectedCard.id,
},
},
),
always(true),
)
.otherwise(always(false)),
),
O.getOrElse(always(false)),
);

const setCardToRound = (selectedCard: Card) =>
pipe(
//
L.id<Round>(),
L.prop('selectedCard'),
L.modify(always(selectedCard)),
);

export const currentPlayerSelectUnrevealedCard: CurrentPlayerSelectUnrevealedCard =
(game) => (player) => (selectedCard) =>
pipe(
//
game,
O.fromPredicate(
flow(
allPass([
checkPlayerIsCurrentPlayer(player),
always(checkPlayerHasTheCard(player)(selectedCard)),
anyPass([
// 第一回合可以選任何牌
isFirstRound,
// 第二回合之後只能視規則選牌
checkSelectCardIsAllowed(selectedCard),
]),
]),
),
),
O.map(prop('rounds')),
O.chain(A.last),
O.map(setCardToRound(selectedCard)),
);
2 changes: 2 additions & 0 deletions packages/domain_layer/src/player/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export * from './type';
export * from './createPlayer';
export * from './toPlayers';
export * from './checkPlayerHasUnrevealedCardOfHands';
export * from './currentPlayerSelectUnrevealedCard';
11 changes: 10 additions & 1 deletion packages/domain_layer/src/round/createRound.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import Random from 'fp-ts/Random';
import O from 'fp-ts/Option';
import { always } from 'utils/fp';

import { OmitIdRound, SelectedPlayerState } from './type';
import { OmitIdRound, Round, SelectedPlayerState } from './type';

interface CreateRound {
(game: Game): OmitIdRound;
Expand Down Expand Up @@ -39,3 +39,12 @@ export const createRound: CreateRound = (game) =>
}),
),
);

export const createIdToGame = (round: O.Option<OmitIdRound>): O.Option<Round> =>
pipe(
round,
O.map((round) => ({
...round,
id: Random.randomInt(1, 1000)().toString(),
})),
);
1 change: 1 addition & 0 deletions packages/domain_layer/src/round/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './type';
export * from './createRound';
2 changes: 2 additions & 0 deletions packages/domain_layer/src/round/type.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Card } from 'card';
import { Player } from 'player';

export enum SelectedPlayerState {
Expand All @@ -14,6 +15,7 @@ export type Round = {
id: string;
currentPlayer: Player;
selectedPlayer?: Player;
selectedCard?: Card;
state: SelectedPlayerState;
};

Expand Down

0 comments on commit 2723a3f

Please sign in to comment.