File size: 6,339 Bytes
a797471
 
5a8a1c7
a797471
 
dbad5ad
a797471
f57d6bf
5a8a1c7
606728b
a797471
 
 
 
5a8a1c7
a797471
 
 
 
 
 
 
 
 
 
 
 
 
5a8a1c7
a797471
 
 
 
 
 
 
 
 
 
 
 
 
dbad5ad
 
5a8a1c7
a797471
 
 
 
dbad5ad
606728b
a797471
 
 
 
 
 
e64c363
a797471
5a8a1c7
606728b
 
5a8a1c7
 
a797471
606728b
 
 
a797471
 
 
720abd1
dbad5ad
a797471
 
dbad5ad
 
5a8a1c7
dbad5ad
 
a797471
dbad5ad
 
5a8a1c7
 
720abd1
dbad5ad
 
5a8a1c7
 
dbad5ad
5a8a1c7
dbad5ad
5a8a1c7
dbad5ad
 
 
a797471
 
 
 
 
 
dbad5ad
 
 
a797471
 
 
 
 
 
 
 
 
 
dbad5ad
f57d6bf
 
 
 
 
 
 
 
 
5a8a1c7
 
 
 
 
 
a797471
 
5a8a1c7
 
a797471
606728b
 
a797471
 
 
 
 
5a8a1c7
a41a64b
5a8a1c7
 
a797471
606728b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a797471
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
from collections import defaultdict

import seaborn as sns
import streamlit as st
import streamlit.components.v1 as components
import plotly.graph_objects as go
import plotly.express as px
import numpy as np
import pandas as pd
from scipy.stats import norm


st.set_page_config(page_title='Can you be truly random ?', layout = 'wide', page_icon = 'favicon.jpg', initial_sidebar_state = 'auto')

cm = sns.light_palette("green", as_cmap=True)


# Custom CSS to styles
st.markdown("""
    <style>
        button {
            padding-top: 50px !important;
            padding-bottom: 50px !important;
        }
    </style>
""", unsafe_allow_html=True)




def set_state(x):
    if x == 1: st.toast('The journey begins!', icon='😍')
    st.session_state.stage = x

        
def reset_game():
    set_state(0)
    if 'n_buttons' not in st.session_state:
        st.session_state.n_buttons = 2
    st.session_state.history = []
    st.session_state.preds = defaultdict(lambda: defaultdict(int))
    st.session_state.pnl = [0]
    st.session_state.min = [0]
    st.session_state.max = [0]
    st.session_state.max_history = 10

if 'stage' not in st.session_state:
    reset_game()

st.title('Can your brain be random?')
n_plays = 50

if st.session_state.stage == 0:
    st.button('Begin', on_click=set_state, args=[1],use_container_width=True)
    
    st.session_state.n_buttons = st.slider(label="How many buttons to play with?", min_value=2, max_value=5,value=2)
    
    st.markdown(f'You will be presented with {st.session_state.n_buttons} buttons to randomly choose from')
    
    st.markdown(f"At each round, I will try to predict which button you click")
    st.session_state.max_history = st.slider(label="How much memory can I use?", min_value=2, max_value=20,value=10)
    
    st.markdown(f"I will examine {st.session_state.max_history}-long sequences you played in the past")
    st.markdown('This will help me make a guess')
    st.markdown(f"If I get it right, I earn {st.session_state.n_buttons-1} point(s), otherwise you earn 1 point")
    st.subheader(f'This is a fair game, as you have {(1-1/st.session_state.n_buttons):.0%} chances of winning')
    st.markdown(f"Play as long as you want and try to beat me!")
    st.markdown(f"I will start showing you my strategy from {n_plays} rounds")
    
        
    
