Computer Science
Gilmour AcademyLancerTech
  • Our Curriculum and Department
  • Intro to Programming
    • 1: Parts of a Computer
    • 2: Parts of Python
    • 3: DRY Turtle
    • 4: Turtle Design App
    • Wordle with Turtles
    • 5: Interactive Turtles
    • OLD 5: Replit, GitHub, and repositories (Oh my!)
    • 6: Raspberry Pi / GoPiGo
    • 7: Kivy
  • Intro to Web Design
    • 1: Internet?
    • 2: Websites?
    • 3: Bootstrap Template
    • 4: Graphics and Branding
    • 5: Collaboration
    • 6: Advanced Editing
    • Publish Static HTML
  • AP Computer Science
    • 1: Logic & Instances
    • 2: How Java Works
    • 3: Data Types & Flow
    • 4: Strings
    • 5: Objects & References
    • 6: Inheritance & Algorithms
    • 7: Data Structures
    • 8: Sorting
    • 9: Review
    • Data Science
  • Web App Dev
    • 1: Core Concepts
    • 2: MVT Pattern
    • 3: Hello Flask
    • 4: Install Flaskinni
    • 5: Tour Flaskinni
    • 6: Visualize Your App
    • 7: Theme & Blueprint
    • 8: Standup Your DB
    • 9: Advanced Topics
    • 10: Deployment
  • 2D Game Design
    • Class Overview
    • Gamemaker Studio 2 and Github Setup
    • Game 1: Bouncing Ball
    • Turning in your games
    • Game 2: Maze
    • Game 3: Ping Pong
    • Game 4: Breakout
    • Game 5: Tank Battle
    • Game 6 Highlights
    • DO NOT DO:
    • Game 7: Final Project
    • Publish to Opera
    • FAQ
  • 3D Game Design
    • 1: Class Overview
    • 2: Installation
    • 3: Exploring the Unity UI
    • Game 1: Rolling Ball
    • Game 2: Tanks
    • Game 3: Third Person Platformer
    • Game 4: Final project
    • FAQs
    • OLD: Distance Learning Setup
    • OLD: GIT
  • 3D Modeling & Fabrication
    • Installation
    • Fusion 360 Interface and Sketch Modeling
    • Primitive Modeling
    • Patterns
    • Appearances and Rendering
    • Building Community Gallery Page 2023
    • Parametric Modeling
    • 3D Printing Concerns
    • Assemblies and Mechanical Design
    • Laser Cutting
    • Sculpt Tools
    • Milling Concerns
  • Robotics 7
    • Software Installation
    • Python basics (trinket.io)
    • Python Turtle
    • Programming for the Ev3
    • Setting up for clarity
  • Robotics 8
    • Replit
    • Python review
    • Kivy Basics
    • Calculator
  • Competitive Robotics
    • Hardware Team
      • CAD Examples
      • Elevators
    • Software Team
      • Command Pattern
      • Example Command
      • Subsystem
      • Running Your Code
      • Under the Hood
      • RoadRunner
      • Vision Processing
  • Archives
    • Adiletta Archives
      • Old Web
        • Ex: WordPress CMS
      • ItP
        • OLD: Parts of Python (old -- Mr. A)
        • OLD: 5: Raspberry Pi
        • OLD: 6: Deploying Code
        • OLD 7: Nav Algorithm
    • Vanek Archives
      • OLD Robotics 8
        • OLD: End of Class Project
      • OLD Competitive Robotics
        • Untitled
        • Webots Videos
      • OLD Robotics 7
        • Trinket Introduction
        • Lists: x/y position
        • Functions: Math program
        • Lists: Grocery List
        • Study Guide Program
        • Tic Tac Toe Game
        • Dice Roller Program
        • Visualization
        • Dice Roller + Visualization
        • OpenSCAD: Installation
        • OpenSCAD: Command Sheet and Intro
        • OpenSCAD: Difference
        • OpenSCAD: Variables
        • OpenSCAD: Union
        • OpenSCAD: For Loops
        • OpenSCAD: Final Project
      • OLD Art I - Blender Sculpting
        • Class Overview
        • Installation
        • Lesson 1 - Tools
        • Lesson 2 - Detail
        • Lesson 3 - Base Mesh: Metaballs
        • Lesson 4: Converting metaballs and adding detail
        • Lesson 5: Masking, Hiding, and Working with Multiple Objects
        • Lesson 6: Joining Objects & Basing
        • Lesson 7: Sculpture Painting
        • Student Gallery: Animal Sculpts
        • Lesson 8: 3D Compositon
        • Lesson 9: The Project - Putting it all together
        • Lesson 10: Developing the image further
        • Lesson 11: Layout the base metaball mesh.
        • Lesson 12: Final Detail
        • Lesson 13: Basing and Painting
        • Final Project Gallery
      • OLD Fab
        • OLD Building Community Project Gallery
        • Copy of Building Community Project Gallery
        • old Building Community Project Gallery
      • OLD: Turtle Design App
      • OLD Arduino Robotics 8
        • Arduino Basic Commands Cheat Sheet
        • Logging into Tinkercad
        • Arduino, Circuits, LEDs and Resistors
        • Functions and Variables
        • Serial Monitor
        • Buttons and Interrupts
        • Traffic Light Project
        • Potentiometers + Servos
        • Piezo Buzzer and Tone();
        • Sequencer Project
        • Arrays and for loops
        • Extra Loop Practice
        • Refining the Sequencer
        • Servos
        • Ultrasonic Sensors
        • Final Project
