Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
6a90a25
feat(testing): complete unit test
LauraSchlueter22 Aug 5, 2025
15cef4e
feat(testing): add comment for github test
LauraSchlueter22 Aug 5, 2025
e24f794
Merge pull request #1 from Labubu-Who/feat/testing
yuan-cloud Aug 5, 2025
ba5b9f6
feat(methods) : temp scoreboard functions
yuan-cloud Aug 5, 2025
d0b74c6
Merge pull request #2 from Labubu-Who/connect-db-score-board
yuan-cloud Aug 6, 2025
7912075
feat(testing): fix sign up backend
LauraSchlueter22 Aug 6, 2025
04bbc1d
Merge pull request #3 from Labubu-Who/feat/testing
LauraSchlueter22 Aug 6, 2025
4a58a7c
feat(bug): fix mislabel for useAuth
LauraSchlueter22 Aug 6, 2025
1060872
Merge pull request #4 from Labubu-Who/feat/scoreboard
LauraSchlueter22 Aug 6, 2025
c0472c4
feat(scoreboard): added scoreboard to UI
LauraSchlueter22 Aug 6, 2025
04be9a4
Merge pull request #5 from Labubu-Who/feat/scoreboard2
LauraSchlueter22 Aug 6, 2025
bba1c4a
feat(mobile): added react native and converted all web code to mobile…
Aug 7, 2025
1ed0d28
Merge pull request #6 from Labubu-Who/mobile
LauraSchlueter22 Aug 7, 2025
e104635
Remove broken submodule 'mobile'
Aug 7, 2025
a738f44
feat(mobile): converted web app to mobile app, auth not yet included
Aug 7, 2025
a701f86
Merge pull request #7 from Labubu-Who/mobile
harleydi Aug 7, 2025
2d4c7ae
feat(debug): fix login route
LauraSchlueter22 Aug 7, 2025
a28da0f
Merge pull request #8 from Labubu-Who/feat/debug
LauraSchlueter22 Aug 7, 2025
e64ad7d
feat(debug): added current player highlight on scoreboard
LauraSchlueter22 Aug 7, 2025
e0861b3
Merge pull request #9 from Labubu-Who/feat/debug
LauraSchlueter22 Aug 7, 2025
c8243f7
feat(music): connected music to board
LauraSchlueter22 Aug 7, 2025
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
5 changes: 5 additions & 0 deletions babel.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export default {
presets: ['@babel/preset-env', '@babel/preset-react'],
};


8 changes: 8 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@ export default defineConfig([
'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }],
},
},
{
files: ['**/*.test.js', '**/*.test.jsx'],
languageOptions: {
globals: {
...globals.jest,
},
},
},
// Override for server files to enable node globally and make sure backend not in a browser environment
{
files: ['server/**/*.js', 'db.js'],
Expand Down
11 changes: 11 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export default {
setupFilesAfterEnv: ['./jest.setup.js'],
transform: {
'^.+\\.jsx?$': 'babel-jest',
},
extensionsToTreatAsEsm: ['.jsx'],
testEnvironment: 'jsdom',
moduleNameMapper: {
'\\.(css|less|scss|sass)$': 'identity-obj-proxy',
},
};
5 changes: 5 additions & 0 deletions jest.setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/* eslint-env node */
import { TextEncoder, TextDecoder } from 'util';

global.TextEncoder = TextEncoder;
global.TextDecoder = TextDecoder;
8 changes: 8 additions & 0 deletions mobile/.expo/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
> Why do I have a folder named ".expo" in my project?
The ".expo" folder is created when an Expo project is started using "expo start" command.
> What do the files contain?
- "devices.json": contains information about devices that have recently opened this project. This is used to populate the "Development sessions" list in your development builds.
- "settings.json": contains the server configuration that is used to serve the application manifest.
> Should I commit the ".expo" folder?
No, you should not share the ".expo" folder. It does not contain any information that is relevant for other developers working on the project, it is specific to your machine.
Upon project creation, the ".expo" folder is already added to your ".gitignore" file.
3 changes: 3 additions & 0 deletions mobile/.expo/devices.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"devices": []
}
305 changes: 305 additions & 0 deletions mobile/App.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,305 @@
import { Button, ImageBackground, Pressable, StyleSheet, Text, TextInput, View } from 'react-native';
import Header from './components/Header';
import { useEffect, useState } from 'react';
import Card from './components/Card';

