ginchiostro commited on
Commit
aa443f8
·
verified ·
1 Parent(s): 0d25514

Upload 6 files

Browse files
Files changed (6) hide show
  1. Briscola_app.py +272 -0
  2. Dockerfile +11 -0
  3. briscola_players.py +493 -0
  4. environment.py +170 -0
  5. templates/.DS_Store +0 -0
  6. templates/Briscola.html +658 -0
Briscola_app.py ADDED
@@ -0,0 +1,272 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #open -na Google\ Chrome --args --user-data-dir=/tmp/temporary-chrome-profile-dir --disable-web-security to avoid "CORS request blocked ..."
2
+
3
+
4
+ import os
5
+ import posix
6
+
7
+ from flask import request
8
+ from flask import Flask, render_template
9
+ from flask import jsonify
10
+
11
+ from flask import send_from_directory
12
+
13
+ import tensorflow as tf
14
+ import briscola_players as players
15
+ import environment as env
16
+
17
+ import numpy as np
18
+
19
+ app = Flask(__name__, static_folder=None)
20
+
21
+ partita = env.Briscola_env()
22
+
23
+ player_1 = None
24
+ client_player = None
25
+ card_player_1 = 'Not played yet'
26
+
27
+ client_previous_card = 'Not played yet'
28
+ pl_1_previous_card = 'Not played yet'
29
+
30
+
31
+ from huggingface_hub import snapshot_download
32
+
33
+
34
+ # Download the repo locally
35
+ model_dir = snapshot_download(repo_id="ginchiostro/briscola_model")
36
+ saved_model_path = os.path.join(model_dir, "MLP_best_model")
37
+
38
+ # Load the TensorFlow SavedModel
39
+ loaded_model = tf.keras.models.load_model(saved_model_path)
40
+
41
+ best_model = players.MyModel_dense(compute_prob_winning = False)
42
+ best_model.compile()
43
+ best_model(np.linspace(1,100,250)[np.newaxis][np.newaxis])
44
+ best_model.set_weights(loaded_model.get_weights())
45
+
46
+
47
+
48
+ dataset_dir = snapshot_download(repo_id="ginchiostro/briscola_images", repo_type="dataset")
49
+
50
+
51
+ def string_to_card(card):
52
+ res = [3, 3]
53
+ if card[0] == '8':
54
+ res[0] = 'Fante'
55
+ elif card[0] == '9':
56
+ res[0] = 'Cavallo'
57
+ elif card[0] == '0':
58
+ res[0] = 'Re'
59
+ elif card[0] == '1':
60
+ res[0] = 'Asso'
61
+ else:
62
+ res[0] = card[0]
63
+ res[1] = card[1:]
64
+ if card == 'Not played yet':
65
+ return 'Not played yet'
66
+ else:
67
+ return res[0] + ' ' + res[1]
68
+
69
+ def card_to_string(string):
70
+ card = string.split()
71
+ res = [3, 3]
72
+ if card[0] == 'Fante':
73
+ res[0] = '8'
74
+ elif card[0] == 'Cavallo':
75
+ res[0] = '9'
76
+ elif card[0] == 'Re':
77
+ res[0] = '0'
78
+ elif card[0] == 'Asso':
79
+ res[0] = '1'
80
+ else:
81
+ res[0] = card[0]
82
+ res[1] = card[1]
83
+
84
+ return res[0] + res[1]
85
+
86
+
87
+ @app.route('/new_game_vs_det', methods = ['POST'])
88
+ def new_game_det():
89
+ global partita
90
+ partita.reset()
91
+
92
+ global player_1
93
+ global client_player
94
+ global card_player_1
95
+ global client_previous_card
96
+ global pl_1_previous_card
97
+
98
+ card_player_1 = 'Not played yet'
99
+
100
+ client_previous_card = 'Not played yet'
101
+ pl_1_previous_card = 'Not played yet'
102
+
103
+ first_hand, briscola = partita.start_game()
104
+
105
+ if 0 == np.random.randint(2):
106
+ coin_flip_player_1_is_first = True
107
+ else:
108
+ coin_flip_player_1_is_first = False
109
+
110
+ player_1 = players.DeterministicPlayer(first_hand[0], briscola, coin_flip_player_1_is_first)
111
+ client_player = players.HumanPlayer(first_hand[1], briscola, not coin_flip_player_1_is_first)
112
+
113
+ if client_player.is_first_player:
114
+ first_pl_message = 'I will be playing first'
115
+ else:
116
+ first_pl_message = 'I will be playing second'
117
+ card_player_1 = player_1.policy(0, None, False)
118
+
119
+ response = {
120
+ 'first_player': first_pl_message,
121
+ 'my_hand': [string_to_card(card) for card in client_player.hand],
122
+ 'briscola': string_to_card(client_player.briscola),
123
+ 'card_player_1': string_to_card(card_player_1),
124
+ 'client_previous_card' : 'Not played yet',
125
+ 'pl_1_previous_card' : 'Not played yet'
126
+ }
127
+ return jsonify(response)
128
+
129
+ @app.route('/New_game_vs_random', methods = ['POST'])
130
+ def new_game_rand():
131
+ global partita
132
+ partita.reset()
133
+
134
+ global player_1
135
+ global client_player
136
+ global card_player_1
137
+ global client_previous_card
138
+ global pl_1_previous_card
139
+
140
+ card_player_1 = 'Not played yet'
141
+
142
+ client_previous_card = 'Not played yet'
143
+ pl_1_previous_card = 'Not played yet'
144
+
145
+ first_hand, briscola = partita.start_game()
146
+
147
+ if 0 == np.random.randint(2):
148
+ coin_flip_player_1_is_first = True
149
+ else:
150
+ coin_flip_player_1_is_first = False
151
+
152
+ player_1 = players.RandomPlayer(first_hand[0], briscola, coin_flip_player_1_is_first)
153
+ client_player = players.HumanPlayer(first_hand[1], briscola, not coin_flip_player_1_is_first)
154
+
155
+ if client_player.is_first_player:
156
+ first_pl_message = 'I will be playing first'
157
+ else:
158
+ first_pl_message = 'I will be playing second'
159
+ card_player_1 = player_1.policy(0, None, False)
160
+
161
+ response = {
162
+ 'first_player': first_pl_message,
163
+ 'my_hand': [string_to_card(card) for card in client_player.hand],
164
+ 'briscola': string_to_card(client_player.briscola),
165
+ 'card_player_1': string_to_card(card_player_1),
166
+ 'client_previous_card' : 'Not played yet',
167
+ 'pl_1_previous_card' : 'Not played yet'
168
+ }
169
+ return jsonify(response)
170
+
171
+ @app.route('/New_game_vs_best', methods = ['POST'])
172
+ def new_game_best():
173
+ global partita
174
+ partita.reset()
175
+
176
+ global player_1
177
+ global client_player
178
+ global card_player_1
179
+ global client_previous_card
180
+ global pl_1_previous_card
181
+
182
+ card_player_1 = 'Not played yet'
183
+
184
+ client_previous_card = 'Not played yet'
185
+ pl_1_previous_card = 'Not played yet'
186
+
187
+ first_hand, briscola = partita.start_game()
188
+
189
+ if 0 == np.random.randint(2):
190
+ coin_flip_player_1_is_first = True
191
+ else:
192
+ coin_flip_player_1_is_first = False
193
+
194
+ player_1 = players.DeepPlayer(first_hand[0], briscola, coin_flip_player_1_is_first)
195
+ client_player = players.HumanPlayer(first_hand[1], briscola, not coin_flip_player_1_is_first)
196
+ player_1.model = best_model
197
+
198
+ #print('beginning of the game: ', player_1.hand)
199
+ if client_player.is_first_player:
200
+ first_pl_message = 'I will be playing first'
201
+ else:
202
+ first_pl_message = 'I will be playing second'
203
+ card_player_1 = player_1.policy(0, None, False)
204
+
205
+ response = {
206
+ 'first_player': first_pl_message,
207
+ 'my_hand': [string_to_card(card) for card in client_player.hand],
208
+ 'briscola': string_to_card(client_player.briscola),
209
+ 'card_player_1': string_to_card(card_player_1),
210
+ 'client_previous_card' : 'Not played yet',
211
+ 'pl_1_previous_card' : 'Not played yet'
212
+ }
213
+ return jsonify(response)
214
+
215
+
216
+ @app.route('/play_hand', methods = ['POST'])
217
+ def my_play_hand():
218
+ global partita
219
+ global player_1
220
+ global client_player
221
+ global card_player_1
222
+ global client_previous_card
223
+ global pl_1_previous_card
224
+
225
+ message = request.get_json(force = True)
226
+ card_player_2 = card_to_string(message['my_card'])
227
+
228
+ if card_player_1 == 'Not played yet':
229
+ card_player_1 = player_1.policy(0., card_player_2, False)
230
+
231
+ pl_1_previous_card = card_player_1
232
+ client_previous_card = card_player_2
233
+
234
+
235
+ new_card_player_1, new_card_player_2, played_cards, is_end_game, player_1_wins, player_1_pts, player_2_pts = partita.step(card_player_1, card_player_2, player_1.is_first_player)
236
+
237
+ player_1.gain_info_after_a_hand(new_card_player_1, card_player_1, played_cards, player_1_wins, player_1_pts, player_2_pts)
238
+
239
+ client_player.gain_info_after_a_hand(new_card_player_2, card_player_2, played_cards, not player_1_wins, player_1_pts, player_2_pts)
240
+
241
+ if client_player.is_first_player:
242
+ first_pl_message = 'I will be playing first'
243
+ card_player_1 = 'Not played yet'
244
+ else:
245
+ first_pl_message = 'I will be playing second'
246
+ if len(player_1.hand) > 0:
247
+ card_player_1 = player_1.policy(0, None, False)
248
+ else:
249
+ card_player_1 = ' The game is over'
250
+
251
+ response = {
252
+ 'first_player': first_pl_message,
253
+ 'my_hand': [string_to_card(card) for card in client_player.hand],
254
+ 'briscola': string_to_card(client_player.briscola),
255
+ 'card_player_1': string_to_card(card_player_1),
256
+ 'client_previous_card' : string_to_card(client_previous_card),
257
+ 'pl_1_previous_card': string_to_card(pl_1_previous_card),
258
+ 'points_pl_1': str(player_1.player_1_pts),
259
+ 'points_pl_2': str(player_1.player_2_pts)
260
+ }
261
+ return jsonify(response)
262
+
263
+ @app.route("/")
264
+ def index():
265
+ return render_template("Briscola.html")
266
+
267
+ @app.route('/static/<path:filename>')
268
+ def serve_static(filename):
269
+ return send_from_directory(os.path.join(dataset_dir, 'static'), filename)
270
+
271
+
272
+ app.run(debug=False, use_reloader=False, host="0.0.0.0", port=7860)
Dockerfile ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.10
2
+
3
+ WORKDIR /app
4
+
5
+ COPY . /app
6
+
7
+ RUN RUN pip install "tensorflow==2.13.*" "keras<3" pandas numpy flask huggingface_hub
8
+
9
+ EXPOSE 7860
10
+
11
+ CMD ["python", "Briscola_app.py"]
briscola_players.py ADDED
@@ -0,0 +1,493 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Briscola environment and players. Contains nn for deep player"""
2
+ ### Briscola environment and players
3
+ #
4
+ import sys #for human player
5
+
6
+ import numpy as np
7
+ import pandas as pd
8
+ import tensorflow as tf
9
+ import environment as env
10
+
11
+ semi = ['bastoni', 'coppe', 'denari', 'spade']
12
+ numeri = ['2', '3', '4', '5', '6', '7', '8', '9', '0', '1']
13
+ my_points = [ 0, 10, 0, 0, 0, 0, 2, 3, 4, 11]
14
+ my_cards = pd.DataFrame({i:[1 for j in numeri] for i in semi}, index = numeri)
15
+ my_cards['points'] = my_points
16
+ sorted_cards = my_cards.sort_values(by = ['points'], ascending = False)
17
+
18
+
19
+ all_cards_in_strings = [i + j for i in numeri for j in semi]
20
+ all_cards_in_strings.append('None')
21
+ sorted_cards_in_string = sorted(all_cards_in_strings)
22
+
23
+
24
+ ###General Player class
25
+ class Player(env.Briscola_env):
26
+ def __init__(self, cards_in_my_hand, briscola, is_first_player):
27
+ super().__init__()
28
+ self.is_first_player = is_first_player
29
+ self.briscola = briscola
30
+ self.hand = cards_in_my_hand
31
+
32
+ #get cards seen
33
+ self.cards_seen = set([card for card in cards_in_my_hand]+[briscola])
34
+
35
+ self.player_1_pts = 0
36
+ self.player_2_pts = 0
37
+
38
+ self.initial_states = [None]*4 #needed for rnn
39
+
40
+
41
+ def gain_info_after_a_hand(self, cards_received, card_played, cards_seen, player_1_wins,
42
+ player_1_pts, player_2_pts):
43
+ """
44
+ Updates: cards_seen, hand, is_first_player
45
+ """
46
+ self.update_cards_seen(cards_seen)
47
+ self.update_my_hand(cards_received, card_played)
48
+ self.is_first_player = player_1_wins
49
+ self.update_points(player_1_pts, player_2_pts)
50
+
51
+
52
+ def update_points(self, player_1_pts, player_2_pts):
53
+ """
54
+ Updates the points after each round
55
+ """
56
+ self.player_1_pts += player_1_pts
57
+ self.player_2_pts += player_2_pts
58
+
59
+
60
+ def update_cards_seen(self, cards):
61
+ """
62
+ Returns: None
63
+ Updates: self.cards_seen
64
+ """
65
+ self.cards_seen.add(cards[0])
66
+ self.cards_seen.add(cards[1])
67
+
68
+
69
+ def update_my_hand(self, card_received, card_played):
70
+ hand_withot_dropped_card = [my_card for my_card in self.hand if my_card != card_played]
71
+ if not card_received == None:
72
+ self.hand = hand_withot_dropped_card + [card_received]
73
+ self.cards_seen.add(card_received)
74
+ else:
75
+ self.hand = hand_withot_dropped_card
76
+
77
+
78
+ def new_game(self, cards_in_my_hand, briscola, is_first_player):
79
+ """
80
+ Re-initializes the player
81
+ """
82
+ self.is_first_player = is_first_player
83
+ self.briscola = briscola
84
+ self.hand = cards_in_my_hand
85
+
86
+ #get cards seen
87
+ self.cards_seen = set([card for card in cards_in_my_hand]+[briscola])
88
+ self.player_1_pts = 0
89
+ self.player_2_pts = 0
90
+ self.initial_states = [None]*4 #needed for rnn
91
+
92
+
93
+ ###Human player
94
+ class HumanPlayer(Player):
95
+ def __init__(self, cards_in_my_hand, briscola, is_first_player):
96
+ super().__init__(cards_in_my_hand, briscola, is_first_player)
97
+
98
+ def policy(self, epsilon, first_played_card = None, vs_human = False):
99
+ if self.is_first_player:
100
+ print('I am the first player')
101
+ else:
102
+ print('I am the second player and my opponent played', first_played_card)
103
+ print('briscola:', self.briscola)
104
+ my_hand = [card for card in self.hand]
105
+ if len(my_hand) == 1:
106
+ print('My points:', self.player_1_pts)
107
+ print('Points of my opponent:', self.player_2_pts)
108
+ print('hand:', my_hand)
109
+ played_card = input("I play the following:")
110
+ return played_card
111
+
112
+
113
+ ###Deterministic "greedy" player
114
+ class DeterministicPlayer(Player):
115
+ def __init__(self, cards_in_my_hand, briscola, is_first_player):
116
+ super().__init__(cards_in_my_hand, briscola, is_first_player)
117
+
118
+ def deterministic_policy_first_player(self):
119
+ """
120
+ Plays the card with less points
121
+ """
122
+ numbers_of_cards = [card[0] for card in self.hand]
123
+ points = sorted_cards.loc[numbers_of_cards, 'points'].values
124
+ min_points = min(points)
125
+
126
+ cards_with_min_pts =[]
127
+ for card in self.hand:
128
+ if sorted_cards.loc[card[0], 'points'] == min_points:
129
+ cards_with_min_pts.append(card)
130
+ if len(cards_with_min_pts) > 0:
131
+
132
+ cards_with_min_pts_without_briscola = []#if i can win without a briscola i do that
133
+ for card in cards_with_min_pts:
134
+ if card[1:] != self.briscola:
135
+ cards_with_min_pts_without_briscola.append(card)
136
+ if len(cards_with_min_pts_without_briscola) > 0:
137
+ card_with_min_points = cards_with_min_pts_without_briscola
138
+ return cards_with_min_pts[np.random.randint(len(cards_with_min_pts))]
139
+
140
+
141
+ def deterministic_policy_second_player(self, first_played_card):
142
+ """
143
+ First determines if we can win. If we can win the round, plays the card
144
+ with less points that makes us win. Otherwise, plays the card with less points.
145
+ """
146
+ my_victories = []
147
+ for card in self.hand:
148
+ if 'second_played_card' == self.which_card_wins_a_round(first_played_card, card):
149
+ my_victories.append(card)
150
+
151
+ if len(my_victories) == 0:
152
+ return self.deterministic_policy_first_player()
153
+ else:
154
+ numbers_of_cards = [card[0] for card in my_victories]
155
+ points = sorted_cards.loc[numbers_of_cards, 'points'].values
156
+ min_points = min(points)
157
+
158
+ cards_with_min_pts =[]
159
+ for card in my_victories:
160
+ if sorted_cards.loc[card[0], 'points'] == min_points:
161
+ cards_with_min_pts.append(card)
162
+ return cards_with_min_pts[np.random.randint(len(cards_with_min_pts))]
163
+
164
+
165
+ def policy(self, epsilon, first_played_card = None, vs_human = False):
166
+ if self.is_first_player:
167
+ card = self.deterministic_policy_first_player()
168
+ if vs_human:
169
+ print('opponent plays ', card)
170
+ return card
171
+ else:
172
+ card = self.deterministic_policy_second_player(first_played_card)
173
+ if vs_human:
174
+ print('opponent plays ', card)
175
+ return card
176
+
177
+ ###Random player
178
+
179
+ class RandomPlayer(Player):
180
+ def __init__(self, cards_in_my_hand, briscola, is_first_player):
181
+ super().__init__(cards_in_my_hand, briscola, is_first_player)
182
+
183
+ def policy(self, epsilon, first_played_card = None, vs_human = False):
184
+ card = self.hand[np.random.randint(len(self.hand))]
185
+ if vs_human:
186
+ print('opponent plays ', card)
187
+ return card
188
+
189
+ ###Deep player
190
+
191
+ ##Model
192
+ class FinalLayer(tf.keras.layers.Layer):
193
+ def __init__(self):
194
+ super(FinalLayer, self).__init__()
195
+
196
+ def call(self, my_input, final_output):
197
+ """
198
+ Returns a tensor of shape [batch_size, 3].
199
+ Changes final_output assigning [1.,1.,1.] (resp.[0.,0.,0.]) if I have already won (resp. lost).
200
+
201
+ final_output.shape == [batch_size, 3] and my_input.shape == [batch_size, number_of_rounds, features]
202
+ """
203
+ results = my_input[:,-1,-1] #1d array with 1. if pl_1 wins, 0. if it loses, 0.5 otherwise
204
+ #1d array with 1. if pl_1 wins, 0. otherwise
205
+ winning = tf.reshape(tf.floor(results), (results.shape[0],1))
206
+
207
+ #1d array with 0. if pl_1 loses, 1. otherwise
208
+ losing = tf.reshape(1- tf.floor(1-results), (results.shape[0],1))
209
+
210
+ #needed for taking elementwise max-min.
211
+ winning_tensor = tf.concat([winning, winning, winning], axis = 1)
212
+ losing_tensor = tf.concat([losing, losing, losing], axis = 1)
213
+
214
+ #Changes final_output assigning [1.,1.,1.] if I have already won
215
+ first = tf.math.maximum(winning_tensor, final_output)
216
+
217
+ #Changes final_output assigning [0.,0.,0.] lost
218
+ second = tf.math.minimum(losing_tensor,first)
219
+ return second
220
+
221
+
222
+ class MyModel(tf.keras.Model):
223
+ """
224
+ To initialize set compute_prob_winning=False if the model estimates the number of points, set it to True if it estimates
225
+ the probability of winning the game.
226
+
227
+ The call has two parameters, initial_states=[None]*4 and my_return_sequences=False.
228
+ - initial_states are the initial states for the gru layers.
229
+ - my_return_sequences returns a list of the time-distributed outputs of the gru layers,
230
+ and the time-distributed outputs of the dense layer + final layer.
231
+ """
232
+ def __init__(self, compute_prob_winning=True, simplified=False):
233
+ super(MyModel, self).__init__()
234
+ self.compute_prob_winning = compute_prob_winning
235
+ self.simplified = simplified
236
+
237
+ self.gru1 = tf.keras.layers.GRU(200, return_sequences = True)
238
+ self.gru2 = tf.keras.layers.GRU(200, return_sequences = True)
239
+ if not simplified:
240
+ self.gru3 = tf.keras.layers.GRU(200, return_sequences = True)
241
+ self.gru4 = tf.keras.layers.GRU(200, return_sequences = True)
242
+
243
+ if self.compute_prob_winning:
244
+ self.dense = tf.keras.layers.Dense(3, activation = "sigmoid")
245
+ self.final = FinalLayer()
246
+ self.td1 = tf.keras.layers.TimeDistributed(self.dense)
247
+ self.td2 = tf.keras.layers.TimeDistributed(self.final)
248
+ else:
249
+ self.dense = tf.keras.layers.Dense(3, activation = "tanh")
250
+ self.td1 = tf.keras.layers.TimeDistributed(self.dense)
251
+
252
+ def call(self, state, initial_states=[None]*4, my_return_sequences=False):
253
+ h_1 = self.gru1(state, initial_state=initial_states[0])#initial states has shape (batch_size, 200)
254
+ h_2 = self.gru2(h_1, initial_state=initial_states[1])
255
+ if not my_return_sequences:
256
+ if not self.simplified:
257
+ h_3 = self.gru3(h_2, initial_state = initial_states[2])
258
+ h_4 = self.gru4(h_3, initial_state = initial_states[3])
259
+ final_output = self.dense(h_4[:,-1,:])
260
+
261
+ if self.compute_prob_winning:
262
+ return [h_1[:,-1,:], h_2[:,-1,:], h_3[:,-1,:], h_4[:,-1,:]], self.final(state, final_output)
263
+ else:
264
+ return [h_1[:,-1,:], h_2[:,-1,:], h_3[:,-1,:], h_4[:,-1,:]], final_output
265
+ else:
266
+ final_output = self.dense(h_2[:,-1,:])
267
+
268
+ if self.compute_prob_winning:
269
+ #the last 2 h-states are never used whene simplified == True
270
+ return [h_1[:,-1,:], h_2[:,-1,:], h_2[:,-1,:], h_2[:,-1,:]], self.final(state, final_output)
271
+ else:
272
+ #the last 2 h-states are never used whene simplified == True
273
+ return [h_1[:,-1,:], h_2[:,-1,:], h_2[:,-1,:], h_2[:,-1,:]], final_output
274
+ else:
275
+ if not self.simplified:
276
+ h_3 = self.gru3(h_2, initial_state = initial_states[2])
277
+ h_4 = self.gru4(h_3, initial_state = initial_states[3])
278
+ tdense = self.td1(h_4)
279
+
280
+ if self.compute_prob_winning:
281
+ #Apply FinalLayer (if I already won or lost replace the prob with 1. or 0 respectively)
282
+ #to each output, by reshaping the outputs.
283
+
284
+ reshaped_tdense = tf.reshape(tdense, (tdense.shape[1] * tdense.shape[0], tdense.shape[2]))
285
+ reshaped_input = tf.reshape(state, ( state.shape[1] * state.shape[0],1, state.shape[2]))
286
+ final_result = self.final(reshaped_input, reshaped_tdense)
287
+ return [h_1, h_2, h_3, h_4], tf.reshape(final_result, tdense.shape)
288
+ else:
289
+ return [h_1, h_2, h_3, h_4], tdense
290
+
291
+ else:
292
+ tdense = self.td1(h_2)
293
+
294
+ if self.compute_prob_winning:
295
+ #Apply FinalLayer (if I already won or lost replace the prob with 1. or 0 respectively)
296
+ #to each output, by reshaping the outputs.
297
+
298
+ reshaped_tdense = tf.reshape(tdense, (tdense.shape[1] * tdense.shape[0], tdense.shape[2]))
299
+ reshaped_input = tf.reshape(state, ( state.shape[1] * state.shape[0],1, state.shape[2]))
300
+ final_result = self.final(reshaped_input, reshaped_tdense)
301
+
302
+ #the last 2 h-states are never used whene simplified == True
303
+ return [h_1, h_2, h_2, h_2], tf.reshape(final_result, tdense.shape)
304
+
305
+ else:
306
+ #the last 2 h-states are never used whene simplified == True
307
+ return [h_1, h_2, h_2, h_2], tdense
308
+
309
+ class MyModel_dense(tf.keras.Model):
310
+ """
311
+ If the nn is approximating the probability of winning, initialize with compute_prob_winning = True,
312
+ if it is approximating the (weighted) average of the number of points we make per round initialize with
313
+ compute_prob_winning = False.
314
+
315
+ The call has the parameter initial_states = [None]*4, just to make it compatible with MyModel.
316
+ """
317
+ def __init__(self, compute_prob_winning = True):
318
+ super(MyModel_dense, self).__init__()
319
+
320
+ self.my_layers = [tf.keras.layers.Dense(200, activation = "tanh"),
321
+ tf.keras.layers.Dense(200, activation = "tanh")]
322
+
323
+ if compute_prob_winning:
324
+ self.my_layers.append(tf.keras.layers.Dense(3, activation = "sigmoid"))
325
+ self.final = FinalLayer()
326
+ else:
327
+ self.my_layers.append(tf.keras.layers.Dense(3, activation = "tanh"))
328
+
329
+ self.compute_prob_winning = compute_prob_winning
330
+
331
+ def call(self, state, initial_states = [None]*4):
332
+ current_state = tf.identity(state)
333
+ state = state[:,-1,:] #to make it compatible with the input of MyModel
334
+
335
+ for layer in self.my_layers:
336
+ state = layer(state)
337
+
338
+ if self.compute_prob_winning:
339
+ return [None]*4, self.final(current_state, state)
340
+ else:
341
+ return [None]*4, state
342
+
343
+
344
+ ## Deep player
345
+ class DeepPlayer(Player):
346
+ def __init__(self, cards_in_my_hand, briscola, is_first_player):
347
+ super().__init__(cards_in_my_hand, briscola, is_first_player)
348
+ self.model = MyModel()
349
+ self.cards_not_seen = set(all_cards_in_strings)
350
+ self.cards_not_seen.remove('None')
351
+ self.cards_not_seen = self.cards_not_seen.difference(self.cards_seen)
352
+ self.as_opponent = False
353
+
354
+ def one_hot(self, is_first_player, hand , briscola,
355
+ player_1_pts, player_2_pts, cards_seen, card_opponent = None):
356
+ """
357
+ Gets one hot state for nn.
358
+ """
359
+ if card_opponent == None:
360
+ card_opponent = 'None'
361
+ result1 = [1. if is_first_player else 0.]
362
+ position = []
363
+
364
+ for card in hand:
365
+ position.append(sorted_cards_in_string.index(card))
366
+ if len(position)<3:
367
+ position = position + [40]*(3-len(position))
368
+
369
+ card_1 = np.eye(41)[position[0]]
370
+ card_2 = np.eye(41)[position[1]]
371
+ card_3 = np.eye(41)[position[2]]
372
+ briscola = np.eye(41)[sorted_cards_in_string.index(briscola)]
373
+
374
+ pts = np.array([player_1_pts, player_2_pts])/120 if not self.as_opponent else np.array([player_2_pts, player_1_pts])/120
375
+ if pts[0] > 0.5:
376
+ winning = [1.]
377
+ elif pts[1] > 0.5:
378
+ winning = [0.]
379
+ else:
380
+ winning = [.5]
381
+
382
+ card_opponent_oh = np.eye(41)[sorted_cards_in_string.index(card_opponent)]
383
+
384
+ encode_seen_cards = np.zeros(41)
385
+ for i in range(len(sorted_cards_in_string)):
386
+ if sorted_cards_in_string[i] in cards_seen:
387
+ encode_seen_cards[i] = 1
388
+
389
+ result = np.concatenate([result1, card_1, card_2, card_3, briscola, pts, card_opponent_oh, encode_seen_cards, winning ])
390
+ return result[np.newaxis].astype('float32')
391
+
392
+
393
+ #Auxuliary 1 -- see policy last rounds
394
+ def last_two_rounds(self, my_hand, opponent_hand, played_card):
395
+ if played_card != None:
396
+ best_option = ('s', - 200, 200) #(card, my pts, opp points)
397
+ for card in my_hand:
398
+ remaining_cards = [_ for _ in my_hand if _ != card]
399
+ remaining_opp_cards = [_ for _ in opponent_hand if _ != played_card]
400
+ _, _, _, _, player_1_wins, player_1_pts, player_2_pts = self.step(card, played_card, False, update_global_var = False)
401
+ my_points = player_1_pts
402
+ opp_points = player_2_pts
403
+ _, _, _, _, _, player_1_pts, player_2_pts = self.step(remaining_cards[0], remaining_opp_cards[0], player_1_wins, update_global_var = False)
404
+ my_points += player_1_pts
405
+ opp_points += player_2_pts
406
+ if my_points > best_option[1]:
407
+ best_option = [card, my_points, opp_points]
408
+ return best_option
409
+ else:
410
+ best_option = ('s', -200, 200)
411
+ for card in my_hand:
412
+ local_option = self.last_two_rounds(opponent_hand, my_hand, card)
413
+ if local_option[2] > best_option[1]:
414
+ best_option = (card, local_option[2],local_option[1])
415
+ return best_option
416
+
417
+ #Auxuliary 2 -- see policy last rounds
418
+ def last_three_rounds(self, my_hand, opponent_hand, played_card):
419
+ if played_card != None:
420
+ best_option = ('s', - 200, 200) #(card, my pts, opp points)
421
+ for card in my_hand:
422
+ remaining_cards = [_ for _ in my_hand if _ != card]
423
+ remaining_opp_cards = [_ for _ in opponent_hand if _ != played_card]
424
+
425
+ _, _, _, _, player_1_wins, player_1_pts, player_2_pts = self.step(card, played_card, False, update_global_var = False)
426
+ my_points = player_1_pts
427
+ opp_points = player_2_pts
428
+ if not player_1_wins:
429
+ opponent_best_option = self.last_two_rounds(remaining_opp_cards, remaining_cards, None)
430
+
431
+ if my_points + opponent_best_option[2] > best_option[1]:
432
+ #I make more points with this card so I switch
433
+ best_option = (card, my_points + opponent_best_option[2], opp_points + opponent_best_option[1])
434
+ else:
435
+ next_best_option = self.last_two_rounds(remaining_cards, remaining_opp_cards, None)
436
+ if my_points + next_best_option[1] > best_option[1]:
437
+ #I make more points with this card so I switch
438
+ best_option = (card, my_points + next_best_option[1], opp_points + next_best_option[2])
439
+ return best_option
440
+ else:
441
+ best_option = ('s', -200, 200)
442
+ for card in my_hand:
443
+ local_option = self.last_three_rounds(opponent_hand, my_hand, card)
444
+ if local_option[2] > best_option[1]:
445
+ #I make more points with this card so I switch
446
+ best_option = (card, local_option[2],local_option[1])
447
+ return best_option
448
+
449
+ #top play for last 3 rounds (when we know opponent's hand) assuming we are playing vs top player.
450
+ def policy_last_rounds(self, cards_seen, my_hand, pl_pts, briscola, played_card=None):
451
+ cards_not_seen = set(all_cards_in_strings)
452
+ cards_not_seen.remove('None')
453
+ opponent_hand = list(cards_not_seen.difference(cards_seen))
454
+
455
+ if len(opponent_hand) < len(my_hand):
456
+ opponent_hand.append(briscola)
457
+
458
+ if len(my_hand) == 3:
459
+ best_option = self.last_three_rounds(my_hand, opponent_hand, played_card)
460
+ elif len(my_hand) == 2:
461
+ best_option = self.last_two_rounds(my_hand, opponent_hand, played_card)
462
+ else:
463
+ best_option = (my_hand[0], - 200, 200)
464
+
465
+ return best_option[0]
466
+
467
+
468
+
469
+ def policy(self, epsilon, first_player_card=None, vs_human=False):
470
+
471
+ oh_state = self.one_hot(is_first_player = self.is_first_player, hand = self.hand,
472
+ briscola = self.briscola, player_1_pts = self.player_1_pts,
473
+ player_2_pts = self.player_2_pts, cards_seen = self.cards_seen,
474
+ card_opponent = first_player_card)
475
+ initial_states, total_q_vals = self.model(oh_state[np.newaxis], self.initial_states)
476
+ self.initial_states = initial_states
477
+ q_vals = total_q_vals[0][:len(self.hand)]
478
+
479
+ if np.random.rand() < epsilon:
480
+ return self.hand[np.random.randint(len(self.hand))]
481
+ else:
482
+ if len(self.cards_seen) < 37:
483
+ card = self.hand[tf.argmax(q_vals).numpy()]
484
+ if vs_human:
485
+ print('opponent plays ', card)
486
+ return card
487
+ else:
488
+ #pl knows opponent's hand
489
+ card = self.policy_last_rounds(self.cards_seen, self.hand, self.player_1_pts, self.briscola, played_card = first_player_card)
490
+ if vs_human:
491
+ print('opponent plays ', card)
492
+ return card
493
+
environment.py ADDED
@@ -0,0 +1,170 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Briscola environment and players. Contains nn for deep player"""
2
+
3
+ ### Briscola environment and players
4
+ import numpy as np
5
+ import pandas as pd
6
+ import tensorflow as tf
7
+
8
+ semi = ['bastoni', 'coppe', 'denari', 'spade']
9
+ numeri = ['2', '3', '4', '5', '6', '7', '8', '9', '0', '1']
10
+ my_points = [ 0, 10, 0, 0, 0, 0, 2, 3, 4, 11]
11
+ my_cards = pd.DataFrame({i:[1 for j in numeri] for i in semi}, index = numeri)
12
+ my_cards['points'] = my_points
13
+ sorted_cards = my_cards.sort_values(by=['points'], ascending=False)
14
+
15
+
16
+ class Briscola_env:
17
+ def __init__(self):
18
+ self.points_player_1 = 0 #player 1 is the first player at round 1
19
+ self.points_player_2 = 0
20
+ self.cards_in_the_deck = np.array([numero+seme for numero in numeri for seme in semi])
21
+ np.random.shuffle(self.cards_in_the_deck)
22
+ self.round = 20
23
+ self.index_top_card = 0
24
+
25
+ #choose a briscola
26
+ self.briscola = self.cards_in_the_deck[0] #pick a briscola
27
+ self.index_top_card = 1
28
+ self.is_briscola_on_the_table = True
29
+
30
+
31
+ def draw_cards(self, number_cards):
32
+ """
33
+ Draws number_cards from the deck.
34
+
35
+ Returns: a list of length number_cards
36
+ Updades: self.cards_in_the_deck
37
+ """
38
+ if len(self.cards_in_the_deck)- self.index_top_card > 0:
39
+ drawn = [ _ for _ in self.cards_in_the_deck[self.index_top_card : self.index_top_card + number_cards]]
40
+ self.index_top_card = self.index_top_card + number_cards
41
+ return drawn
42
+ else:
43
+ if self.is_briscola_on_the_table:
44
+ self.is_briscola_on_the_table = False
45
+ return [self.briscola]
46
+ else:
47
+ raise ValueError('Tried to draw from an empty deck..')
48
+
49
+
50
+ def start_game(self):
51
+ """
52
+ Deals 3 cards for each player
53
+
54
+ Returns: (first_three_cards, second_three_cards), briscola
55
+ Updates: self.cards_in_the_deck by removing the 6 cards sampled
56
+ """
57
+ first_three_cards = self.draw_cards(3)
58
+ second_three_cards = self.draw_cards(3)
59
+ return (first_three_cards, second_three_cards), self.briscola
60
+
61
+
62
+ def which_card_wins_a_round(self, first_played_card, second_played_card, passed_briscola=None):
63
+ """
64
+ Determines which card wins a round
65
+
66
+ Returns: either 'first_played_card' or 'second_played_card'
67
+ Updates: nothing
68
+ """
69
+ if passed_briscola == None:
70
+ briscola = self.briscola[1:]
71
+ else:
72
+ briscola = passed_briscola[1:]
73
+
74
+ if first_played_card[1:] == briscola and second_played_card[1:]!= briscola:
75
+ return 'first_played_card'
76
+ elif first_played_card[1:] != briscola and second_played_card[1:] == briscola:
77
+ return 'second_played_card'
78
+ elif first_played_card[1:] != second_played_card[1:]:
79
+ return 'first_played_card'
80
+ else:
81
+ if sorted_cards.loc[first_played_card[0], 'points'] > 0 or sorted_cards.loc[second_played_card[0], 'points'] > 0:
82
+ return 'first_played_card' if sorted_cards.loc[first_played_card[0], 'points'] > sorted_cards.loc[second_played_card[0], 'points'] else 'second_played_card'
83
+ else:
84
+ return 'first_played_card' if int(first_played_card[0]) > int(second_played_card[0]) else 'second_played_card'
85
+
86
+
87
+ def pre_step(self, player_1, player_2, epsilon, vs_human=False):
88
+ """
89
+ The player who plays second decides his action based on the other player's action
90
+
91
+ Returns: card_player_1, card_player_2
92
+ Updates: nothing
93
+ """
94
+ if player_1.is_first_player:
95
+ card_player_1 = player_1.policy(epsilon, None, vs_human)
96
+ card_player_2 = player_2.policy(epsilon, card_player_1, vs_human)
97
+ else:
98
+ card_player_2 = player_2.policy(epsilon, None, vs_human)
99
+ card_player_1 = player_1.policy(epsilon, card_player_2, vs_human)
100
+ return card_player_1, card_player_2
101
+
102
+
103
+ def step(self, card_player_1, card_player_2, order, update_global_var=True):
104
+ """
105
+ order == True -> card_first_player is played first
106
+ - Determines if player_1 wins a round
107
+ - Updates the points and the cards in the deck
108
+
109
+ - Draws 2 cards after the round if there are cards in the deck
110
+
111
+ Returns: new_card_player_1, new_card_player_2, played_cards, is_end_game, player_1_wins
112
+ Updates: - self.cards_in_the_deck if it deals from the deck
113
+ - self.points_player_1
114
+ - self.points_player_2
115
+ - self.round
116
+ """
117
+ #Determines who wins and how many points
118
+ [first_card, second_card] = [card_player_1, card_player_2] if order else [card_player_2, card_player_1]
119
+ winner = self.which_card_wins_a_round(first_card, second_card)
120
+ points = my_cards.loc[first_card[0], 'points'] + my_cards.loc[second_card[0], 'points']
121
+ played_cards = (first_card, second_card)
122
+
123
+ player_1_wins = winner == 'first_played_card' if order else not winner == 'first_played_card'
124
+
125
+ is_briscola_on_the_table = self.is_briscola_on_the_table
126
+
127
+ if is_briscola_on_the_table:
128
+ #draws the new cards
129
+ card_winner = self.draw_cards(1)
130
+ card_loser = self.draw_cards(1)
131
+
132
+ if player_1_wins:
133
+ if update_global_var:
134
+ self.points_player_1 += points
135
+ if is_briscola_on_the_table: #deals the new cards
136
+ new_card_player_1 = card_winner
137
+ new_card_player_2 = card_loser
138
+ else:
139
+ if update_global_var:
140
+ self.points_player_2 += points
141
+ if is_briscola_on_the_table: #deals the new cards
142
+ new_card_player_2 = card_winner
143
+ new_card_player_1 = card_loser
144
+
145
+ if update_global_var:
146
+ self.round = self.round - 1
147
+ is_end_game = self.round == 0
148
+
149
+ player_1_pts = points if player_1_wins else 0
150
+ player_2_pts = 0 if player_1_wins else points
151
+
152
+
153
+ if is_briscola_on_the_table:
154
+ return new_card_player_1[0], new_card_player_2[0], played_cards, is_end_game, player_1_wins, player_1_pts, player_2_pts
155
+ else:
156
+ return None, None, played_cards, is_end_game, player_1_wins, player_1_pts, player_2_pts
157
+
158
+
159
+ def reset(self):
160
+ self.points_player_1 = 0 #player 1 is the first player at round 1
161
+ self.points_player_2 = 0
162
+ np.random.shuffle(self.cards_in_the_deck)
163
+ self.round = 20
164
+ self.index_top_card = 0
165
+
166
+ #choose a briscola
167
+ self.briscola = self.cards_in_the_deck[0] #pick a briscola
168
+ self.index_top_card = 1
169
+ self.is_briscola_on_the_table = True
170
+
templates/.DS_Store ADDED
Binary file (6.15 kB). View file
 