Powered by GitBook
On this page
  • Activity 1: Getting Started
  • Understanding the Class Header and Interfaces
  • Properties and Encapsulation
  • Accessor Methods (Getters)
  • Test-Driven Development
  • Adding GameDataManager and Testing
  • Activity 2: Building Methods
  • Method 1: getAverageScore()
  • Method 2: getSalesPercentages()
  • Method 3: hasSalesData()
  • Method 4: hasCriticReviews()
  • Method 5: getDominantRegion()
  • Method 6: getRegionalSales()
  • Method 7: Implementation of Comparable
  • Activity 3: Results
  • Creating Your Own Analysis
  • Activity 4: Recursion
  • Understanding Recursion
  • Why Use Recursion?
  • Implementing Recursion in Game Data Analysis
  • Your Task
  • Example: Recursive Method for Maximum Critic Score

Was this helpful?

Export as PDF
  1. AP Computer Science

Data Science

Previous9: ReviewNextWeb App Dev

Last updated 1 month ago

Was this helpful?

I wanted to put our own spin on the Data lab from CollegeBoard for this project. Rather than just following the standard lab, we're going to analyze real-world video game sales data that will give you hands-on experience with classes, interfaces, and data processing in Java.

For this project, we'll be using a comprehensive dataset containing information about video games, including sales figures, critic ratings, user scores, and more. I've sourced this dataset from Kaggle: .

This project will give you practical experience with:

  • Creating and using custom classes

  • Working with interfaces

  • Processing CSV data files

  • Implementing search and analysis algorithms

  • Visualizing data patterns

Activity 1: Getting Started

Our project requires two main Java classes:

  • GameDataRow - Represents a single game record from our dataset

  • GameDataManager - Handles loading and analyzing collections of game data

Let's start by looking at how to set up the GameDataRow class:

/**
 * The GameDataRow class represents a single video game from the dataset
 * and provides methods for analyzing game data including sales, ratings, and more.
 */
public class GameDataRow implements Comparable<GameDataRow> {
    // Instance variables will go here
    // Methods will go here
}

Understanding the Class Header and Interfaces

The class header public class GameDataRow implements Comparable<GameDataRow> has several important components:

  1. public - This access modifier makes the class accessible from outside its package.

  2. class GameDataRow - Defines a new class named GameDataRow.

  3. implements Comparable<GameDataRow> - Indicates that this class implements the Comparable interface.

The Comparable Interface

The Comparable interface is part of the Java Collections Framework and enables objects of a class to be compared to each other. When a class implements Comparable, it must provide an implementation of the compareTo() method:

@Override
public int compareTo(GameDataRow other) {
    // if you want to implement Comparable, you'll need this sort of thing
    if (this.number > other.number) return -1;
    if (this.number < other.number) return 1;
    return 0;
}

The compareTo() method must return:

  • A negative value if this object should be ordered before the other object

  • Zero if both objects are equal

  • A positive value if this object should be ordered after the other object

In our implementation, we're sorting games by global sales in descending order (highest sales first).

Other Common Interfaces in Java

While we're only using Comparable in this project, here are other common interfaces you might encounter:

  • Serializable - Marks a class as capable of being serialized (converted to a byte stream).

public class MyClass implements Serializable { ... }
  • Runnable - Used for classes whose instances will be executed by a thread.

    public class MyTask implements Runnable {
        public void run() {
            // Code to be executed in a thread
        }
    }
  • ActionListener - Used for handling action events, commonly in GUI applications.

    public class ButtonHandler implements ActionListener {
        public void actionPerformed(ActionEvent e) {
            // Handle button click
        }
    }
  • Iterable - Allows an object to be the target of the "for-each loop" statement.

    public class MyCollection<T> implements Iterable<T> {
        public Iterator<T> iterator() {
            // Return an iterator
        }
    }

Properties and Encapsulation

Now, let's determine what properties our GameDataRow class needs. Our CSV file contains 17 columns of data about each game, and we want to represent each data point as instance variables in our class.

Looking at the structure of our CSV file, we need to create properties that match each column:

