Skip to content

Commit

Permalink
clean up, add hot potato
Browse files Browse the repository at this point in the history
  • Loading branch information
tonyd33 committed Jan 5, 2025
1 parent 637f126 commit 74918aa
Show file tree
Hide file tree
Showing 14 changed files with 589 additions and 243 deletions.
16 changes: 13 additions & 3 deletions client/components/game/game.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -131,17 +131,27 @@ const Game = function (props: GameProps) {
case "nomnom":
startEatAnimation();
break;
case "hurt":
handlePop();
break;
case "inverted":
toast({ description: `Playing effect ${currGameDelta.delta.type}` });
setTimeout(handlePop, 1000);
break;
case "statusChanges":
setTimeout(handlePop, 500);
setTimeout(handlePop, 10);
break;
case "itemChanges":
setTimeout(handlePop, 50);
setTimeout(handlePop, 10);
break;
case "reload":
startReloadAnimation({ live: delta.lives, blank: delta.blanks });
break;
case "gg":
toast({ description: `Playing effect ${currGameDelta.delta.type}` });
toast({
title: "Game finished",
description: `${delta.winner} has won!`,
});
setTimeout(handlePop, 1000);
break;
default:
Expand Down
4 changes: 4 additions & 0 deletions client/components/game/item-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ const itemTooltip: Record<Item, string> = {
[Item.handcuff]: "Skip the opponent's next turn.",
[Item.handsaw]: "Deal double damage next time you shoot.",
[Item.nothing]: "Nothing here, wait for the next round!",
[Item.inverter]: "Swaps blank and live rounds in the chamber.",
[Item.hotPotato]: "Give your opponent a hot potato.",
};

const ItemButton = React.memo(function (props: ItemButtonProps) {
Expand All @@ -37,9 +39,11 @@ const ItemButton = React.memo(function (props: ItemButtonProps) {
case Item.magnifyingGlass:
case Item.pop:
case Item.handsaw:
case Item.inverter:
onUseItem({ type: ActionType.useItem, which: slot, item });
break;
case Item.handcuff:
case Item.hotPotato:
onUseItem({
type: ActionType.useItem,
which: slot,
Expand Down
10 changes: 10 additions & 0 deletions client/components/game/item-icon.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import React, { useMemo } from "react";
import { Item } from "@shared/game/types";
import {
GiBodySwapping,
GiCardboardBox,
GiHandcuffs,
GiHandSaw,
GiMagnifyingGlass,
GiPotato,
GiShinyApple,
GiSodaCan,
} from "react-icons/gi";
Expand All @@ -31,6 +33,10 @@ const ItemIcon = React.memo(function (props: ItemIconProps) {
return GiHandcuffs;
case Item.handsaw:
return GiHandSaw;
case Item.hotPotato:
return GiPotato;
case Item.inverter:
return GiBodySwapping;
case Item.nothing:
return GiCardboardBox;
default:
Expand All @@ -51,6 +57,10 @@ const ItemIcon = React.memo(function (props: ItemIconProps) {
return "text-gray-700";
case Item.nothing:
return "text-slate-500";
case Item.hotPotato:
return "text-orange-500";
case Item.inverter:
return "text-gray-700";
default:
ensureUnreachable(item);
}
Expand Down
10 changes: 8 additions & 2 deletions client/components/game/lobby-screen.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { Item, WaitingLobby } from "@shared/game/types";
import {
HandsawDamageStackBehavior,
Item,
WaitingLobby,
} from "@shared/game/types";
import LobbyScreen from "./lobby-screen";
import { Meta, StoryObj } from "@storybook/react";

Expand All @@ -22,7 +26,9 @@ const waiting: WaitingLobby = {
spectators: ["burly-yellow-groundhog", "rambunctious-cyan-tortoise"],
creator: "burly-yellow-groundhog",
settings: {
stackHandsaws: false,
handsawStackLimit: 1,
handsawDamageStackBehavior: HandsawDamageStackBehavior.add,
handcuffCooldownTurns: 1,
itemDistribution: Object.values(Item).filter((x) => x !== Item.nothing),
players: 2,
},
Expand Down
12 changes: 7 additions & 5 deletions client/components/game/settings-dialog-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -205,8 +205,8 @@ const SettingsDialogButton = React.memo(function (
defaultValues: settings,
});

// Update settings when changed
useEffect(() => {
console.log("settings", settings);
for (const [k, v] of Object.entries(settings)) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
form.setValue(k as any, v);
Expand Down Expand Up @@ -234,12 +234,14 @@ const SettingsDialogButton = React.memo(function (
className="space-y-6"
>
<div className="flex flex-col space-y-2">
<SettingsSwitchFormField
<SettingsSliderFormField
control={form.control}
disabled={!canSave}
name="stackHandsaws"
tooltip="Allow players to stack handsaws. Stupidly unfair, not recommended."
label="Stack handsaws"
name="handsawStackLimit"
tooltip="Number of handsaws allowed to be stacked. Not recommended to change this."
label="Handsaw stack limit"
min={0}
max={10}
/>
<SettingsSliderFormField
control={form.control}
Expand Down
2 changes: 2 additions & 0 deletions client/components/game/status-carousel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ const statusTooltip: Record<StatusType, string> = {
[StatusType.handcuffed]: "Player's turn will be skipped.",
[StatusType.sawed]: "Player's next shot will deal double damage.",
[StatusType.slipperyHands]: "Player cannot be handcuffed.",
[StatusType.hotPotatoReceiver]:
"Player will receive the hot potato. If inventory is full, damage is taken instead.",
};

const StatusDisplay = React.memo(function (props: StatusDisplayProps) {
Expand Down
6 changes: 5 additions & 1 deletion client/components/game/status-icon.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useMemo } from "react";
import { Item, Status, StatusType } from "@shared/game/types";
import { GiBabyBottle, GiHandcuffs, GiHandSaw } from "react-icons/gi";
import { GiBabyBottle, GiHandcuffs, GiHandSaw, GiPotato } from "react-icons/gi";
import { ensureUnreachable } from "@shared/typescript";
import classNames from "classnames";

Expand All @@ -20,6 +20,8 @@ const StatusIcon = React.memo(function (props: StatusIconProps) {
return GiBabyBottle;
case StatusType.sawed:
return GiHandSaw;
case StatusType.hotPotatoReceiver:
return GiPotato;
default:
ensureUnreachable(type);
}
Expand All @@ -32,6 +34,8 @@ const StatusIcon = React.memo(function (props: StatusIconProps) {
return "text-gray-700";
case StatusType.slipperyHands:
return "text-blue-700";
case StatusType.hotPotatoReceiver:
return "text-orange-500";
default:
ensureUnreachable(type);
}
Expand Down
9 changes: 9 additions & 0 deletions server/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,14 @@ module.exports = {
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-unused-vars': [
'warn',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
caughtErrorsIgnorePattern: '^_',
destructuredArrayIgnorePattern: '^_',
},
],
},
};
161 changes: 136 additions & 25 deletions server/src/game/game.forwarders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,19 @@ import {
StatusType,
GameDelta,
Bullet,
Delta,
Item,
} from '@shared/game/types';
import Result from '@shared/true-myth/result';
import {
applyPlayerStatusChanges,
applyLazyDeltas,
applyPlayerStatusChangesDelta,
DeterministicDelta,
doneGame,
forwardDeltaResults,
generateEndOfTurnStatusDeltas,
generateRefillPlayersDelta,
generateStatusIndex,
Lazy,
} from './game.utils';

export function forwardStripSawedStatus(
Expand All @@ -28,37 +33,34 @@ export function forwardStripSawedStatus(
})
.map((s) => ({ type: 'rm', index: s.index, playerId: player }));

return Result.ok([
{
game: applyPlayerStatusChanges(game, statusChanges),
delta: { type: 'statusChanges', statusChanges },
},
]);
return applyPlayerStatusChangesDelta(game, {
type: 'statusChanges',
statusChanges,
});
}

export function forwardStartTurn(game: Game): Result<GameDeltas, string> {
return Result.ok([{ game, delta: { type: 'noop' } }]);
}

export function forwardEndTurn(game: Game): Result<GameDeltas, string> {
return generateEndOfTurnStatusDeltas(game);
return forwardEndOfTurnStatusChanges(game);
}
export function forwardNextTurn(game: Game): Result<GameDeltas, string> {
let out: Result<GameDeltas, string> = Result.ok([
{ game: game, delta: { type: 'noop' } },
return R.flow(Result.ok([{ game: game, delta: { type: 'noop' as const } }]), [
(deltas) => forwardDeltaResults(forwardEndTurn, deltas),
(deltas) =>
forwardDeltaResults((game) => {
const newTurn = (game.turn + 1) % game.playerStates.length;
return Result.ok([
{
game: { ...game, turn: newTurn },
delta: { type: 'pass', turn: newTurn },
},
]);
}, deltas),
(deltas) => forwardDeltaResults(forwardStartTurn, deltas),
]);
out = forwardDeltaResults(forwardEndTurn, out);
out = forwardDeltaResults((game) => {
const newTurn = (game.turn + 1) % game.playerStates.length;
return Result.ok([
{
game: { ...game, turn: newTurn },
delta: { type: 'pass', turn: newTurn },
},
]);
}, out);
out = forwardDeltaResults(forwardStartTurn, out);
return out;
}

export function forwardNextRound(game: Game): Result<GameDeltas, string> {
Expand All @@ -82,15 +84,124 @@ export function forwardNextRound(game: Game): Result<GameDeltas, string> {
);
}

/** Things that should happen before transferring control back to the player */
/**
* Things that should happen before transferring control back to the player.
* TODO: Squash multiple itemChanges and statusChanges into one
*/
export function forwardPreTransferControls(
game: Game,
): Result<GameDeltas, string> {
if (doneGame(game)) {
return Result.ok([{ game, delta: { type: 'gg' } }]);
const winner = game.playerStates.find((p) => p.health > 0)?.id;
if (!winner) return Result.err('Programmer is an idiot, sorry');
return Result.ok([{ game, delta: { type: 'gg', winner: winner } }]);
} else if (game.gun.length === 0) {
return forwardNextRound(game);
} else {
return Result.ok([{ game, delta: { type: 'noop' } }]);
}
}

export function forwardEndOfTurnStatusChanges(
outerGame: Game,
): Result<GameDeltas, string> {
// fuck it, we're flattening all status changes into a single array as opposed
// to a arrays of an array of singular status changes for atomicity of
// status changes. doesn't really make a difference right now anyhow, since
// status changes are associative
const lazyDeltas = outerGame.playerStates.flatMap((player) =>
player.statuses.flatMap(
(status): Lazy<DeterministicDelta[]> =>
(game: Game) => {
const turns = status.turns ?? Number.POSITIVE_INFINITY;
const upsertedStatus = {
type: 'statusChanges' as const,
statusChanges: [
{
playerId: player.id,
type: 'upsert' as const,
status: {
...status,
turns: turns - 1,
},
},
],
};
const removedStatus = {
type: 'statusChanges' as const,
statusChanges: [
{
playerId: player.id,
type: 'rm' as const,
index: status.index,
},
],
};
switch (status.type) {
case StatusType.handcuffed: {
if (turns > 0) return [upsertedStatus];
else {
const slipperyIndex = generateStatusIndex();
return [
removedStatus,
...(game.settings.handcuffCooldownTurns >= 1
? [
{
type: 'statusChanges' as const,
statusChanges: [
{
playerId: player.id,
type: 'upsert' as const,
status: {
type: StatusType.slipperyHands,
turns: game.settings.handcuffCooldownTurns - 1,
index: slipperyIndex,
},
},
],
},
]
: []),
];
}
}
// alright, clean this fuckin shit ass code up
// TODO: Clean up code
case StatusType.hotPotatoReceiver: {
if (turns > 0) return [upsertedStatus];
const updatedPlayer = game.playerStates.find(
(p) => p.id === player.id,
)!;
const firstEmptySlotIndex = updatedPlayer.items.findIndex(
(i) => i === Item.nothing,
);
if (firstEmptySlotIndex === -1) {
return [
removedStatus,
{ type: 'hurt', dmg: 1, who: player.id },
];
} else {
return [
removedStatus,
{
type: 'itemChanges',
itemChanges: [
{
playerId: player.id,
slot: firstEmptySlotIndex,
item: Item.hotPotato,
},
],
},
];
}
}
default:
return [turns > 0 ? upsertedStatus : removedStatus];
}
},
),
);

return applyLazyDeltas(outerGame, lazyDeltas);
}
Loading

0 comments on commit 74918aa

Please sign in to comment.