diff --git a/guessquest/games/migrations/0003_rename_total_score_temperaturegamesession_score_and_more.py b/guessquest/games/migrations/0003_rename_total_score_temperaturegamesession_score_and_more.py new file mode 100644 index 0000000..debfc34 --- /dev/null +++ b/guessquest/games/migrations/0003_rename_total_score_temperaturegamesession_score_and_more.py @@ -0,0 +1,23 @@ +# Generated by Django 5.1.6 on 2025-04-21 02:44 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('games', '0002_rename_user_temperaturegamesession_player_and_more'), + ] + + operations = [ + migrations.RenameField( + model_name='temperaturegamesession', + old_name='total_score', + new_name='score', + ), + migrations.AlterField( + model_name='temperaturequestion', + name='user_guess', + field=models.FloatField(blank=True, null=True), + ), + ] diff --git a/guessquest/games/models.py b/guessquest/games/models.py index 96ff543..3f67fac 100644 --- a/guessquest/games/models.py +++ b/guessquest/games/models.py @@ -1,5 +1,6 @@ from django.db import models from django.utils.timezone import now +from . import weather_services #TODO LEADERBOARD @@ -11,43 +12,46 @@ def update_high_score(self, score): if score > self.high_score: self.high_score = score - - # returns username as a string def __str__(self): return self.username class TemperatureGameSession(models.Model): player = models.ForeignKey(Player, on_delete=models.CASCADE) - total_score = models.IntegerField(default=0) + score = models.IntegerField(default=0) questions_left = models.IntegerField(default=5) time_created = models.DateTimeField(auto_now_add=True) game_status = models.CharField(max_length=10, choices=[("active", "Active"), ("completed", "Completed")], default='active') - def update_score(self, points): - self.total_score += points + def create_question(self) : + city = weather_services.get_random_city() + actual_temperature = weather_services.get_city_temperature(city) + question = TemperatureQuestion.objects.create(game=self, city=city, actual_temperature=actual_temperature) self.questions_left -= 1 - if self.is_game_over(): - self.game_status = "completed" + return question + def get_latest_question(self): + return self.questions.last() + def update_score(self, points): + self.score += points + def end_game(self): + self.game_status = "completed" + self.player.update_high_score(self.score) self.save() - - def is_game_over(self): + self.player.save() + self.delete() + def no_questions_left(self): + if self.questions_left == 0: + self.game_status = 'completed' return self.questions_left == 0 class TemperatureQuestion(models.Model): game = models.ForeignKey(TemperatureGameSession, related_name="questions", on_delete=models.CASCADE) city = models.CharField(max_length=50) - user_guess = models.FloatField(null=False, blank=False) + user_guess = models.FloatField(null=True, blank=True) actual_temperature = models.FloatField(null=False, blank=False) time_created = models.DateTimeField(auto_now_add=True) time_limit = models.IntegerField(default=30) # 30 seconds def __str__(self): return f"What is the current temperature of {self.city}?" - - def check_guess(self): # calculates and returns points - #scoring algorithm subject to change - error = abs(self.actual_temperature - self.user_guess) - points = max(0, 250 - int(error * 10)) - - return points \ No newline at end of file + \ No newline at end of file diff --git a/guessquest/games/services.py b/guessquest/games/services.py deleted file mode 100644 index efb24ed..0000000 --- a/guessquest/games/services.py +++ /dev/null @@ -1,27 +0,0 @@ -import requests -from django.conf import settings -import json - -class CityNotFoundError(Exception): - pass -class CoordinatesNotFoundError(Exception): - pass -def get_city_temperature(city) : - lat,lon = city_to_coords(city) - url = f"https://api.openweathermap.org/data/2.5/weather?lat={lat}&lon={lon}&appid={settings.OPENWEATHER_API_KEY}&units=imperial" - response = requests.get(url) - if response.status_code != 200: - raise CoordinatesNotFoundError(f"The coordinates, {lat}, {lon} was not found") - data = response.json() - return data["main"]["temp"] -def city_to_coords(city) : - url = f"http://api.openweathermap.org/geo/1.0/direct?q={city},&appid={settings.OPENWEATHER_API_KEY}" - response = requests.get(url) - if response.status_code != 200: - raise CityNotFoundError(f"The city, {city} was not found") - data = response.json() - return data[0]["lat"], data[0]["lon"] -# Temperature scoring algorithm -def calculate_score(actual_temp, user_guess): - error = abs(actual_temp - user_guess) - return max(0, 250 - int(error * 10)) \ No newline at end of file diff --git a/guessquest/games/static/css/image1.jpg b/guessquest/games/static/css/image1.jpg new file mode 100644 index 0000000..4a8ac6d Binary files /dev/null and b/guessquest/games/static/css/image1.jpg differ diff --git a/guessquest/games/static/css/style.css b/guessquest/games/static/css/style.css index 7b90a2a..955b170 100644 --- a/guessquest/games/static/css/style.css +++ b/guessquest/games/static/css/style.css @@ -14,12 +14,18 @@ body { min-height: 100vh; } +body.start { + background-image: url("start_bg.jpeg"); + background-size: 100% auto; +} + .start-screen { font-size: 75px; } .start-screen #player-form { font-size: 35px; + display: none; } .start-screen #playername { @@ -27,7 +33,8 @@ body { } .start-screen #player-enter { - font-size: 30px; + font-size: 25px; + padding: 5px; background-color: black; border: none; color: white; @@ -110,3 +117,23 @@ body { opacity: 0.85; cursor: pointer; } + +#question-num { + font-size: 30px; +} + +.progress-container { + background-color: white; + border: 2px solid black; + border-radius: 10px; + width: 100%; + height: 30px; + margin: 10px 0 20px; + overflow: hidden; +} + +.progress-bar { + background-color: rgb(0, 229, 255); + height: 100%; + transition: width 0.3s ease; +} diff --git a/guessquest/games/static/js/spotifyGame.js b/guessquest/games/static/js/spotifyGame.js new file mode 100644 index 0000000..753c96a --- /dev/null +++ b/guessquest/games/static/js/spotifyGame.js @@ -0,0 +1,71 @@ +let questionIdx = 0; +const questionList = [ + { + question: "Like a Rolling Stone", + choices: ["Bob Dylan", "Marvin Gaye", "Stevie Wonder", "Selena Gomez"], + answer: "Bob Dylan" + }, + { + question: "Strawberry Fields Forever", + choices: ["Fleetwood Mac", "The Beatles", "Aretha Franklin", "Ariana Grande"], + answer: "The Beatles" + }, + { + question: "Bohemian Rapsody", + choices: ["Billie Holiday", "John Lennon", "Queen", "Madonna"], + answer: "Queen" + } +]; +let score = 0; + +function newGame() { + questionIdx = 0; + score = 0; + document.getElementById("score").textContent = `Score: ${score}`; + game(); +} + +function game() { + const question = questionList[questionIdx].question; + const answer = questionList[questionIdx].answer; + const options = questionList[questionIdx].choices; + + document.getElementById("question").textContent = question; + document.getElementById("options").innerHTML = ""; + options.forEach((option) => { + const button = document.createElement("button"); + button.textContent = option; + button.onclick = () => checkAnswer(option, answer); + document.getElementById("options").appendChild(button); + }); +} + +function checkAnswer(choice, answer) { + if (choice == answer) { + score += 10; + document.getElementById("score").textContent = `Score: ${score}`; + } + questionIdx++; + if (questionIdx < 3) { + game(); + } else { + endGame(); + } +} + +function endGame() { + document.querySelector(".question-body").classList.add("hidden"); + document.querySelector(".restart").classList.remove("hidden"); + document.getElementById("question").textContent = ""; + document.getElementById("options").innerHTML = ""; + document.getElementById("score").textContent = `Final Score: ${score}`; +} + +function restartGame() { + document.querySelector(".question-body").classList.remove("hidden"); + document.querySelector(".restart").classList.add("hidden"); + document.getElementById("score").textContent = ""; + newGame(); +} + +newGame(); \ No newline at end of file diff --git a/guessquest/games/static/js/startScreen.js b/guessquest/games/static/js/startScreen.js new file mode 100644 index 0000000..33b5c12 --- /dev/null +++ b/guessquest/games/static/js/startScreen.js @@ -0,0 +1,15 @@ +const text = "GuessQuest?"; +const guessTextDiv = document.getElementById("startText"); +let index = 0; + +function revealNextLetter() { + if (index < text.length) { + guessTextDiv.textContent = text.substring(0, index + 1); + index++; + setTimeout(revealNextLetter, 200); + } else { + document.getElementById("player-form").style.display = "block"; + } +} + +setTimeout(revealNextLetter, 600); diff --git a/guessquest/games/templates/default.html b/guessquest/games/templates/default.html deleted file mode 100644 index 21fa2fb..0000000 --- a/guessquest/games/templates/default.html +++ /dev/null @@ -1,9 +0,0 @@ - - - - Default Page - - -