// Instance variables
private int index;
private String name;
private String platform;
private float yearOfRelease;
private String genre;
private String publisher;
private float naSales;
private float euSales;
private float jpSales;
private float otherSales;
private float globalSales;
private float criticScore;
private float criticCount;
private String userScore; // Note: String type, not float
private float userCount;
private String developer;
private String rating;

// Static variable to track number of games analyzed
private static int gamesAnalyzed = 0;

// Constants for regional sales references
public static final int NA_REGION = 0;
public static final int EU_REGION = 1;
public static final int JP_REGION = 2;
public static final int OTHER_REGION = 3;

Encapsulation Review

Remember that encapsulation is one of the four fundamental OOP concepts, along with inheritance, polymorphism, and abstraction. Encapsulation means bundling data and methods that operate on that data within a single unit (the class) and restricting direct access to some of the object's components.

Notice all our instance variables are marked as private. This is a key principle of encapsulation:

  1. Private instance variables: By making our instance variables private, we prevent outside classes from directly accessing or modifying them.

  2. Public accessor methods: We provide controlled access through getter methods (and sometimes setter methods).

  3. Data validation: Through these methods, we can validate any changes to ensure they meet our requirements.

Good encapsulation ensures that the internal representation of an object can't be seen from outside the object's definition, which creates a "black box" effect.

Constructors

We need constructors to initialize our GameDataRow objects. A well-designed class should have multiple constructors to provide flexibility in object creation:

/**
 * Primary constructor for creating a new GameDataRow object with all fields
 */
public GameDataRow(int index, String name, String platform, float yearOfRelease, 
                   String genre, String publisher, float naSales, float euSales, 
                   float jpSales, float otherSales, float globalSales, 
                   float criticScore, float criticCount, String userScore, 
                   float userCount, String developer, String rating) {
    this.index = index;
    this.name = name;
    this.platform = platform;
    this.yearOfRelease = yearOfRelease;
    this.genre = genre;
    this.publisher = publisher;
    this.naSales = naSales;
    this.euSales = euSales;
    this.jpSales = jpSales;
    this.otherSales = otherSales;
    this.globalSales = globalSales;
    this.criticScore = criticScore;
    this.criticCount = criticCount;
    this.userScore = userScore;
    this.userCount = userCount;
    this.developer = developer;
    this.rating = rating;
    gamesAnalyzed++;
}

/**
 * Simplified constructor with essential fields only
 */
public GameDataRow(String name, String platform, float yearOfRelease, 
                   String genre, String publisher, float globalSales) {
    this.index = -1; // Default value for index
    this.name = name;
    this.platform = platform;
    this.yearOfRelease = yearOfRelease;
    this.genre = genre;
    this.publisher = publisher;
    this.naSales = 0.0f;
    this.euSales = 0.0f;
    this.jpSales = 0.0f;
    this.otherSales = 0.0f;
    this.globalSales = globalSales;
    this.criticScore = 0.0f;
    this.criticCount = 0.0f;
    this.userScore = "0.0";
    this.userCount = 0.0f;
    this.developer = "Unknown";
    this.rating = "Not Rated";
    gamesAnalyzed++;
}

/**
 * Copy constructor that creates a deep copy of another GameDataRow object
 */
public GameDataRow(GameDataRow other) {
    this.index = other.index;
    this.name = other.name;
    this.platform = other.platform;
    this.yearOfRelease = other.yearOfRelease;
    this.genre = other.genre;
    this.publisher = other.publisher;
    this.naSales = other.naSales;
    this.euSales = other.euSales;
    this.jpSales = other.jpSales;
    this.otherSales = other.otherSales;
    this.globalSales = other.globalSales;
    this.criticScore = other.criticScore;
    this.criticCount = other.criticCount;
    this.userScore = other.userScore;
    this.userCount = other.userCount;
    this.developer = other.developer;
    this.rating = other.rating;
    gamesAnalyzed++;
}

Notice I've included three constructors:

  1. A primary constructor with all fields

  2. A simplified constructor with only essential fields

  3. A copy constructor to create a deep copy of an existing object

Accessor Methods (Getters)

Now we need accessor methods (getters) for all our instance variables. This is a perfect opportunity to use AI responsibly. Rather than typing out all these repetitive methods, you can ask Claude or an IDE's chatbot to generate them for you.

For example, you could say:

Please generate all the accessor methods for the GameDataRow class based on the instance variables.

This isn't plagiarism or cutting corners - it's using a tool to handle the tedious, repetitive aspects of coding so you can focus on the more challenging and creative aspects like designing algorithms and implementing the business logic.

Here's a snippet showing what a few of these accessor methods would look like:

// Accessor methods (getters)
public int getIndex() { return index; }
public String getName() { return name; }
public String getPlatform() { return platform; }
public float getYearOfRelease() { return yearOfRelease; }
public String getGenre() { return genre; }
public String getPublisher() { return publisher; }
// ... and so on for all instance variables

