By aquantum


2019-03-12 08:53:12 8 Comments

I am making plant growth program, and it works right now. But compared to other programs that interact between classes, mine looks inefficient to work. (for example, in Growth class, add "Environment" into init function and expressed as self.Environment.~) I tried to interpret other codes, but too complicated to understand as a beginner...

Is there better way to edit my code to interact between classes? Thanks in advance!

import requests
import random
from matplotlib import pyplot as plt
from time import sleep

class Environment:
    """Getting 5 day weather forecast from weather API and choosing soil type that would be 
    the base information for the plant growth"""
    def __init__(self, weather = None, temperature = None, temp_return = None, soil_type = None):
        self.weather = weather
        self.temperature = temperature
        self.temp_return = temp_return
        self.soil_type = soil_type
        self.daily_weather_list = []

    def get_weather(self):
        # Error checking format
        while True:
            self.zip_code = input("Enter your zip code to get weather info: ")
            print('-'*30,"\n")
            self.url = "http://api.openweathermap.org/data/2.5/forecast?appid=a9fed4c32128f18e6142d3bd49fb5f7d&units=metric&zip=" + self.zip_code
            self.response = requests.get(self.url) 
            self.result = self.response.json()
            if self.result["cod"] != "404":
                self.weather_list = self.result['list']
                a = -8 
                # Extract 5 day weather forecast from json format
                for i in range(1, int(round((len(self.weather_list))/8+1,0))):
                    a += 8
                    self.weather = self.result['list'][a]['weather'][0]['description']
                    self.temperature = self.result['list'][a]['main']['temp']
                    self.daily_weather_list.append(self.weather)

                    print("Day", i, "Weather: {}".format(self.result['list'][a]['weather'][0]['description']))
                    print("Day", i, "Temperature: {} °C".format(self.result['list'][a]['main']['temp']))
                    print('-'*30, "\n")

                    if self.temperature >= 35:
                        self.temp_return = "high"
                    elif 35 > self.temperature >= 10:
                        self.temp_return = "moderate"
                    else:
                        self.temp_return = "low"
                break

            else:
                print("***ZIP CODE NOT FOUND***\n")
                continue

    def choose_soil_type(self):
        # Error checking format
        while True:
            self.soil_type = input("Please choose soil type - [1]-alkaline, [2]-neutral, [3]-acidic: ")
            print('-'*30, "\n")
            if self.soil_type not in '123':
                print("***NOT A VALID SOIL TYPE***")
                continue
            else:
                break

class Plants:
    """User choice of plants. After the selection, properties of the selected plant are provided."""
    def __init__(self, preferred_sunshine = None, preferred_water = None, preferred_fertilizer = None, preferred_temp = None, preferred_soil = None):
        self.preferred_sunshine = preferred_sunshine
        self.preferred_water = preferred_water
        self.preferred_fertilizer = preferred_fertilizer
        self.preferred_temp = preferred_temp
        self.preferred_soil = preferred_soil

    def choose_plant(self):
        while True:
            print("**Plant information")
            print("*lemon - prefer moderate water, less fertilizer, neutral soil")
            print("*blueberry - prefer more water, moderate fertilizer, acidic soil")
            print("*pear - prefer less water, more fertilizer, alkaline soil\n")
            sleep(1)
            self.choose = input("Please choose your plant to breed [1]-lemon, [2]-blueberry, [3]-pear): ")
            print('-'*30, "\n")

            # Error checking if wrong plant name is entered
            if self.choose not in '123':
                print("***NOT A VALID PLANT***\n")
                continue
            # Locate properties of each plant
            if self.choose == "1":
                self.preferred_sunshine = 9
                self.preferred_water = 5
                self.preferred_fertilizer = 3
                self.preferred_temp = "high"
                self.preferred_soil = "neutral"
                break
            elif self.choose == "2":
                self.preferred_sunshine = 6
                self.preferred_water = 8
                self.preferred_fertilizer = 5
                self.preferred_temp = "moderate"
                self.preferred_soil = "acidic"
                break
            elif self.choose == "3":
                self.preferred_sunshine = 3
                self.preferred_water = 3
                self.preferred_fertilizer = 7
                self.preferred_temp = "low"
                self.preferred_soil = "alkaline"
                break

        print("Next, you have to determine the amount of water and fertilizer in consideration of the weather.\n")
        sleep(2)
        print("Removing weed would be automatically performed, and you will get the full score if it is done in 3 times.\n")
        sleep(2)
        print("The goal is to reach 500% fruit growth for 5 days.")
        print('-'*30, "\n")

