Skip to content

Commit

Permalink
Version 0.9.1 - 26-12-2023
Browse files Browse the repository at this point in the history
  • Loading branch information
DanielDekhtyar committed Dec 26, 2023
1 parent 1203be5 commit f98a664
Show file tree
Hide file tree
Showing 4 changed files with 153 additions and 95 deletions.
22 changes: 21 additions & 1 deletion Final Project/classes/button.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
- img_height (int): The height of the button image.
- _rect (pygame.Rect): The rectangular area occupied by the button on the screen.
- _active (bool): A flag indicating whether the button is clickable or not.
- _letter_button_clicked (bool): A flag indicating if it is not a letter button then it will remain False for the whole duration
Methods:
- draw(screen: pygame.Surface): Draws the button on the specified surface.
Expand Down Expand Up @@ -41,7 +42,10 @@ def __init__(self, img: pygame.Surface, img_width, img_hight, button_name):
# Initially the button is clickable
self._clickable: bool = True
self._hovering: bool = False
self._button_name = button_name
self._button_name: str = button_name
# Indicates if it is a letter button and if it was clicked
# If it is not a letter button then it will remain False for the whole duration
self._letter_button_clicked: bool = False

# This function draws the button on the screen
def draw(self, screen: pygame.Surface):
Expand All @@ -52,6 +56,22 @@ def check_hovering(self, mouse_pos):
# Check if the mouse is hovering over the button
self._hovering = self._rect.collidepoint(mouse_pos)

@property
def letter_button_clicked(self) -> bool:
return self._letter_button_clicked

@letter_button_clicked.setter
def letter_button_clicked(self, value: bool):
self._letter_button_clicked = value

@property
def position(self) -> tuple[int, int]:
return self._rect.x, self._rect.y

@position.setter
def position(self, value: tuple[int, int]):
self._rect.x, self._rect.y = value

@property
def clickable(self) -> bool:
return self._clickable
Expand Down
21 changes: 10 additions & 11 deletions Final Project/project.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# Learning Python with CS50
# Final Project !!!
# https://cs50.harvard.edu/python/2022/project/

"""
Learning Python with CS50
Final Project !!!
https://cs50.harvard.edu/python/2022/project/
Author : Daniel Dekhtyar
Email : denik2707@gmail.com
LinkedIn : https://www.linkedin.com/in/daniel-dekhtyar/
Expand Down Expand Up @@ -34,12 +35,11 @@ def main():
# Set screen size and alow the screen to be resizable
# (0, 0) means that the screen size will be set automatically
screen: pygame.Surface = pygame.display.set_mode((0, 0), pygame.RESIZABLE)

# Render the start screen with the background image and the title and buttons
# Returns the buttons for later use in the game loop
# HACK: make it return just the button list without the exit button separately using return_button_clicked
all_button_instances, exit_button = start_screen.render_start_screen(
screen)
all_button_instances, exit_button = start_screen.render_start_screen(screen)

# The Game loop. It will run until 'is_playing' is set to False. aka exit the game
is_playing: bool = True
Expand All @@ -49,23 +49,22 @@ def main():
# Mouse position determined at the start of the game loop to save computer resources.
# Otherwise, the pygame.mouse.get_pos() function would be called in every function.
mouse_pos: tuple[int, int] = pygame.mouse.get_pos()

# Making pointing hand cursor when hovering over a button
game_loop.mouse_when_over_button(all_button_instances, mouse_pos)

# Get the name of the button that was clicked. If no button clicked, it gets None
button_name = button_clicked(all_button_instances, mouse_pos)

# Create a set of all the names of the level buttons
level_names = {"Level 1", "Level 2", "Level 3", "Level 4"}

for event in pygame.event.get():

"""
If one of the level buttons is clicked, the game will start the level.
When the exit button on the game screen is clicked, game_logic will return False,
The game loop will stop and the game will exit
The If Else statement is written in the way that it does because otherwise
you need to click the exit button twice to exit the game.
One time to exit the game_logic loop and the second time to exit the main game loop.
Expand Down
156 changes: 86 additions & 70 deletions Final Project/src/game_screen.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from font import set_font