Test-Driven Development

Let's talk about Test-Driven Development (TDD) - a software development approach where you write tests before writing the actual implementation code. This practice has been widely adopted by professional developers because it leads to more reliable, maintainable code.

In TDD, the process follows three simple steps, often called "Red-Green-Refactor":

  1. Write a test that fails (Red)

  2. Write the minimal code to make the test pass (Green)

  3. Improve the code without changing its behavior (Refactor)

For our project, I've prepared a complete main method that serves as a test suite. It will systematically test all the required functionality of your GameDataRow class and tell you exactly what's missing or not working correctly.

/**
 * Main method for testing the GameDataRow class
 */
public static void main(String[] args) {
    System.out.println("===== GAME DATA ROW TEST SUITE =====\n");
    int testsPassed = 0;
    int totalTests = 9;
    
    try {
        // Test 1: Basic constructor and accessors
        GameDataRow game1 = new GameDataRow(
            1, "Wii Sports", "Wii", 2006.0f, "Sports", "Nintendo", 
            41.49f, 29.02f, 3.77f, 8.46f, 82.74f, 
            76.0f, 51.0f, "8.0", 322.0f, "Nintendo", "E"
        );
        
        if (game1.getName().equals("Wii Sports") && 
            game1.getPlatform().equals("Wii") &&
            game1.getGlobalSales() == 82.74f) {
            System.out.println("✓ Test 1 Passed: Basic constructor and accessors working");
            testsPassed++;
        } else {
            System.out.println("✗ Test 1 Failed: Basic constructor or accessors not working correctly");
        }
        
        // Test 2: Simplified constructor
        GameDataRow game2 = new GameDataRow("Super Mario Bros.", "NES", 1985.0f, 
                                           "Platform", "Nintendo", 40.24f);
        
        if (game2.getName().equals("Super Mario Bros.") && 
            game2.getGlobalSales() == 40.24f) {
            System.out.println("✓ Test 2 Passed: Simplified constructor working");
            testsPassed++;
        } else {
            System.out.println("✗ Test 2 Failed: Simplified constructor not working correctly");
        }
        
        // Test 3: Copy constructor
        GameDataRow game3 = new GameDataRow(game1);
        
        if (game3.getName().equals(game1.getName()) && 
            game3.getGlobalSales() == game1.getGlobalSales() &&
            game3 != game1) {  // Different objects
            System.out.println("✓ Test 3 Passed: Copy constructor working");
            testsPassed++;
        } else {
            System.out.println("✗ Test 3 Failed: Copy constructor not working correctly");
        }
        
        // Test 4: Static counter
        int expectedCount = 3;  // game1, game2, game3
        if (GameDataRow.getGamesAnalyzed() == expectedCount) {
            System.out.println("✓ Test 4 Passed: Static counter working");
            testsPassed++;
        } else {
            System.out.println("✗ Test 4 Failed: Static counter expected " + 
                              expectedCount + " but got " + 
                              GameDataRow.getGamesAnalyzed());
        }
        
        // Test 5: Get regional sales
        if (Math.abs(game1.getRegionalSales(GameDataRow.NA_REGION) - 41.49f) < 0.01f) {
            System.out.println("✓ Test 5 Passed: Regional sales accessor working");
            testsPassed++;
        } else {
            System.out.println("✗ Test 5 Failed: Regional sales accessor not working correctly");
        }
        
        // Test 6: Dominant region
        if (game1.getDominantRegion().equals("North America")) {
            System.out.println("✓ Test 6 Passed: Dominant region calculation working");
            testsPassed++;
        } else {
            System.out.println("✗ Test 6 Failed: Dominant region expected 'North America' but got '" + 
                              game1.getDominantRegion() + "'");
        }
        
        // Test 7: Sales percentages
        float[] percentages = game1.getSalesPercentages();
        if (percentages.length == 4 && 
            Math.abs(percentages[0] - 50.14f) < 0.1f) {  // ~50.14% for NA
            System.out.println("✓ Test 7 Passed: Sales percentages calculation working");
            testsPassed++;
        } else {
            System.out.println("✗ Test 7 Failed: Sales percentages not calculated correctly");
        }
        
        // Test 8: User score numeric conversion
        if (Math.abs(game1.getUserScoreNumeric() - 8.0f) < 0.01f) {
            System.out.println("✓ Test 8 Passed: User score numeric conversion working");
            testsPassed++;
        } else {
            System.out.println("✗ Test 8 Failed: User score numeric conversion not working correctly");
        }
        
        // Test 9: compareTo implementation (formerly Test 10)
        if (game1.compareTo(game2) < 0 &&  // game1 has higher sales
            game2.compareTo(game1) > 0) {
            System.out.println("✓ Test 9 Passed: Comparable implementation working");
            testsPassed++;
        } else {
            System.out.println("✗ Test 9 Failed: Comparable implementation not working correctly");
        }
        
    } catch (Exception e) {
        System.out.println("\nTest failed with exception: " + e.getMessage());
        e.printStackTrace();
    }
    
    // Summary
    System.out.println("\n===== TEST SUMMARY =====");
    System.out.println("Passed: " + testsPassed + "/" + totalTests + " tests");
    
    if (testsPassed == totalTests) {
        System.out.println("🎉 Congratulations! All tests passed!");
    } else {
        System.out.println("❌ Some tests failed. Review the output above to see which tests failed.");
    }
}

Adding GameDataManager and Testing

Now that your GameDataRow class is complete, let's work with collections of games:

Activity 2: Building Methods

Now that we have our class structure and properties defined, it's time to add meaningful functionality to our GameDataRow class. To complete this class, you'll need to implement several methods that analyze game data in different ways.

I'm providing you with a few utility methods that handle some of the trickier aspects of parsing data from the CSV file, such as handling non-numeric user scores. Let's first look at these provided methods:

/**
 * Determines if the game has user reviews
 * @return true if user score and count are available
 */
public boolean hasUserReviews() {
    try {
        return Float.parseFloat(userScore) > 0 && userCount > 0;
    } catch (NumberFormatException e) {
        return false;
    }
}

/**
 * Convert user score from string to float, handling special values
 * @return user score as float or 0.0 if not available
 */
public float getUserScoreNumeric() {
    try {
        return Float.parseFloat(userScore);
    } catch (NumberFormatException e) {
        return 0.0f; // Return 0 for "tbd" or other non-numeric values
    }
}

/**
 * Returns a string representation of the game data
 */
@Override
public String toString() {
    StringBuilder sb = new StringBuilder();
    sb.append(name).append(" (").append(yearOfRelease).append(")");
    sb.append("\nPlatform: ").append(platform);
    sb.append("\nGenre: ").append(genre);
    sb.append("\nPublisher: ").append(publisher);
    sb.append("\nDeveloper: ").append(developer);
    sb.append("\nRating: ").append(rating);
    sb.append("\nGlobal Sales: ").append(globalSales).append(" million units");
    sb.append("\nDominant Region: ").append(getDominantRegion());
    
    if (hasCriticReviews()) {
        sb.append("\nCritic Score: ").append(criticScore).append("/100 (").append(criticCount).append(" critics)");
    }
    
    if (hasUserReviews()) {
        sb.append("\nUser Score: ").append(userScore).append("/10 (").append(userCount).append(" users)");
    }
    
    return sb.toString();
}

I'm giving you these methods because:

  1. The hasUserReviews() and getUserScoreNumeric() methods handle data conversion and error checking that might be challenging

  2. The toString() method is lengthy but straightforward, and implementing it wouldn't teach you much

Now, let's focus on the methods you need to implement:

Method 1: getAverageScore()

Return Type: float

This method calculates the average between critic and user scores. However, there's a challenge: critic scores are on a scale of 0-100, while user scores are on a scale of 0-10.

Requirements:

  • Return a float representing the normalized average score on a scale of 0-10

  • Normalize the critic score to a 0-10 scale by dividing by 10

  • Handle cases where one or both scores are missing/invalid

  • Return the appropriate average or individual score based on what's available

  • Return 0.0f if no scores are available

Key Considerations:

  • What constitutes an "available" score? (Hint: check for positive values)

  • How do you handle the case where only one score type is available?

Hints:

You can paste this code into your method as a starting guide:

        // Convert critic score to scale of 10 like user score

        // fetch the user score using the built-in converter

        // check if any score is 0
            // if not, return the average of the two / 2.0f
            // else return the one with the number

Method 2: getSalesPercentages()

Return Type: float[]

This method calculates what percentage of the global sales comes from each region (North America, Europe, Japan, and Other).

Requirements:

  • Return an array of float values representing percentages for each region

  • The array should have 4 elements, one for each region

  • Use the defined constants (NA_REGION, EU_REGION, etc.) for array indices

  • Handle the case where globalSales is zero to avoid division by zero

Key Considerations:

  • The formula for calculating percentage is: (regional sales / global sales) * 100

  • If globalSales is zero, return an array of zeros to avoid division by zero

Method 3: hasSalesData()

Return Type: boolean

This method determines whether the game has any sales data available.

Requirements:

  • Return true if any sales data (regional or global) is available

  • Return false if all sales fields are zero

Key Considerations:

  • Check all sales fields (naSales, euSales, jpSales, otherSales, globalSales)

  • Use logical OR operators to efficiently check multiple conditions

Method 4: hasCriticReviews()

Return Type: boolean

This method determines whether the game has critic review data available.

Requirements:

  • Return true if the game has both a critic score and a count of critics

  • Return false if either piece of information is missing or zero

