Introduction: The "Friends" Episode Suggestor
When Friends became available on Netflix, it was both a blessing and a curse for Friends fans. On one hand, we can all be thrilled to have such a great show streamable anywhere, anytime for our viewing pleasure... but on the other hand, with ten seasons' worth of episodes at our fingertips, the dilemma of picking which episode to watch becomes more difficult than ever before!
Rather than trying to tackle this decision on my own, I decided to write a program to help me. Using the Java programming language, I created the Friends Episode Suggestor. Among the features of this program are:
-Random episode selection and suggestion from any or all of the ten seasons
-A bar of checkboxes for selecting which seasons you want to watch and which you'd rather avoid
-Memory of the most recently suggested episodes to avoid repeat suggestions
-A color scheme matching Monica's apartment :)
In this Instructable, I will be displaying and explaining the features of the program itself, followed by a tour of the code piece-by-piece so that anyone who is interested can understand and expand the program so that it can encompass any other show (or book series or comic book line or movie saga...) of interest. For convenience, I have attached the EpisodeSuggestor.java file for anyone who wishes to edit or get a closer look at it.
A final note before I begin: I am entering this project in the Coded Creations contest, and as such, any votes, support, and/or constructive comments that you can contribute would be greatly appreciated!
Attachments
Step 1: The Basic Display
Shown here is the display window when you first run the program. Notice the swanky colors.
What we've got:
-The white box is a text area where the program will display the episode suggestions or other messages to the user.
-Clicking the "Random" button causes the program to print out a random episode suggestion chosen from whichever seasons are checked on the bottom. If no seasons are checked, the program will ask the user to select a season or start with the season 1 pilot.
-Clicking the "Reset" button clears the program's local memory of which episodes have been suggested. While the program is running, it keeps a list of which episodes from the indicated seasons have been suggested and which have not. Clicking "Reset" tells the program to start these lists over as though no episodes have yet been suggested.
-Clicking the "Clear" button clears text from the white text area, just in case the user prefers to keep the window looking tidy. Clicking "Clear" will NOT reset the suggested memory list like the "Reset" button does.
-At the bottom of the screen are ten check boxes, one for each season. If a box is checked, the program may suggest episodes from the corresponding season. If a box is unchecked, the program will not suggest episodes from that season. The user can check/uncheck boxes as he/she sees fit throughout the run of the program without adversely affecting the random suggestion processing. Note: if no boxes are checked and the user clicks "Random," the program will print a warning that at least one season needs to be checked for a random episode to be generated.
Step 2: Viewing and Running the Code
As of now, I don't know how to package java files so they can be run outside of a command prompt or an IDE, so what I will do in the ensuing steps is show screenshots of the code, accompanied by explanations of each screenshot. Ialso have the EpisodeSuggestor.java file attached so anyone who is interested can open/run it with Java editing software.
For my Java coding, I almost exclusively use Eclipse -- partly because it's what I have used in programming classes, and partly because I like the Eclipse color-coding scheme. :) The image accompanying this step is a screenshot of Eclipse on my computer, with the EpisodeSuggestor.java file opened for editing.
For those readers who are not familiar with Java or programming in general, the rest of this Instructable will be a tour of the Java code that makes the EpisodeSuggestor function, and therefore may or may not be of interest. If reading code is not your cup of tea, I thank you for your interest thus far in my project, and I encourage you to vote for this Instructable in the Coded Creations Contest! (https://www.instructables.com/contest/codedcreations/) If, however, you are interested in seeing the nuts and bolts of how my program works, let's dive right in!
Step 3: The Imports
To start things out, we have to import some tools from the Java libraries to make EpisodeSuggestor possible.
Most of the GUI (Graphic User Interface) components we need come from javax.swing, and the event handling for buttons and checkboxes comes from java.awt.event. Both of these are automatically available when you start a new project in Eclipse.
Step 4: The Class Header
I tried to JavaDoc this code fairly well, so there are little blue comments at the beginning of each method trying to describe what is going on in that section.
This particular image is the header for the whole EpisodeSuggestor class. In keeping with the way I learned to make GUIs, I had EpisodeSuggestor extend JPanel so I could directly place the GUI components in it and set it as the main window content pane. I also had EpisodeSuggestor implement the ActionListener interface so that the class, itself, would listen for action events (like the clicking of a button or the checking of a checkbox).
For those of you well versed in Java GUI programming, I know that I could have written individual action handlers for each of my interactive components, but for the relatively small number of possible action commands that the program might handle, this method seemed like a sensible and easy-to-understand way to approach things.
Step 5: The Main Method
This is the section where I define the main method of EpisodeSuggestor, which instantiates a new JFrame to be the window and a new EpisodeSuggestor object to be the window's content pane. Setting a new EpisodeSuggestor as the window's content pane here paves the way for the EpisodeSuggestor class' constructor to set up the actual look of the display and the interactive elements. As for specific window things:
-I set the window's bounds to 200,200,400,400 (so it is a 400x400 pixel window, at position 200,200 on the screen)
-I set the default close operation as EXIT_ON_CLOSE, in keeping with typical GUI practice, so the window will close when the user clicks the red x at the top of the window
-I set the window to be non-resizable because resizing the window messes with the organization of the checkboxes, and the layout of the program looks best at the defined size (400x400).
-I set the window's visibility to true so it could be seen on the computer screen. (Kind of important)
Step 6: Some Private Variables
In this section, I declare a handful of variables that we will need later in the program. I declare them here outside of a method so they can be accessible in multiple methods later on without being passed in as parameters.
-The first two variables are Colors. One is purple (for the background), and the other is yellow (for the buttons and the text area border)
-The JTextArea "text" is the main white text area where the program prints information for the user
-The JCheckBox array "seasons" is the array of checkboxes that will represent the seasons at the bottom of the main display screen
-The two-dimensional Boolean array "watched" will be set up and used in later methods to keep track of which episodes have and have not been suggested.
-The Boolean array "preferredSeasons" is used to hold values for each of the ten seasons. "true" indicates that that season's checkbox is checked, and that season should be used for random episode generation
Step 7: The Constructor
This section starts the constructor of the EpisodeSuggestor class which, as the JavaDoc comment suggests, will set up the text area, buttons, and checkboxes of the display.
What is shown here is the first part of the constructor, wherein the JTextArea "text" is set up. As some tidiness measures, I turned on linewrap and set the wrap style to word, so that lines of text too long to fit in the text area will break between words and wrap to the next line (Makes the messages in the text area easier to read). I also included a scrollpane on the text area so that when there are too many lines of text trying to be displayed, a scrollbar will appear to allow the user to navigate the text pane and read earlier lines. Without the scrollpane, the lines of text would just keep listing below the actual scope of the text area, and they would not be readable to the user.
The last few lines create a yellow and a black border to go around the text area (meant to sort of simulate the picture frame around the peephole on Monica's door). Notice that the custom yellow color made earlier is used for part of this border.
Step 8: The Buttons
This section shows the instantiation and setup of each of the three buttons from the main display. For each button:
-The button is instantiated and given an appropriate text label
-The button's action listener is set to be this EpisodePicker object, allowing all buttons' action events to be handled by one actionPerformed method defined later on.
-The button's background is set to the yellow color defined in "Some Private Variables"
The setupWatched method is called inside of the reset button section in order to set up the array that will keep track of which episodes have been suggested (and presumably watched). That method is described later in this Instructable.
Finally, a black button border is instantiated and used for each of the three buttons.
Step 9: The Season Checkboxes
This section is a bit more complicated than the preceding sections because it involves setting up an array of checkboxes.
First, a new JPanel "checkBoxes" is instantiated. This will hold the season checkboxes in the order they are created. The background of this panel is set to the "wall" color (purple) so it will blend in with the background of the main display window. I set the layout of the checkBoxes panel to a FlowLayout so the buttons would be organized in the order they are added with even spacing between them.
In the next line, I instantiate "seasons" (from Some Private Variables) as a new checkbox array of size ten. Each position in the array corresponds to a season.
In the for-loop, each box:
-Gets a title, which is just its position in the array plus one. So the checkbox at seasons[0] will have the title 1, corresponding to season 1. Season 10 will be at index 9 in the array because that's just how Java arrays are numbered.
-Is instantiated with this title
-Has the current EpisodeSuggestor set as its ActionListener
-Is set to be automatically selected when the program opens
-Gets a background color to match the main display background ("wall" color)
-Is added to the "checkboxes" JPanel
Step 10: Panel Layouts
This section has my JPanel layouts, which sum to organize the layout of the main display in its entirety.
In the first image:
-The JPanel "buttonHolder" is designated to hold the three main buttons ("random", "reset", and "clear"). It has a GridLayout with 3 rows, 1 column, and 10 pixels spacing between rows and columns. As buttons are added to the "buttonHolder", each one takes up a row. The background is set to "wall" to match the purple background.
-The JPanel "seasonPanel" holds the season checkboxes and associated label. I set a grid layout with two rows and one column. The top row holds the JPanel "labelHolder", which in turn holds the JLabel "Pick episodes from season: ". I set the background of the "labelHolder" panel and the "seasonPanel" to "wall" to match the main purple background, and I added the checkbox panel "checkBoxes" to the bottom row of "seasonPanel".
-The JPanel "textHolder" simply holds the main text area. Since the scrollbar "scroll" holds the text area, I add "scroll" to the "textHolder" panel instead of adding "text". Like the other panels, the background of "textHolder" gets set to the purple "wall" color.
In the second image, we've got larger panels. As a generalization, I set the background color of all panels to "wall" to match the purple background of the other panels:
-The JPanel "top" represents the top of the display window, and it holds the text area panel "textHolder" only.
-The JPanel "bottom" represents the bottom half of the display window, with the exception of the season checkboxes. It is set up with a grid layout (1 row, three columns). The JPanels "ph1" and "ph2" are just placeholder panels for the left and right columns of "bottom". The middle column is taken up by "buttonHolder", the panel from earlier which holds the three buttons.
-I set the layout of this EpisodePicker object to be a BorderLayout so I can place things on the edges easily.
-The JPanel "canvas" represents all of the main display with the exception of the season checkbox panel. I set canvas to be a grid with 2 rows and 1 column. The top row holds the "top" panel, and the bottom row holds the "bottom" panel.
-Finally, I add "canvas" to the center of this EpisodePicker object, and I add "seasonPanel" to the bottom (using the BorderLayout). I did this so that "seasonPanel" could stretch across the entire bottom of the screen while the three buttons would just take up the middle third.
Phew! Now that all of the GUI setup and layout is out of the way, we can look at the backend working parts of the program which store and generate random episode suggestions.
Step 11: The ActionPerformed Method
This is the method which handles any action event (clicking of a button or checking/unchecking of a checkbox) from the main display.
First, the method gets the String command associated with whatever action event is passed in. This string corresponds to the label from the GUI component where the command came from. For example, clicking the "Random" button sends an action event to actionPerformed, and the action command string from that event will be "Random". Using the string passed in, the program figures out how to respond to the action with a switch block. For those of you who don't know, a switch block is an alternative to a line of if/else blocks. I send in "cmd" to the switch, and each case is a possible value of "cmd" which should correspond to some response from the program. The following are the different cases and their associated responses:
-"Random" --> I use an int array of size 2 to represent a suggestion (index 0 is the season number, and index 1 is the episode number). The getRandom method (to be described later) returns a random suggestion, and the display method prints out the suggestion nicely on the text area.
-"Reset" --> calls the resetWatched method, which resets the watched array, essentially setting all episodes to "not suggested". After calling resetWatched, the program displays a message saying the watched list has been updated.
-"Clear" --> Sets the text area text to an empty string, effectively clearing all text from that JTextArea.
-If "cmd" is a number 1-10, that means the command came from changing the checked/unchecked state of one of the season checkboxes. I call the addSeason method and pass the appropriate season number, so addSeason can determine how to change that season's position in the "preferredSeasons" array.
-As a default, I set actionPerformed to respond to an unrecognized command by printing the command in the white text area. This was mostly for debugging purposes, and is unlikely to occur during the regular running of the program.
Step 12: The GetRandom Method
This method is really the meat of the random episode suggestion feature of the program.
First, the program checks to make sure that at least one season's checkbox is marked. If this check were not in place, the program would try to randomly generate episodes indefinitely because none of the generated episodes will match a checked season (because there are no checked seasons). If no seasons are checked, getRandom prints a warning to the user and returns the array {1,1}, which corresponds to the Pilot.
If at least one season is checked, getRandom moves on to generating a random suggestion. An int array is instantiated to hold the suggestion, and a Boolean variable "hasValidSuggestion" is defined to indicate whether the suggestion held in "suggestion" is valid (has not been recently suggested).
The way the while loop section works is: getRandom will generate new episode suggestions until one of two cases is met: either the generated suggestion has not been recently suggested and is thus valid (setting the value of "hasValidSuggestion" to true) or 1000 random episodes have been generated without a valid suggestion occuring, indicating that all possible episodes in the selected seasons have already been selected. If you're curious about why I chose 1000 times, do leave a comment and I can explain myself there. :)
After the while loop, I first check to see whether the while loop was stopped because "count" reached 1000 before a valid suggestion was generated. I take this to mean that all episodes in the selected seasons have been suggested and the user needs to select other seasons or reset the "watched" list. In this case, the suggestion is set to the Pilot and a warning is printed for the user to heed before proceeding.
Finally, if the "if(count == 1000)" condition was not met, then the while loop was stopped because a valid suggestion was generated. getRandom calls the markSuggested method to mark in the "watched" array that this particular episode has now been suggested, and returns the suggestion for display.
Step 13: The GetSeason Method
The remaining methods to discuss are mostly helper methods for generating valid, random episode suggestions. The first of these that I'll talk about is the getSeason method.
As its size indicates, getSeason is fairly simple.
First, it picks a random number between 1 and 10 (inclusive). Then, if the position in "preferredSeasons" corresponding to that season number is false (which means that season's checkbox is not checked), a new random season is generated. getSeason repeats this pattern until a season number is generated that corresponds to a "true" in "preferredSeasons" (a checked season box). This season is then returned.
As the comment indicates, the getSeason method has the potential to run indefinitely if all positions in "preferredSeasons" are false. Therefore, it is important to make sure getSeason is not called by other methods if all checkboxes are unchecked.
Step 14: The GetEpisode Method
Much like getSeason, getEpisode is quite simple.
It first instantiates an int variable named "episode".
Now, a quirk of Friends is that all seasons have 24 episodes except for season 10, which has 17. That means that I can't just generate a random number between 1 and 24 for the episode, because episodes 18-24 of season 10 don't exist and therefore shouldn't be suggested. To adjust for this, getEpisode first checks to see if the suggested season is season 10. If it is, getEpisode generates and returns a random number between 1 and 17. If the suggested season is not 10, getEpisode generates and returns a random number between 1 and 24.
Step 15: The MarkSuggested Method
markSuggested takes as a parameter the int array "suggestion", which has a season suggestion at index 0, and an episode suggestion at index 1. The int variables "seasonIndex" and "episodeIndex" are just included for clarity, and the 2D Boolean array "watched" is set to true at the position corresponding to this season/episode pairing, indicating that this particular episode has been suggested.
Step 16: The HasBeenSuggested Method
The hasBeenSuggested Method is meant to check whether a given suggestion (passed in as a parameter) has been recently suggested, return true if it has, and return false if it hasn't. It does so by simply returning the value from the "watched" array at the position corresponding to this specific season/episode suggestion.
Step 17: The NoSeasonSelected Method
The noSeasonSelected method is called to check whether there is at least one season box that is checked. noSeasonSelected returns true if no season boxes are selected, and false if at least one box is checked.
The Boolean variable "allFalse" is declared and set to true. Then, using a for:each loop cycling through "preferredSeasons", noSeasonSelected checks to see if any positions in "preferredSeasons" are true. If it finds a position that is true, "allFalse" is set to false and returned.
Step 18: The SetupWatched and ResetWatched Methods
The setupWatched and resetWatched methods, as their names imply, deal with maintaining the "watched" Boolean array which keeps track of which episodes have been suggested.
setupWatched instantiates the "watched" array declared earlier in "Some Private Variables" as a two-dimensional Boolean array with dimensions 10x24 (ten seasons and 24 episodes per season -- There are 7 positions in the array corresponding to season 10 that don't represent anything because season 10 has only 17 episodes. I decided to make the array like this and have the 7 unused positions, rather than making a complicated structure like a linked array or something that would have no unused space) and then calls resetWatched to fill the positions in the array
resetWatched cycles through each array in "watched" and within those cycles, loops through each position in the array, setting every value to "false". This, in essence, resets the program's database so that it appears as though no episodes have been suggested (watched) yet.
Step 19: The AddSeason Method
The addSeason method essentially updates a given season's position in the "preferredSeasons" array to correspond with the checked/unchecked state of its checkbox. A season is passed in as a parameter, and addSeason first checks to see whether the checkbox associated with that season is selected. If it is not, "preferredSeasons" at the index associated with this season is set to false. Otherwise, that position is set to true.
As a reminder, the "preferredSeasons" array is used to represent whether or not a given season is checked, and thus capable of being suggested when the user clicks the "Random" button.
Step 20: The Display Method
The display method provides an easy way to print a suggestion on the main text area with only a single method call (display(suggestion);) rather than having to manually interact with the text area every time some method calls for printing a suggestion on the screen.
The way display works is:
-A String is built which reads "Season: " and then the season number (suggestion array at index 0), followed by "Episode: " followed by the episode number (suggestion array at index 1) punctuated with a newline character so no text is appended at the end of this line.
-The String is appended to the text area
After display, we see the end bracket for the EpisodeSuggestor class, meaning that we're done!
Step 21: Expansions and Improvements
I have really enjoyed writing and using the Episode Suggestor, and every use brings new ideas for improvement to the program. Among those ideas are serialization of the watched playlist for persistent data storage and plugging into a text document of episode titles so that they can be displayed alongside Season and Episode numbers. Another possibility that this code possesses is the potential to expand to other series. By changing the dimensions of the "watched" and "preferredSeasons" arrays and with a few tweaks to the checkboxes and associated algorithms, the EpisodeSuggestor.java code can be adapted to suggest random choices of any number of other TV shows or other series-style entertainment. All you need is some creativity and a bit of patience.
I would like to thank you very much for your interest in my Instructable, and feel free to post comments, questions, and/or suggestions as you see fit! Also, if you haven't already, please vote for this project in the Coded Creations contest found at the following link!
https://www.instructables.com/contest/codedcreations/
Happy coding!