Skip to main content
Added more notes based off comments as well as adjusted the code just a bit more towards correctness.
Source Link
S0AndS0
  • 968
  • 1
  • 6
  • 15

Some notes before diving-in...

  • it's probably a good idea to get a snack and drink; I'm a bit verbose and am about to compress some years of knowledge

  • __bar__ when spoken is "dunder bar" , and the phylum that theirthey're classified under are "magic methods"

  • what I share is not gospel as such, but a collection of tricks I wish someone had shown me when I was getting into Python

... okay back on track.

#!/usr/bin/env python

import time
import random


print_separator = "".join(['_' for _ in range(9)])
__author__ = "S0AndS0"

#
# Functions
#

def question(message):
    """ Returns response to `message` from user """
    return input("{message}? ".format(message = message))


#
# Classes
#

class Gone_Fishing(dict):
    """
    Gone_Fishing is a simple simulation inspired by
    [Python - Fishing Simulator](https://codereview.stackexchange.com/q/217357/197446)

    ## Arguments

    - `fishes`, `dict`ionary such as `{'cod': {'amount': 0, 'chances': [1, 2]}}`
    - `min_chance`, `int`eger of min number that `random.randint` may generate
    - `max_chance`, `int`eger of max number that `random.randint` may generate
    """

    def __init__(self, fishes, min_chance = 1, max_chance = 10, **kwargs):
        super(Gone_Fishing, self).__init__(**kwargs)
        self.update(fishes = fishes,
                    chances = {'min': min_chance, 'max': max_chance})

    @staticmethod
    def questionkeep_fishing(message, expected):
        """ ReturnsReturn response`bool`ean of if `response` to `message` frommatches user`expected` """
        return input("{message}? ".format(messageresponse = message))

    @staticmethod
    def keep_fishingquestion(response, expectedmessage):
        """ Return `bool`ean of if `response` matches `expected` """
        if not response or not isinstance(response, str):
            return False

        return response.lower() == expected

    @property
    def dump_cooler(self):
        """
        Returns `score`, a `dict`ionary similar to `{'cod': 5, 'tire': 2}`,
        after printing and reseting _`amount`s_ caught
        """
        score = {}
        for fish, data in self['fishes'].items():
            if data['amount'] > 0:
                score.update({fish: data['amount']})
                if data['amount'] > 1 and data.get('plural'):
                    fish = data['plural']

                print("{amount} {fish}".format(**{
                    'fish': fish,
                    'amount': data['amount']}))

                data['amount'] = 0

        return score

    def catch(self, chance):
        """ Returns `None` or name of `fish` caught based on `chance` """
        caught = []
        for fish, data in self['fishes'].items():
            if chance in data['chances']:
                caught.append(fish)

        return caught

    def main_loop(self):
        """
        Asks questions, adds to _cooler_ anything caught, and prints score when finished
        """
        first = True
        message = 'Go fishing'
        expected = 'yes'
        while self.keep_fishing(self.question(message), expected):
            time.sleep(1)
            if first:
                first = False
                message = "Keep fishing"

            chances = random.randint(self['chances']['min'], self['chances']['max'])
            caught = self.catch(chances)
            if caught:
                for fish in caught:
                    self['fishes'][fish]['amount'] += 1
                    fancy_fish = ' '.join(fish.split('_')).title()
                    print("You caught a {fish}".format(fish = fancy_fish))
            else:
                print("Nothing was caught this time.")

        print("{0}\nThanks for playing".format(print_separator))
        if True in [x['amount'] > 0 for x in self['fishes'].values()]:
            print("You caught")
            self.dump_cooler
        print(print_separator)


if __name__ == '__main__':
    """
    This block of code is not executed during import
    and instead is usually run when a file is executed,
    eg. `python gone_fishing.py`, making it a good
    place for simple unit tests and example usage.
    """
    gone_fishing = Gone_Fishing(
        fishes = {
            'cod': {'amount': 0, 'chances': [1]},
            'salmon': {'amount': 0, 'chances': [5]},
            'shark': {'amount': 0, 'chances': [9, 10], 'plural': 'sharks'},
            'wild_fish': {'amount': 0, 'chances': [7], 'plural': 'wild_fishes'},
            'old_shoe': {'amount': 0, 'chances': [10, 15], 'plural': 'old_shoes'},
            'tire': {'amount': 0, 'chances': [2, 19], 'plural': 'tires'},
        },
        min_chances = 0,
        max_chances = 20,
    )

    gone_fishing.main_loop()

Note from the future; check the comments of this answer for some swell suggestions from @Izaak van Dongen.

