33import io
44from random import randint , choice , sample
55import asyncio
6- import time
76import aiohttp
87import os
98from pygame import mixer
109
1110
1211class Tinder :
1312 def __init__ (self ):
13+ # setup for pygame mixer
1414 mixer .init ()
15+
16+ # getting the directory folder for use later when opening files
1517 self .dir = os .path .dirname (os .path .realpath (__file__ ))
18+
19+ # setting up the tkinter root
1620 self .root = tk .Tk ()
1721 self .root .title ("Cat Tinder" )
1822 self .root .geometry ("400x500" )
1923 self .root .minsize (400 , 500 )
2024 self .root .maxsize (400 , 500 )
2125 self .root .configure (background = 'black' )
2226 self .root .protocol ("WM_DELETE_WINDOW" , self .on_closing )
23- #self.root.bind('<Motion>', self.motion)
27+
28+ # getting screen width and height for use with teleporting window/jumpscare
2429 self .screen_x = self .root .winfo_screenwidth ()
2530 self .screen_y = self .root .winfo_screenheight ()
31+
32+ # setting class variables to be used later
2633 self .jumpscare = False
2734 self .loop = asyncio .get_event_loop ()
2835 self .session = None
29- self .images = list ()
30-
31-
36+ self .cats = list ()
37+
3238 def start (self ):
39+ '''Starts the Tinder application'''
40+
41+ # getting a cache of cat info
3342 self .loop .run_until_complete (self .get_cache ())
34- self .new_image ()
3543
44+ # starting the program loop
45+ self .new_image ()
3646
3747 async def get_cache (self ):
38- print ("refreshing image cache" )
39- t = time .time ()
48+ '''Gets a cache of cat data and adds it to the self.cats list'''
49+
50+ # if we haven't created a session yet, do so
4051 if not self .session :
4152 self .session = aiohttp .ClientSession ()
53+
54+ # Run 10 times to get 10 cats
4255 for i in range (10 ):
56+ # initialize a dict of cat data
4357 cat_data = dict ()
44- if randint (1 ,10 ) == 5 and i :
45- image_number = randint (1 ,10 )
46- im = Image .open (os .path .join (self .dir , os .path .join ("res" , os .path .join ("images" , f"{ image_number } .jpg" ))))
58+
59+ # randomly make jumpscares happen, but not on the first image
60+ if randint (1 , 10 ) == 5 and i :
61+ # get a random number for an image
62+ image_number = randint (1 , 10 )
63+ # open and resize the image using Pillow
64+ im = Image .open (os .path .join (self .dir , os .path .join ("res" ,
65+ os .path .join ("images" , f"{ image_number } .jpg" ))))
4766 im = im .resize ((self .screen_x , self .screen_y - 150 ), Image .NEAREST )
67+ # make the image a tkinter image
4868 image = ImageTk .PhotoImage (im )
49- cat_data .update ({"image" : image })
69+ # update the cat data dict
70+ cat_data .update ({"image" : image })
5071 cat_data .update ({"jumpscare" : True })
5172 else :
73+ # set jumpscare to False because it isnt a jumpscare image
5274 cat_data .update ({"jumpscare" : False })
75+
76+ # get a url from The Cat API
5377 async with self .session .get ('https://api.thecatapi.com/v1/images/search' ) as res :
5478 data = await res .json ()
5579 url = data [0 ]['url' ]
80+ # get image data from that url
5681 async with self .session .get (url ) as res :
5782 image_bytes = await res .read ()
83+ # open and the image in pillow
5884 im = Image .open (io .BytesIO (image_bytes ))
5985 im = im .resize ((400 , 440 ), Image .NEAREST )
86+ # make the image a tkinter image
6087 image = ImageTk .PhotoImage (im )
61- cat_data .update ({"image" :image })
62- async with self .session .get ("https://www.pawclub.com.au/assets/js/namesTemp.json" ) as res :
63- data = await res .json ()
64- letter = choice (list ('acdefghijklmnopqrstuvwxyz' ))
65- cat = choice (data [letter ])
66- cat_data .update ({"name" :cat ["name" ]})
67- cat_data .update ({"gender" :cat ["gender" ]})
68- async with self .session .get ("https://gist.githubusercontent.com/mbejda/453fdb77ef8d4d3b3a67/raw/e8334f09109dc212892406e25fdee03efdc23f56/hobbies.txt" ) as res :
69- text = await res .text ()
70- all_hobbies = text .split ("\n " )
71- hobby_list = sample (all_hobbies , 5 )
72- list_of_hobbies = "\n •" .join (hobby_list )
73- hobbies = f"Hobbies:\n •{ list_of_hobbies } "
74- cat_data .update ({"hobbies" :hobbies })
75- age = str (randint (1 ,15 ))
76- cat_data .update ({"age" :age })
77- miles = randint (1 ,5 )
78- location = f"{ miles } miles away"
79- cat_data .update ({"location" :location })
80- self .images .append (cat_data )
81- print (time .time ()- t )
88+ # update the cat data dict
89+ cat_data .update ({"image" : image })
8290
91+ # get a random name
92+ async with self .session .get (
93+ "https://www.pawclub.com.au/assets/js/namesTemp.json" ) as res :
94+ data = await res .json ()
95+ # get a random letter for the name
96+ # Note: website doesn't have any b names which is why it is left out here
97+ letter = choice (list ('acdefghijklmnopqrstuvwxyz' ))
98+ # randomly choose a name from the json with that letter
99+ cat = choice (data [letter ])
100+ # update the cat data dict
101+ cat_data .update ({"name" : cat ["name" ]})
102+ cat_data .update ({"gender" : cat ["gender" ]})
103+
104+ # get 5 random hobbies
105+ async with self .session .get (
106+ "https://gist.githubusercontent.com/mbejda/" +
107+ "453fdb77ef8d4d3b3a67/raw/e8334f09109dc212892406e25fdee03efdc23f56/" +
108+ "hobbies.txt"
109+ ) as res :
110+ text = await res .text ()
111+ # split the raw text of hobbies into a list
112+ all_hobbies = text .split ("\n " )
113+ # get 5 of those hobbies
114+ hobby_list = sample (all_hobbies , 5 )
115+ # join those 5 hobbies into a bulleted list (string)
116+ list_of_hobbies = "\n •" .join (hobby_list )
117+ hobbies = f"Hobbies:\n •{ list_of_hobbies } "
118+ # update the cat_data dict
119+ cat_data .update ({"hobbies" : hobbies })
120+
121+ # get a random age between 1 and 15 (avg lifespan of a cat)
122+ age = str (randint (1 , 15 ))
123+ # update the cat data dict
124+ cat_data .update ({"age" : age })
125+
126+ # get a random number of miles away between 1 and 5
127+ miles = randint (1 , 5 )
128+ location = f"{ miles } miles away"
129+ # update the cat data dict
130+ cat_data .update ({"location" : location })
131+ self .cats .append (cat_data )
83132
84133 def all_children (self ):
134+ '''Used to get all children of the root window
135+
136+ Returns
137+ -------
138+ A `list` of tkinter objects that are children of the root window'''
139+
140+ # get children
85141 children = self .root .winfo_children ()
142+ # loop through the children
86143 for item in children :
144+ # if the child has children, add them to the list
87145 if item .winfo_children ():
88146 children .extend (item .winfo_children ())
147+ # return the full list of children
89148 return children
90149
150+ def new_image (self , cat = None ):
151+ '''Main functionality of the application
91152
92- def new_image (self , cat = None ):
153+ param
154+ cat: dict -- if you want to run the function without getting a new cat'''
155+
156+ # if the previous image was a jumpscare, resize the window and reset the variable
93157 if self .jumpscare :
94158 self .root .maxsize (400 , 500 )
95159 self .jumpscare = False
160+
161+ # get all children of the root window
96162 widget_list = self .all_children ()
97163 for item in widget_list :
164+ # forget packs all of the children (This clears the window)
98165 item .pack_forget ()
99- self .frame = tk .Frame (self .root , bg = "black" )
166+
167+ # make a new Frame
168+ self .frame = tk .Frame (self .root , bg = "black" )
169+
170+ # if a dict wasn't passed to the function, get a dict from self.cats
100171 if not cat :
101172 try :
102- cat = self .images .pop (0 )
173+ cat = self .cats .pop (0 )
103174 except IndexError :
175+ # the cache is empty so refill it
104176 self .loop .run_until_complete (self .get_cache ())
105- cat = self .images .pop (0 )
177+ cat = self .cats .pop (0 )
178+
179+ # getting cat variables from the dict
106180 cat_name = cat ["name" ]
107181 image = cat ["image" ]
108182 jumpscare = cat ["jumpscare" ]
109183 gender = cat ["gender" ].capitalize ()
110184 hobbies = cat ["hobbies" ]
111185 age = cat ["age" ]
112186 location = cat ["location" ]
187+
188+ # if the image is not a jumpscare, add a Text widget with the cat name
113189 if not jumpscare :
114- name = tk .Text (self .frame , width = 40 , height = 1 )
190+ # make the Text widget
191+ name = tk .Text (self .frame , width = 40 , height = 1 )
192+ # tag to make all text in the widget centered
115193 name .tag_configure ("center" , justify = tk .CENTER )
194+ # insert the cat name into the widget
116195 name .insert ("1.0" , cat_name )
196+ # add the centered tag to all text in the widget
117197 name .tag_add ("center" , "1.0" , tk .END )
198+ # disable the widget so the user can't type in it
118199 name .configure (state = "disabled" )
119- name .pack (side = tk .TOP )
120- tk .Label (self .frame , image = image ).pack (side = tk .TOP )
200+ # pack the widget on the top of the frame
201+ name .pack (side = tk .TOP )
202+
203+ # make a Label widget with the cat/jumpscare image and pack it
204+ tk .Label (self .frame , image = image ).pack (side = tk .TOP )
205+
206+ # the image is a jumpscare, so do jumpscare things
121207 if jumpscare :
208+ # remember that this image is a jumpscare
122209 self .jumpscare = True
210+
211+ # allow the root window to get bigger
123212 self .root .maxsize (self .screen_x , self .screen_y )
213+ # make the root window bigger (makes jumpscare image scarier)
124214 self .root .geometry (f"{ self .screen_x } x{ self .screen_y } +0+0" )
125- mixer .music .load (os .path .join (self .dir , os .path .join ("res" , os .path .join ("sounds" , "jumpscare.mp3" ))))
215+
216+ # play a jumpscare sound
217+ mixer .music .load (
218+ os .path .join (self .dir , os .path .join (
219+ "res" , os .path .join ("sounds" , "jumpscare.mp3" ))
220+ )
221+ )
126222 mixer .music .play ()
127- tk .Button (self .frame , text = "Like" , background = "green" , command = self .new_image ).pack (side = tk .BOTTOM )
223+
224+ # make a button to allow the user to pass through the image
225+ # Note: since everyone likes scary monsters, only make a Like button
226+ tk .Button (
227+ self .frame , text = "Like" , background = "green" , command = self .new_image
228+ ).pack (side = tk .BOTTOM )
229+
230+ # image was not a jumpscare, don't do jumpscare things
128231 else :
129- tk .Button (self .frame , text = "Like" , background = "green" , command = self .new_image ).pack (side = tk .RIGHT )
130- tk .Button (self .frame , text = "Dislike" , background = "red" , command = self .new_image ).pack (side = tk .LEFT )
232+ # setting up like and dislike buttons on opposite sides of the screen
233+ tk .Button (
234+ self .frame , text = "Like" , background = "green" , command = self .new_image
235+ ).pack (side = tk .RIGHT )
236+ tk .Button (
237+ self .frame , text = "Dislike" , background = "red" , command = self .new_image
238+ ).pack (side = tk .LEFT )
239+
240+ # defining button functions
131241 def back_to_photo ():
242+ '''Resets the window with the same cat for when the user
243+ goes to bio and clicks back'''
244+
245+ # calls the new image function and passes the current cat dict
132246 self .new_image (cat )
247+
133248 def get_bio ():
249+ '''Creates the Bio Widget for the current cat'''
250+
251+ # get all children of the root window
134252 widget_list = self .all_children ()
135253 for item in widget_list :
254+ # forget packs all of the children (This clears the window)
136255 item .pack_forget ()
137- self .frame = tk .Frame (self .root , bg = "black" , height = 450 , width = 400 )
256+
257+ # make a new Frame for the bio
258+ self .frame = tk .Frame (self .root , bg = "black" , height = 450 , width = 400 )
259+
260+ # makes a Text widget on the Frame
138261 bio = tk .Text (self .frame )
262+
263+ # inserting all of the Bio to the text widget
139264 bio .insert (tk .END , f"Name: { cat_name } \n " )
140265 bio .insert (tk .END , f"Age: { age } \n " )
141266 bio .insert (tk .END , f"Gender: { gender } \n " )
142267 bio .insert (tk .END , f"Location: { location } \n " )
143- bio .insert (tk .END , f"{ hobbies } \n " )
268+ bio .insert (tk .END , f"{ hobbies } \n " )
269+ # disabling the widget so users can't edit it
144270 bio .configure (state = "disabled" )
145- bio .pack (side = tk .TOP )
146- tk .Button (self .frame , text = "Like" , background = "green" , command = self .new_image ).pack (side = tk .RIGHT )
147- tk .Button (self .frame , text = "Dislike" , background = "red" , command = self .new_image ).pack (side = tk .LEFT )
148- tk .Button (self .root , text = "Back To Photo" , background = "blue" , command = back_to_photo ).pack (side = tk .BOTTOM )
271+ # packing the bio
272+ bio .pack (side = tk .TOP )
273+
274+ # setting up like/dislike/Back to Photo buttons on the bio screen
275+ tk .Button (
276+ self .frame , text = "Like" , background = "green" , command = self .new_image
277+ ).pack (side = tk .RIGHT )
278+ tk .Button (
279+ self .frame , text = "Dislike" , background = "red" , command = self .new_image
280+ ).pack (side = tk .LEFT )
281+ tk .Button (
282+ self .root , text = "Back To Photo" , background = "blue" , command = back_to_photo
283+ ).pack (side = tk .BOTTOM )
284+
285+ # packing the frame
149286 self .frame .pack ()
150- tk .Button (self .frame , text = "Bio" , background = "blue" , command = get_bio ).pack (side = tk .BOTTOM )
287+
288+ # making and packing the Bio button for users to look at the cat's bio
289+ tk .Button (
290+ self .frame , text = "Bio" , background = "blue" , command = get_bio
291+ ).pack (side = tk .BOTTOM )
292+
293+ # packing the frame
151294 self .frame .pack ()
152- self .root .mainloop ()
153295
296+ # starting the main tkinter loop
297+ self .root .mainloop ()
154298
155299 def on_closing (self ):
300+ '''Teleports the window if the user tries to close the app using the red X'''
301+
302+ # checks if the image is a jumpscare (if so, can't teleport the
303+ # window because it takes up the entire screen)
156304 if not self .jumpscare :
305+ # get the max x and y values that the window can teleport
306+ # to without going off the screen
157307 max_x , max_y = self .screen_x - 400 , self .screen_y - 500
308+ # getting the random x and y values to teleport to
158309 x , y = randint (0 , max_x ), randint (0 , max_y )
310+ # moving the window to those x and y coordinates
159311 self .root .geometry (f"+{ x } +{ y } " )
160312
161313
162- def motion (self ,event ):
163- frame_x , frame_y = self .frame .winfo_x (), self .frame .winfo_y ()
164- x , y = event .x , event .y
165- if x > 300 and y < 15 :
166- move_x = (400 - x ) + frame_x
167- move_y = (15 - y ) - frame_y
168- self .root .geometry (f"400x500+{ move_x } +{ move_y } " )
169-
170-
314+ # checks if this file is the main file being run
171315if __name__ == "__main__" :
172- Tinder ().start ()
316+ # start the application
317+ Tinder ().start ()
0 commit comments