Skip to content

Commit

Permalink
feat: Add bonus points to Block interface and update Section interface
Browse files Browse the repository at this point in the history
The Block interface now includes a `bonus_points` property of type number. Additionally, the Section interface has been added with properties `id`, `group`, and `url`.
  • Loading branch information
drikusroor committed Jul 9, 2024
1 parent 4a9e89b commit 021edec
Show file tree
Hide file tree
Showing 5 changed files with 75 additions and 22 deletions.
6 changes: 5 additions & 1 deletion frontend/src/API.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,11 +160,15 @@ interface ScoreIntermediateResultParams {
result: unknown;
}

interface ScoreIntermediateResultResponse {
score: number;
}

export const scoreIntermediateResult = async ({
session,
participant,
result,
}: ScoreIntermediateResultParams) => {
}: ScoreIntermediateResultParams): Promise<ScoreIntermediateResultResponse | null> => {
try {
const vars = {
session_id: session.id,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import React from 'react';
import { vi } from 'vitest';
import { vi, describe, beforeEach, afterEach, test, expect } from 'vitest';
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import MockAdapter from 'axios-mock-adapter';
import axios from 'axios';
import * as API from '../../API';

import MatchingPairs, { SCORE_FEEDBACK_DISPLAY } from './MatchingPairs';

let mock;
let mock: MockAdapter;

vi.mock("@/components/PlayButton/PlayCard", () => ({
default: props => <div data-testid="play-card" {...props} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,57 +5,84 @@ import { scoreIntermediateResult } from "../../API";
import useBoundStore from "@/util/stores";

import PlayCard from "./PlayCard";
import Section from "@/types/Section";
import Session from "@/types/Session";
import Participant from "@/types/Participant";

export const SCORE_FEEDBACK_DISPLAY = {
SMALL_BOTTOM_RIGHT: 'small-bottom-right',
LARGE_TOP: 'large-top',
HIDDEN: 'hidden',
}

interface Card extends Section {
turned: boolean;
inactive: boolean;
matchClass: string;
seen: boolean;
noevents: boolean;
boardposition: number;
timestamp: number;
response_interval_ms: number | string;
}

interface MatchingPairsProps {
playSection: (index: number) => void;
sections: Card[];
playerIndex: number;
showAnimation: boolean;
finishedPlaying: () => void;
scoreFeedbackDisplay?: string;
submitResult: (result: any) => void;
view: string;
}

const MatchingPairs = ({
playSection,
// technically these are Sections, but we're adding some extra properties to them in a hacky way,
// which should be fixed in the future
sections,
playerIndex,
showAnimation,
finishedPlaying,
scoreFeedbackDisplay = SCORE_FEEDBACK_DISPLAY.LARGE_TOP,
submitResult,
view
}) => {
}: MatchingPairsProps) => {

const block = useBoundStore(state => state.block);
const bonusPoints = block?.bonus_points || 0;
const xPosition = useRef(-1);
const yPosition = useRef(-1);
const [firstCard, setFirstCard] = useState({});
const [secondCard, setSecondCard] = useState({});
const [firstCard, setFirstCard] = useState<Card | null>(null);
const [secondCard, setSecondCard] = useState<Card | null>(null);
const [feedbackText, setFeedbackText] = useState('Pick a card');
const [feedbackClass, setFeedbackClass] = useState('');
const [inBetweenTurns, setInBetweenTurns] = useState(false);
const [score, setScore] = useState(null);
const [score, setScore] = useState<number | null>(null);
const [total, setTotal] = useState(bonusPoints);

const columnCount = sections.length > 6 ? 4 : 3;

const participant = useBoundStore(state => state.participant);
const session = useBoundStore(state => state.session);
const participant = useBoundStore(state => state.participant) as Participant;
const session = useBoundStore(state => state.session) as Session;
const setError = useBoundStore(state => state.setError);

const registerUserClicks = (posX, posY) => {
const registerUserClicks = (posX: number, posY: number) => {
xPosition.current = posX;
yPosition.current = posY;
}

// Show (animated) feedback after second click on second card or finished playing
const showFeedback = (score) => {
const showFeedback = (score: number) => {

const turnedCards = sections.filter(s => s.turned);

// Check if this turn has finished
if (turnedCards.length === 2) {
// update total score & display current score
setTotal(total + score);
let fbclass;
let fbclass: string = '';
switch (score) {
case 10:
fbclass = 'fblucky';
Expand Down Expand Up @@ -85,25 +112,33 @@ const MatchingPairs = ({
}
}

const checkMatchingPairs = async (index) => {
const checkMatchingPairs = async (index: number) => {
const currentCard = sections[index];
const turnedCards = sections.filter(s => s.turned);
if (turnedCards.length < 2) {
if (turnedCards.length === 1) {

// This is the second card to be turned
currentCard.turned = true;
setSecondCard(currentCard);

// set no mouse events for all but current
sections.forEach(section => section.noevents = true);
currentCard.noevents = true;
currentCard.boardposition = parseInt(index) + 1;
currentCard.boardposition = index + 1;
currentCard.timestamp = performance.now();
currentCard.response_interval_ms = Math.round(currentCard.timestamp - firstCard.timestamp);

const firstCardTimestamp = firstCard?.timestamp ?? 0;
currentCard.response_interval_ms = Math.round(currentCard.timestamp - firstCardTimestamp);

// check for match
const first_card = firstCard;
const second_card = currentCard;
try {
const scoreResponse = await scoreIntermediateResult({ session, participant, result: { first_card, second_card } });
if (!scoreResponse) {
throw new Error('We cannot currently proceed with the game. Try again later');
}
setScore(scoreResponse.score);
showFeedback(scoreResponse.score);
} catch {
Expand All @@ -116,7 +151,7 @@ const MatchingPairs = ({
// turn first card, disable events
currentCard.turned = true;
currentCard.noevents = true;
currentCard.boardposition = parseInt(index) + 1;
currentCard.boardposition = index + 1;
currentCard.timestamp = performance.now();
// reset response interval in case this card has a value from a previous turn
currentCard.response_interval_ms = '';
Expand All @@ -131,8 +166,8 @@ const MatchingPairs = ({
finishedPlaying();
// remove matched cards from the board
if (score === 10 || score === 20) {
sections.find(s => s === firstCard).inactive = true;
sections.find(s => s === secondCard).inactive = true;
sections.find(s => s === firstCard)!.inactive = true;
sections.find(s => s === secondCard)!.inactive = true;
}
setFirstCard(null);
setSecondCard(null)
Expand All @@ -148,7 +183,7 @@ const MatchingPairs = ({
setFeedbackText('');
} else {
setFeedbackText('Pick a card');
setScore('');
setScore(null);
setFeedbackClass('');
}
setInBetweenTurns(false);
Expand All @@ -168,7 +203,7 @@ const MatchingPairs = ({
/>}

<div className={classNames("playing-board", columnCount === 3 && "playing-board--three-columns")}>
{Object.keys(sections).map((index) => (
{sections.map((_section, index) => (
<PlayCard
key={index}
onClick={() => {
Expand Down Expand Up @@ -197,13 +232,21 @@ const MatchingPairs = ({
)
}

interface ScoreFeedbackProps {
scoreFeedbackDisplay?: string;
score: number | null;
feedbackText: string;
feedbackClass: string;
total: number;
}

const ScoreFeedback = ({
scoreFeedbackDisplay = SCORE_FEEDBACK_DISPLAY.LARGE_TOP,
score,
feedbackText,
feedbackClass,
total,
}) => {
}: ScoreFeedbackProps) => {
return (
<div className={
classNames(
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/types/Block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ export default interface Block {
slug: string;
description: string;
image?: IImage;
}
bonus_points: number;
}
5 changes: 5 additions & 0 deletions frontend/src/types/Section.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export default interface Section {
id: number;
group: string;
url: string;
}

0 comments on commit 021edec

Please sign in to comment.