export default function App() {
const [gridSize, setGridSize] = useState(6);
const [board, setBoard] = useState([]);
const [gameStarted, setGameStarted] = useState(false);
const [selectedCards, setSelectedCards] = useState([]);
const [matchedCards, setMatchedCards] = useState([]);
const [numOfFlips, setNumOfFlips] = useState(0);
const [gameWon, setGameWon] = useState(false);
const [dim, setDim] = useState();
const [paused, setPaused] = useState(false);
const imgArr = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'];
const [reset, setReset] = useState(false);

// checking for matched cards and game completion
useEffect(() => {
if (selectedCards.length === 2) {
const [firstCard, secondCard] = selectedCards;

if (firstCard.number === secondCard.number) {
setMatchedCards(prev => [...prev, firstCard.number]);
setSelectedCards([]);

// Check for win condition
if (matchedCards.length + 1 === gridSize) {
setGameWon(true);
}
} else {
const timeoutId = setTimeout(() => {
setSelectedCards([]);
}, 1000);

return () => clearTimeout(timeoutId);
}
}
}, [selectedCards]);



useEffect(() => {
if (reset) {
setBoard([]);
setGridSize(6);
setGameStarted(false);
setSelectedCards([]);
setGameWon(false);
setMatchedCards([]);
setNumOfFlips(0);
setPaused(false);
setReset(false);
setDim([]);
}
}, [reset]);

const startGame = (e) => {
e.preventDefault();
setGameStarted(true);
const totalCards = gridSize * 2;
const pairCount = Math.floor(totalCards / 2);
const numbers = [...Array(pairCount).keys()].map((n) => n + 1);
const shuffledCards = [...numbers, ...numbers]
.sort(() => Math.random() - 0.5)
.slice(0, totalCards);

const imageAssign = assignImgHelper(imgArr, shuffledCards);
const newShuffledCards = shuffledCards.map((number, index) => ({
id: index,
number,
letter: imageAssign[number],
}));
setBoard(newShuffledCards);
setDim(Math.ceil(Math.sqrt(newShuffledCards.length)));
};

const handleFlipCard = (index, value) => {
if (paused) return;
setNumOfFlips(numOfFlips + 1);
const currCard = selectedCards.find((card) => card.index === index);

if (!currCard && selectedCards.length < 2) {
setSelectedCards((prevSelectedCards) => [
...prevSelectedCards,
{ index, number: value.number, letter: value.letter },
]);
} else if (!currCard && selectedCards.length === 2) {
setSelectedCards([{ index, number: value.number, letter: value.letter }]);
}
};

const assignImgHelper = (imgArr, numArr) => {
const uniqueNums = [...new Set(numArr)];
const obj = {};

for (let i = 0; i < uniqueNums.length; i++) {
obj[uniqueNums[i]] = imgArr[i % imgArr.length];
}
return obj;
};

const handleReset = (e) => {
startGame(e);
setReset(true);
};

let pauseButton;
let resetButton;

if (!gameWon && numOfFlips === 0) {
pauseButton = null;
resetButton = null;
} else {
pauseButton = (
<Pressable
onPress={() => setPaused(p => !p)}
style={({ pressed }) => [
styles.baseButton,
paused ? styles.pausedState : styles.activeState,
pressed && (paused ? styles.pausedPressed : styles.activePressed)
]}
>
<Text style={styles.pauseButtonText}>{paused ? 'Resume' : 'Pause'}</Text>
</Pressable>
);
resetButton = (
<Pressable
onPress={handleReset}
style={({ pressed }) => [
styles.resetBase,
pressed && styles.resetPressed
]}
>
<Text style={styles.resetButtonText}>Reset Game</Text>
</Pressable>
);
}

return (
<ImageBackground style={styles.container} source={require('./assets/background.png')}>
<Header
flips={numOfFlips}
gameWon={gameWon}
gameStarted={gameStarted}
paused={paused}
/>
{gameStarted && (
<View style={[styles.gridContainer, { width: dim * 96 + (dim - 1) * 16 }]}>
{board.map((value, index) => (
<Card
key={index}
index={index}
value={value.number}
letter={value.letter}
selectedCards={selectedCards}
matchedCards={matchedCards}
onClick={() => handleFlipCard(index, value)}
/>
))}
</View>
)}
{pauseButton}
{resetButton}
{!gameStarted && (
<View style={styles.menuContainer}>
<View style={styles.inputContainer}>
<Text style={styles.label}>Enter Grid Size:</Text>
<TextInput
style={styles.input}
keyboardType="numeric"
placeholder="6"
min={2}
max={8}
onChangeText={(text) => setGridSize(Number(text))}
/>
</View>
<Pressable
style={({ pressed }) => [
styles.button,
pressed && styles.buttonPressed
]}
onPress={startGame}
>
<Text style={styles.buttonText}>Start Game</Text>
</Pressable>
</View>
)}
</ImageBackground>
);
}