def make_pred(max_history=st.session_state.max_history,alpha=2):
    history = st.session_state.history
    
    denominator = 0
    scores = {str(i):0 for i in range(st.session_state.n_buttons)}
    recent = np.array(history[-max_history:])
    data = []
    for i in range(1,len(history)):
        past = np.array(history[-i-max_history:-i])
        
        played = history[-i]
        decay = np.exp(-alpha*np.linspace(1,0,len(past)))
        same = past == recent[-len(past):]
        similarity = (same*decay).sum() / decay.sum()
        weight = np.exp(-alpha*i/(len(history)+1)) * similarity # len(history)/(len(history)+i) * similarity
        scores[played] += weight
        denominator += weight
        if similarity > 0.5:
            data.append([played,similarity,weight,i] + [None]* (max_history-len(past)) +list(same))
    if not denominator:
        return get_random_play(),data
    result = {str(i):scores[str(i)]/denominator for i in range(st.session_state.n_buttons)}
    return result, data
    
def get_random_play():
    return {str(i):1/st.session_state.n_buttons for i in range(st.session_state.n_buttons)}
        

def update_pnl(user_win):
    current_score = st.session_state.pnl[-1]
    current_score += -1 if user_win else (st.session_state.n_buttons-1)
    st.session_state.pnl.append(current_score)
    expected_change = (2**0.5) * ((st.session_state.n_buttons-1)/st.session_state.n_buttons)
    st.session_state.min.append(st.session_state.min[-1]-expected_change)
    st.session_state.max.append(st.session_state.max[-1]+expected_change)
    
def user_select(i,choice):
    st.session_state.history.append(str(i))
    if i == choice:
        st.toast("I win!", icon='πŸ€ͺ')
        update_pnl(user_win=False)
    else:
        st.toast('Well done!', icon='😍')
        update_pnl(user_win=True)
    
    # refresh_preds()

def compute_perf():
    data = np.array(st.session_state.pnl)
    if len(data) > 1:
        data = data[1:] - data[:-1]
        perf = data.mean()
        std = data.std()
        win = (data>0).mean()
        sharpe = perf / std
        return win,sharpe
    else:
        return 0,0




if st.session_state.stage == 1:
    pred,data = make_pred()
    
    choice = max(pred,key=pred.get)
    progress = min(1.,float(len(st.session_state.history))/n_plays)
    my_bar = st.progress(progress)
    
    cols = st.columns(st.session_state.n_buttons)
    for i,col in enumerate(cols):
        col.button(str(i),on_click=user_select, args=[str(i),choice],
        use_container_width=True)
        
    col1,col2 = st.columns(2)
    
    win,sharpe = compute_perf()
    
    if progress >= 1.:

        if sharpe > 0.1: 
            emoticon = "πŸš€"
        elif sharpe < 0:
            emoticon = "πŸ“‰"
        else:
            emoticon = "🧠"
        text = f'%age win={win:.0%} Sharpe={sharpe:.3f} {emoticon}'
        col1.subheader('My earnings so far...')
        col1.write(text)
        dev = abs(st.session_state.pnl[-1] / st.session_state.max[-1])
        chance = 1 - norm.cdf(dev)
        col1.write(f'There is only {chance:.2%} chances of achieving this by chance')
        fig = px.line(st.session_state.pnl)
        fig.update_layout(showlegend=False)
        fig.update_layout(xaxis={'visible': True, 'showticklabels': False})
        col1.plotly_chart(fig, use_container_width=True)

        cols = ['you_played','similarity','weight','how_long_ago'] +  [f'same_{i}' for i in range(st.session_state.max_history)]
        data = pd.DataFrame(data,columns=cols).set_index('how_long_ago').sort_values('weight',ascending=False)[:10]
        col2.subheader(f'In the past, you played')
        col2.dataframe(data.style.background_gradient(cmap=cm), use_container_width=True)
        won = choice == st.session_state.history[-1]
        col2.write(f'So I chose {choice} (p={pred[choice]:.0%}) and ' + ('won πŸš€' if won else 'lost πŸ“‰'))
        
    st.button('Start over', on_click=reset_game, args=[],
        use_container_width=True)