```"""
El Farol Bar Problem
What happens when patrons want to go to the bar up to
60% occupancy, but don't beyond that point.
Yogi Berra: "That place is so popular, no one goes there
any more."
"""

import random

from indra.agent import Agent
from indra.composite import Composite
from indra.display_methods import BLUE, RED
from indra.env import Env
from registry.registry import get_env, get_group, get_prop, set_env_attr
from registry.registry import user_tell, run_notice, user_log_notif
from indra.space import DEF_HEIGHT, DEF_WIDTH
from indra.utils import init_props

DEBUG = False

MODEL_NAME = "el_farol"
DRINKERS = "At bar"
NON_DRINKERS = "At home"
BAR_ATTEND = "Bar attendees"
POPULATION = "population"
ATTENDANCE = "attendance"
AGENTS_DECIDED = "agents_decided"
OPT_OCCUPANCY = "opt_occupancy"
MOTIV = "motivation"

DEF_POPULATION = 10
DEF_MOTIV = 0.6
DISC_AMT = .01
MIN_MOTIV = 0.05
DEF_OPTIMAL_OCCUPANCY = int(DEF_MOTIV * DEF_POPULATION)
NUM_DRINKERS = DEF_POPULATION // 2
NUM_NON_DRINKERS = DEF_POPULATION - NUM_DRINKERS

def get_decision(agent):
"""
Makes a decision for the agent whether or not to go to the bar
"""
return random.random() <= agent[MOTIV]

def discourage(unwanted):
"""
Discourages extra drinkers from going to the bar by decreasing motivation.
Chooses drinkers randomly from the drinkers that went to the bar.
"""
discouraged = 0
drinkers = get_group(DRINKERS)
while unwanted:
if DEBUG:
user_tell("The members are: " + drinkers.members)
rand_name = random.choice(list(drinkers.members))
rand_agent = drinkers[rand_name]

if DEBUG:
user_tell("drinker ", rand_agent, " = "
+ repr(drinkers[rand_agent]))

rand_agent[MOTIV] = max(rand_agent[MOTIV] - DISC_AMT,
MIN_MOTIV)
discouraged += 1
unwanted -= 1
return discouraged

def drinker_action(agent):
drinkers = get_group(DRINKERS)
non_drinkers = get_group(NON_DRINKERS)

changed = True
decision = get_decision(agent)
bar = get_env()
bar.attrs[AGENTS_DECIDED] += 1

attendance = bar.get_attr(ATTENDANCE, 0)
opt_occupancy = bar.get_attr(OPT_OCCUPANCY)
agents_decided = bar.get_attr(AGENTS_DECIDED)
if agents_decided == bar.get_attr(POPULATION, 0):
if attendance > opt_occupancy:
extras = attendance - opt_occupancy
discourage(extras)
bar.set_attr(AGENTS_DECIDED, 0)
bar.set_attr(ATTENDANCE, 0)

if decision:
bar.set_attr(ATTENDANCE, attendance + 1)
if agent.primary_group() == non_drinkers:
changed = False
drinkers)
else:
if agent.primary_group() == drinkers:
changed = False
non_drinkers)

# return False means to move
return changed

def create_drinker(name, i, props=None):
"""
Create an agent.
"""
return Agent(name + str(i), action=drinker_action,
attrs={MOTIV: DEF_MOTIV})

def create_non_drinker(name, i, props=None):
"""
Create an agent.
"""
return Agent(name + str(i), action=drinker_action,
attrs={MOTIV: DEF_MOTIV})

def setup_attendance(pop_hist):
"""
Set up our pop hist object to record exchanges per period.
"""
pop_hist.record_pop(BAR_ATTEND, 0)

def attendance(pop_hist):
pop_hist.record_pop(BAR_ATTEND,
get_env().attrs[ATTENDANCE])

def attendance_report(env):
return("El Farol attendees on day "
+ str(env.get_periods())
+ ": " + str(env.attrs[ATTENDANCE]))

def set_env_attrs():
user_log_notif("Setting env attrs for " + MODEL_NAME)
set_env_attr("pop_hist_func", attendance)
set_env_attr("census_func", attendance_report)

def set_up(props=None):
"""
A func to set up run that can also be used by test code.
"""
init_props(MODEL_NAME, props)

drinkers = Composite(DRINKERS, {"color": RED},
member_creator=create_drinker,
num_members=get_prop('population',
DEF_POPULATION) // 2)

non_drinkers = Composite(NON_DRINKERS, {"color": BLUE},
member_creator=create_non_drinker,
num_members=get_prop('population',
DEF_POPULATION) // 2)
bar = Env(MODEL_NAME,
height=get_prop('grid_height', DEF_HEIGHT),
width=get_prop('grid_width', DEF_WIDTH),
members=[drinkers, non_drinkers],
pop_hist_setup=setup_attendance)
population = len(drinkers) + len(non_drinkers)
bar.set_attr(POPULATION, population)
bar.set_attr(OPT_OCCUPANCY, int(population * DEF_MOTIV))
bar.set_attr(AGENTS_DECIDED, 0)
bar.set_attr(ATTENDANCE, 0)
set_env_attrs()

def main():
set_up()
run_notice(MODEL_NAME)
get_env()()
return 0

if __name__ == "__main__":
main()
```