const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
menuContainer: {
alignItems: 'center',
justifyContent: 'center',
gap: 16,
},
inputContainer: {
flexDirection: 'row',
alignItems: 'center',
gap: 8,
backgroundColor: 'transparent',
},
label: {
fontSize: 20,
color: 'white',
textShadowColor: 'rgba(0, 0, 0, 0.1)',
textShadowOffset: { width: 0, height: 0 },
textShadowRadius: 4,
},
input: {
width: 64,
paddingHorizontal: 8,
paddingVertical: 4,
backgroundColor: 'white',
borderRadius: 6,
borderWidth: 2,
borderColor: '#f3f4f6',

},
button: {
width: 120,
paddingVertical: 12,
backgroundColor: '#A1D6D4',
borderRadius: 8,
shadowColor: '#000',
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.1,
shadowRadius: 2,
elevation: 2,
},
buttonPressed: {
backgroundColor: '#41A5A4',
},
buttonText: {
fontSize: 18,
color: '#535A53',
textAlign: 'center',
},
gridContainer: {
flexDirection: 'row',
flexWrap: 'wrap',
gap: 16,
justifyContent: 'center',
padding: 16,
},
baseButton: {
marginTop: 16,
paddingHorizontal: 16,
paddingVertical: 8,
borderRadius: 12,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.25,
shadowRadius: 3.84,
elevation: 5,
},
pausedState: {
backgroundColor: '#A1D6D4',
},
activeState: {
backgroundColor: '#DD7F56',
},
pausedPressed: {
backgroundColor: '#637A31',
},
activePressed: {
backgroundColor: '#41A5A4',
},
pauseButtonText: {
color: 'white',
fontSize: 16,
fontWeight: '500',
textAlign: 'center',
},
resetBase: {
marginTop: 16,
paddingHorizontal: 16,
paddingVertical: 8,
borderRadius: 12,
backgroundColor: '#EF476F',
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.25,
shadowRadius: 3.84,
elevation: 5,
},
resetPressed: {
backgroundColor: '#D62839',
},
resetButtonText: {
color: 'white',
fontSize: 16,
fontWeight: '500',
textAlign: 'center',
},
});
29 changes: 29 additions & 0 deletions mobile/app.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"expo": {
"name": "tester",
"slug": "tester",
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/icon.png",
"userInterfaceStyle": "light",
"newArchEnabled": true,
"splash": {
"image": "./assets/splash-icon.png",
"resizeMode": "contain",
"backgroundColor": "#ffffff"
},
"ios": {
"supportsTablet": true
},
"android": {
"adaptiveIcon": {
"foregroundImage": "./assets/adaptive-icon.png",
"backgroundColor": "#ffffff"
},
"edgeToEdgeEnabled": true
},
"web": {
"favicon": "./assets/favicon.png"
}
}
}
Binary file added mobile/assets/background.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added mobile/assets/component_structure.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added mobile/assets/iphone_mockup.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added mobile/assets/iphone_mockup_sm.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added mobile/assets/labubuA.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added mobile/assets/labubuB.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added mobile/assets/labubuC.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added mobile/assets/labubuD.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added mobile/assets/labubuE.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added mobile/assets/labubuF.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added mobile/assets/labubuG.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added mobile/assets/labubuH.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added mobile/assets/user_schema.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added mobile/assets/wireframe_desktop.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added mobile/assets/wireframe_mobile.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading