-
-
Save MikePandel/fe0e4e7f894098327aa1d094c1e49346 to your computer and use it in GitHub Desktop.
#!/usr/bin/env python3 | |
import string | |
import os # Used for clearing the term window | |
import time # Used for 5 second buffer at the end of the game | |
import random # To pick random word from list | |
from tkinter import * # GUI | |
import tkinter as tk # GUI | |
from tkinter import messagebox # GUI Message Box | |
''' | |
Start of Console Code | |
''' | |
class hangman: | |
played_word = "" # Word in play | |
gameboard = [] # Playing game board | |
gameboard_finished = [] # End-State game board | |
guess = '' # Guess that's made | |
guess_archieve = ['Guesses:'] # Creates list of all guesses | |
lives = ['Lives(5):'] # Players life count | |
end_state = False # Is the game over | |
# List create from random word generator | |
word_list = ['stun','amuse','comment','systematic','adviser','argument','chemistry','ward','goal','knot','confession','desk','opinion','dilute','horoscope','number','overall','dark','girl','association','reserve','shrink','autonomy','worker','confrontation','mountain','conception','corpse','prestige','family','belief','mobile','trouble','temptation'] | |
def set_Word(self): | |
word = random.choice(self.word_list) # Using random to grab random word from word_list | |
self.played_word = word | |
def set_finished_board(self,word): | |
word_list_finished = list(word) | |
self.gameboard_finished = word_list_finished | |
def set_create_board(self,word): | |
word_list_playing = ['_'] * len(word) | |
self.gameboard = word_list_playing | |
def set_move(self,guess,location): | |
self.gameboard[location] = guess | |
def set_guess(self,player_guess): | |
if(player_guess in self.guess_archieve): # Check if guess has already been made | |
print("You have already tried to play " + player_guess) | |
elif(player_guess in self.gameboard_finished): # Checking if guess is in found in gameboard_finished | |
for position,char in enumerate(self.gameboard_finished): | |
if char== player_guess: # Checks for all chances of the guess within gameboard_finished | |
self.set_move(self,player_guess,position) | |
self.guess_archieve.append(player_guess) | |
else: | |
self.lives.append('x') # Add x to lives | |
self.guess_archieve.append(player_guess) | |
def get_eg_status(self): | |
if(len(self.lives) == 6): | |
os.system('cls' if os.name == 'nt' else 'clear') # Clear term | |
self.end_state = True | |
messagebox.showinfo("GAME OVER!", "GAME OVER: Thanks for playing! \n Answer:\t" + str(''.join([str(elem) for elem in self.gameboard_finished]))) | |
main_form.quit() | |
elif(self.gameboard == self.gameboard_finished): | |
os.system('cls' if os.name == 'nt' else 'clear') # Clear term | |
self.end_state = True | |
messagebox.showinfo("Congrats!", "You won! Thanks for playing!") | |
main_form.quit() | |
def get_user_guess(self,letter): | |
char = str(letter) | |
if(len(char) == 1 and char.isalpha()): | |
self.set_guess(self,char.lower()) | |
else: | |
print("Guess must be a single letter!") | |
game = hangman # Create Game Object | |
game.set_Word(game) # Word in play | |
game.set_create_board(game,game.played_word) # game board | |
game.set_finished_board(game,game.played_word) # end-state | |
''' | |
End of Console Code | |
''' | |
''' | |
Build GUI interface using Tkinter | |
''' | |
main_form = Tk() # Create Form Object | |
main_form.title("Hangman") | |
main_form.geometry("600x310") # Set form size | |
main_form.resizable(0,0) # Disables Resizing | |
# GUI Vars | |
alphaList = list(string.ascii_lowercase) # Creates Alpha list | |
game.gameboard | |
gui_gameboard = tk.Label(main_form, text=game.gameboard ,font = "Verdana 30 bold") | |
gui_gameboard.pack(side="top") | |
gui_guess_archieve = tk.Label(main_form, text=game.guess_archieve ,font = "Verdana 10 bold") | |
gui_guess_archieve.pack() | |
gui_guess_archieve.place(bordermode=OUTSIDE, x=200, y=260) | |
gui_lives = tk.Label(main_form, text=game.lives ,font = "Verdana 10 bold") | |
gui_lives.pack() | |
gui_lives.place(bordermode=OUTSIDE, x=200, y=280) | |
def btn_Click(self,letter): | |
self.config(state="disabled") | |
game.get_user_guess(game,letter.lower()) | |
gui_gameboard['text'] = game.gameboard | |
gui_guess_archieve['text'] = game.guess_archieve | |
gui_lives['text'] = game.lives | |
game.get_eg_status(game) # Check End-Game Status | |
print(letter) | |
def create_button(letter,xpos,ypos,index): | |
letter = tk.Button(main_form, text=(alphaList[index].upper()),command = lambda: btn_Click(letter,alphaList[index].upper())) | |
letter.pack() | |
letter.place(bordermode=OUTSIDE, height=50, width=100,x=xpos,y=ypos) | |
def populate_board(): # Generates Form with Alpha. buttons | |
c = 0 | |
startpos = 60 | |
xpos = 0 | |
ypos = startpos | |
while(c < 26): | |
# Formating Buttons | |
if(c == 6): | |
ypos = startpos + 50 | |
xpos = 0 | |
elif(c == 12): | |
ypos = startpos + 100 | |
xpos = 0 | |
elif(c == 18): | |
ypos = startpos + 150 | |
xpos = 0 | |
elif(c == 24): | |
ypos = startpos + 200 | |
xpos = 0 | |
create_button(alphaList[c],xpos,ypos,c) | |
xpos = xpos + 100 | |
c = c + 1 | |
populate_board() | |
main_form.mainloop() | |
''' | |
End of GUI Build | |
''' | |
`#!/usr/bin/env python3
import os
import random
import string
import tkinter as tk
from tkinter import messagebox
import requests
from bs4 import BeautifulSoup
from PIL import Image, ImageTk
import io
def get_random_word(difficulty):
"""
Scrape a random word from randomword.com that meets the difficulty criteria.
Easy: 3-5 letters, Medium: 6-8 letters, Hard: 9+ letters.
"""
criteria = {
'easy': (3, 5),
'medium': (6, 8),
'hard': (9, 100)
}
min_len, max_len = criteria.get(difficulty, (6, 8))
# Loop until a word meeting the criteria is found.
while True:
try:
response = requests.get("https://randomword.com")
if response.status_code != 200:
continue
soup = BeautifulSoup(response.text, 'html.parser')
word_elem = soup.find(id="random_word")
if word_elem:
word = word_elem.text.strip().lower()
if min_len <= len(word) <= max_len:
return word
except Exception as e:
print("Error fetching random word:", e)
def get_word_hint(word):
"""
Attempt to scrape Wiktionary for an image hint for the given word.
Returns an image URL if found, otherwise None.
"""
url = f"https://en.wiktionary.org/wiki/{word}"
try:
response = requests.get(url)
if response.status_code != 200:
return None
soup = BeautifulSoup(response.text, 'html.parser')
# Look for the first image tag in the entry
img = soup.find("img")
if img and img.get('src'):
src = img['src']
if src.startswith("//"):
src = "https:" + src
elif src.startswith("/"):
src = "https://en.wiktionary.org" + src
return src
except Exception as e:
print("Error fetching hint image:", e)
return None
def get_word_definition(word):
"""
Attempt to scrape Wiktionary for a text definition of the word.
Returns the first definition found, or None if not available.
"""
url = f"https://en.wiktionary.org/wiki/{word}"
try:
response = requests.get(url)
if response.status_code != 200:
return None
soup = BeautifulSoup(response.text, 'html.parser')
# Attempt to find the English definitions.
english_span = soup.find("span", id="English")
if english_span:
# The header for English is typically an h2. Look for the first
- after that header.
for sibling in english_span.parent.next_siblings:
if sibling.name == "ol":
li = sibling.find("li")
if li:
definition = li.text.strip()
return definition
# Fallback: try to return the first ordered list definition.
ol = soup.find("ol")
if ol:
li = ol.find("li")
if li:
return li.text.strip()
except Exception as e:
print("Error fetching definition:", e)
return None
def load_hint_image(url):
"""
Downloads the image from the provided URL and returns a resized PhotoImage.
"""
try:
response = requests.get(url)
if response.status_code == 200:
image_data = response.content
image = Image.open(io.BytesIO(image_data))
image = image.resize((150, 150), Image.ANTIALIAS)
return ImageTk.PhotoImage(image)
except Exception as e:
print("Error loading hint image:", e)
return None
class Hangman:
def init(self, difficulty):
# Use scraping to choose a random word based on difficulty.
self.played_word = get_random_word(difficulty)
# Attempt to get an image hint URL from Wiktionary.
self.hint_url = get_word_hint(self.played_word)
lives_by_difficulty = {'easy': 7, 'medium': 5, 'hard': 3}
self.lives = lives_by_difficulty.get(difficulty, 5)
self.finished_board = list(self.played_word)
self.gameboard = ['_'] * len(self.played_word)
self.guess_archive = []
def process_guess(self, guess):
guess = guess.lower()
if guess in self.guess_archive:
print(f"You already guessed '{guess}'.")
return
self.guess_archive.append(guess)
if guess in self.finished_board:
for index, letter in enumerate(self.finished_board):
if letter == guess:
self.gameboard[index] = guess
else:
self.lives -= 1
def check_game_status(self):
if self.lives <= 0:
return "lost"
elif self.gameboard == self.finished_board:
return "won"
return "ongoing"
Global variables for game state and GUI components
game = None
main_form = None
letter_buttons = {}
gameboard_label = None
archive_label = None
lives_label = None
hint_label = None
current_difficulty = None
Keep a reference to the hint image to prevent garbage collection
hint_photo_ref = None
def start_game(difficulty):
global game, letter_buttons, gameboard_label, archive_label, lives_label, hint_label, hint_photo_ref, current_difficulty
current_difficulty = difficulty
# Clear any existing widgets from the main window.
for widget in main_form.winfo_children():
widget.destroy()
# Create a new game instance.
game = Hangman(difficulty)
# Set up labels for game board, guess archive, and lives.
gameboard_label = tk.Label(main_form, text=" ".join(game.gameboard), font="Verdana 30 bold")
gameboard_label.pack(pady=10)
archive_label = tk.Label(main_form, text="Guesses: " + ", ".join(game.guess_archive), font="Verdana 10 bold")
archive_label.pack(pady=5)
lives_label = tk.Label(main_form, text=f"Lives: {game.lives}", font="Verdana 10 bold")
lives_label.pack(pady=5)
# Load and display the hint image if available;
# Otherwise, attempt to show a text definition as an alternative hint.
if game.hint_url:
hint_img = load_hint_image(game.hint_url)
if hint_img:
hint_photo_ref = hint_img
hint_label = tk.Label(main_form, image=hint_photo_ref)
hint_label.pack(pady=5)
else:
definition = get_word_definition(game.played_word)
if definition:
hint_label = tk.Label(main_form, text=f"Hint: {definition}", font="Verdana 10 italic", wraplength=500)
else:
hint_label = tk.Label(main_form, text="No hint available", font="Verdana 10 italic")
hint_label.pack(pady=5)
else:
definition = get_word_definition(game.played_word)
if definition:
hint_label = tk.Label(main_form, text=f"Hint: {definition}", font="Verdana 10 italic", wraplength=500)
else:
hint_label = tk.Label(main_form, text="No hint available", font="Verdana 10 italic")
hint_label.pack(pady=5)
# Create a frame for the alphabet letter buttons.
buttons_frame = tk.Frame(main_form)
buttons_frame.pack(pady=10)
letter_buttons.clear()
alpha = list(string.ascii_lowercase)
rows, cols = 4, 7 # Arrange letters in a grid.
r, c = 0, 0
for letter in alpha:
btn = tk.Button(buttons_frame, text=letter.upper(), width=4, height=2)
btn.config(command=lambda l=letter, b=btn: on_letter_click(l, b))
btn.grid(row=r, column=c, padx=5, pady=5)
letter_buttons[letter] = btn
c += 1
if c >= cols:
c = 0
r += 1
# Bind keyboard input to the main window.
main_form.bind("<Key>", on_key_press)
def on_letter_click(letter, button):
global game
# Disable the letter button once pressed.
button.config(state="disabled")
game.process_guess(letter)
update_labels()
status = game.check_game_status()
if status in ("won", "lost"):
if status == "lost":
msg = f"GAME OVER: The word was '{game.played_word}'."
else:
msg = "Congratulations! You won!"
if messagebox.askyesno("Game Over", msg + "\nPlay again?"):
start_game(current_difficulty)
else:
main_form.destroy()
def on_key_press(event):
letter = event.char.lower()
if letter in string.ascii_lowercase and letter in letter_buttons:
button = letter_buttons[letter]
if button['state'] == tk.NORMAL:
on_letter_click(letter, button)
def update_labels():
gameboard_label.config(text=" ".join(game.gameboard))
archive_label.config(text="Guesses: " + ", ".join(game.guess_archive))
lives_label.config(text=f"Lives: {game.lives}")
def show_start_screen():
# Clear the window for the start screen.
for widget in main_form.winfo_children():
widget.destroy()
title_label = tk.Label(main_form, text="Welcome to Hangman!", font="Verdana 24 bold")
title_label.pack(pady=20)
difficulty_label = tk.Label(main_form, text="Select Difficulty:", font="Verdana 14")
difficulty_label.pack(pady=10)
difficulty_var = tk.StringVar(value="medium")
difficulties = [("Easy", "easy"), ("Medium", "medium"), ("Hard", "hard")]
for text, mode in difficulties:
rb = tk.Radiobutton(main_form, text=text, variable=difficulty_var, value=mode, font="Verdana 12")
rb.pack(anchor="w", padx=100)
start_button = tk.Button(main_form, text="Start Game", font="Verdana 14",
command=lambda: start_game(difficulty_var.get()))
start_button.pack(pady=20)
def main():
global main_form
main_form = tk.Tk()
main_form.title("Hangman")
main_form.geometry("600x500")
main_form.resizable(False, False)
show_start_screen()
main_form.mainloop()
if name == 'main':
main()
`
Below is a summary of all the major changes made from the original code:
Object-Oriented Refactoring & Separation of Concerns:
The game logic was encapsulated in an instantiated Hangman class instead of using static methods and attributes. This improved clarity and maintainability by separating game state from the GUI.
Improved GUI Layout and Input Handling:
The GUI now arranges letter buttons in a grid and uses clear labels to display the game board, guessed letters, and remaining lives.
Both mouse clicks and keyboard inputs are supported, so players can type their guesses as well as click on buttons.
Difficulty Selection and Dynamic Lives:
A start screen was added that lets the player choose a difficulty level (easy, medium, or hard). The difficulty setting dynamically adjusts the number of lives available to the player.
Dynamic Word Selection via Web Scraping:
Instead of a hardcoded word list, the game now scrapes a public dictionary website (using randomword.com) to fetch a random word that meets the chosen difficulty criteria (based on word length).
Enhanced Hint System with Scraping:
The code attempts to scrape Wiktionary for an image hint corresponding to the chosen word.
If no image hint is available or the image fails to load, it falls back to scraping a text definition as a clue.
The hint (either image or text) is then displayed in the game’s GUI.
Game Restart Without Closing the Application:
When the game ends (win or lose), a message box prompts the user to either play again or exit. If the player chooses to continue, a new game is initialized without closing the window.
External Dependencies Added:
The updated code uses additional libraries such as requests and beautifulsoup4 for web scraping and Pillow for image handling, ensuring that dynamic content (words and hints) can be loaded from the web.
The minimum requirement of GUI as follows:

A GUI should be made using Images. Images would show on after clicking on New Word
Button. The user is provided with a Textbox in which the correct word corresponding to the
image is typed. An OK button would take the input from the user. Apart from it a virtual
Keyboard is there to type the words. A counter for number of correct guesses is also given,
utmost the user can give 3 wrong answers, and hence he need to start a new game after it. The
correct names corresponding to each image and the hints are to be stored in an external database.