Key Considerations:

  • Both criticScore and criticCount should be positive values

Method 5: getDominantRegion()

Return Type: String

This method determines which region has the highest sales for this game.

Requirements:

  • Compare sales across all regions and find the highest

  • Return a string representing that region ("North America", "Europe", "Japan", or "Other")

  • Handle ties by choosing the first region encountered with the maximum value

Key Considerations:

  • You'll need to track the maximum sales value and the corresponding region

  • You can use a switch statement to convert from region index to region name (see example below)

Method 6: getRegionalSales()

Return Type: float

This method returns the sales figures for a specific region.

Requirements:

  • Accept an int parameter representing the region (use the class constants)

  • Return the sales figure (as a float) for the specified region

  • Handle all four possible regions: NA_REGION, EU_REGION, JP_REGION, OTHER_REGION

Key Considerations:

  • Use a switch statement to select the appropriate sales figure based on the region parameter

Switch Statement Example

A switch statement allows you to select one of many code blocks to be executed. Here's how you might implement the getRegionalSales method using a switch statement:

// Example of how to use a switch statement to return sales for a specific region
// Example switch statement to convert a day number to a day name
int dayNumber = 3;
String dayName;

switch(dayNumber) {
    case 1:
        dayName = "Monday";
        break;
    case 2:
        dayName = "Tuesday";
        break;
    case 3:
        dayName = "Wednesday";
        break;
    case 4:
        dayName = "Thursday";
        break;
    case 5:
        dayName = "Friday";
        break;
    case 6:
        dayName = "Saturday";
        break;
    case 7:
        dayName = "Sunday";
        break;
    default:
        dayName = "Invalid day";
        break;
}

The switch statement evaluates the regionIndex parameter and executes the code block that matches the case. The default case handles any values that don't match any specified cases.

Method 7: Implementation of Comparable

Return Type: int

You need to implement the compareTo method from the Comparable interface.

Requirements:

  • Override the compareTo method to compare games based on global sales

  • Higher sales should sort before lower sales (descending order)

  • Return negative value if this game has higher sales than the other game

  • Return positive value if this game has lower sales than the other game

  • Return zero if both games have equal sales

Key Considerations:

  • Remember that returning a negative value means "this" comes before "other" in the sorted order

Activity 3: Results

Let's add one final method to our GameDataRow class before moving on to working with collections of game data:

// Static method to get count of games analyzed
public static int getGamesAnalyzed() {
    return gamesAnalyzed;
}

Understanding Static Variables and Methods

This method introduces an important concept in Java: static members. Unlike instance variables and methods that belong to specific objects, static members belong to the class itself.

Here's what you need to know:

  • Static variables (like our gamesAnalyzed counter) are shared across all instances of a class. There's only one copy in memory, regardless of how many objects you create.

  • Static methods operate at the class level rather than the instance level. They can be called using the class name without creating an object: GameDataRow.getGamesAnalyzed().

  • Cannot use this in static context: Since static methods don't operate on a specific instance, you cannot use the this keyword inside them. There's no "current object" to reference! If you try to use this in a static method, you'll get a compiler error.

  • Access limitations: Static methods can only directly access other static members (variables or methods). They cannot directly access instance variables or methods without referencing a specific object.

In our case, we're using the static variable gamesAnalyzed to track how many game objects have been created throughout our program's execution. Each constructor increments this counter, and our static method lets us retrieve the current count.

Creating Your Own Analysis

Now it's time to put your skills to work! Modify the GameDataManager.java file to add your own custom analysis of the video game data.

Add at least one new method to the GameDataManager class that performs an interesting analysis. Here are some ideas:

  1. Year Analysis: Which year had the highest average game quality? Most releases?

  2. Developer Success Rate: For developers with multiple games, which ones have the highest average scores?

  3. Cross-Platform Analysis: Which games appeared on the most platforms? Do multi-platform games sell better?

  4. Sales vs. Ratings Correlation: Do higher-rated games generally sell better? Is there a difference between critic and user ratings?

  5. Genre Evolution: How have the popularity of different genres changed over time?

Once you've added your analysis method(s), modify the main method to call your new method and display the results.

Example Custom Analysis Method

Here's what your custom analysis might look like (don't just copy this - create your own!):

/**
 * Analyze which platforms have the highest-rated games on average
 */