def render_game_screen(word_cls, all_button_instances, screen) -> None:
def render_game_screen(word, all_button_instances, screen) -> None:
# Reuse the code from start_screen.py as the background screen is the same
# Render the screen surface
start_screen.render_bg(screen)
Expand All @@ -22,13 +22,15 @@ def render_game_screen(word_cls, all_button_instances, screen) -> None:
exit_button = start_screen.render_exit_button(screen)

# Renders the Topic : <topic> text
render_topic_text(word_cls.topic, screen)
render_topic_text(word.topic, screen)

# Rended the masked word in the middle of the screen
render_masked_word(word_cls, screen)
render_masked_word(word, screen)

# Render the alphabet buttons
render_alphabet_buttons(screen, all_button_instances)
render_letter_buttons(screen, all_button_instances)

put_v_or_x(screen, all_button_instances, word)

return exit_button

Expand Down Expand Up @@ -92,77 +94,91 @@ def render_masked_word(word: Word, screen: pygame.Surface) -> pygame.Rect:
screen.blit(render, rect)


def render_alphabet_buttons(
# Function to render the letter buttons in rows and columns
def render_letter_buttons(
screen: pygame.Surface, all_button_instances: list[Button]
) -> tuple[Button]:
) -> None:
# Load letter images into a dictionary
letter_images: dict[str, pygame.Surface] = {}
for letter in string.ascii_uppercase:
letter_images[letter] = pygame.image.load(
f"CS50P/Final Project/images/letters/{letter}.png"
)

# Function to render the letter buttons in rows and columns
def render_letter_buttons(
screen: pygame.Surface, letter_images: dict[str, pygame.Surface]
) -> tuple[Button]:
# The dimensions of the buttons
button_width, button_height = 81, 93

# The margin between buttons
button_margin_x, button_margin_y = 10, 10

# 26 letters in the alphabet / 2 rows = 13 letters per row
max_buttons_per_row = 13

# Total number of buttons to be displayed
total_buttons = len(letter_images)
rows = (total_buttons + max_buttons_per_row - 1) // max_buttons_per_row

# The X position of the first button (A)
start_x = (
screen.get_width()
- (max_buttons_per_row * (button_width + button_margin_x) - button_margin_x)
) // 2

# The Y position of the first button (A)
start_y = (
(
screen.get_height()
- (rows * (button_height + button_margin_y) - button_margin_y)
)
// 2
) + screen.get_height() * 0.35

# Counts how many buttons was created
buttons_count = 0

# Iterate through each letter and its corresponding image
for letter, image in letter_images.items():
# Create a Button instance for the letter
letter_button = Button(image, button_width, button_height, letter)
letter_button.x = start_x
letter_button.y = start_y

# Draw the letter image onto the button
letter_button.draw(screen)

# Append the button instance to the list
all_button_instances.append(letter_button)
buttons_count += 1

start_x += button_width + button_margin_x

# Move to the next row if the maximum buttons per row is reached
if buttons_count % max_buttons_per_row == 0:
start_x = (
screen.get_width()
- (
max_buttons_per_row * (button_width + button_margin_x)
- button_margin_x
)
) // 2
start_y += button_height + button_margin_y

# Render the letter buttons
render_letter_buttons(screen, letter_images)
# The dimensions of the buttons
button_width, button_height = 81, 93

# The margin between buttons
button_margin_x, button_margin_y = 10, 10

# 26 letters in the alphabet / 2 rows = 13 letters per row
max_buttons_per_row = 13

# Total number of buttons to be displayed
total_buttons = len(letter_images)
rows = (total_buttons + max_buttons_per_row - 1) // max_buttons_per_row

# The X position of the first button (A)
start_x = (
screen.get_width()
- (max_buttons_per_row * (button_width + button_margin_x) - button_margin_x)
) // 2

# The Y position of the first button (A)
start_y = (
(
screen.get_height()
- (rows * (button_height + button_margin_y) - button_margin_y)
)
// 2
) + screen.get_height() * 0.35

# Counts how many buttons was created
buttons_count = 0

# Iterate through each letter and its corresponding image
for letter, image in letter_images.items():
# Create a Button instance for the letter
letter_button = Button(image, button_width, button_height, letter)
letter_button.x = start_x
letter_button.y = start_y