templates/Briscola.html ADDED
@@ -0,0 +1,658 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Briscola</title>
7
+ <link rel="icon" href="data:,">
8
+ <link rel="preconnect" href="https://fonts.googleapis.com">
9
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10
+ <link href="https://fonts.googleapis.com/css2?family=Cinzel:wght@400;700&display=swap" rel="stylesheet">
11
+ <style>
12
+ :root {
13
+ --card-height: 22vmin;
14
+ --card-width: calc(var(--card-height) * 0.63); /* Maintain aspect ratio (150/238) */
15
+ --gold-glow: 0 0 2.5vmin 1vmin #ffd700;
16
+ }
17
+
18
+ * {
19
+ box-sizing: border-box;
20
+ font-family: 'Cinzel', serif;
21
+ }
22
+
23
+ body {
24
+ background-image: url('static/background.jpg');
25
+ background-size: cover;
26
+ background-position: center;
27
+ background-repeat: no-repeat;
28
+ color: #E0E0E0;
29
+ margin: 0;
30
+ padding: 0;
31
+ display: flex;
32
+ justify-content: center;
33
+ align-items: center;
34
+ min-height: 100vh;
35
+ overflow: hidden;
36
+ font-size: clamp(14px, 1.2vw, 18px); /* Responsive base font */
37
+ }
38
+
39
+ #game-container {
40
+ position: relative;
41
+ width: 100vw;
42
+ height: 100vh;
43
+ display: flex;
44
+ justify-content: center;
45
+ align-items: center;
46
+ }
47
+
48
+ #start-screen {
49
+ display: flex;
50
+ gap: 2vw;
51
+ text-align: center;
52
+ background: rgba(0, 0, 0, 0.5);
53
+ padding: 4vmin;
54
+ border-radius: 15px;
55
+ transition: opacity 0.5s ease-out;
56
+ }
57
+
58
+ .start-button {
59
+ border: 2px solid #B0BEC5;
60
+ border-radius: 10px;
61
+ cursor: pointer;
62
+ transition: transform 0.3s, box-shadow 0.3s;
63
+ width: 22vmin;
64
+ height: 15vmin;
65
+ }
66
+
67
+ .start-button:hover {
68
+ transform: scale(1.05);
69
+ box-shadow: 0 0 15px #B0BEC5;
70
+ }
71
+
72
+ #game-board {
73
+ visibility: hidden;
74
+ opacity: 0;
75
+ transition: opacity 0.5s ease-in;
76
+ width: 100%;
77
+ height: 100%;
78
+ position: absolute;
79
+ }
80
+
81
+ #game-board.visible {
82
+ visibility: visible;
83
+ opacity: 1;
84
+ }
85
+
86
+ #game-status {
87
+ position: absolute;
88
+ top: 2vh;
89
+ left: 50%;
90
+ transform: translateX(-50%);
91
+ display: flex;
92
+ gap: 4vw;
93
+ align-items: center;
94
+ background: rgba(0,0,0,0.75);
95
+ padding: 1vmin 3vmin;
96
+ border-radius: 50px;
97
+ font-size: clamp(16px, 1.8vmin, 24px);
98
+ font-weight: bold;
99
+ text-shadow: 1px 1px 3px rgba(0,0,0,0.8);
100
+ border: 1px solid rgba(255, 255, 255, 0.2);
101
+ box-shadow: 0 4px 15px rgba(0,0,0,0.5);
102
+ z-index: 100;
103
+ }
104
+ #game-status p {
105
+ margin: 0;
106
+ }
107
+
108
+ #opponent-avatar-container {
109
+ position: absolute;
110
+ top: 12vh;
111
+ left: 50%;
112
+ transform: translateX(-50%);
113
+ z-index: 50; /* Below status but above board */
114
+ }
115
+
116
+ #opponent-avatar-img {
117
+ width: 18vmin;
118
+ height: 18vmin;
119
+ border-radius: 50%;
120
+ border: 3px solid rgba(176, 190, 197, 0.8);
121
+ box-shadow: 0 4px 15px rgba(0,0,0,0.7);
122
+ }
123
+
124
+ #deck-area {
125
+ position: absolute;
126
+ left: 5vw;
127
+ top: 50%;
128
+ transform: translateY(-50%);
129
+ text-align: center;
130
+ }
131
+
132
+ #image_briscola {
133
+ width: var(--card-width);
134
+ height: var(--card-height);
135
+ transform: rotate(90deg) translateX(calc(var(--card-height) * -0.25));
136
+ border-radius: 8px;
137
+ box-shadow: 0 4px 8px rgba(0,0,0,0.5);
138
+ }
139
+
140
+ #deck-pile {
141
+ width: var(--card-width);
142
+ height: var(--card-height);
143
+ background-image: url('static/cards/back.jpg');
144
+ background-size: cover;
145
+ border-radius: 8px;
146
+ position: absolute;
147
+ top: 50%;
148
+ left: 50%;
149
+ transform: translate(-50%, -50%);
150
+ box-shadow: 0 4px 8px rgba(0,0,0,0.5);
151
+ }
152
+
153
+ #title_briscola {
154
+ font-size: clamp(20px, 2.5vmin, 32px);
155
+ font-weight: bold;
156
+ margin-top: calc(var(--card-height) * 0.65);
157
+ }
158
+
159
+ #table-area {
160
+ position: absolute;
161
+ top: 48%;
162
+ left: 50%;
163
+ transform: translate(-50%, -50%);
164
+ width: calc(var(--card-width) * 2 + 4vmin);
165
+ height: calc(var(--card-height) * 1.2); /* Increased height for separation */
166
+ z-index: 5;
167
+ }
168
+
169
+ .played-card {
170
+ width: var(--card-width);
171
+ height: var(--card-height);
172
+ position: absolute;
173
+ border-radius: 8px;
174
+ box-shadow: 0 4px 8px rgba(0,0,0,0.5);
175
+ transition: transform 0.5s ease-in-out, box-shadow 0.3s ease;
176
+ }
177
+
178
+ .played-card.glow {
179
+ box-shadow: var(--gold-glow);
180
+ }
181
+
182
+ #opponent_card_img {
183
+ left: 0;
184
+ top: 0; /* Align to the top of the table area */
185
+ }
186
+ #player_card_img {
187
+ right: 0;
188
+ bottom: 0; /* Align to the bottom of the table area */
189
+ }
190
+
191
+ #player-hand {
192
+ position: absolute;
193
+ bottom: 8vh;
194
+ left: 50%;
195
+ transform: translateX(-50%);
196
+ display: flex;
197
+ justify-content: center;
198
+ gap: calc(var(--card-width) * -0.4); /* Overlap cards */
199
+ z-index: 10;
200
+ }
201
+
202
+ #resign-button {
203
+ margin-top: 2vmin;
204
+ padding: 1.2vmin 2.5vmin;
205
+ font-size: clamp(14px, 1.5vmin, 20px);
206
+ font-weight: bold;
207
+ color: #E0E0E0;
208
+ background-color: #757575; /* Muted gray */
209
+ border: 1px solid #B0BEC5;
210
+ border-radius: 10px;
211
+ cursor: pointer;
212
+ transition: background-color 0.3s, transform 0.3s;
213
+ }
214
+
215
+ #resign-button:hover {
216
+ background-color: #616161;
217
+ transform: scale(1.05);
218
+ }
219
+
220
+ #resign-button:disabled {
221
+ background-color: #424242;
222
+ cursor: not-allowed;
223
+ color: #757575;
224
+ }
225
+
226
+ .hand-card {
227
+ width: var(--card-width);
228
+ height: var(--card-height);
229
+ border: none;
230
+ padding: 0;
231
+ cursor: pointer;
232
+ transition: transform 0.3s ease, box-shadow 0.3s;
233
+ border-radius: 8px;
234
+ box-shadow: 0 4px 8px rgba(0,0,0,0.5);
235
+ }
236
+
237
+ .hand-card:hover {
238
+ transform: translateY(-3vh) scale(1.05);
239
+ z-index: 10;
240
+ }
241
+
242
+ .modal-overlay {
243
+ position: fixed;
244
+ top: 0;
245
+ left: 0;
246
+ width: 100%;
247
+ height: 100%;
248
+ background: rgba(0, 0, 0, 0.7);
249
+ display: flex;
250
+ justify-content: center;
251
+ align-items: center;
252
+ z-index: 1000;
253
+ }
254
+
255
+ .modal-content {
256
+ background: #212121;
257
+ padding: 4vmin 6vmin;
258
+ border-radius: 15px;
259
+ text-align: center;
260
+ border: 2px solid #B0BEC5;
261
+ box-shadow: 0 5px 25px rgba(0,0,0,0.5);
262
+ color: #E0E0E0;
263
+ }
264
+
265
+ .modal-content h2 {
266
+ color: #ffd700;
267
+ font-size: 2.5em;
268
+ margin-top: 0;
269
+ margin-bottom: 20px;
270
+ }
271
+
272
+ .modal-content p {
273
+ font-size: 1.5em;
274
+ margin: 10px 0;
275
+ }
276
+
277
+ .modal-button, #play-again-button {
278
+ margin-top: 3vmin;
279
+ padding: 2vmin 4vmin;
280
+ font-size: 1.2em;
281
+ font-weight: bold;
282
+ border: none;
283
+ border-radius: 10px;
284
+ cursor: pointer;
285
+ transition: background-color 0.3s, transform 0.3s;
286
+ }
287
+
288
+ #play-again-button {
289
+ color: #212121;
290
+ background-color: #B0BEC5;
291
+ }
292
+
293
+ #play-again-button:hover {
294
+ background-color: #E0E0E0;
295
+ transform: scale(1.05);
296
+ }
297
+
298
+ .modal-buttons {
299
+ display: flex;
300
+ justify-content: center;
301
+ gap: 3vmin;
302
+ margin-top: 3vmin;
303
+ }
304
+
305
+ #confirm-resign-button {
306
+ background-color: #c62828; /* Red */
307
+ color: white;
308
+ }
309
+ #confirm-resign-button:hover {
310
+ background-color: #d32f2f;
311
+ transform: scale(1.05);
312
+ }
313
+
314
+ #cancel-resign-button {
315
+ background-color: #B0BEC5;
316
+ color: #212121;
317
+ }
318
+
319
+ #cancel-resign-button:hover {
320
+ background-color: #E0E0E0;
321
+ transform: scale(1.05);
322
+ }
323
+
324
+ </style>
325
+ </head>
326
+ <body>
327
+ <div id="game-container">
328
+ <div id="start-screen">
329
+ <input src="static/random.jpg" id="New_game_vs_random" class="start-button" type="image">
330
+ <input src="static/greedy.jpg" id="New_game_vs_det" class="start-button" type="image">
331
+ <input src="static/best_deep_player.jpg" id="New_game_vs_best" class="start-button" type="image">
332
+ </div>
333
+
334
+ <div id="game-board">
335
+ <div id="game-status">
336
+ <p id="who_I_play"></p>
337
+ <p id="Rounds"></p>
338
+ </div>
339
+
340
+ <div id="opponent-avatar-container" style="display: none;">
341
+ <img id="opponent-avatar-img" alt="Opponent">
342
+ </div>
343
+
344
+ <div id="deck-area">
345
+ <div id="deck-pile"></div>
346
+ <img id="image_briscola" src="static/cards/back.jpg" style="visibility:hidden;">
347
+ <p id="title_briscola" style="color:#B0BEC5;"></p>
348
+ <button id="resign-button">Resign</button>
349
+ </div>
350
+
351
+ <div id="table-area">
352
+ <img id="opponent_card_img" class="played-card" src="static/cards/back.jpg" style="visibility:hidden;">
353
+ <img id="player_card_img" class="played-card" src="static/cards/back.jpg" style="visibility:hidden;">
354
+ </div>
355
+
356
+ <div id="player-hand">
357
+ <input id="card_0" class="hand-card" type="image" src="static/cards/back.jpg" style="visibility: hidden">
358
+ <input id="card_1" class="hand-card" type="image" src="static/cards/back.jpg" style="visibility: hidden">
359
+ <input id="card_2" class="hand-card" type="image" src="static/cards/back.jpg" style="visibility: hidden">
360
+ </div>
361
+
362
+ </div>
363
+ </div>
364
+
365
+ <div id="end-game-modal" class="modal-overlay" style="display: none;">
366
+ <div class="modal-content">
367
+ <h2 id="modal-title"></h2>
368
+ <p id="modal-my-score"></p>
369
+ <p id="modal-opponent-score"></p>
370
+ <button id="play-again-button">Play Again</button>
371
+ </div>
372
+ </div>
373
+
374
+ <div id="resign-confirm-modal" class="modal-overlay" style="display: none;">
375
+ <div class="modal-content">
376
+ <h2>Do you want to resign?</h2>
377
+ <div class="modal-buttons">
378
+ <button id="confirm-resign-button" class="modal-button">Yes, resign</button>
379
+ <button id="cancel-resign-button" class="modal-button">No, go back</button>
380
+ </div>
381
+ </div>
382
+ </div>
383
+
384
+
385
+ <script>
386
+ let counter = 0;
387
+ let client_points = 0;
388
+ let opponent_points = 0;
389
+ let address = 'http://192.168.1.13:7860';
390
+
391
+ const opponentCardImg = document.getElementById('opponent_card_img');
392
+ const playerCardImg = document.getElementById('player_card_img');
393
+ const resignButton = document.getElementById('resign-button');
394
+ const resignConfirmModal = document.getElementById('resign-confirm-modal');
395
+ const confirmResignButton = document.getElementById('confirm-resign-button');
396
+ const cancelResignButton = document.getElementById('cancel-resign-button');
397
+
398
+ function set_cards(parsedResponse, length = 3) {
399
+ let numb_cards = 34 - 2 * counter;
400
+ document.getElementById('Rounds').innerText = (numb_cards > 0) ? `${numb_cards} Cards Left` : '';
401
+
402
+ resignButton.disabled = false;
403
+ for (let i = 0; i < 3; i++) {
404
+ let id_element = 'card_' + i;
405
+ const cardElement = document.getElementById(id_element);
406
+
407
+ if (i < length) {
408
+ const cardString = parsedResponse.my_hand[i];
409
+ if (cardString && !cardString.includes('The game is over')) {
410
+ let card_name = cardString.replace(' ', '_') + '.jpg';
411
+ cardElement.src = 'static/cards/' + card_name;
412
+ cardElement.style.visibility = 'visible';
413
+ cardElement.disabled = false;
414
+ } else {
415
+ cardElement.style.visibility = 'hidden';
416
+ cardElement.disabled = true;
417
+ }
418
+ } else {
419
+ cardElement.style.visibility = 'hidden';
420
+ cardElement.disabled = true;
421
+ }
422
+ }
423
+
424
+ if (counter == 0) {
425
+ let briscola_name = parsedResponse.briscola.replace(' ', '_') + '.jpg';
426
+ document.getElementById('image_briscola').src = 'static/cards/' + briscola_name;
427
+ document.getElementById('image_briscola').style.visibility = 'visible';
428
+ document.getElementById('title_briscola').innerText = '';
429
+ }
430
+ if (counter >= 17) {
431
+ document.getElementById('image_briscola').style.visibility = 'hidden';
432
+ document.getElementById('deck-pile').style.visibility = 'hidden';
433
+ document.getElementById('title_briscola').innerText = '';
434
+ }
435
+ }
436
+
437
+ function opponent_card_appears(parsedResponse) {
438
+ if (parsedResponse.card_player_1 != 'Not played yet') {
439
+ let name_card_played_by_opp = parsedResponse.card_player_1.replace(' ', '_') + '.jpg';
440
+ opponentCardImg.src = 'static/cards/' + name_card_played_by_opp;
441
+ opponentCardImg.style.visibility = 'visible';
442
+ } else {
443
+ opponentCardImg.style.visibility = 'hidden';
444
+ }
445
+ }
446
+
447
+ function startNewGame(endpoint, opponentName, opponentImageSrc) {
448
+ document.getElementById('who_I_play').innerHTML = `VS ${opponentName}`;
449
+ counter = 0;
450
+ client_points = 0;
451
+ opponent_points = 0;
452
+
453
+ const avatarContainer = document.getElementById('opponent-avatar-container');
454
+ const avatarImg = document.getElementById('opponent-avatar-img');
455
+
456
+ avatarImg.src = opponentImageSrc;
457
+ avatarContainer.style.display = 'block';
458
+
459
+ document.getElementById('deck-pile').style.visibility = 'visible';
460
+ document.getElementById('start-screen').style.opacity = '0';
461
+
462
+ setTimeout(() => {
463
+ document.getElementById('start-screen').style.display = 'none';
464
+ document.getElementById('game-board').classList.add('visible');
465
+ }, 500);
466
+
467
+
468
+ fetch(`${address}/${endpoint}`, { method: 'POST' })
469
+ .then(response => response.json())
470
+ .then(parsedResponse => {
471
+ opponent_card_appears(parsedResponse);
472
+ set_cards(parsedResponse);
473
+ });
474
+ }
475
+
476
+ function resetUIForNewGame() {
477
+ document.getElementById('game-board').classList.remove('visible');
478
+ document.getElementById('opponent-avatar-container').style.display = 'none'; // Hide avatar
479
+ setTimeout(() => {
480
+ document.getElementById('start-screen').style.display = 'flex';
481
+ setTimeout(() => document.getElementById('start-screen').style.opacity = '1', 50);
482
+ }, 500);
483
+ }
484
+
485
+ function showEndGameModal(winnerMessage, myScore, opponentScore) {
486
+ document.getElementById('modal-title').innerText = winnerMessage;
487
+ document.getElementById('modal-my-score').innerText = `Your Score: ${myScore}`;
488
+ document.getElementById('modal-opponent-score').innerText = `Opponent's Score: ${opponentScore}`;
489
+ document.getElementById('end-game-modal').style.display = 'flex';
490
+ }
491
+
492
+ document.getElementById('New_game_vs_det').onclick = () => startNewGame('new_game_vs_det', 'GREEDY PLAYER', 'static/greedy.jpg');
493
+ document.getElementById('New_game_vs_best').onclick = () => startNewGame('New_game_vs_best', 'BEST PLAYER', 'static/best_deep_player.jpg');
494
+ document.getElementById('New_game_vs_random').onclick = () => startNewGame('New_game_vs_random', 'RANDOM PLAYER', 'static/random.jpg');
495
+
496
+ document.getElementById('play-again-button').onclick = () => {
497
+ document.getElementById('end-game-modal').style.display = 'none';
498
+ resetUIForNewGame();
499
+ };
500
+
501
+ function play_a_hand(answer) {
502
+ if (counter <= 20) {
503
+ fetch(`${address}/play_hand`, { method: 'POST', body: JSON.stringify(answer) })
504
+ .then(response => response.json())
505
+ .then(parsedResponse => {
506
+ let timeout = 1000;
507
+ if (opponentCardImg.style.visibility == 'hidden') {
508
+ timeout = 1500;
509
+ }
510
+
511
+ let name_card_played_by_opp = parsedResponse.pl_1_previous_card.replace(' ', '_') + '.jpg';
512
+ opponentCardImg.src = 'static/cards/' + name_card_played_by_opp;
513
+ opponentCardImg.style.visibility = 'visible';
514
+
515
+ setTimeout(() => {
516
+ if (counter < 20) {
517
+ if (parsedResponse.card_player_1 !== 'Not played yet') {
518
+ opponentCardImg.classList.add('glow');
519
+ } else {
520
+ playerCardImg.classList.add('glow');
521
+ }
522
+ } else {
523
+ if (Number(parsedResponse.points_pl_2) > client_points) {
524
+ playerCardImg.classList.add('glow');
525
+ } else if (Number(parsedResponse.points_pl_1) > opponent_points) {
526
+ opponentCardImg.classList.add('glow');
527
+ }
528
+ }
529
+
530
+ opponent_points = Number(parsedResponse.points_pl_1);
531
+ client_points = Number(parsedResponse.points_pl_2);
532
+
533
+ setTimeout(() => {
534
+ playerCardImg.classList.remove('glow');
535
+ opponentCardImg.classList.remove('glow');
536
+ playerCardImg.style.visibility = 'hidden';
537
+ playerCardImg.style.transform = '';
538
+ playerCardImg.style.transition = '';
539
+
540
+
541
+ opponent_card_appears(parsedResponse);
542
+ let n = parsedResponse.my_hand.length;
543
+ set_cards(parsedResponse, n);
544
+
545
+ if (counter == 20) {
546
+ opponentCardImg.style.visibility = 'hidden';
547
+
548
+ let winnerMessage;
549
+ if (opponent_points > client_points) {
550
+ winnerMessage = 'You Lost!';
551
+ } else if (opponent_points < client_points) {
552
+ winnerMessage = 'You Won!';
553
+ } else {
554
+ winnerMessage = 'It\'s a Tie!';
555
+ }
556
+ showEndGameModal(winnerMessage, client_points, opponent_points);
557
+ }
558
+ }, 1500); // Time for player to see the glow
559
+ }, timeout);
560
+ });
561
+ }
562
+ }
563
+
564
+ function createCardClickHandler(cardIndex) {
565
+ return function () {
566
+ for (let i = 0; i < 3; i++) {
567
+ document.getElementById('card_' + i).disabled = true;
568
+ }
569
+ resignButton.disabled = true;
570
+
571
+ const cardElement = document.getElementById('card_' + cardIndex);
572
+
573
+ const startRect = cardElement.getBoundingClientRect();
574
+ playerCardImg.src = cardElement.src;
575
+ playerCardImg.style.visibility = 'visible';
576
+
577
+ const endRect = playerCardImg.getBoundingClientRect();
578
+ const translateX = startRect.left - endRect.left;
579
+ const translateY = startRect.top - endRect.top;
580
+
581
+ playerCardImg.style.transform = `translate(${translateX}px, ${translateY}px)`;
582
+
583
+ cardElement.style.visibility = 'hidden';
584
+
585
+ requestAnimationFrame(() => {
586
+ playerCardImg.style.transition = 'transform 0.4s ease-in';
587
+ playerCardImg.style.transform = 'translate(0, 0)';
588
+ });
589
+
590
+
591
+ let list1 = cardElement.src.toString().split('/');
592
+ let list = list1[list1.length - 1].split('_');
593
+ let card_played_in_string = list[0] + ' ' + list[1].split('.')[0];
594
+ let answer = { my_card: card_played_in_string };
595
+ counter = counter + 1;
596
+ play_a_hand(answer);
597
+ };
598
+ }
599
+
600
+ document.getElementById('card_0').onclick = createCardClickHandler(0);
601
+ document.getElementById('card_1').onclick = createCardClickHandler(1);
602
+ document.getElementById('card_2').onclick = createCardClickHandler(2);
603
+
604
+ resignButton.onclick = () => {
605
+ resignConfirmModal.style.display = 'flex';
606
+ resignButton.disabled = true;
607
+ for (let i = 0; i < 3; i++) {
608
+ document.getElementById('card_' + i).disabled = true;
609
+ }
610
+ };
611
+
612
+ cancelResignButton.onclick = () => {
613
+ resignConfirmModal.style.display = 'none';
614
+ resignButton.disabled = false;
615
+ for (let i = 0; i < 3; i++) {
616
+ const card = document.getElementById('card_' + i);
617
+ if(card.style.visibility === 'visible') {
618
+ card.disabled = false;
619
+ }
620
+ }
621
+ };
622
+
623
+ confirmResignButton.onclick = () => {
624
+ resignConfirmModal.style.display = 'none';
625
+ showEndGameModal('You Lost!', client_points, opponent_points);
626
+ };
627
+
628
+ </script>
629
+
630
+ <script>
631
+ (function(){
632
+ async function postJSON(path, body={}){
633
+ try{
634
+ const r = await fetch(path,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)});
635
+ const j = await r.json();
636
+ console.log('[Briscola] response', path, j);
637
+ window.__BRISCOLA_STATE__ = j;
638
+ }catch(e){console.error('[Briscola] error', path, e);}
639
+ }
640
+ function byButtonText(t){
641
+ return Array.from(document.querySelectorAll('button'))
642
+ .find(b=>b.textContent.trim().toLowerCase()===t.toLowerCase());
643
+ }
644
+ window.addEventListener('DOMContentLoaded',()=>{
645
+ // auto start a game so backend visibly works
646
+ postJSON('/New_game_vs_best');
647
+ const opp = byButtonText('Opponent');
648
+ const resign = byButtonText('Resign');
649
+ const again = byButtonText('Play Again');
650
+ if(opp) opp.addEventListener('click', ()=>postJSON('/New_game_vs_best'));
651
+ if(again) again.addEventListener('click', ()=>postJSON('/New_game_vs_best'));
652
+ if(resign)resign.addEventListener('click', ()=>postJSON('/play_hand',{my_card:'Not played yet'}));
653
+ });
654
+ })();
655
+ </script>
656
+
657
+ </body>
658
+ </html>