Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

show down #48

Merged
merged 2 commits into from
Oct 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/web/src/app/games/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ export default function PlayPage({ params }: { params: { id: string } }) {
const privateCards = reorderedPlayers.reduce(
(acc, player, seat) => {
if (player) {
acc[seat + 1] = getRandomCards();
acc[seat + 1] = player.cards ?? [];
}
return acc;
},
Expand Down
2 changes: 2 additions & 0 deletions apps/web/src/app/games/[id]/state/actions/gameActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
privateCardsDecryptionHandler,
receivedPrivateCardHandler,
receivedPublicCardsHandler,
receivedShowDown,
} from "./gameEventHandlers";

import { decryptCardShareZkey, shuffleEncryptDeckZkey } from "@jeton/zk-deck";
Expand Down Expand Up @@ -66,6 +67,7 @@ function setGameEventListeners(game: Jeton) {
game.addListener?.(GameEventTypes.AWAITING_BET, awaitingPlayerBetHandler);
game.addListener?.(GameEventTypes.PLAYER_PLACED_BET, playerPlacedBetHandler);
game.addListener?.(GameEventTypes.RECEIVED_PUBLIC_CARDS, receivedPublicCardsHandler);
game.addListener?.(GameEventTypes.SHOW_DOWN, receivedShowDown);
console.log("set game event listeners end");
}

Expand Down
36 changes: 35 additions & 1 deletion apps/web/src/app/games/[id]/state/actions/gameEventHandlers.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { stat } from "fs";
import {
type AwaitingBetEvent,
GameStatus,
Expand All @@ -9,6 +8,7 @@ import {
getGameStatus,
type playerShufflingEvent,
} from "@jeton/ts-sdk";
import type { ShowDownEvent } from "@jeton/ts-sdk";
import type { ReceivedPublicCardsEvent } from "@jeton/ts-sdk";
import { PublicCardRounds } from "@jeton/ts-sdk";
import { state$ } from "../state";
Expand All @@ -21,12 +21,26 @@ export function newPlayerCheckedInHandler(player: Player) {
}

export function handStartedHandler({ dealer }: HandStartedEvent) {
console.log("hand started handler", dealer);
const gameState$ = state$.gameState;
gameState$.dealer.set(dealer);
gameState$.status.set(GameStatus.Shuffle);
gameState$.players.forEach((player) => {
player.cards.set(undefined);
player.bet.set(0);
player.winAmount.set(undefined);
player.roundAction.set(undefined);
});
gameState$.myCards.set(undefined);
gameState$.flopCards.set(undefined);
gameState$.riverCard.set(undefined);
gameState$.turnCard.set(undefined);
gameState$.pot.set(0);
gameState$.betState.set(undefined);
}

export function playerShufflingHandler(player: playerShufflingEvent) {
console.log("player shuffling handler");
state$.gameState.shufflingPlayer.set(player);
state$.gameState.status.set(GameStatus.Shuffle);
}
Expand Down Expand Up @@ -83,8 +97,15 @@ export function playerPlacedBetHandler({
state$.gameState.betState.availableActions.set(availableActions);
}

export function clearPlayersRoundAction() {
state$.gameState.players.forEach((player) => {
player.roundAction.set(undefined);
});
}

export function receivedPublicCardsHandler({ cards, round }: ReceivedPublicCardsEvent) {
console.log("received public cards", round, cards);
clearPlayersRoundAction();
if (round === PublicCardRounds.FLOP) {
state$.gameState.flopCards.set(cards);
} else if (round === PublicCardRounds.RIVER) {
Expand All @@ -93,3 +114,16 @@ export function receivedPublicCardsHandler({ cards, round }: ReceivedPublicCards
state$.gameState.turnCard.set(cards);
}
}

export function receivedShowDown(data: ShowDownEvent) {
console.log("received showdown event", data);
state$.gameState.players.forEach((player) => {
const eventPlayer = data[player.id.get()!];
if (eventPlayer) {
player.cards.set(eventPlayer.cards);
player.balance.set(eventPlayer.player.balance);
player.bet.set(0);
player.winAmount.set(eventPlayer.winAmount);
}
});
}
4 changes: 3 additions & 1 deletion apps/web/src/app/games/[id]/state/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ import {
} from "@jeton/ts-sdk";
import { type Observable, observable } from "@legendapp/state";

type UIPlayer = Player & {
export type UIPlayer = Player & {
roundAction?: {
action: BettingActions;
amount: number;
};
cards?: number[];
winAmount?: number;
};

type GameState = {
Expand Down
10 changes: 5 additions & 5 deletions apps/web/src/utils/seat.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import type { Player } from "@jeton/ts-sdk";
import type { UIPlayer } from "@src/app/games/[id]/state/state";

export const orderPlayersSeats = (
players: (Player | null)[],
players: (UIPlayer | null)[],
mainPlayerId: string,
): (Player | null)[] => {
): (UIPlayer | null)[] => {
const mainPlayerIndex = players.findIndex((player) => player?.id === mainPlayerId);

const mainPlayer = players[mainPlayerIndex];
if (!mainPlayer) {
return players;
}

const playersAfterMainPlayer: (Player | null)[] = players.slice(mainPlayerIndex + 1);
const playersBeforeMainPlayer: (Player | null)[] = players.slice(0, mainPlayerIndex);
const playersAfterMainPlayer: (UIPlayer | null)[] = players.slice(mainPlayerIndex + 1);
const playersBeforeMainPlayer: (UIPlayer | null)[] = players.slice(0, mainPlayerIndex);
return [mainPlayer, ...playersAfterMainPlayer, ...playersBeforeMainPlayer];
};
3 changes: 3 additions & 0 deletions biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@
"useNodejsImportProtocol": "off",
"useEnumInitializers": "off",
"noNonNullAssertion": "off"
},
"complexity": {
"noForEach": "off"
}
}
},
Expand Down
75 changes: 60 additions & 15 deletions packages/ts-sdk/src/Jeton/Jeton.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
OnChainEventTypes,
type OnChainPlayerCheckedInData,
type OnChainPlayerPlacedBetData,
type OnChainShowDownData,
type OnChainShuffledDeckData,
type OnChainTableObject,
} from "@src/OnChainDataSource";
Expand All @@ -14,6 +15,7 @@ import onChainDataMapper, { covertTableInfo } from "@src/OnChainDataSource/onCha
import {
BettingActions,
BettingRounds,
type CardShareAndProof,
type ChipUnits,
type GameEventMap,
GameEventTypes,
Expand All @@ -23,6 +25,7 @@ import {
PlayerStatus,
type PublicCardRounds,
type ReceivedPublicCardsEvent,
type ShowDownEvent,
type TableInfo,
} from "@src/types";
import { type PendingMemo, createPendingMemo } from "@src/utils/PendingMemo";
Expand All @@ -37,7 +40,6 @@ import {
getDeck,
getNumberOfRaisesLeft,
getPlayerByAddress,
getPlayerByIndex,
getPot,
getPrivateCardsIndexes,
getShufflingPlayer,
Expand Down Expand Up @@ -68,7 +70,7 @@ export class Jeton extends EventEmitter<GameEventMap> {
private secretKey: bigint;
private publicKey: Uint8Array;
public gameState?: JGameState;
private myPrivateCardsShares?: [DecryptionCardShare, DecryptionCardShare];
private myPrivateCardsShares?: [CardShareAndProof, CardShareAndProof];
private pendingMemo: PendingMemo;

constructor(config: JetonConfigs) {
Expand Down Expand Up @@ -107,9 +109,31 @@ export class Jeton extends EventEmitter<GameEventMap> {
this.onChainDataSource.on(OnChainEventTypes.SHUFFLED_DECK, this.playerShuffledDeck);
this.onChainDataSource.on(OnChainEventTypes.CARDS_SHARES_RECEIVED, this.receivedCardsShares);
this.onChainDataSource.on(OnChainEventTypes.PLAYER_PLACED_BET, this.receivedPlayerBet);
this.onChainDataSource.on(OnChainEventTypes.SHOW_DOWN, this.receivedShowDown);
this.onChainDataSource.listenToTableEvents(this.tableInfo.id);
}

private receivedShowDown = async (data: OnChainShowDownData) => {
if (!this.gameState) throw new Error(" must exist");
const players = this.gameState.players;
const eventData: ShowDownEvent = {};
for (const [index, player] of players.entries()) {
eventData[player.id] = {
player,
winAmount: data.winningAmounts[index]!,
cards: [data.privateCards[index * 2]!, data.privateCards[index * 2 + 1]!],
};
}
this.emit(GameEventTypes.SHOW_DOWN, eventData);

const onChainTableObject = await this.pendingMemo.memoize(this.queryGameState);
this.gameState = onChainDataMapper.convertJetonState(onChainTableObject);
// TODO: timeout is not a good solution
setTimeout(() => {
this.gameStarted(onChainTableObject);
}, 3000);
};

private receivedPlayerBet = async (data: OnChainPlayerPlacedBetData) => {
console.log("received Player bet", data);
const onChainTableObject = await this.pendingMemo.memoize(this.queryGameState);
Expand Down Expand Up @@ -147,7 +171,7 @@ export class Jeton extends EventEmitter<GameEventMap> {
return;
}
if (nextPlayer === null) {
//TODO: showdown
this.createAndShareSelfPrivateKeyShares(onChainTableObject);
return;
}
// don't send awaiting bet to ui for small and big blind
Expand Down Expand Up @@ -199,20 +223,22 @@ export class Jeton extends EventEmitter<GameEventMap> {

// if the new player is active player then the game has just started
if (newPlayer.status !== PlayerStatus.sittingOut) {
const dealerIndex = newJState.dealerIndex;
this.emit(GameEventTypes.HAND_STARTED, { dealer: newJState.players[dealerIndex]! });
const shufflingPlayer = getShufflingPlayer(onChainTableObject);
if (shufflingPlayer)
this.emit(
GameEventTypes.PLAYER_SHUFFLING,
onChainDataMapper.convertPlayer(shufflingPlayer),
);
this.checkForActions(onChainTableObject);
this.gameStarted(onChainTableObject);
}

this.gameState = newJState;
};

private gameStarted(state: OnChainTableObject) {
const newJState = onChainDataMapper.convertJetonState(state);
const dealerIndex = newJState.dealerIndex;
this.emit(GameEventTypes.HAND_STARTED, { dealer: newJState.players[dealerIndex]! });
const shufflingPlayer = getShufflingPlayer(state);
if (shufflingPlayer)
this.emit(GameEventTypes.PLAYER_SHUFFLING, onChainDataMapper.convertPlayer(shufflingPlayer));
this.checkForActions(state);
}

private playerShuffledDeck = async (data: OnChainShuffledDeckData) => {
console.log("player shuffled deck");
const onChainTableObject = await this.pendingMemo.memoize(this.queryGameState);
Expand Down Expand Up @@ -260,7 +286,20 @@ export class Jeton extends EventEmitter<GameEventMap> {
this.myPrivateCardsShares = proofsAndShares
.filter((pas) => myPrivateCardsIndexes.includes(pas.cardIndex))
.sort((a, b) => a.cardIndex - b.cardIndex)
.map((pas) => pas.decryptionCardShare) as [DecryptionCardShare, DecryptionCardShare];
.map((pas) => pas) as [CardShareAndProof, CardShareAndProof];
this.onChainDataSource.cardsDecryptionShares(this.tableInfo.id, sharesToSend, proofsToSend);
}

private async createAndShareSelfPrivateKeyShares(state: OnChainTableObject) {
if (!this.myPrivateCardsShares) {
this.myPrivateCardsShares = (await this.createDecryptionShareProofsFor(
getPrivateCardsIndexes(state, this.playerId),
state,
)) as [CardShareAndProof, CardShareAndProof];
}

const proofsToSend = this.myPrivateCardsShares.map((pas) => pas.proof);
const sharesToSend = this.myPrivateCardsShares.map((pas) => pas.decryptionCardShare);
this.onChainDataSource.cardsDecryptionShares(this.tableInfo.id, sharesToSend, proofsToSend);
}

Expand Down Expand Up @@ -303,8 +342,14 @@ export class Jeton extends EventEmitter<GameEventMap> {

if (!this.myPrivateCardsShares) throw new Error("must be available");
const [firstCardIndex, secondCardIndex] = getPrivateCardsIndexes(state, this.playerId);
const firstCardShares = [getCardShares(state, firstCardIndex), this.myPrivateCardsShares[0]];
const secondCardShares = [getCardShares(state, secondCardIndex), this.myPrivateCardsShares[1]];
const firstCardShares = [
getCardShares(state, firstCardIndex),
this.myPrivateCardsShares[0].decryptionCardShare,
];
const secondCardShares = [
getCardShares(state, secondCardIndex),
this.myPrivateCardsShares[1].decryptionCardShare,
];
const firstPrivateCard = this.zkDeck.decryptCard(firstCardIndex, deck, firstCardShares);
const secondPrivateCard = this.zkDeck.decryptCard(secondCardIndex, deck, secondCardShares);

Expand Down
7 changes: 5 additions & 2 deletions packages/ts-sdk/src/Jeton/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,13 @@ export function isActivePlayer(tableObject: OnChainTableObject, playerId: string
return !!tableObject.roster.players.find((p) => p.addr === playerId);
}

export function getPrivateCardsIndexes(tableObject: OnChainTableObject, playerId: string) {
export function getPrivateCardsIndexes(
tableObject: OnChainTableObject,
playerId: string,
): [number, number] {
const playerPosition = tableObject.roster.players.findIndex((p) => p.addr === playerId);
if (playerPosition === -1) throw new Error("player does not exist");
return [playerPosition * 2, playerPosition * 2 + 1] as const;
return [playerPosition * 2, playerPosition * 2 + 1];
}

export function getDeck(tableObject: OnChainTableObject) {
Expand Down
16 changes: 14 additions & 2 deletions packages/ts-sdk/src/OnChainDataSource/AptosOnChainDataSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,14 @@ import {
contractCheckedInEventType,
contractFoldEventType,
contractRaiseEventType,
contractShowDownEventType,
contractShuffleEventType,
contractSmallBlindEventType,
} from "@src/contracts/contractData";
import { createTableInfo } from "@src/contracts/contractDataMapper";
import {
type ChainEvents,
type ShowDownEvent,
callCallContract,
callCheckInContract,
callDecryptCardShares,
Expand Down Expand Up @@ -49,7 +52,7 @@ export class AptosOnChainDataSource
this.address = address;
}

private publishEvent(event: { data: { sender_addr: string }; indexed_type: string }) {
private publishEvent(event: ChainEvents) {
console.log("publish event", event);
switch (event.indexed_type) {
case contractCheckedInEventType:
Expand Down Expand Up @@ -99,6 +102,15 @@ export class AptosOnChainDataSource
address: event.data.sender_addr,
});
break;
case contractShowDownEventType: {
console.log("publishing", OnChainEventTypes.SHOW_DOWN);
const data = (event as ShowDownEvent).data;
this.emit(OnChainEventTypes.SHOW_DOWN, {
publicCards: data.public_cards.map((p) => Number(p)),
privateCards: data.private_cards.map((p) => Number(p)),
winningAmounts: data.winning_amounts.map((a) => Number(a)),
});
}
}
}

Expand All @@ -117,7 +129,7 @@ export class AptosOnChainDataSource
// TODO: parse events and emit
const pollingTable = this.pollingTables[tableId];
if (pollingTable) {
pollingTable.lastEventBlockHeight = events[0].transaction_block_height;
pollingTable.lastEventBlockHeight = events[0]!.transaction_block_height;
pollingTable.timerId = setTimeout(this.pollTableEvents.bind(this, tableId), POLLING_INTERVAL);
}
};
Expand Down
10 changes: 9 additions & 1 deletion packages/ts-sdk/src/OnChainDataSource/onChainEvents.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ import type { BettingActions, BettingRounds, PublicCardRounds } from "../types";
export enum OnChainEventTypes {
PLAYER_CHECKED_IN = "player-checked-in",
SHUFFLED_DECK = "shuffled-deck",
CARDS_SHARES_RECEIVED = "private-cards-shares",
CARDS_SHARES_RECEIVED = "cards-shares",
PLAYER_PLACED_BET = "player-placed-bet",
SHOW_DOWN = "show-down",
}

export type OnChainPlayerCheckedInData = {
Expand All @@ -24,9 +25,16 @@ export type OnChainPlayerPlacedBetData = {
address?: string;
};

export type OnChainShowDownData = {
privateCards: number[];
publicCards: number[];
winningAmounts: number[];
};

export type OnChainEventMap = {
[OnChainEventTypes.PLAYER_CHECKED_IN]: [OnChainPlayerCheckedInData];
[OnChainEventTypes.SHUFFLED_DECK]: [OnChainShuffledDeckData];
[OnChainEventTypes.CARDS_SHARES_RECEIVED]: [OnChainCardsSharesData];
[OnChainEventTypes.PLAYER_PLACED_BET]: [OnChainPlayerPlacedBetData];
[OnChainEventTypes.SHOW_DOWN]: [OnChainShowDownData];
};
Loading