First off I think your use case is a nifty way of getting into Python, and it looks like aside from the bugs that others have already pointed out you'll likely soon be unstoppable.


However, instead of _simplifying_ the code I'd suggest modularizing as well as making use of `__doc__` strings. It'll make adding features much easier in the future, and if you so choose, allow for making a full application with [_`Kivy`_](https://kivy.org/), [_`Blender`_](https://www.blender.org/), or one of the other many GUI frameworks for Python development. Plus modularizing or abstraction allows for simplifying the intentions/usage.


> 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"_ ](https://www.urbandictionary.com/define.php?term=dunder), and the _phylum_ that they're classified under are [_"magic methods"_](https://rszalski.github.io/magicmethods/)
>
> - 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.


Here's some example code inspired by yours that shows some of what I was going on about in your question's comments...


```python
#!/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()
```


... okay there's a _bit_ going on up there, so feel free to dissect it's operation by adding [_`breakpoints`_](https://stackoverflow.com/a/6980836/2632107) or _`print(something)`_ lines.


___

Here's what output of running the above script may look like

```
# 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
_________
```


___


Taking it from the top `print_separator = "".join(['_' for _ in range(9)])` is what I like to use when generating strings of repeating characters because it's easy to make something that outputs `_-_-_` via `"-".join(['_' for _ in range(3)])`.


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


------


By defining a class that inherits from the built in `dict`ionary `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...


```python
print(gone_fishing)
# -> {'cod': {'amount': 2, 'chances': [1]}, ...}
```


> ... and while I'm on the tangent of getting info back out...


```python
print(gone_fishing.main_loop.__doc__)
# Or
# help(gone_fishing.main_loop)
```


> ... will print the previously mentioned `__doc__` strings.


... 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.


------


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.


> 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...

```python
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_ `dict`ionary, 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.


> 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.


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...


```python
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 `dict`ionary, 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_](https://math.stackexchange.com/users/657433/s0ands0) in regards to graph modeling and prioritization.


------


The `@staticmethod` and other [_`decorators`_](https://www.geeksforgeeks.org/decorators-in-python/) are ways of denoting a special use `method`. In the case of `property`s they operate _similarly_ to `Object` properties, eg...


```python
class Test_Obj:
    pass

o = Test_Obj()
o.foo = 'Foo'

print(o.foo)
# -> Foo
```


... 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 `staticmethod`s, 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...


```python
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 also the various `.format()` usages are to show ways of _future prepping_ (for perhaps using `f strings` in the future), as well as making strings somewhat more explicit.


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`.


> 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 `decorat`ing.
>
> Generally I use `staticmethod`s 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.


And the bit with `if True in [x['amount'] > 0 for x in self['fishes'].values()]` is something that _masks_ data using [_`list comprehensions`_](https://docs.python.org/3/tutorial/datastructures.html#list-comprehensions), I'll advise against getting too _fancy_ with'em, and instead try to utilize'em whenever it doesn't make code less readable. Also don't get to attached to such cleverness because [_`numpy`_](http://www.numpy.org/), [_`pandas`_](https://pandas.pydata.org/), or one of the many other libraries, will preform similar tasks far faster.


------


The things happening bellow the `if __name__ == '__main__':`, aside from the _doc string_ ...


> Side note for those new to Python; sure you could call'em _"dunder docs"_ and those _in the know_ would know what you where saying, but they'd also likely _smize_ at ya too, and saying _"dundar doc string"_ if timed when a listener is drinking could have messy consequences... so "pro-tip", callem _"doc strings"_ to be _`super` `class`y_ when talking about Python code ;-)


```python
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'},
})
```


... and how the above is parsed could take _some words_ to do a full _stack trace_, but the gist is that `chances` is a `list` that you could even have overlapping integers, eg. a `shark` who had an `old_shoe` inside could be...


```python
gone_fishing['fishes']['shark']['chances'].append(5)
```


... though without adjustments to other values that would make for a very large shoal of soul hungry sharks.


> 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.


------


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.


------


The arguments that I didn't assign, `min_chance` and `max_chance`, much like the `chances` with `sharks` could be updated similarly, eg...


```python
gone_fishing['chances']['max'] = 20
```


... though initializing a new trip would look like...


```python
another_fishing_trip = 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,
)
```


... which serves as an example of something you'd be wise to avoid doing to your own code, swapping words especially isn't going to win any points from a _future self_ or other developers.


___


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.


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`_](https://en.wikipedia.org/wiki/Feature_creep). Please keep us posted if ya make something more out of your learning project.