Side note; the __init__ method is one of many that are called implicitly by preforming some action with an object, eg. __add__ is called implicitly by using + between two Objects with a __add__ method (side-side note, I'll get into why that was an a and not an an in a bit), which is why the following works with lists...

list_one = [3, 2, 1]
list_two = [0, -1, -2]

list_one + list_two
# -> [3, 2, 1, 0, -1, -2]

That bit with **kwargs stands for key word arguments which passes things as a bare dictionary, the other syntax you may run across is *args, which passes things as a bare list of arguments; there be some fanciness that can be done with this syntax that I'll not get into at this point other than saying that context matters. However, you'll find some examples of passing an unwrapped dictionary, such as to format via print("{amount} {fish}".format(**{...})), which hint hint, is a great way of passing variable parameter names.

... it's possible to call self.update() from within SomeThing.___init__ without causing confusion of intent, where as to have SomeThing still operate as a dictionary, eg. assigning something = SomeThing(spam = 'Spam') without causing errors, one should use super(SomeThing, self).__init__(**kwargs) to allow Python to preform it's voodoo with figuring out which inheriting class'll take responsibility for those arguments. Side note, that does mean that one could do class SomeThing(dict, Iterator), and have that mean something but I'll not get into that here; kinda already covered that specifically on math stack in regards to graph modeling and prioritization.

That does mean that one could do class SomeThing(dict, Iterator), and have that mean something but I'll not get into that here; kinda already covered that specifically on math stack in regards to graph modeling and prioritization.

... but can only be gotten not set, which makes'em a great place to stash dynamic or semiprivate properties about an objectObject.

In the case of staticmethods, they're not passed a reference to self so cannot easily access or modify saved states, but they can be more easily used without initializing so operate similarly to regular functions, eg...

responses = []

responses.append(Gone_Fishing.question("Where to"))
print("I heard -> {response}".format(response = responses[-1]))
for _ in range(7):
    responses.append(Gone_Fishing.question("... are you sure"))
    print("I heard -> {response}".format(response = responses[-1]))

print("Okay... though...")

Note from the future; as pointed out by @Maarten Fabré I indeed slipped in some superfluous use of the staticmethod decorator, good catch there, and this'll now serve as an example of getting carried away when decorating.

Generally I use staticmethods when I've a class that isn't concerned with it's internal state but isn't large enough to warrant it's own file, very edge case kinda thing, and usually it means that I should probably split'em out into a file that organizes similar functions. Hopefully recent edits now look closer to proper for future readers.

That bit within the main_loop method with while self.keep_fishing(self.question(message), expected), when unwrapped I think you'll really like, it's returning True or False at the top of every iteration based on asking the user a question and comparing their response with what's expected.

There's certainly more room for improvement, eg. having gone_fishing['fishes'][fish_name]['ammount']gone_fishing['fishes'][fish_name]['amount'] subtracted from, while adding to gone_fishing['cooler'] or similar structure; just for a start. But this was all just to expose quick-n-dirty methods of organizing the problem space with Object Oriented Programing.

Some notes before diving-in...

  • it's probably a good idea to get a snack and drink; I'm a bit verbose and am about to compress some years of knowledge

  • __bar__ when spoken is "dunder bar" , and the phylum that their classified under are "magic methods"

... okay back on track.

#!/usr/bin/env python

import time
import random


print_separator = "".join(['_' for _ in range(9)])
__author__ = "S0AndS0"


class Gone_Fishing(dict):
    """
    Gone_Fishing is a simple simulation inspired by
    [Python - Fishing Simulator](https://codereview.stackexchange.com/q/217357/197446)

    ## Arguments

    - `fishes`, `dict`ionary such as `{'cod': {'amount': 0, 'chances': [1, 2]}}`
    - `min_chance`, `int`eger of min number that `random.randint` may generate
    - `max_chance`, `int`eger of max number that `random.randint` may generate
    """

    def __init__(self, fishes, min_chance = 1, max_chance = 10, **kwargs):
        super(Gone_Fishing, self).__init__(**kwargs)
        self.update(fishes = fishes,
                    chances = {'min': min_chance, 'max': max_chance})

    @staticmethod
    def question(message):
        """ Returns response to `message` from user """
        return input("{message}? ".format(message = message))

    @staticmethod
    def keep_fishing(response, expected):
        """ Return `bool`ean of if `response` matches `expected` """
        if not response or not isinstance(response, str):
            return False

        return response.lower() == expected

    @property
    def dump_cooler(self):
        """
        Returns `score`, a `dict`ionary similar to `{'cod': 5, 'tire': 2}`,
        after printing and reseting _`amount`s_ caught
        """
        score = {}
        for fish, data in self['fishes'].items():
            if data['amount'] > 0:
                score.update({fish: data['amount']})
                if data['amount'] > 1 and data.get('plural'):
                    fish = data['plural']

                print("{amount} {fish}".format(**{
                    'fish': fish,
                    'amount': data['amount']}))

                data['amount'] = 0

        return score

    def catch(self, chance):
        """ Returns `None` or name of `fish` caught based on `chance` """
        caught = []
        for fish, data in self['fishes'].items():
            if chance in data['chances']:
                caught.append(fish)

        return caught

    def main_loop(self):
        """
        Asks questions, adds to _cooler_ anything caught, and prints score when finished
        """
        first = True
        message = 'Go fishing'
        expected = 'yes'
        while self.keep_fishing(self.question(message), expected):
            time.sleep(1)
            if first:
                first = False
                message = "Keep fishing"

            chances = random.randint(self['chances']['min'], self['chances']['max'])
            caught = self.catch(chances)
            if caught:
                for fish in caught:
                    self['fishes'][fish]['amount'] += 1
                    fancy_fish = ' '.join(fish.split('_')).title()
                    print("You caught a {fish}".format(fish = fancy_fish))
            else:
                print("Nothing was caught this time.")

        print("{0}\nThanks for playing".format(print_separator))
        if True in [x['amount'] > 0 for x in self['fishes'].values()]:
            print("You caught")
            self.dump_cooler
        print(print_separator)


if __name__ == '__main__':
    """
    This block of code is not executed during import
    and instead is usually run when a file is executed,
    eg. `python gone_fishing.py`, making it a good
    place for simple unit tests and example usage.
    """
    gone_fishing = Gone_Fishing(
        fishes = {
            'cod': {'amount': 0, 'chances': [1]},
            'salmon': {'amount': 0, 'chances': [5]},
            'shark': {'amount': 0, 'chances': [9, 10], 'plural': 'sharks'},
            'wild_fish': {'amount': 0, 'chances': [7], 'plural': 'wild_fishes'},
            'old_shoe': {'amount': 0, 'chances': [10, 15], 'plural': 'old_shoes'},
            'tire': {'amount': 0, 'chances': [2, 19], 'plural': 'tires'},
        },
        min_chances = 0,
        max_chances = 20,
    )

    gone_fishing.main_loop()

That bit with **kwargs stands for key word arguments which passes things as a bare dictionary, the other syntax you may run across is *args, which passes things as a bare list of arguments; there be some fanciness that can be done with this syntax that I'll not get into at this point. However, you'll find some examples of passing an unwrapped dictionary, such as to format via print("{amount} {fish}".format(**{...})), which hint hint, is a great way of passing variable parameter names.

... it's possible to call self.update() from within SomeThing.___init__ without causing confusion of intent, where as to have SomeThing still operate as a dictionary, eg. assigning something = SomeThing(spam = 'Spam') without causing errors, one should use super(SomeThing, self).__init__(**kwargs) to allow Python to preform it's voodoo with figuring out which inheriting class'll take responsibility for those arguments. Side note, that does mean that one could do class SomeThing(dict, Iterator), and have that mean something but I'll not get into that here; kinda already covered that specifically on math stack in regards to graph modeling and prioritization.

... but can only be gotten not set, which makes'em a great place to stash dynamic or semiprivate properties about an object.

In the case of staticmethods, they're not passed a reference to self so cannot easily access or modify saved states, but they can be more easily used without initializing, eg...

responses = []

responses.append(Gone_Fishing.question("Where to"))
print("I heard -> {response}".format(response = responses[-1]))
for _ in range(7):
    responses.append(Gone_Fishing.question("... are you sure"))
    print("I heard -> {response}".format(response = responses[-1]))

print("Okay... though...")

That bit within the main_loop method with while self.keep_fishing(self.question(message), expected), when unwrapped I think you'll really like, it's returning True or False at the top of every iteration based on asking the user a question and comparing their response with what's expected.

There's certainly more room for improvement, eg. having gone_fishing['fishes'][fish_name]['ammount'] subtracted from, while adding to gone_fishing['cooler'] or similar structure; just for a start. But this was all just to expose quick-n-dirty methods of organizing the problem space with Object Oriented Programing.

Some notes before diving-in...

  • it's probably a good idea to get a snack and drink; I'm a bit verbose and am about to compress some years of knowledge

  • __bar__ when spoken is "dunder bar" , and the phylum that they're classified under are "magic methods"

  • what I share is not gospel as such, but a collection of tricks I wish someone had shown me when I was getting into Python

... okay back on track.

#!/usr/bin/env python

import time
import random


print_separator = "".join(['_' for _ in range(9)])
__author__ = "S0AndS0"

#
# Functions
#

def question(message):
    """ Returns response to `message` from user """
    return input("{message}? ".format(message = message))


#
# Classes
#

class Gone_Fishing(dict):
    """
    Gone_Fishing is a simple simulation inspired by
    [Python - Fishing Simulator](https://codereview.stackexchange.com/q/217357/197446)

    ## Arguments

    - `fishes`, `dict`ionary such as `{'cod': {'amount': 0, 'chances': [1, 2]}}`
    - `min_chance`, `int`eger of min number that `random.randint` may generate
    - `max_chance`, `int`eger of max number that `random.randint` may generate
    """

    def __init__(self, fishes, min_chance = 1, max_chance = 10, **kwargs):
        super(Gone_Fishing, self).__init__(**kwargs)
        self.update(fishes = fishes,
                    chances = {'min': min_chance, 'max': max_chance})

    @staticmethod
    def keep_fishing(message, expected):
        """ Return `bool`ean of if `response` to `message` matches `expected` """
        response = question(message)
        if not response or not isinstance(response, str):
            return False

        return response.lower() == expected

    @property
    def dump_cooler(self):
        """
        Returns `score`, a `dict`ionary similar to `{'cod': 5, 'tire': 2}`,
        after printing and reseting _`amount`s_ caught
        """
        score = {}
        for fish, data in self['fishes'].items():
            if data['amount'] > 0:
                score.update({fish: data['amount']})
                if data['amount'] > 1 and data.get('plural'):
                    fish = data['plural']

                print("{amount} {fish}".format(**{
                    'fish': fish,
                    'amount': data['amount']}))

                data['amount'] = 0

        return score

    def catch(self, chance):
        """ Returns `None` or name of `fish` caught based on `chance` """
        caught = []
        for fish, data in self['fishes'].items():
            if chance in data['chances']:
                caught.append(fish)

        return caught

    def main_loop(self):
        """
        Asks questions, adds to _cooler_ anything caught, and prints score when finished
        """
        first = True
        message = 'Go fishing'
        expected = 'yes'
        while self.keep_fishing(message, expected):
            time.sleep(1)
            if first:
                first = False
                message = "Keep fishing"

            chances = random.randint(self['chances']['min'], self['chances']['max'])
            caught = self.catch(chances)
            if caught:
                for fish in caught:
                    self['fishes'][fish]['amount'] += 1
                    fancy_fish = ' '.join(fish.split('_')).title()
                    print("You caught a {fish}".format(fish = fancy_fish))
            else:
                print("Nothing was caught this time.")

        print("{0}\nThanks for playing".format(print_separator))
        if True in [x['amount'] > 0 for x in self['fishes'].values()]:
            print("You caught")
            self.dump_cooler
        print(print_separator)


if __name__ == '__main__':
    """
    This block of code is not executed during import
    and instead is usually run when a file is executed,
    eg. `python gone_fishing.py`, making it a good
    place for simple unit tests and example usage.
    """
    gone_fishing = Gone_Fishing(
        fishes = {
            'cod': {'amount': 0, 'chances': [1]},
            'salmon': {'amount': 0, 'chances': [5]},
            'shark': {'amount': 0, 'chances': [9, 10], 'plural': 'sharks'},
            'wild_fish': {'amount': 0, 'chances': [7], 'plural': 'wild_fishes'},
            'old_shoe': {'amount': 0, 'chances': [10, 15], 'plural': 'old_shoes'},
            'tire': {'amount': 0, 'chances': [2, 19], 'plural': 'tires'},
        },
        min_chances = 0,
        max_chances = 20,
    )

    gone_fishing.main_loop()

Note from the future; check the comments of this answer for some swell suggestions from @Izaak van Dongen.

Side note; the __init__ method is one of many that are called implicitly by preforming some action with an object, eg. __add__ is called implicitly by using + between two Objects with a __add__ method (side-side note, I'll get into why that was an a and not an an in a bit), which is why the following works with lists...

list_one = [3, 2, 1]
list_two = [0, -1, -2]

list_one + list_two
# -> [3, 2, 1, 0, -1, -2]

That bit with **kwargs stands for key word arguments which passes things as a bare dictionary, the other syntax you may run across is *args, which passes things as a bare list of arguments; there be some fanciness that can be done with this syntax that I'll not get into at this point other than saying that context matters. However, you'll find some examples of passing an unwrapped dictionary, such as to format via print("{amount} {fish}".format(**{...})), which hint hint, is a great way of passing variable parameter names.

... it's possible to call self.update() from within SomeThing.___init__ without causing confusion of intent, where as to have SomeThing still operate as a dictionary, eg. assigning something = SomeThing(spam = 'Spam') without causing errors, one should use super(SomeThing, self).__init__(**kwargs) to allow Python to preform it's voodoo with figuring out which inheriting class'll take responsibility for those arguments.

That does mean that one could do class SomeThing(dict, Iterator), and have that mean something but I'll not get into that here; kinda already covered that specifically on math stack in regards to graph modeling and prioritization.

... but can only be gotten not set, which makes'em a great place to stash dynamic or semiprivate properties about an Object.

In the case of staticmethods, they're not passed a reference to self so cannot easily access or modify saved states, but they can be more easily used without initializing so operate similarly to regular functions, eg...

responses = []

responses.append(question("Where to"))
print("I heard -> {response}".format(response = responses[-1]))
for _ in range(7):
    responses.append(question("... are you sure"))
    print("I heard -> {response}".format(response = responses[-1]))

print("Okay... though...")

Note from the future; as pointed out by @Maarten Fabré I indeed slipped in some superfluous use of the staticmethod decorator, good catch there, and this'll now serve as an example of getting carried away when decorating.

Generally I use staticmethods when I've a class that isn't concerned with it's internal state but isn't large enough to warrant it's own file, very edge case kinda thing, and usually it means that I should probably split'em out into a file that organizes similar functions. Hopefully recent edits now look closer to proper for future readers.

That bit within the main_loop method with while self.keep_fishing(message, expected), when unwrapped I think you'll really like, it's returning True or False at the top of every iteration based on asking the user a question and comparing their response with what's expected.

There's certainly more room for improvement, eg. having gone_fishing['fishes'][fish_name]['amount'] subtracted from, while adding to gone_fishing['cooler'] or similar structure; just for a start. But this was all just to expose quick-n-dirty methods of organizing the problem space with Object Oriented Programing.

Fixed some bugs, added features, and corrected a conjunction for their explicit function...
Source Link
S0AndS0
  • 968
  • 1
  • 6
  • 15
#!/usr/bin/env python

import time
import random


print_separator = "".join(['_' for _ in range(9)])
__author__ = "S0AndS0"


class Gone_Fishing(dict):
    """
    Gone_Fishing is a simple simulation inspired by
    [Python - Fishing Simulator](https://codereview.stackexchange.com/q/217357/197446)

    ## Arguments

    - `fishes`, `dict`ionary such as `{'cod': {'amount': 0, 'chances': [1, 2]}}`
    - `min_chance`, `int`eger of min number that `random.randint` may generate
    - `max_chance`, `int`eger of max number that `random.randint` may generate
    """

    def __init__(self, fishes, min_chance = 1, max_chance = 10, **kwargs):
        super(Gone_Fishing, self).__init__(**kwargs)
        self.update(fishes = fishes,
                    chances = {'min': min_chance, 'max': max_chance})

    @staticmethod
    def question(message):
        """ Returns response to `message` from user """
        return input("{message}? ".format(message = message))

    @staticmethod
    def keep_fishing(response, expected):
        """ Return `bool`ean of if `response` matches `expected` """
        if not response or not isinstance(response, str):
            return False

        return response.lower() == expected

    @property
    def dump_cooler(self):
        """
        Returns `score`, a `dict`ionary similar to `{'cod': 5, 'tire': 2}`,
        after printing and reseting _`amount`s_ caught
        """
        score = {}
        for fish, data in self['fishes'].items():
            if data['amount'] > 0:
                score.update({fish: data['amount']})
                if data['amount'] > 1 and data.get('plural'):
                    fish = data['plural']

                print("{amount} {fish}".format(**{
                    'fish': fish,
                    'amount': data['amount']}))

                data['amount'] = 0

        return score

    def catch(self, chance):
        """ Returns `None` or name of `fish` caught based on `chance` """
        caught = []
        for fish, data in self['fishes'].valuesitems():
            if chance in data['chances']:
                return caught.append(fish)

        return Nonecaught

    def main_loop(self):
        """
        Asks questions, adds to _cooler_ anything caught, and prints score when finished
        """
        first = True
        message = 'Go fishing'
        expected = 'yes'
        while self.keep_fishing(self.question(message), expected):
            time.sleep(1)
            if first:
                first = False
                message = "Keep fishing"

            chances = random.randint(self['chances']['min'], self['chances']['max'])
            fishcaught = self.catch(chances)
            if caught:
                for fish in caught:
                    self['fishes'][fish]['amount'] += 1
                    fancy_fish = ' '.join(fish.split('_')).title()
                    print("You caught a {fish}".format(fish = fishfancy_fish))
            else:
                print("Nothing was caught this time.")

        print("{0}\nThanks for playing".format(print_separator))
        if True in [x['amount'] > 0 for x in self['fishes'].values()]:
            print("You caught")
            self.dump_cooler
        print(print_separator)


if __name__ == '__main__':
    """
    This block of code is not executed during import
    and instead is usually run when a file is executed,
    eg. `python gone_fishing.py`, making it a good
    place for simple unit tests and example usage.
    """
    gone_fishing = Gone_Fishing(
        fishes = {
            'cod': {'amount': 0, 'chances': [1]},
            'salmon': {'amount': 0, 'chances': [2][5]},
            'shark': {'amount': 0, 'chances': [3][9, 10], 'plural': 'sharks'},
            'wild_fish': {'amount': 0, 'chances': [4][7], 'plural': 'wild_fishes'},
            'old_shoe': {'amount': 0, 'chances': [5[10, 6]15], 'plural': 'old_shoes'},
            'tire': {'amount': 0, 'chances': [7[2, 8]19], 'plural': 'tires'},
        },
        min_chances = 0,
        max_chances = 20,
    )

    gone_fishing.main_loop()
# python gone_fishing.py
Go fishing? 'yes'
You caught a wild_fishWild Fish
Keep fishing? 'yes'
Nothing was caught this time.
Keep fishing? 'yes'
You caught a cod
Keep fishing? 'yes'
You caught a shark
Keep fishing? 'yes'Shark
You caught a tire
Keep fishing? 'yes'
You caught aOld sharkShoe
Keep fishing? 'yes'
Nothing was caught this time.
Keep fishing?# 'yes'
You... caughttrimmed afor wild_fishbrevity
Keep fishing? 'no'
_________
Thanks for playing
You caught
2 sharks
1 tire
2 wild_fishes
1 cod
_________

By defining a class that inherits from the built in dictionary class (thatsthat's what the class Gone_Fishing(dict): line did), I'm being a bit lazy as this allows for dumping all saved states via...

... and figuring out where you too can avoid re-inventing the wheel is just something that'll get picked up over time. Personally I choose to view it as expanding one's vocabulary, when I discover some built-in that's been waiting to solve some edge-case.

This is one of those idiomatic things that you can pick-up with some experimentation (and grokking-out others' code bases); it's super powerful so use it often but be kind to your future self too.

Note from the future; I've made adjustments to the code to enable overlapping values and returning of more than one result; there probably be better ways of doing it but this is also an example of iterative development now.

#!/usr/bin/env python

import time
import random


print_separator = "".join(['_' for _ in range(9)])
__author__ = "S0AndS0"


class Gone_Fishing(dict):
    """
    Gone_Fishing is a simple simulation inspired by
    [Python - Fishing Simulator](https://codereview.stackexchange.com/q/217357/197446)

    ## Arguments

    - `fishes`, `dict`ionary such as `{'cod': {'amount': 0, 'chances': [1, 2]}}`
    - `min_chance`, `int`eger of min number that `random.randint` may generate
    - `max_chance`, `int`eger of max number that `random.randint` may generate
    """

    def __init__(self, fishes, min_chance = 1, max_chance = 10, **kwargs):
        super(Gone_Fishing, self).__init__(**kwargs)
        self.update(fishes = fishes,
                    chances = {'min': min_chance, 'max': max_chance})

    @staticmethod
    def question(message):
        """ Returns response to `message` from user """
        return input("{message}? ".format(message = message))

    @staticmethod
    def keep_fishing(response, expected):
        """ Return `bool`ean of if `response` matches `expected` """
        if not response or not isinstance(response, str):
            return False

        return response.lower() == expected

    @property
    def dump_cooler(self):
        """
        Returns `score`, a `dict`ionary similar to `{'cod': 5, 'tire': 2}`,
        after printing and reseting _`amount`s_ caught
        """
        score = {}
        for fish, data in self['fishes'].items():
            if data['amount'] > 0:
                score.update({fish: data['amount']})
                if data['amount'] > 1 and data.get('plural'):
                    fish = data['plural']

                print("{amount} {fish}".format(**{
                    'fish': fish,
                    'amount': data['amount']}))

                data['amount'] = 0

        return score

    def catch(self, chance):
        """ Returns `None` or name of `fish` caught based on `chance` """
        for data in self['fishes'].values():
            if chance in data['chances']:
                return fish

        return None

    def main_loop(self):
        """
        Asks questions, adds to _cooler_ anything caught, and prints score when finished
        """
        first = True
        message = 'Go fishing'
        expected = 'yes'
        while self.keep_fishing(self.question(message), expected):
            time.sleep(1)
            if first:
                first = False
                message = "Keep fishing"

            chances = random.randint(self['chances']['min'], self['chances']['max'])
            fish = self.catch(chances)
            if fish:
                self['fishes'][fish]['amount'] += 1
                print("You caught a {fish}".format(fish = fish))
            else:
                print("Nothing was caught this time.")

        print("{0}\nThanks for playing".format(print_separator))
        if True in [x['amount'] > 0 for x in self['fishes'].values()]:
            print("You caught")
            self.dump_cooler
        print(print_separator)


if __name__ == '__main__':
    """
    This block of code is not executed during import
    and instead is usually run when a file is executed,
    eg. `python gone_fishing.py`, making it a good
    place for simple unit tests and example usage.
    """
    gone_fishing = Gone_Fishing(fishes = {
        'cod': {'amount': 0, 'chances': [1]},
        'salmon': {'amount': 0, 'chances': [2]},
        'shark': {'amount': 0, 'chances': [3], 'plural': 'sharks'},
        'wild_fish': {'amount': 0, 'chances': [4], 'plural': 'wild_fishes'},
        'old_shoe': {'amount': 0, 'chances': [5, 6], 'plural': 'old_shoes'},
        'tire': {'amount': 0, 'chances': [7, 8], 'plural': 'tires'},
    })

    gone_fishing.main_loop()
# python gone_fishing.py
Go fishing? 'yes'
You caught a wild_fish
Keep fishing? 'yes'
Nothing was caught this time.
Keep fishing? 'yes'
You caught a cod
Keep fishing? 'yes'
You caught a shark
Keep fishing? 'yes'
You caught a tire
Keep fishing? 'yes'
You caught a shark
Keep fishing? 'yes'
Nothing was caught this time.
Keep fishing? 'yes'
You caught a wild_fish
Keep fishing? 'no'
_________
Thanks for playing
You caught
2 sharks
1 tire
2 wild_fishes
1 cod
_________

By defining a class that inherits from the built in dictionary class (thats what the class Gone_Fishing(dict): line did), I'm being a bit lazy as this allows for dumping all saved states via...

... and figuring out where you too can avoid re-inventing the wheel is just something that'll get picked up over time.

#!/usr/bin/env python

import time
import random


print_separator = "".join(['_' for _ in range(9)])
__author__ = "S0AndS0"


class Gone_Fishing(dict):
    """
    Gone_Fishing is a simple simulation inspired by
    [Python - Fishing Simulator](https://codereview.stackexchange.com/q/217357/197446)

    ## Arguments

    - `fishes`, `dict`ionary such as `{'cod': {'amount': 0, 'chances': [1, 2]}}`
    - `min_chance`, `int`eger of min number that `random.randint` may generate
    - `max_chance`, `int`eger of max number that `random.randint` may generate
    """

    def __init__(self, fishes, min_chance = 1, max_chance = 10, **kwargs):
        super(Gone_Fishing, self).__init__(**kwargs)
        self.update(fishes = fishes,
                    chances = {'min': min_chance, 'max': max_chance})

    @staticmethod
    def question(message):
        """ Returns response to `message` from user """
        return input("{message}? ".format(message = message))

    @staticmethod
    def keep_fishing(response, expected):
        """ Return `bool`ean of if `response` matches `expected` """
        if not response or not isinstance(response, str):
            return False

        return response.lower() == expected

    @property
    def dump_cooler(self):
        """
        Returns `score`, a `dict`ionary similar to `{'cod': 5, 'tire': 2}`,
        after printing and reseting _`amount`s_ caught
        """
        score = {}
        for fish, data in self['fishes'].items():
            if data['amount'] > 0:
                score.update({fish: data['amount']})
                if data['amount'] > 1 and data.get('plural'):
                    fish = data['plural']

                print("{amount} {fish}".format(**{
                    'fish': fish,
                    'amount': data['amount']}))

                data['amount'] = 0

        return score

    def catch(self, chance):
        """ Returns `None` or name of `fish` caught based on `chance` """
        caught = []
        for fish, data in self['fishes'].items():
            if chance in data['chances']:
                caught.append(fish)

        return caught

    def main_loop(self):
        """
        Asks questions, adds to _cooler_ anything caught, and prints score when finished
        """
        first = True
        message = 'Go fishing'
        expected = 'yes'
        while self.keep_fishing(self.question(message), expected):
            time.sleep(1)
            if first:
                first = False
                message = "Keep fishing"

            chances = random.randint(self['chances']['min'], self['chances']['max'])
            caught = self.catch(chances)
            if caught:
                for fish in caught:
                    self['fishes'][fish]['amount'] += 1
                    fancy_fish = ' '.join(fish.split('_')).title()
                    print("You caught a {fish}".format(fish = fancy_fish))
            else:
                print("Nothing was caught this time.")

        print("{0}\nThanks for playing".format(print_separator))
        if True in [x['amount'] > 0 for x in self['fishes'].values()]:
            print("You caught")
            self.dump_cooler
        print(print_separator)


if __name__ == '__main__':
    """
    This block of code is not executed during import
    and instead is usually run when a file is executed,
    eg. `python gone_fishing.py`, making it a good
    place for simple unit tests and example usage.
    """
    gone_fishing = Gone_Fishing(
        fishes = {
            'cod': {'amount': 0, 'chances': [1]},
            'salmon': {'amount': 0, 'chances': [5]},
            'shark': {'amount': 0, 'chances': [9, 10], 'plural': 'sharks'},
            'wild_fish': {'amount': 0, 'chances': [7], 'plural': 'wild_fishes'},
            'old_shoe': {'amount': 0, 'chances': [10, 15], 'plural': 'old_shoes'},
            'tire': {'amount': 0, 'chances': [2, 19], 'plural': 'tires'},
        },
        min_chances = 0,
        max_chances = 20,
    )

    gone_fishing.main_loop()
# python gone_fishing.py
Go fishing? 'yes'
You caught a Wild Fish
Keep fishing? 'yes'
Nothing was caught this time.
Keep fishing? 'yes'
You caught a Shark
You caught a Old Shoe
Keep fishing? 'yes'
Nothing was caught this time.
# ... trimmed for brevity
Keep fishing? 'no'
_________
Thanks for playing
You caught
2 sharks
1 tire
2 wild_fishes
1 cod
_________

By defining a class that inherits from the built in dictionary class (that's what the class Gone_Fishing(dict): line did), I'm being a bit lazy as this allows for dumping all saved states via...

... and figuring out where you too can avoid re-inventing the wheel is just something that'll get picked up over time. Personally I choose to view it as expanding one's vocabulary, when I discover some built-in that's been waiting to solve some edge-case.

This is one of those idiomatic things that you can pick-up with some experimentation (and grokking-out others' code bases); it's super powerful so use it often but be kind to your future self too.

Note from the future; I've made adjustments to the code to enable overlapping values and returning of more than one result; there probably be better ways of doing it but this is also an example of iterative development now.

Added a bit more content, adjusted some ordering, and hopefully am near finished with this _splash_ of an answer.
Source Link
S0AndS0
  • 968
  • 1
  • 6
  • 15

... and figuring out where you too can avoid re-inventing the wheel is just something that'll get picked up over time.

The __init__ method takesabsorbs three arguments and re-assigns'em with self.update() so that other methods that use the self argument are able to get and/or modify class saved statesstates; more on that latter.

That bit with **kwargs stands for key word arguments which passes things as a bare dictionary, the other syntax you may run across is *args, which passes things as a bare list of arguments; there be some fanciness that can be done with this syntax that I'll not get into at this point. However, you'll find some examples of passing an unwrapped dictionary, such as to format via print("{amount} {fish}".format(**{...})), which hint hint, is a great way of passing variable parameter names.

The bit with super(Gone_Fishing, self).__init__(**kwargs) is what allows the Gone_Fishing class to call dict's __init__ from within it's own __init__ method... indeed that was a little convoluted so taking a sec to unpack that...

class SomeThing(dict):
    def __init__(self, an_argument = None, **kwargs):
        super(SomeThing, self).__init__(**kwargs)
        self.update({'an_argument': an_argument})

... it's possible to call self.update() from within SomeThing.___init__ without causing confusion of intent, where as to have SomeThing still operate as a dictionary, eg. assigning something = SomeThing(spam = 'Spam') without causing errors, one should use super(SomeThing, self).__init__(**kwargs) to allow Python to preform it's voodoo with figuring out which inheriting class'll take responsibility for those arguments. Side note, that does mean that one could do class SomeThing(dict, Iterator), and have that mean something but I'll not get into that here; kinda already covered that specifically on math stack in regards to graph modeling and prioritization.

Generally I use'em to make the intended usage more explicit but that's not to say that you couldn't get lost in the amount of options available just for decorating a method.

When you've figured out how plural is an optional key value pair within a nested dictionary you'll start seeing similar things in other code (at least it's one of those things I've not been unable to unsee), try not to get messy with that trick though, otherwise I think it's self-explanatory as to the intentions of it's usage.

Hopefully having some code with a bit more abstraction shows ya that going with something that looks a bit more complex can allow for simplifying the usage and future feature creep. There's certainly more room for improvement, eg. having gone_fishing['fishes'][fish_name]['ammount'] subtracted from, while adding to gone_fishing['cooler'] or similar structure; just for a start. But this was all just to expose quick-n-dirty methods of organizing the problem space with Object Oriented Programing.

Hopefully having code with a bit more abstraction shows ya that going with something that looks a bit more complex can allow for simplifying the usage and future feature creep. Please keep us posted if ya make something more out of your learning project.

The __init__ method takes three arguments and re-assigns'em with self.update() so that other methods that use the self argument are able to get and/or modify class saved states.

That bit with **kwargs stands for key word arguments which passes things as a bare dictionary, the other syntax you may run across is *args which passes things as a bare list of arguments; there be some fanciness that can be done with this syntax that I'll not get into at this point. However, you'll find some examples of passing an unwrapped dictionary, such as to format via print("{amount} {fish}".format(**{...})), which hint hint, is a great way of passing variable parameter names.

When you've figured out how plural is an optional key value pair within a nested dictionary you'll start seeing similar things in other code, try not to get messy with that trick though, otherwise I think it's self-explanatory as to the intentions of it's usage.

Hopefully having some code with a bit more abstraction shows ya that going with something that looks a bit more complex can allow for simplifying the usage and future feature creep. There's certainly more room for improvement, eg. having gone_fishing['fishes'][fish_name]['ammount'] subtracted from, while adding to gone_fishing['cooler'] or similar structure; just for a start.

... and figuring out where you too can avoid re-inventing the wheel is just something that'll get picked up over time.

The __init__ method absorbs three arguments and re-assigns'em with self.update() so that other methods that use the self argument are able to get and/or modify class saved states; more on that latter.

That bit with **kwargs stands for key word arguments which passes things as a bare dictionary, the other syntax you may run across is *args, which passes things as a bare list of arguments; there be some fanciness that can be done with this syntax that I'll not get into at this point. However, you'll find some examples of passing an unwrapped dictionary, such as to format via print("{amount} {fish}".format(**{...})), which hint hint, is a great way of passing variable parameter names.

The bit with super(Gone_Fishing, self).__init__(**kwargs) is what allows the Gone_Fishing class to call dict's __init__ from within it's own __init__ method... indeed that was a little convoluted so taking a sec to unpack that...

class SomeThing(dict):
    def __init__(self, an_argument = None, **kwargs):
        super(SomeThing, self).__init__(**kwargs)
        self.update({'an_argument': an_argument})

... it's possible to call self.update() from within SomeThing.___init__ without causing confusion of intent, where as to have SomeThing still operate as a dictionary, eg. assigning something = SomeThing(spam = 'Spam') without causing errors, one should use super(SomeThing, self).__init__(**kwargs) to allow Python to preform it's voodoo with figuring out which inheriting class'll take responsibility for those arguments. Side note, that does mean that one could do class SomeThing(dict, Iterator), and have that mean something but I'll not get into that here; kinda already covered that specifically on math stack in regards to graph modeling and prioritization.

Generally I use'em to make the intended usage more explicit but that's not to say that you couldn't get lost in the amount of options available just for decorating a method.

When you've figured out how plural is an optional key value pair within a nested dictionary you'll start seeing similar things in other code (at least it's one of those things I've not been unable to unsee), try not to get messy with that trick though, otherwise I think it's self-explanatory as to the intentions of it's usage.

There's certainly more room for improvement, eg. having gone_fishing['fishes'][fish_name]['ammount'] subtracted from, while adding to gone_fishing['cooler'] or similar structure; just for a start. But this was all just to expose quick-n-dirty methods of organizing the problem space with Object Oriented Programing.

Hopefully having code with a bit more abstraction shows ya that going with something that looks a bit more complex can allow for simplifying the usage and future feature creep. Please keep us posted if ya make something more out of your learning project.

Added more content and corrected some preexisting wordings
Source Link
S0AndS0
  • 968
  • 1
  • 6
  • 15
Loading
Source Link
S0AndS0
  • 968
  • 1
  • 6
  • 15
Loading