Default Page

- - diff --git a/guessquest/games/templates/game_selection.html b/guessquest/games/templates/game_selection.html index c7b4265..cff2046 100644 --- a/guessquest/games/templates/game_selection.html +++ b/guessquest/games/templates/game_selection.html @@ -4,47 +4,97 @@ Home Page
-

Choose Game to Play!

+

Hello player {{player}}

+

Choose a Game to Play!

- {% for game in available_games %} -
-
- {{game.name}} + +
+ {% for game in available_games %} +
+ + {{ game.name }} + +
{{ game.name }}
+ Play Now +
+ {% endfor %}
- {% endfor %} diff --git a/guessquest/games/templates/startScreen.html b/guessquest/games/templates/startScreen.html index 05cb407..b63a6f8 100644 --- a/guessquest/games/templates/startScreen.html +++ b/guessquest/games/templates/startScreen.html @@ -8,9 +8,9 @@ - +
-

GuessQuest?

+

{% csrf_token %} diff --git a/guessquest/games/templates/weatherGame.html b/guessquest/games/templates/weatherGame.html index 5633008..b9bb293 100644 --- a/guessquest/games/templates/weatherGame.html +++ b/guessquest/games/templates/weatherGame.html @@ -6,15 +6,25 @@ Weather game - {% comment %} - - {% endcomment %} + {% if feedback %} +
-