# Draw the letter image onto the button
letter_button.draw(screen)

# Append the button instance to the list
all_button_instances.append(letter_button)
buttons_count += 1

start_x += button_width + button_margin_x

# Move to the next row if the maximum buttons per row is reached
if buttons_count % max_buttons_per_row == 0:
start_x = (
screen.get_width()
- (
max_buttons_per_row * (button_width + button_margin_x)
- button_margin_x
)
) // 2
start_y += button_height + button_margin_y


def put_v_or_x(
screen: pygame.Surface, all_button_instances: list[Button], word: Word
) -> None:
for button in all_button_instances:
if button.letter_button_clicked:
if button.name in word.word:
render_v_or_x_image(screen, button, "v")
else:
render_v_or_x_image(screen, button, "x")


def render_v_or_x_image(screen: pygame.Surface, button: Button, image_name) -> None:
image = pygame.image.load(f"CS50P/Final Project/images/{image_name}.png")

pos_x = button.x
pos_y = button.y

screen.blit(image, (pos_x, pos_y))
49 changes: 36 additions & 13 deletions Final Project/src/hangman_game.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
from src import hangman_game
from src import game_screen
from src import game_loop
from font import set_font
import project


Expand All @@ -35,15 +34,17 @@ def game_logic(screen, level, all_button_instances):

# If mouse hovers, it will change the cursor to a pointing hand
game_loop.mouse_when_over_button(all_button_instances, mouse_pos)

# When a letter button was clicked, guess_letters will check if the guessed letter is in the word.
# If it is, word.guessed_letters_index is changed to True in the corresponding index.
is_guessed = guess_letter(word, all_button_instances, mouse_pos)
is_guessed = guess_letter(screen, word, all_button_instances, mouse_pos)

# If a letter was guessed, the game screen will be updated
if is_guessed:
screen.fill((255, 255, 255))
exit_button = game_screen.render_game_screen(word, all_button_instances, screen)
exit_button = game_screen.render_game_screen(
word, all_button_instances, screen
)

# Update the screen once after processing events
pygame.display.update()
Expand All @@ -69,10 +70,10 @@ def get_word_cls(level):
def get_random_word(level) -> tuple[str, str]:
# Path to where all the csv files with the words are located within the project
csv_path = r"CS50P/Final Project/data/"

# Get the CSV file name based on the level
csv_file = f"{level}.csv"

# Join the path to the file
path_to_csv = os.path.join(csv_path, csv_file)

Expand All @@ -93,22 +94,44 @@ def get_random_word(level) -> tuple[str, str]:


def guess_letter(
word: Word, all_button_instances: list[pygame.Rect], mouse_pos: tuple[int]
screen: pygame.Surface,
word: Word,
all_button_instances: list[pygame.Rect],
mouse_pos: tuple[int],
) -> None:
# Getting the button.name of the button that was clicked. If no button was clicked, it will get None
button_clicked: str | None = project.button_clicked(all_button_instances, mouse_pos)
button_name: str | None = project.button_clicked(all_button_instances, mouse_pos)

# Initializing is_guessed that will be used to check if a letter was guessed or not
is_guessed = False

if button_clicked is not None and button_clicked != "Exit":

# Get the button instance
button = get_button_instance(button_name, all_button_instances)

if button_name is not None and button_name != "Exit":
# Goes over all the letters in the word

# Indicate that the letter button was clicked
button.letter_button_clicked = True

for i, char in enumerate(word.word):
# If the letter that was guessed is in the word, change the guessed_letters_index to True
if char == button_clicked:
if char == button_name:
# Make the letter visible in the masked word
word.guessed_letters_index[i] = True

# Indicates that the letter was guessed
is_guessed = True


game_screen.render_v_or_x_image(screen, button, "x")

# It returned to indicate that the game screen needs to be updated
return is_guessed


def get_button_instance(button_name: str, all_button_instances: list[pygame.Rect]):
# Goes over all the buttons in the list
for button in all_button_instances:
# If the button.name matches the button_name, it will return the button
if button.name == button_name:
return button

0 comments on commit f98a664

Please sign in to comment.