class Growth:
    """Based on the plant choice and environment settings, user can select treatment amounts for each day,
    and calculate accumulated fruit size to determine success or failure."""
    def __init__(self, Environment, Plants, water = 0, fertilizer = 0, weed = True):
        self.Environment = Environment
        self.Plants = Plants
        self.water = water
        self.fertilizer = fertilizer
        self.weed = weed

    def treatment(self):
        self.plot_list = []
        self.tracker = 1
        self.fruit_size = 0.1
        # 5 day iteration from each day's weather forecast
        for items in self.Environment.daily_weather_list:
            # Adjust preferred amount of water based on the weather forecast
            if "rain" in items.lower() and "drizzle" not in items.lower():
                self.Plants.preferred_water -= 3
                print("Since it's raining, try to decrease the amount of water.\n")
                sleep(1)
            elif "drizzle" in items.lower():
                self.Plants.preferred_water -= 1
                print("Since it's drizzling, try to decrease the amount of water a little bit.\n")
                sleep(1)
            elif "clear sky" in items.lower():
                self.Plants.preferred_water += 2
                print("Since it's sunny outside, try to increase the amount of water.\n")
                sleep(1)

            # Error checking format
            # Determine effectiveness of water and fertilizer amount
            while True:
                print("Day", self.tracker)
                self.water = int(input("Please enter the amount of water (scale 0-10): "))
                if self.water not in range(11):
                    print("***NOT A VALID AMOUNT***")
                    continue
                if self.water == self.Plants.preferred_water:
                    self.water_score = 10
                    break
                elif self.Plants.preferred_water - 2 <= self.water <= self.Plants.preferred_water + 2:
                    self.water_score = 7
                    break
                else:
                    self.water_score = 4 
                    break

            while True:
                self.fertilizer = int(input("Please enter the amount of fertilizer (scale 0-10): "))
                if self.fertilizer not in range(11):
                    print("***NOT A VALID AMOUNT***")
                    continue
                if self.fertilizer == self.Plants.preferred_fertilizer:
                    self.fertilizer_score = 10
                    break
                elif self.Plants.preferred_fertilizer - 2 <= self.fertilizer <= self.Plants.preferred_fertilizer + 2:
                    self.fertilizer_score = 7
                    break
                else:
                    self.fertilizer_score = 4  
                    break

            # Automated weed removal: random score
            count = 0
            while self.weed == True:
                x = 1* random.random()
                count += 1
                if x < 0.3:
                    self.weed == False
                    print("You have removed the weeds", count, "times to help the plant grow. Good job!\n")
                    break
            if count <= 3:
                self.weed_score = 10
            else:
                self.weed_score = 5

            # Calculate probability of fruit growth. Weight of parameters: water 30%, fertilizer 30%, soil type 10%, - user selection
            # weed 20%, temperature 10% - random
            self.probability = 0
            if self.Environment.soil_type == self.Plants.preferred_soil:
                self.probability += random.uniform(0.7, 0.9) * 0.1
            else:
                self.probability += random.uniform(0.3, 0.5) * 0.1

            if self.Environment.temp_return == self.Plants.preferred_temp:
                self.probability += random.uniform(0.7, 0.9) * 0.1
            else:
                self.probability += random.uniform(0.3, 0.5) * 0.1

            if self.water_score == 10:
                self.probability += random.uniform(0.7, 0.9) * 0.3
            elif self.water_score == 7:
                self.probability += random.uniform(0.4, 0.6) * 0.3
            elif self.water_score == 4:
                self.probability += random.uniform(0.1, 0.3) * 0.3

            if self.fertilizer_score == 10:
                self.probability += random.uniform(0.7, 0.9) * 0.3
            elif self.fertilizer_score == 7:
                self.probability += random.uniform(0.4, 0.6) * 0.3
            elif self.fertilizer_score == 4:
                self.probability += random.uniform(0.1, 0.3) * 0.3

            if self.weed_score == 10:
                self.probability += random.uniform(0.7, 0.9) * 0.2
            else:
                self.probability += random.uniform(0.3, 0.5) * 0.2

            self.fruit_size = self.fruit_size + self.fruit_size * self.probability
            self.tracker += 1
            # For plotting fruit growth
            self.plot_list.append((self.tracker-1, round(((self.fruit_size - 0.1)/0.1)*100, 0)))
            print("Accumulated growth check: ", round(((self.fruit_size - 0.1)/0.1)*100, 0),"% growth")
            print('-'*30, "\n")

        print("Fruit size was increased by {}%".format(round(((self.fruit_size - 0.1)/0.1)*100, 0)))
        print('-'*30, "\n")

        # Determine the success or failure of fruit growth to a desired point
        if self.fruit_size > 0.6:
            print("Great job! You successfully raised the fruit of", self.Plants.choose, "to a desired point.")
        else:
            print("I'm sorry to inform you that you failed to raise the fruit of", self.Plants.choose, "to a desired point.")