Score: {{score}}

-

Question {{questionNum}}

-

+

Question {{questionsNum}}

+
+ {% if questionsNum == 1 %} +
+ {% elif questionsNum == 2 %} +
+ {% elif questionsNum == 3 %} +
+ {% elif questionsNum == 4 %} +
+ {% elif questionsNum == 5 %} +
+ {% endif %} +

Guess the weather in {{city}}.

{% csrf_token %} @@ -23,13 +33,74 @@

Score: {{score}}

type="number" id="guess" name="guess" - placeholder="Enter your guess in (celsius)" + placeholder="Enter your guess in (fahrenheit)" required />
+ {% elif end %} +
+
+ + +
+
+ +
+
+ +
+
+ {% else %} +
+

Question {{questionsNum}}

+
+ {% if questionsNum == 1 %} +
+ {% elif questionsNum == 2 %} +
+ {% elif questionsNum == 3 %} +
+ {% elif questionsNum == 4 %} +
+ {% elif questionsNum == 5 %} +
+ {% endif %} +
+

+ Your Total Score: 0 + {{ message }} + +

+
+ {% csrf_token %} + +
+
+ + {% endif %} diff --git a/guessquest/games/tests.py b/guessquest/games/tests.py index b431bbb..c8372a4 100644 --- a/guessquest/games/tests.py +++ b/guessquest/games/tests.py @@ -4,6 +4,7 @@ from datetime import timedelta import json from .models import Player, TemperatureGameSession, TemperatureQuestion +from . import weather_services class PlayerModelTests(TestCase): def test_create_player(self): @@ -33,7 +34,7 @@ def test_create_game_session(self): """Test creating a game session with default values""" game = TemperatureGameSession.objects.create(player=self.player) self.assertEqual(game.player.username, "testuser") - self.assertEqual(game.total_score, 0) + self.assertEqual(game.score, 0) self.assertEqual(game.questions_left, 5) self.assertEqual(game.game_status, "active") self.assertIsNotNone(game.time_created) @@ -42,25 +43,26 @@ def test_update_score(self): """Test updating score and questions_left""" game = TemperatureGameSession.objects.create(player=self.player) game.update_score(100) - self.assertEqual(game.total_score, 100) - self.assertEqual(game.questions_left, 4) + self.assertEqual(game.score, 100) self.assertEqual(game.game_status, "active") def test_game_over(self): """Test that game status changes when no questions are left""" game = TemperatureGameSession.objects.create(player=self.player, questions_left=1) game.update_score(100) - self.assertEqual(game.total_score, 100) + game.questions_left -= 1 + self.assertEqual(game.no_questions_left) + self.assertEqual(game.score, 100) self.assertEqual(game.questions_left, 0) self.assertEqual(game.game_status, "completed") - def test_is_game_over(self): - """Test the is_game_over method""" + def test_no_questions_left(self): + """Test the no_questions_left method""" game = TemperatureGameSession.objects.create(player=self.player) - self.assertFalse(game.is_game_over()) + self.assertFalse(game.no_questions_left()) game.questions_left = 0 - self.assertTrue(game.is_game_over()) + self.assertTrue(game.no_questions_left()) class TemperatureQuestionTests(TestCase): def setUp(self): @@ -137,7 +139,7 @@ def test_sign_in_get(self): """Test GET request to sign_in view""" response = self.client.get(reverse('sign_in')) self.assertEqual(response.status_code, 200) - self.assertTemplateUsed(response, 'sign_in.html') + self.assertTemplateUsed(response, 'startScreen.html') def test_sign_in_post_existing_user(self): """Test POST request to sign_in with existing username""" @@ -170,13 +172,13 @@ def test_sign_in_post_new_user(self): fetch_redirect_response=False ) - def test_start_game_get(self): + def test_start_weather_game_get(self): """Test GET request to start_game view""" response = self.client.get( - reverse('start_temp', kwargs={'player_id': self.test_player.id}) + reverse('weather_game', kwargs={'player_id': self.test_player.id}) ) self.assertEqual(response.status_code, 200) - self.assertTemplateUsed(response, 'weather_game.html') + self.assertTemplateUsed(response, 'weatherGame.html') def test_calculate_score(self): """Test the calculate_score utility function""" @@ -237,13 +239,11 @@ def test_model_integration(self): user_guess=guesses[i], actual_temperature=actuals[i] ) - - points = question.check_guess() - total_points += points - game.update_score(points) + total_points += weather_services.calculate_score() + weather_services.process_weather_guess(game, question, question.user_guess) # Verify final state - self.assertEqual(game.total_score, total_points) + self.assertEqual(game.score, total_points) self.assertEqual(game.questions_left, 0) self.assertEqual(game.game_status, "completed") diff --git a/guessquest/games/views.py b/guessquest/games/views.py index 99c8abe..a17720c 100644 --- a/guessquest/games/views.py +++ b/guessquest/games/views.py @@ -1,58 +1,51 @@ from django.shortcuts import render, get_object_or_404, redirect from django.http import JsonResponse import json -from .models import Player, TemperatureGameSession +from .models import Player, TemperatureGameSession, TemperatureQuestion +from django.views.decorators.http import require_POST from django.http import HttpResponse from rest_framework.views import APIView from rest_framework.response import Response from .trivia_service import TriviaService import random import requests -from .services import calculate_score +from . import weather_services # Create your views here. -cities = ["New York", "Pairs", "Tokyo", "London", "Los Angeles", "Bangkok", "San Francisco", "Barcelona", "Shanghai", "Dubai", "Vienna", "Rome", "Berlin"] def sign_in(request): if request.method == "GET": return render (request, "startScreen.html") elif request.method == "POST": username = request.POST.get("playername") player, created = Player.objects.get_or_create(username=username) - return redirect(f'/games?player_id={player.id}') # Will change this to game_selection screen - + return redirect(f'/games?player_id={player.id}') def weather_game(request, player_id): if request.method == "GET": - request.session['score'] = 0 - request.session['questionNum'] = 1 - request.session['city'] = random.choice(cities) - info = { - 'score': request.session['score'], - 'questionNum': request.session['questionNum'], - 'city': request.session['city'] - } + player = get_object_or_404(Player, id=player_id) + game, question = weather_services.create_weather_game(player) + weather_services.store_weather_session_data(request, player, game, question) + info = weather_services.build_game_context(game.score, game.questions_left, question.city, question.actual_temperature, True) return render(request, "weatherGame.html", info) elif request.method == "POST": - - guess = int(request.POST.get('guess')) - city = request.session['city'] - url = f'http://api.openweathermap.org/data/2.5/weather?q={city}&appid={OPENWEATHER_API_KEY}&units=metric' - response = requests.get(url) - weather_data = response.json() - answer = int(weather_data['main']['temp']) - print(weather_data) - score = calculate_score(answer, guess) + if 'guess' in request.POST: + guess = int(request.POST.get('guess')) + player, game, question = weather_services.get_weather_post_data(request) + score = weather_services.process_weather_guess(game, question, guess) + feedback = weather_services.get_feedback(score, game.score, game.questions_left, False) + return render(request, "weatherGame.html", feedback) + elif 'next' in request.POST: + player, game, question = weather_services.get_weather_post_data(request) + if game.no_questions_left(): + game.end_game() + end = True + info = {'end': end, 'id' : player.id} + return render(request, "weatherGame.html", info) + next_question = game.create_question() + weather_services.store_weather_session_question(request, next_question) + info = weather_services.build_game_context(game.score, game.questions_left, next_question.city, next_question.actual_temperature, True) + return render(request, "weatherGame.html", info) - request.session['score'] += score - request.session['questionNum'] += 1 - request.session['city'] = random.choice(cities) - info = { - 'score': request.session['score'], - 'questionNum': request.session['questionNum'], - 'city': request.session['city'] - } - if(request.session['questionNum'] >= 3): - return redirect(trivia_game) - return render(request, "weatherGame.html", info) + def trivia_game(request, player_id): if request.method == "GET": return render(request, "triviaGame.html") @@ -88,18 +81,21 @@ def game_selection(request): 'id': 'temperature', 'name': 'Weather Game', 'description': 'Guess the correct temperature and earn points!', + 'image': 'css/selectionImages/weather.jpg', 'url': f'/temperature/{player_id}' }, { 'id': 'trivia', 'name': 'Trivia Game', 'description': 'Trivia Game!', + 'image': 'css/selectionImages/trivia.jpg', 'url': f'/trivia/{player_id}' }, { 'id': 'spotify', 'name': 'Spotify Game', 'description': 'Spotify Game!', + 'image': 'css/selectionImages/spotify.jpg', 'url': f'/spotify/{player_id}' } ] diff --git a/guessquest/games/weather_services.py b/guessquest/games/weather_services.py new file mode 100644 index 0000000..c60db3c --- /dev/null +++ b/guessquest/games/weather_services.py @@ -0,0 +1,98 @@ +import requests +from django.conf import settings +from . import models +import json +import random +from django.shortcuts import get_object_or_404 + +# Global +cities = [ + "New York", "Paris", "Tokyo", "London", "Los Angeles", "Bangkok", + "San Francisco", "Barcelona", "Shanghai", "Dubai", "Vienna", + "Rome", "Berlin", "Chicago", "Houston", "Phoenix", "Philadelphia", + "San Antonio", "San Diego", "Dallas", "Austin", "Seattle", + "Denver", "Boston", "Las Vegas" +] +perfectGuess = ["Perfect", "Your luck is incredible", "Now that's a GUESS"] +goodGuess = ["Nice Guess", "That was close", "How did you know?", "Cool", "Awesome"] +badGuess = ["You can guess better", "Better luck next time", "Not even close"] + +perfectGif = ['css/gifs/perfect/1.gif', 'css/gifs/perfect/2.gif', 'css/gifs/perfect/3.gif'] +goodGif = ['css/gifs/good/1.gif', 'css/gifs/good/2.gif', 'css/gifs/good/3.gif'] +badGif = ['css/gifs/bad/1.gif', 'css/gifs/bad/2.gif', 'css/gifs/bad/3.gif'] +class CityNotFoundError(Exception): + pass +class CoordinatesNotFoundError(Exception): + pass +def get_city_temperature(city, metric=False) : + lat,lon = city_to_coords(city) + units = 'metric' if metric else 'imperial' + url = f"https://api.openweathermap.org/data/2.5/weather?lat={lat}&lon={lon}&appid={settings.OPENWEATHER_API_KEY}&units={units}" + response = requests.get(url) + if response.status_code != 200: + raise CoordinatesNotFoundError(f"The coordinates, {lat}, {lon} was not found") + data = response.json() + return data["main"]["temp"] +def city_to_coords(city) : + url = f"http://api.openweathermap.org/geo/1.0/direct?q={city},&appid={settings.OPENWEATHER_API_KEY}" + response = requests.get(url) + if response.status_code != 200: + raise CityNotFoundError(f"The city, {city} was not found") + data = response.json() + return data[0]["lat"], data[0]["lon"] +def get_random_city() : + return random.choice(cities) +def calculate_score(actual_temp, user_guess): + error = abs(actual_temp - user_guess) + return max(0, 250 - int(error * 10)) +def process_weather_guess(game, question, guess): + score = calculate_score(question.actual_temperature, guess) + game.update_score(score) + game.questions_left -= 1 + game.save() + return score +def create_weather_game(player): + game = models.TemperatureGameSession.objects.create(player=player) + question = game.create_question() + return game, question +def store_weather_session_data(request, player, game, question): + request.session['game_id'] = game.id + request.session['question_id'] = question.id + request.session['player_id'] = player.id +def store_weather_session_question(request, question): + request.session['question_id'] = question.id +def get_weather_post_data(request): + game_id = request.session.get('game_id') + question_id = request.session.get('question_id') + player_id = request.session.get('player_id') + game = get_object_or_404(models.TemperatureGameSession, id=game_id) + question = get_object_or_404(models.TemperatureQuestion, id=question_id) + player = get_object_or_404(models.Player, id=player_id) + return player, game, question +def build_game_context(score, questions_left, city, actual_temperature, feedback): + return { + 'score': score, + 'questionsNum': 5 - questions_left, + 'city': city, + 'actualTemperature' : actual_temperature, + 'feedback' : feedback + } +def get_feedback(score, player_score, questions_left, feedback): + message = "" + if(score == 250): + message = random.choice(perfectGuess) + gif = random.choice(perfectGif) + elif(score > 150): + message = random.choice(goodGuess) + gif = random.choice(goodGif) + else: + message = random.choice(badGuess) + gif = random.choice(badGif) + + return{ + 'score': player_score, + 'questionsNum': 5 - questions_left, + 'message': message, + 'feedback' : feedback, + 'gif': gif + } \ No newline at end of file