public void analyzePlatformQuality() {
    Map<String, Float> totalScores = new HashMap<String, Float>();
    Map<String, Integer> gameCounts = new HashMap<String, Integer>();
    
    // Collect data
    for (GameDataRow game : games) {
        if (game.hasCriticReviews()) {
            String platform = game.getPlatform();
            totalScores.put(platform, totalScores.getOrDefault(platform, 0.0f) + game.getCriticScore());
            gameCounts.put(platform, gameCounts.getOrDefault(platform, 0) + 1);
        }
    }
    
    // Calculate averages and find the best
    String bestPlatform = "";
    float bestScore = 0.0f;
    
    System.out.println("\n===== PLATFORM QUALITY ANALYSIS =====");
    System.out.println("Platform\tAvg. Critic Score\tGame Count");
    
    for (String platform : totalScores.keySet()) {
        int count = gameCounts.get(platform);
        if (count >= 10) { // Only consider platforms with enough games
            float avgScore = totalScores.get(platform) / count;
            System.out.printf("%s\t\t%.1f\t\t%d\n", platform, avgScore, count);
            
            if (avgScore > bestScore) {
                bestScore = avgScore;
                bestPlatform = platform;
            }
        }
    }
    
    System.out.println("\nBest-rated platform: " + bestPlatform + 
                       " with average score " + bestScore);
}

In the main method, you would add a call to your new analysis:

// Call your custom analysis
manager.analyzePlatformQuality();

Requirements for Your Custom Analysis

Your analysis should:

  • Process data from multiple games

  • Use loops and conditionals appropriately

  • Output meaningful results

  • Include comments explaining your approach

  • Handle edge cases (like missing data)

Activity 4: Recursion

In this final activity, you'll explore recursion - a powerful programming technique where a method calls itself to solve a problem by breaking it down into smaller instances of the same problem.

Understanding Recursion

Recursion is a fundamental concept in computer science that provides elegant solutions to certain types of problems. A recursive method has two essential components:

  1. Base case(s): The simplest scenario(s) that can be solved directly without further recursion

  2. Recursive case(s): Where the method calls itself with a simpler version of the problem

Let's examine a simple example - calculating the factorial of a number:

/**
 * Calculates the factorial of n (n!)
 * @param n a non-negative integer
 * @return the factorial of n
 */
public static int factorial(int n) {
    // Base case
    if (n == 0 || n == 1) {
        return 1;
    }
    // Recursive case
    else {
        return n * factorial(n - 1);
    }
}

When calculating factorial(5):

  1. The method first checks if n equals 0 or 1 (our base case) - it doesn't, so we move to the recursive case

  2. It returns 5 * factorial(4)

  3. Now it calculates factorial(4), which is 4 * factorial(3)

  4. This continues until we reach factorial(1), which returns 1 (base case)

  5. Then the recursion "unwinds": 1 * 2 * 3 * 4 * 5 = 120

Why Use Recursion?

Recursion offers several advantages:

  1. Elegance: Recursive solutions can be concise and elegant for problems that would be complex to solve iteratively

  2. Divide and conquer: Recursion naturally implements divide-and-conquer strategies, breaking down large data sets into smaller pieces

  3. Tree traversal: For hierarchical data structures like trees, recursion provides a natural approach

  4. Problem decomposition: Complex problems become simpler when broken into smaller, similar sub-problems

Implementing Recursion in Game Data Analysis

Looking at our GameDataManager class, several methods could benefit from a recursive approach. For example, finding games within a specific criteria could be implemented using a binary search algorithm rather than the current linear search.

Here's an example of how we could improve the findGamesByTitle method using a recursive binary search (assuming the games list is sorted by title):

/**
 * Find a game by title using recursive binary search
 * @param title the title to search for
 * @return the game if found, null otherwise
 */
public GameDataRow findGameByTitle(String title) {
    // Sort the games by title first (if not already sorted)
    Collections.sort(games, (g1, g2) -> g1.getName().compareTo(g2.getName()));
    
    // Call the recursive helper method
    return findGameByTitleHelper(title, 0, games.size() - 1);
}

/**
 * Recursive helper method for binary search
 */
private GameDataRow findGameByTitleHelper(String title, int low, int high) {
    // Base case: element not found
    if (low > high) {
        return null;
    }
    
    // Calculate middle index
    int mid = (low + high) / 2;
    
    // Get the game at the middle
    GameDataRow midGame = games.get(mid);
    
    // Compare the middle element
    int comparison = midGame.getName().compareTo(title);
    
    // If found, return it
    if (comparison == 0) {
        return midGame;
    }
    // If the middle element is greater, search the left half
    else if (comparison > 0) {
        return findGameByTitleHelper(title, low, mid - 1);
    }
    // Otherwise, search the right half
    else {
        return findGameByTitleHelper(title, mid + 1, high);
    }
}

The recursive binary search has a time complexity of O(log n), which is much more efficient than the O(n) time complexity of linear search, especially with large datasets.

Another example would be implementing a recursive merge sort:

/**
 * Sort games by a certain criteria using merge sort
 * @param criteria the sorting criteria (e.g., "sales", "score")
 */
public void sortGamesByMergeSort(String criteria) {
    games = mergeSort(games, criteria, 0, games.size() - 1);
}

/**
 * Recursive merge sort implementation
 */
