-
Notifications
You must be signed in to change notification settings - Fork 0
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
Added voting feature and strikethrough on invalid answers, next question button #7
base: main
Are you sure you want to change the base?
Changes from all commits
4237eab
164171b
0a5be59
1c72e61
97c8889
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -24,11 +24,17 @@ const NUM_CATEGORIES = CATEGORIES.length; | |||||||||||||||||||||||||
|
||||||||||||||||||||||||||
export type Phase = "write" | "review"; | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
export type answer = { | ||||||||||||||||||||||||||
answer: string; | ||||||||||||||||||||||||||
valid: boolean; | ||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
export type B = { | ||||||||||||||||||||||||||
categories: string[]; | ||||||||||||||||||||||||||
userIds: UserId[]; | ||||||||||||||||||||||||||
// Info of the current round. | ||||||||||||||||||||||||||
answers: Record<UserId, string[]>; | ||||||||||||||||||||||||||
answers: Record<UserId, answer[]>; | ||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we need a step back and revisit our data structures for the match state(s). I thought about it for a while and here is what I came up with: type B = {
// The N categories, common for each round.
categories: string[];
// The players.
userIds: UserId[];
// Answers entered by all the users for all the categories for a round.
answers: Record<UserId, string>[];
// User votes for the previous answers. This is how many points the voter says it is worth.
// Note that the first user in the data structure is the writer and the second is the voter.
votes: Record<UserId, Record<UserId, number>>[]
// The current round index.
round: number;
// The current phase.
phase: Phase
// If in the "review" phase, what category are we reviewing.
categoryIndex: number;
}
type PB = {
// Answers as the user is entering them in the Read phase.
answers: string[];
} This is assuming we ditch everything between rounds (otherwise we would need an extra nested array for the Round). I don't know if we need a |
||||||||||||||||||||||||||
playerVotes: Record<string, Record<UserId, number>>; | ||||||||||||||||||||||||||
round: number; | ||||||||||||||||||||||||||
roundStep: number; | ||||||||||||||||||||||||||
simlmx marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||
phase: Phase; | ||||||||||||||||||||||||||
|
@@ -47,6 +53,11 @@ type WritePayload = { | |||||||||||||||||||||||||
answer: string; | ||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
type VotePayload = { | ||||||||||||||||||||||||||
userId: string; | ||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No need to pass the
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Scratch that we need a |
||||||||||||||||||||||||||
answer: string; | ||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
altough it's fine to keep the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Whoops disregard, other comment referring to this was hidden for some reason There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You're right to be confused I think we also do need the |
||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
const write: PlayerMove<GS, WritePayload> = { | ||||||||||||||||||||||||||
executeNow({ board, playerboard, payload }) { | ||||||||||||||||||||||||||
const { answer, index } = payload as WritePayload; | ||||||||||||||||||||||||||
|
@@ -69,14 +80,58 @@ const write: PlayerMove<GS, WritePayload> = { | |||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
board.answers = {}; | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
board.playerVotes = {}; | ||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's swap the // We are done with this phase when any player has entered 10 answers.
const weAreDone = answerRound.every((a) => a.length > 0)
if (!weAreDone) {
return;
}
// When we are done we go to the Write phase.
board.phase = 'review' as const;
board.answer = {}
board.playerVotes = {}
... |
||||||||||||||||||||||||||
const allAnswers: string[] = []; | ||||||||||||||||||||||||||
for (const [userId, playerboard] of Object.entries(playerboards)) { | ||||||||||||||||||||||||||
let checkedAnswers: string[] = []; | ||||||||||||||||||||||||||
const playerAnswers = playerboard.answers[board.round]; | ||||||||||||||||||||||||||
board.answers[userId] = playerAnswers; | ||||||||||||||||||||||||||
board.answers[userId] = playerAnswers.map((a) => { | ||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would skip the validation here and simply copy the answers from the If we really want to save the "validity" to the state, we should probably do it in a separate function. If we don't write it to the state we can simply write functions like isAnswerUniqueInCategory(...: {answer: string; allAnswers: Record<UserId, string>}) isAnswerUniqueForUser(...) that we would use in the backend and frontend. In any case I would not worry about it too much for now! |
||||||||||||||||||||||||||
if ( | ||||||||||||||||||||||||||
playerAnswers.filter( | ||||||||||||||||||||||||||
(answer) => answer.toLowerCase() === a.toLowerCase(), | ||||||||||||||||||||||||||
).length > 1 | ||||||||||||||||||||||||||
) { | ||||||||||||||||||||||||||
const valid = checkedAnswers.includes(a) ? false : true; | ||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. const valid = !checkAnswers.includes(a) |
||||||||||||||||||||||||||
checkedAnswers.push(a); | ||||||||||||||||||||||||||
return { | ||||||||||||||||||||||||||
answer: a, | ||||||||||||||||||||||||||
valid: valid, | ||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||
} else { | ||||||||||||||||||||||||||
return { | ||||||||||||||||||||||||||
answer: a, | ||||||||||||||||||||||||||
valid: true, | ||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
allAnswers.push(...playerAnswers); | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
allAnswers.forEach((a) => { | ||||||||||||||||||||||||||
board.playerVotes[a] = {}; | ||||||||||||||||||||||||||
for (const [userId] of Object.entries(playerboards)) { | ||||||||||||||||||||||||||
board.playerVotes[a][userId] = 1; | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||
Comment on lines
+110
to
+115
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See the new proposed
Suggested change
Not sure if there is a simpler way 🤔 |
||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
const vote: PlayerMove<GS, VotePayload> = { | ||||||||||||||||||||||||||
executeNow({ board, playerboard, payload }) { | ||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||
if (board.playerVotes[payload.answer][payload.userId] === 1) { | ||||||||||||||||||||||||||
board.playerVotes[payload.answer][payload.userId] = 0; | ||||||||||||||||||||||||||
} else { | ||||||||||||||||||||||||||
board.playerVotes[payload.answer][payload.userId] = 1; | ||||||||||||||||||||||||||
Comment on lines
+121
to
+124
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm jumping ahead a little here with the logic getting closer to supporting 2-pointers.
Suggested change
EDITED: added the other userId as we need two! |
||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
const nextStep: PlayerMove<GS> = { | ||||||||||||||||||||||||||
executeNow({ board }) { | ||||||||||||||||||||||||||
board.roundStep++; | ||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
const game = { | ||||||||||||||||||||||||||
initialBoards: ({ players }) => { | ||||||||||||||||||||||||||
const board = { | ||||||||||||||||||||||||||
|
@@ -85,6 +140,7 @@ const game = { | |||||||||||||||||||||||||
roundStep: 0, | ||||||||||||||||||||||||||
userIds: players, | ||||||||||||||||||||||||||
answers: {}, | ||||||||||||||||||||||||||
playerVotes: {}, | ||||||||||||||||||||||||||
// TODO No letter to start with, then a count them and then only we pick the letter. | ||||||||||||||||||||||||||
letter: "A", | ||||||||||||||||||||||||||
phase: "write" as const, | ||||||||||||||||||||||||||
|
@@ -100,11 +156,11 @@ const game = { | |||||||||||||||||||||||||
|
||||||||||||||||||||||||||
return { board, playerboards }; | ||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||
playerMoves: { write }, | ||||||||||||||||||||||||||
playerMoves: { write, vote, nextStep }, | ||||||||||||||||||||||||||
minPlayers: 1, | ||||||||||||||||||||||||||
maxPlayers: 10, | ||||||||||||||||||||||||||
} satisfies Game<GS>; | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
export type G = typeof game; | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
export { game, write }; | ||||||||||||||||||||||||||
export { game, write, vote, nextStep }; |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -5,6 +5,7 @@ import { | |||||
makeUseMakeMove, | ||||||
useUsername, | ||||||
useIsPlayer, | ||||||
useMyUserId, | ||||||
} from "@lefun/ui"; | ||||||
|
||||||
import classNames from "classnames"; | ||||||
|
@@ -20,6 +21,7 @@ import { useLingui } from "@lingui/react"; | |||||
|
||||||
import { useFonts } from "./useFonts"; | ||||||
import { UserId } from "@lefun/core"; | ||||||
import { Flag } from "./Flag"; | ||||||
|
||||||
const useSelector = makeUseSelector<GS>(); | ||||||
const useMakeMove = makeUseMakeMove<G>(); | ||||||
|
@@ -83,11 +85,12 @@ function AnswerInput({ index }: { index: number }) { | |||||
<TextBox | ||||||
caption={category} | ||||||
text={newAnswer} | ||||||
readonly={!isPlayer} | ||||||
readOnly={!isPlayer} | ||||||
onChange={(text: string) => { | ||||||
setNewAnswer(text); | ||||||
makeMove("write", { index, answer: text }); | ||||||
}} | ||||||
strikeThrough={false} | ||||||
/> | ||||||
); | ||||||
} | ||||||
|
@@ -96,6 +99,7 @@ function ReviewContent() { | |||||
const userIds = useSelector((state) => state.board.userIds); | ||||||
const roundstep = useSelector((state) => state.board.roundStep); | ||||||
const category = useSelector((state) => state.board.categories[roundstep]); | ||||||
const makeMove = useMakeMove(); | ||||||
return ( | ||||||
<div> | ||||||
<div className="flex justify-center">{category}</div> | ||||||
|
@@ -104,6 +108,7 @@ function ReviewContent() { | |||||
<ReviewPlayer key={userId} userId={userId} /> | ||||||
))} | ||||||
</div> | ||||||
<button onClick={() => makeMove("nextStep")}>Next Question</button> | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
</div> | ||||||
); | ||||||
} | ||||||
|
@@ -123,17 +128,22 @@ function WriteContent() { | |||||
|
||||||
function ReviewPlayer({ userId }: { userId: UserId }) { | ||||||
const username = useUsername(userId); | ||||||
const playerUserId = useMyUserId(); | ||||||
const roundStep = useSelector((state) => state.board.roundStep); | ||||||
const answers = useSelector((state) => state.board.answers[userId]); | ||||||
const answer = answers[roundStep]; | ||||||
|
||||||
return ( | ||||||
<TextBox | ||||||
caption={username || ""} | ||||||
text={answer} | ||||||
readonly={true} | ||||||
captionAlwaysOnTop={true} | ||||||
/> | ||||||
<div className="flex justify-center"> | ||||||
<TextBox | ||||||
caption={username || ""} | ||||||
text={answer.answer} | ||||||
strikeThrough={!answer.valid} | ||||||
readOnly={true} | ||||||
captionAlwaysOnTop={true} | ||||||
/> | ||||||
<Flag userId={playerUserId} answer={answer.answer}></Flag> | ||||||
</div> | ||||||
); | ||||||
} | ||||||
|
||||||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,29 @@ | ||||||
import { UserId } from "@lefun/core"; | ||||||
import { makeUseSelector, makeUseMakeMove } from "@lefun/ui"; | ||||||
import { G, GS } from "categorio-game"; | ||||||
export function Flag({ userId, answer }: { userId: UserId; answer: string }) { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
const useMakeMove = makeUseMakeMove<G>(); | ||||||
const useSelector = makeUseSelector<GS>(); | ||||||
Comment on lines
+5
to
+6
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should probably have a separate file, maybe So this would become a simple import: import { useMakeMove, useSelector } from './hooks' |
||||||
let flagged: number = useSelector( | ||||||
(state) => state.board.playerVotes[answer][userId], | ||||||
); | ||||||
const makeMove = useMakeMove(); | ||||||
console.log(flagged); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
return ( | ||||||
<button | ||||||
onClick={() => makeMove("vote", { userId: userId, answer: answer })} | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
> | ||||||
<svg | ||||||
xmlns="http://www.w3.org/2000/svg" | ||||||
width="24" | ||||||
height="24" | ||||||
viewBox="0 0 24 24" | ||||||
fill={!!flagged ? "#000000" : "#D40000"} | ||||||
className="icon icon-tabler icons-tabler-filled icon-tabler-flag" | ||||||
> | ||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" /> | ||||||
<path d="M4 5a1 1 0 0 1 .3 -.714a6 6 0 0 1 8.213 -.176l.351 .328a4 4 0 0 0 5.272 0l.249 -.227c.61 -.483 1.527 -.097 1.61 .676l.005 .113v9a1 1 0 0 1 -.3 .714a6 6 0 0 1 -8.213 .176l-.351 -.328a4 4 0 0 0 -5.136 -.114v6.552a1 1 0 0 1 -1.993 .117l-.007 -.117v-16z" /> | ||||||
</svg> | ||||||
</button> | ||||||
); | ||||||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.