class Growth_plot:
    """Plot of fruit growth progress"""
    def __init__(self, Growth):
        self.Growth = Growth

    def generate_plot(self):
        plt.scatter(*zip(*self.Growth.plot_list))
        plt.title('Fruit Growth Chart')
        plt.xlabel('Day')
        plt.ylabel('Growth %')
        plt.show()

def start_engine():
    """Core of the plant growth program."""
    print("Welcome to the plant growth game! Please select your location to grow your own plant of your choice.\n")
    sleep(1)
    a = Environment()
    a.get_weather()
    a.choose_soil_type()
    b = Plants()
    b.choose_plant()
    c = Growth(a, b)
    c.treatment()
    d = Growth_plot(c)
    d.generate_plot()

start_engine()

1 comments

@Austin Hastings 2019-03-15 00:39:10

Hello and welcome to CodeReview! It looks like you're just getting started with OO programming in Python. On the plus side, you've organized your classes by separate areas of responsibility and areas of focus: separating the environment from the plants from the growth from the plotting. That's all good, solid OO design work.

Here are some things I think you could improve:

Look beyond the surface

You broke your classes down into obvious parts based on the problem statement. You didn't break them down (yet) based on the actual code you were writing. What's one thing you missed? Interacting with the user!

Every one of your classes tries to "talk" with the user: they contain print calls that send output, and input calls that collect input. You should consider writing some user interface code that handles all that talking for you, and then passing that object to the other parts of your program:

user = UserInterface()
env = Environment(user)
growth = Growth(user)
...

This would let you identify common themes in your interaction with the user -- like repeating a question until you get an acceptable answer -- and coding them in a central place. It would also make it easier to write unit tests. If you can just swap out a "keyboard" object for a "scripted responses" object, its easier to control the program and set up test scenarios.

Separate configuration data from creation

Most of your classes ask the user questions to set up their data. I suggest that you pull the questioning out (see above) and start passing the setup data to the classes as part of object creation:

zip_code = get_zip_code(user)
env = Environment(zip_code)

An alternative might be to create a factory method to put the question, the validation, and possibly data conversion into the class with the object creation. Something like:

plant = Plant.get_crop_from_user(user)

# in class Plant:
@classmethod
get_crop_from_user(cls, user):
    crops = ("lemon", "blueberry", "pear")
    choice = user.show_menu(crops, prompt="What kind of crops to grow?")
    return cls(crops[choice])

Don't assume the internet is fast

When I ran your program, connecting to the openweathermap site to get the weather data took several seconds. You should make it clear when you're doing something over the net, to prevent the user from thinking the program is hung up. Since you can't show a "spinning wheel" effect, settle for just printing a message about what you're doing:

print("Stand by. Getting weather data from openweathermap.org")

Know what to forget

You use a lot of object attributes. A lot of attributes. A lot.

Many of those attributes are never used again. They appear in a method in one or two lines, and then nowhere else in the file.

Those items should be replaced by local variables in the particular methods. For example, in Environment.get_weather you use self.zip_code which never gets used anyplace else. Your only use for it is to construct the query URL for your openweathermap call.

Also, you use self.result. You refer to it several times in in get_weather but it never gets used anyplace else in the code. Both of those could be local variables. Instead of self.zip_code just use zip_code. Instead of self.result use result. They will be valid until the end of the get_weather method, and then they'll be forgotten, which is fine since you only use them in the one place.

Choose your names wisely

In general, you did a pretty good job with names until you got to your start_engine function. Why did you go with a, b, c, and d? Why not env, plants, growth, and plot?

Follow community standards

As they write in the movie:

You did a good job putting your code into the start_engine function, and then calling it:

start_engine()

But start_engine doesn't mean anything to me, except as the last few words of the US national anthem.

On the other hand, main definitely means something to a lot of people. And the officially suggested way to call it is:

if __name__ == '__main__':
    main()

This construction lets other modules import your code without automatically running the game. The only time the if statement will succeed is when you run the program like python myfile.py. If some other module (like a unit test driver) imports your code, the condition will fail and main won't be called. Always do this!

Related Questions

Sponsored Content

2 Answered Questions

[SOLVED] Shopping List program

3 Answered Questions

[SOLVED] Simple Shop Program

1 Answered Questions

[SOLVED] Simple Employee program

2 Answered Questions

[SOLVED] Autoclicker Tkinter Program

1 Answered Questions

[SOLVED] Pig latin translating program

1 Answered Questions

[SOLVED] Random playlists program

2 Answered Questions

[SOLVED] "Time till" program

1 Answered Questions

[SOLVED] Terminal program engine

1 Answered Questions

[SOLVED] Super simple spam program

1 Answered Questions

[SOLVED] Madlibs Program

Sponsored Content