private ArrayList<GameDataRow> mergeSort(ArrayList<GameDataRow> list, String criteria, int left, int right) {
    // Base case: list of size 1 or empty
    if (left >= right) {
        ArrayList<GameDataRow> result = new ArrayList<>();
        if (left == right) {
            result.add(list.get(left));
        }
        return result;
    }
    
    // Find the middle point
    int mid = (left + right) / 2;
    
    // Recursively sort both halves
    ArrayList<GameDataRow> leftHalf = mergeSort(list, criteria, left, mid);
    ArrayList<GameDataRow> rightHalf = mergeSort(list, criteria, mid + 1, right);
    
    // Merge the sorted halves
    return merge(leftHalf, rightHalf, criteria);
}

/**
 * Merge two sorted lists
 */
private ArrayList<GameDataRow> merge(ArrayList<GameDataRow> left, ArrayList<GameDataRow> right, String criteria) {
    ArrayList<GameDataRow> result = new ArrayList<>();
    int leftIndex = 0, rightIndex = 0;
    
    // Compare elements from both lists and add the smaller one to result
    while (leftIndex < left.size() && rightIndex < right.size()) {
        GameDataRow leftGame = left.get(leftIndex);
        GameDataRow rightGame = right.get(rightIndex);
        
        boolean addLeft = false;
        
        // Compare based on criteria
        if (criteria.equals("sales")) {
            addLeft = leftGame.getGlobalSales() >= rightGame.getGlobalSales();
        } else if (criteria.equals("score")) {
            addLeft = leftGame.getCriticScore() >= rightGame.getCriticScore();
        } else {  // Default to name
            addLeft = leftGame.getName().compareTo(rightGame.getName()) <= 0;
        }
        
        if (addLeft) {
            result.add(leftGame);
            leftIndex++;
        } else {
            result.add(rightGame);
            rightIndex++;
        }
    }
    
    // Add remaining elements
    while (leftIndex < left.size()) {
        result.add(left.get(leftIndex++));
    }
    
    while (rightIndex < right.size()) {
        result.add(right.get(rightIndex++));
    }
    
    return result;
}

Your Task

  1. Choose one method from your GameDataRow or GameDataManager class that could benefit from a recursive approach

  2. Reimplement it using recursion

  3. Test your implementation and compare it to the original approach

  4. Document your findings: Was the recursive approach more elegant? More efficient? What challenges did you face?

Alternatively, you can implement a new recursive method that performs a complex analysis that would be difficult to do iteratively. Here are some ideas:

  • Find the most profitable franchise (games with similar names) recursively

  • Implement a recursive algorithm to categorize games into hierarchical genres (e.g., "RPG > Action RPG")

  • Create a recursive method to find "hidden gems" - games with high ratings but low sales

Example: Recursive Method for Maximum Critic Score

Here's an example of how you might implement a recursive method to find the game with the highest critic score within a list of games:

/**
 * Find the game with the highest critic score using recursion
 */
public GameDataRow findGameWithHighestCriticScore() {
    if (games.isEmpty()) {
        return null;
    }
    return findGameWithHighestCriticScoreHelper(0, games.size() - 1);
}

/**
 * Recursive helper to find the game with highest critic score
 */
private GameDataRow findGameWithHighestCriticScoreHelper(int start, int end) {
    // Base case: only one game
    if (start == end) {
        return games.get(start);
    }
    
    // Base case: two games
    if (start + 1 == end) {
        if (games.get(start).getCriticScore() >= games.get(end).getCriticScore()) {
            return games.get(start);
        } else {
            return games.get(end);
        }
    }
    
    // Find middle index
    int mid = (start + end) / 2;
    
    // Recursively find max in both halves
    GameDataRow leftMax = findGameWithHighestCriticScoreHelper(start, mid);
    GameDataRow rightMax = findGameWithHighestCriticScoreHelper(mid + 1, end);
    
    // Return the game with higher critic score
    if (leftMax.getCriticScore() >= rightMax.getCriticScore()) {
        return leftMax;
    } else {
        return rightMax;
    }
}

This method uses a divide-and-conquer approach, recursively finding the maximum in each half of the list and then comparing the results. While this specific example might not be more efficient than a simple loop, it demonstrates the recursive pattern that can be applied to more complex problems.

Remember, not all problems are best solved with recursion. Some considerations:

  • Recursion can lead to stack overflow errors with very large inputs

  • Recursive solutions sometimes have higher memory usage

  • Some problems have simpler iterative solutions

Copy the contents of GameDataManager.java file from this link:

Download the video_games.csv file and place it in your project directory. The location should match the path in the GameDataManager.loadFromCSV() method call.

Video Game Sales and Ratings
https://gist.github.com/dadiletta/2310e45953e3bcb8db3cf151c7c75d06
https://www.kaggle.com/datasets/thedevastator/video-game-sales-and-ratings?resource=download