Skip to content

Commit 0ce0849

Browse files
committed
Finished game
1 parent 290b32f commit 0ce0849

File tree

80 files changed

+34300
-9162
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

80 files changed

+34300
-9162
lines changed

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,8 @@ yarn-error.*
3333

3434
# typescript
3535
*.tsbuildinfo
36+
37+
# amplify
38+
.amplify
39+
amplify_outputs*
40+
amplifyconfiguration*

App.tsx

Lines changed: 286 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,297 @@
1-
import { StatusBar } from 'expo-status-bar';
2-
import { StyleSheet, Text, View } from 'react-native';
1+
import React, { useEffect, useState } from "react";
2+
import {
3+
Button,
4+
View,
5+
StyleSheet,
6+
SafeAreaView,
7+
Text,
8+
ActivityIndicator,
9+
} from "react-native";
10+
11+
import { Amplify } from "aws-amplify";
12+
import { Authenticator, useAuthenticator } from "@aws-amplify/ui-react-native";
13+
14+
import outputs from "./amplify_outputs.json";
15+
import { fetchUserAttributes } from "aws-amplify/auth";
16+
import { Schema } from "./amplify/data/resource";
17+
import { generateClient } from "aws-amplify/data";
18+
19+
const client = generateClient<Schema>();
20+
21+
Amplify.configure(outputs);
22+
23+
const SignOutButton: React.FC = () => {
24+
const { signOut } = useAuthenticator();
325

4-
export default function App() {
526
return (
6-
<View style={styles.container}>
7-
<Text>Open up App.tsx to start working on your app!</Text>
8-
<StatusBar style="auto" />
27+
<View style={styles.signOutButton}>
28+
<Button title="Sign Out" onPress={signOut} />
929
</View>
1030
);
31+
};
32+
33+
type GameState = "idle" | "searching" | "found" | "quiz" | "error";
34+
35+
interface HomeScreenProps {
36+
username: string;
1137
}
1238

39+
const HomeScreen: React.FC<HomeScreenProps> = ({ username }) => {
40+
const [gameState, setGameState] = useState<GameState>("idle");
41+
const [currentQuestion, setCurrentQuestion] = useState<number>(-1);
42+
const [game, setGame] = useState<Schema["Game"]["type"]>();
43+
const handleSearchGame = async (): Promise<void> => {
44+
const currentGames = await client.models.Game.list({
45+
filter: {
46+
playerTwoId: {
47+
eq: "notAssigned",
48+
},
49+
},
50+
});
51+
52+
if (currentGames.data.length > 0) {
53+
await client.models.Game.update({
54+
id: currentGames.data[0].id,
55+
playerTwoId: username,
56+
});
57+
setGameState("found");
58+
59+
client.models.Game.observeQuery({
60+
filter: {
61+
id: {
62+
eq: currentGames.data[0].id,
63+
},
64+
},
65+
}).subscribe(async (game) => {
66+
if (game.items[0].questions.length > 0) {
67+
setGameState("quiz");
68+
setGame(game.items[0]);
69+
}
70+
if (game.items[0].currentQuestion !== currentQuestion) {
71+
setCurrentQuestion((game.items[0].currentQuestion ?? 0) + 1);
72+
}
73+
});
74+
const result = await client.generations.generateQuestions({
75+
description: "",
76+
});
77+
78+
if (result.errors) {
79+
setGameState("error");
80+
return;
81+
}
82+
83+
const updatedGame = await client.models.Game.update({
84+
id: currentGames.data[0].id,
85+
questions: result.data as Schema["Question"]["type"][],
86+
});
87+
88+
if (updatedGame.data) {
89+
setGame(updatedGame.data);
90+
}
91+
// await client.models.Game.update({
92+
// id: currentGames.data[0].id,
93+
// questions: [
94+
// {
95+
// question: "Which country won the FIFA World Cup in 2022?",
96+
// options: ["Brazil", "France", "Argentina", "Germany"],
97+
// correctAnswer: "Argentina",
98+
// category: "Soccer",
99+
// },
100+
// {
101+
// question: "In which sport would you perform a 'slam dunk'?",
102+
// options: ["Volleyball", "Tennis", "Basketball", "Cricket"],
103+
// correctAnswer: "Basketball",
104+
// category: "Basketball",
105+
// },
106+
// {
107+
// question:
108+
// "How many players are there on a standard ice hockey team?",
109+
// options: ["5", "6", "7", "8"],
110+
// correctAnswer: "6",
111+
// category: "Ice Hockey",
112+
// },
113+
// {
114+
// question:
115+
// "In which Olympic sport might you use the 'Fosbury Flop' technique?",
116+
// options: ["Swimming", "Diving", "High Jump", "Gymnastics"],
117+
// correctAnswer: "High Jump",
118+
// category: "Athletics",
119+
// },
120+
// {
121+
// question:
122+
// "Which Grand Slam tennis tournament is played on clay courts?",
123+
// options: ["Wimbledon", "US Open", "Australian Open", "French Open"],
124+
// correctAnswer: "French Open",
125+
// category: "Tennis",
126+
// },
127+
// ],
128+
// });
129+
} else {
130+
setGameState("searching");
131+
const newGame = await client.models.Game.create({
132+
playerOneId: username,
133+
playerTwoId: "notAssigned",
134+
questions: [],
135+
});
136+
client.models.Game.observeQuery({
137+
filter: {
138+
id: {
139+
eq: newGame.data?.id,
140+
},
141+
},
142+
}).subscribe((game) => {
143+
if (game.items[0].questions.length > 0) {
144+
setGameState("quiz");
145+
setGame(game.items[0]);
146+
} else if (game.items[0].playerTwoId !== "notAssigned") {
147+
setGameState("found");
148+
}
149+
if (game.items[0].currentQuestion !== currentQuestion) {
150+
setCurrentQuestion((game.items[0].currentQuestion ?? 0) + 1);
151+
}
152+
});
153+
}
154+
};
155+
156+
const renderContent = (): JSX.Element => {
157+
switch (gameState) {
158+
case "idle":
159+
return (
160+
<>
161+
<Text style={styles.welcomeText}>Welcome {username}!</Text>
162+
<Button title="Search Game" onPress={handleSearchGame} />
163+
</>
164+
);
165+
case "searching":
166+
return (
167+
<>
168+
<Text style={styles.quizText}>Searching for a game now</Text>
169+
<ActivityIndicator style={styles.activityIndicator} size="large" />
170+
</>
171+
);
172+
case "found":
173+
return (
174+
<>
175+
<Text style={styles.quizText}>
176+
Questions are getting generated now...
177+
</Text>
178+
<ActivityIndicator style={styles.activityIndicator} size="large" />
179+
</>
180+
);
181+
case "quiz":
182+
const question = game?.questions[currentQuestion];
183+
if (currentQuestion === game?.questions.length) {
184+
return (
185+
<>
186+
<Text style={styles.quizText}>Quiz is over!</Text>
187+
<Text style={styles.quizText}>
188+
{game?.playerOneScore === game?.playerTwoScore
189+
? "It's a tie!"
190+
: (game?.playerOneScore ?? 0) > (game?.playerTwoScore ?? 0)
191+
? `${
192+
game?.playerOneId === username ? "You" : game?.playerOneId
193+
} won with ${game?.playerOneScore} points!`
194+
: `${
195+
game?.playerTwoId === username ? "You" : game?.playerTwoId
196+
} won with ${game?.playerTwoScore} points!`}
197+
</Text>
198+
</>
199+
);
200+
}
201+
return (
202+
<>
203+
<Text>{question?.question}</Text>
204+
{question?.options.map((option) => (
205+
<Button
206+
key={option}
207+
title={option!}
208+
onPress={() => {
209+
if (option === question.correctAnswer) {
210+
if (game?.playerOneId === username) {
211+
client.models.Game.update({
212+
id: game!.id,
213+
playerOneScore: (game?.playerOneScore ?? 0) + 10,
214+
currentQuestion: currentQuestion,
215+
});
216+
} else {
217+
client.models.Game.update({
218+
id: game!.id,
219+
playerTwoScore: (game?.playerTwoScore ?? 0) + 10,
220+
currentQuestion: currentQuestion,
221+
});
222+
}
223+
} else {
224+
client.models.Game.update({
225+
id: game!.id,
226+
currentQuestion: currentQuestion,
227+
});
228+
}
229+
}}
230+
/>
231+
))}
232+
</>
233+
);
234+
case "error":
235+
return (
236+
<>
237+
<Text style={styles.welcomeText}>There is an error.</Text>
238+
</>
239+
);
240+
default:
241+
return <Text>Unknown state</Text>;
242+
}
243+
};
244+
245+
return <View style={styles.contentContainer}>{renderContent()}</View>;
246+
};
247+
248+
const App: React.FC = () => {
249+
const [username, setUsername] = useState<string>("");
250+
251+
useEffect(() => {
252+
const fetchUsername = async (): Promise<void> => {
253+
const userAttributes = await fetchUserAttributes();
254+
const fetchedUsername = userAttributes?.preferred_username ?? "";
255+
setUsername(fetchedUsername);
256+
};
257+
void fetchUsername();
258+
}, []);
259+
260+
return (
261+
<Authenticator.Provider>
262+
<Authenticator>
263+
<SafeAreaView style={styles.container}>
264+
<SignOutButton />
265+
<HomeScreen username={username} />
266+
</SafeAreaView>
267+
</Authenticator>
268+
</Authenticator.Provider>
269+
);
270+
};
271+
13272
const styles = StyleSheet.create({
14273
container: {
15274
flex: 1,
16-
backgroundColor: '#fff',
17-
alignItems: 'center',
18-
justifyContent: 'center',
275+
},
276+
signOutButton: {
277+
alignSelf: "flex-end",
278+
},
279+
contentContainer: {
280+
flex: 1,
281+
justifyContent: "center",
282+
alignItems: "center",
283+
},
284+
welcomeText: {
285+
fontSize: 18,
286+
fontWeight: "bold",
287+
},
288+
quizText: {
289+
fontSize: 16,
290+
marginBottom: 16,
291+
},
292+
activityIndicator: {
293+
padding: 16,
19294
},
20295
});
296+
297+
export default App;

amplify/auth/resource.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { defineAuth } from "@aws-amplify/backend";
2+
3+
export const auth = defineAuth({
4+
loginWith: {
5+
email: {
6+
verificationEmailSubject: "Welcome to quiz app! Verify your email!",
7+
verificationEmailBody: (code) =>
8+
`Here is your verification code: ${code()}`,
9+
verificationEmailStyle: "CODE",
10+
},
11+
},
12+
userAttributes: {
13+
preferredUsername: {
14+
mutable: true,
15+
required: true,
16+
},
17+
},
18+
});

amplify/backend.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { defineBackend } from '@aws-amplify/backend';
2+
import { auth } from './auth/resource';
3+
import { data } from './data/resource';
4+
5+
/**
6+
* @see https://docs.amplify.aws/react/build-a-backend/ to add storage, functions, and more
7+
*/
8+
defineBackend({
9+
auth,
10+
data,
11+
});

amplify/data/resource.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { type ClientSchema, a, defineData } from "@aws-amplify/backend";
2+
3+
const schema = a.schema({
4+
generateQuestions: a
5+
.generation({
6+
aiModel: a.ai.model("Claude 3 Sonnet"),
7+
systemPrompt: `
8+
You are a quiz question generator.
9+
10+
Create exactly 10 questions, evenly distributed across the categories from the following list [Sport, General Culture, Movies, Art, History]. Ensure the questions are evenly distributed in different difficulty levels.
11+
12+
Requirements for each question:
13+
- The questions should be in English.
14+
- Return the result as a JSON list containing JSON objects.
15+
- Return the question with the JSON key 'question'.
16+
- Include 4 different answer options, with the JSON key 'options', each a string.
17+
- Specify 1 correct answer, with the JSON key 'correctAnswer', in string format.
18+
- Return the category with the JSON key 'category'.
19+
- Questions should not be repeated.
20+
`,
21+
})
22+
.arguments({
23+
description: a.string(),
24+
})
25+
.returns(a.ref("Question").required().array().required())
26+
.authorization((allow) => allow.authenticated()),
27+
Question: a.customType({
28+
question: a.string().required(),
29+
options: a.string().required().array().required(),
30+
correctAnswer: a.string().required(),
31+
category: a.string().required(),
32+
}),
33+
Game: a
34+
.model({
35+
playerOneId: a.string().required(),
36+
playerTwoId: a.string().required(),
37+
questions: a.ref("Question").required().array().required(),
38+
currentQuestion: a.integer().default(0),
39+
playerOneScore: a.integer().default(0),
40+
playerTwoScore: a.integer().default(0),
41+
})
42+
.authorization((allow) => [allow.authenticated()]),
43+
});
44+
45+
export type Schema = ClientSchema<typeof schema>;
46+
47+
export const data = defineData({
48+
schema,
49+
authorizationModes: {
50+
defaultAuthorizationMode: "userPool",
51+
},
52+
});

0 commit comments

Comments
 (0)