Mattimax commited on
Commit
2c8c0d0
·
1 Parent(s): a49310f

Update space

Browse files
Files changed (5) hide show
  1. README.md +8 -7
  2. app.py +605 -0
  3. index.html +0 -19
  4. requirements.txt +9 -0
  5. style.css +0 -28
README.md CHANGED
@@ -1,12 +1,13 @@
1
  ---
2
  title: TrisLLM
3
- emoji: 🏃
4
- colorFrom: indigo
5
- colorTo: blue
6
- sdk: static
 
7
  pinned: false
8
- license: mit
9
- short_description: A space where LLMs can play Tic-Tac-Toe
10
  ---
11
 
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
1
  ---
2
  title: TrisLLM
3
+ emoji: "🤖"
4
+ colorFrom: yellow
5
+ colorTo: red
6
+ sdk: gradio
7
+ app_file: app.py
8
  pinned: false
 
 
9
  ---
10
 
11
+ # LLMArena
12
+
13
+ A space where LLMs can play Tic-Tac-Toe!
app.py ADDED
@@ -0,0 +1,605 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import io
2
+ import os
3
+ import re
4
+ import time
5
+ import math
6
+ import random
7
+ from collections import defaultdict
8
+ from datetime import datetime
9
+
10
+ import numpy as np
11
+ import pandas as pd
12
+ import requests
13
+ from tqdm.auto import tqdm
14
+ from PIL import Image, ImageDraw, ImageFont
15
+ import plotly.graph_objects as go
16
+ from plotly.subplots import make_subplots
17
+ import gradio as gr
18
+ import gistyc
19
+
20
+ # Try optional transformers
21
+ try:
22
+ from transformers import pipeline, AutoTokenizer, AutoModelForCausalLM
23
+ import torch
24
+ TRANSFORMERS_AVAILABLE = True
25
+ except Exception:
26
+ TRANSFORMERS_AVAILABLE = False
27
+
28
+ # Constants for the game
29
+ BOARD_SIZE = 300
30
+ MARGIN = 50
31
+ CELL_SIZE = BOARD_SIZE // 3
32
+
33
+ class TicTacToe:
34
+ def __init__(self):
35
+ self.board = [[' ' for _ in range(3)] for _ in range(3)]
36
+ self.current_player = 'X'
37
+ self.winner = None
38
+ self.game_over = False
39
+ self.move_history = []
40
+
41
+ def make_move(self, row, col):
42
+ if self.board[row][col] == ' ' and not self.game_over:
43
+ self.board[row][col] = self.current_player
44
+ self.move_history.append((row, col, self.current_player))
45
+
46
+ if self.check_winner():
47
+ self.winner = self.current_player
48
+ self.game_over = True
49
+ elif self.is_board_full():
50
+ self.game_over = True
51
+ else:
52
+ self.current_player = 'O' if self.current_player == 'X' else 'X'
53
+ return True
54
+ return False
55
+
56
+ def check_winner(self):
57
+ # Check rows
58
+ for row in self.board:
59
+ if row[0] == row[1] == row[2] != ' ':
60
+ return True
61
+
62
+ # Check columns
63
+ for col in range(3):
64
+ if self.board[0][col] == self.board[1][col] == self.board[2][col] != ' ':
65
+ return True
66
+
67
+ # Check diagonals
68
+ if self.board[0][0] == self.board[1][1] == self.board[2][2] != ' ':
69
+ return True
70
+ if self.board[0][2] == self.board[1][1] == self.board[2][0] != ' ':
71
+ return True
72
+
73
+ return False
74
+
75
+ def is_board_full(self):
76
+ for row in self.board:
77
+ for cell in row:
78
+ if cell == ' ':
79
+ return False
80
+ return True
81
+
82
+ def get_legal_moves(self):
83
+ moves = []
84
+ for i in range(3):
85
+ for j in range(3):
86
+ if self.board[i][j] == ' ':
87
+ moves.append(f"{i+1},{j+1}")
88
+ return moves
89
+
90
+ def get_board_state_description(self):
91
+ """Return a textual description of the current board state"""
92
+ description = "Current board:\n"
93
+ for i in range(3):
94
+ row = []
95
+ for j in range(3):
96
+ if self.board[i][j] == ' ':
97
+ row.append(f"({i+1},{j+1})")
98
+ else:
99
+ row.append(self.board[i][j])
100
+ description += " | ".join(row) + "\n"
101
+
102
+ description += f"\nYou are playing as {self.current_player}. "
103
+ description += f"Legal moves: {', '.join(self.get_legal_moves())}"
104
+ return description
105
+
106
+ def create_board_image(board, highlight_move=None):
107
+ """Create an image of the current board state"""
108
+ img = Image.new('RGB', (BOARD_SIZE + 2*MARGIN, BOARD_SIZE + 2*MARGIN), 'white')
109
+ draw = ImageDraw.Draw(img)
110
+
111
+ # Draw grid lines
112
+ for i in range(1, 3):
113
+ # Vertical lines
114
+ draw.line([(MARGIN + i*CELL_SIZE, MARGIN),
115
+ (MARGIN + i*CELL_SIZE, MARGIN + BOARD_SIZE)], fill='black', width=3)
116
+ # Horizontal lines
117
+ draw.line([(MARGIN, MARGIN + i*CELL_SIZE),
118
+ (MARGIN + BOARD_SIZE, MARGIN + i*CELL_SIZE)], fill='black', width=3)
119
+
120
+ # Draw X and O
121
+ try:
122
+ font = ImageFont.truetype("arial.ttf", 40)
123
+ except:
124
+ font = ImageFont.load_default()
125
+
126
+ for i in range(3):
127
+ for j in range(3):
128
+ x = MARGIN + j * CELL_SIZE + CELL_SIZE // 2
129
+ y = MARGIN + i * CELL_SIZE + CELL_SIZE // 2
130
+
131
+ if board[i][j] == 'X':
132
+ draw.text((x-10, y-20), 'X', fill='blue', font=font)
133
+ elif board[i][j] == 'O':
134
+ draw.text((x-10, y-20), 'O', fill='red', font=font)
135
+
136
+ # Highlight last move
137
+ if highlight_move:
138
+ row, col = highlight_move
139
+ x1 = MARGIN + (col-1) * CELL_SIZE + 5
140
+ y1 = MARGIN + (row-1) * CELL_SIZE + 5
141
+ x2 = MARGIN + col * CELL_SIZE - 5
142
+ y2 = MARGIN + row * CELL_SIZE - 5
143
+ draw.rectangle([x1, y1, x2, y2], outline='green', width=3)
144
+
145
+ return img
146
+
147
+ def write_game_record(moves, model_id_x, model_id_o, result, time_budget, termination):
148
+ """Write game record in PGN-like format"""
149
+ current_utc_datetime = datetime.utcnow()
150
+ utc_date = current_utc_datetime.strftime("%Y.%m.%d")
151
+ utc_time = current_utc_datetime.strftime("%H:%M:%S")
152
+
153
+ moves_str = " ".join([f"{i+1}.{move}" for i, move in enumerate(moves)])
154
+
155
+ final_record = f"""[Event 'Tic Tac Toe LLM Arena']
156
+ [Site 'HuggingFace Spaces']
157
+ [Date '{utc_date}']
158
+ [Time '{utc_time}']
159
+ [PlayerX '{model_id_x}']
160
+ [PlayerO '{model_id_o}']
161
+ [Result '{result}']
162
+ [TimeControl '{time_budget}+0']
163
+ [Termination '{termination}']
164
+
165
+ {moves_str} {result}
166
+ """
167
+ return final_record
168
+
169
+ def determine_termination(game, time_budget_x, time_budget_o):
170
+ """Determine how the game ended"""
171
+ if game.winner:
172
+ return "Win"
173
+ elif game.game_over:
174
+ return "Draw"
175
+ elif time_budget_x <= 0:
176
+ return "Timeout - X lost on time"
177
+ elif time_budget_o <= 0:
178
+ return "Timeout - O lost on time"
179
+ else:
180
+ return "Unknown"
181
+
182
+ def format_elapsed(seconds):
183
+ """Format elapsed time"""
184
+ hours, remainder = divmod(int(seconds), 3600)
185
+ minutes, seconds = divmod(remainder, 60)
186
+ if hours:
187
+ return f"{hours:02d}:{minutes:02d}:{seconds:02d}"
188
+ elif minutes:
189
+ return f"{minutes:02d}:{seconds:02d}"
190
+ else:
191
+ return f"{seconds:02d}"
192
+
193
+ def save_result_file(game_id, model_id_x, model_id_o, termination, result, auth_token, gist_id):
194
+ """Save result to CSV file"""
195
+ timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
196
+ data_str = f"{game_id},{timestamp},{model_id_x},{model_id_o},{termination},{result}\n"
197
+
198
+ with open("tictactoe_results.csv", "a") as file:
199
+ file.write(data_str)
200
+
201
+ # Update Gist if token is provided
202
+ if auth_token and gist_id:
203
+ gist_api = gistyc.GISTyc(auth_token=auth_token)
204
+ gist_api.update_gist(file_name="tictactoe_results.csv", gist_id=gist_id)
205
+
206
+ def save_game_record(final_record, file_name, auth_token):
207
+ """Save game record to Gist"""
208
+ with open(file_name + ".txt", "w") as file:
209
+ file.write(final_record)
210
+
211
+ if auth_token:
212
+ gist_api = gistyc.GISTyc(auth_token=auth_token)
213
+ response_data = gist_api.create_gist(file_name=file_name + ".txt")
214
+ return response_data["id"]
215
+ return "local"
216
+
217
+ def calculate_elo(rank1, rank2, result):
218
+ """Calculate new ELO rating"""
219
+ K = 32
220
+ expected_score1 = 1 / (1 + 10 ** ((rank2 - rank1) / 400))
221
+ new_rank1 = rank1 + K * (result - expected_score1)
222
+ return round(new_rank1)
223
+
224
+ def update_elo_ratings(game_data):
225
+ """Update ELO ratings based on game results"""
226
+ elo_ratings = defaultdict(lambda: 1000)
227
+
228
+ for index, row in game_data.iterrows():
229
+ if row["Result"] == "*":
230
+ continue
231
+
232
+ model1 = row["ModelX"]
233
+ model2 = row["ModelO"]
234
+ result = row["Result"]
235
+
236
+ model1_elo = elo_ratings[model1]
237
+ model2_elo = elo_ratings[model2]
238
+
239
+ if result == "1-0": # ModelX wins
240
+ elo_ratings[model1] = calculate_elo(model1_elo, model2_elo, 1)
241
+ elo_ratings[model2] = calculate_elo(model2_elo, model1_elo, 0)
242
+ elif result == "0-1": # ModelO wins
243
+ elo_ratings[model1] = calculate_elo(model1_elo, model2_elo, 0)
244
+ elo_ratings[model2] = calculate_elo(model2_elo, model1_elo, 1)
245
+ elif result == "1/2-1/2": # Draw
246
+ elo_ratings[model1] = calculate_elo(model1_elo, model2_elo, 0.5)
247
+ elo_ratings[model2] = calculate_elo(model2_elo, model1_elo, 0.5)
248
+
249
+ return elo_ratings
250
+
251
+ def get_leaderboard():
252
+ """Get leaderboard data"""
253
+ try:
254
+ return pd.read_csv("tictactoe_leaderboard.csv")
255
+ except:
256
+ # Create initial leaderboard if doesn't exist
257
+ df = pd.DataFrame(columns=["Model", "ELO Rating", "Games", "Wins", "Losses", "Draws"])
258
+ df.to_csv("tictactoe_leaderboard.csv", index=False)
259
+ return df
260
+
261
+ def load_model_and_tokenizer(model_id):
262
+ """Load model and tokenizer using transformers"""
263
+ if not TRANSFORMERS_AVAILABLE:
264
+ raise ImportError("Transformers library is not available.")
265
+
266
+ tokenizer = AutoTokenizer.from_pretrained(model_id)
267
+ model = AutoModelForCausalLM.from_pretrained(model_id)
268
+
269
+ # If the model doesn't have a pad token, set it to eos token
270
+ if tokenizer.pad_token is None:
271
+ tokenizer.pad_token = tokenizer.eos_token
272
+
273
+ return model, tokenizer
274
+
275
+ def generate_move_with_context(model, tokenizer, game, max_length=50):
276
+ """Generate a move using the model with full game context"""
277
+ # Create a detailed prompt with board state and strategy hints
278
+ board_description = game.get_board_state_description()
279
+
280
+ prompt = f"""You are playing Tic Tac Toe as {game.current_player}.
281
+ {board_description}
282
+
283
+ Strategy considerations:
284
+ - Try to win by getting three in a row
285
+ - Block your opponent if they are about to win
286
+ - The center (2,2) is a strong position
287
+ - Corners (1,1), (1,3), (3,1), (3,3) are good positions
288
+ - Try to create multiple threats at once
289
+
290
+ Make your move by responding with only the coordinates in the format: row,col
291
+ For example: 2,2
292
+
293
+ Your move: """
294
+
295
+ # Encode the prompt
296
+ inputs = tokenizer(prompt, return_tensors="pt", truncation=True, max_length=512)
297
+
298
+ # Generate text with the model
299
+ with torch.no_grad():
300
+ outputs = model.generate(
301
+ inputs.input_ids,
302
+ max_new_tokens=max_length,
303
+ num_return_sequences=1,
304
+ pad_token_id=tokenizer.pad_token_id,
305
+ do_sample=True,
306
+ temperature=0.8, # Higher temperature for more variety
307
+ top_p=0.9,
308
+ top_k=50,
309
+ repetition_penalty=1.1,
310
+ )
311
+
312
+ # Decode the generated text
313
+ generated_text = tokenizer.decode(outputs[0], skip_special_tokens=True)
314
+
315
+ # Extract the generated part (after the prompt)
316
+ generated_part = generated_text[len(prompt):].strip()
317
+
318
+ # Try to find a move in the format "row,col"
319
+ move_pattern = r'(\d)\s*[,.\-:;]?\s*(\d)'
320
+ matches = re.findall(move_pattern, generated_part)
321
+
322
+ # Try to find a valid move
323
+ legal_moves = game.get_legal_moves()
324
+
325
+ # First, check if any of the found matches are legal moves
326
+ for match in matches:
327
+ if len(match) == 2:
328
+ move_str = f"{match[0]},{match[1]}"
329
+ if move_str in legal_moves:
330
+ return move_str
331
+
332
+ # If no valid move found in the matches, try to extract from the text more liberally
333
+ for move in legal_moves:
334
+ if move in generated_part:
335
+ return move
336
+
337
+ # If still no valid move, try a different approach - look for numbers
338
+ numbers = re.findall(r'\b[1-3]\b', generated_part)
339
+ if len(numbers) >= 2:
340
+ move_str = f"{numbers[0]},{numbers[1]}"
341
+ if move_str in legal_moves:
342
+ return move_str
343
+
344
+ # Final fallback: choose a random legal move
345
+ if legal_moves:
346
+ return random.choice(legal_moves)
347
+ else:
348
+ return "2,2" # Should never happen if game isn't over
349
+
350
+ def play_game(model_id_x, model_id_o):
351
+ """Main game function"""
352
+ if not TRANSFORMERS_AVAILABLE:
353
+ gr.Error("Transformers library is not available. Please install it to use this feature.")
354
+ return
355
+
356
+ TIME_BUDGET = 120 # Increased time budget for more complex reasoning
357
+ prompt = "Make your move (format: row,col where row and col are 1,2,3):"
358
+
359
+ # Initialize game
360
+ game = TicTacToe()
361
+
362
+ # Load models
363
+ try:
364
+ model_x, tokenizer_x = load_model_and_tokenizer(model_id_x)
365
+ model_o, tokenizer_o = load_model_and_tokenizer(model_id_o)
366
+ except Exception as e:
367
+ gr.Error(f"Error loading models: {e}")
368
+ return
369
+
370
+ moves = []
371
+ game_images = []
372
+ time_budget_x = TIME_BUDGET
373
+ time_budget_o = TIME_BUDGET
374
+ last_move = None
375
+
376
+ # Create initial board image
377
+ image = create_board_image(game.board)
378
+ game_images.append(np.array(image))
379
+ yield image
380
+
381
+ # Progress bars
382
+ x_bar = tqdm(total=time_budget_x, desc=f"{model_id_x.split('/')[-1]}:",
383
+ bar_format="{desc} {n:.0f}s left | Elapsed: {elapsed}")
384
+ o_bar = tqdm(total=time_budget_o, desc=f"{model_id_o.split('/')[-1]}:",
385
+ bar_format="{desc} {n:.0f}s left | Elapsed: {elapsed}")
386
+
387
+ # Game loop
388
+ max_moves = 9 # Maximum possible moves in tic tac toe
389
+ move_count = 0
390
+
391
+ while not game.game_over and move_count < max_moves:
392
+ current_model = model_x if game.current_player == 'X' else model_o
393
+ current_tokenizer = tokenizer_x if game.current_player == 'X' else tokenizer_o
394
+ current_time_budget = time_budget_x if game.current_player == 'X' else time_budget_o
395
+
396
+ # Generate move
397
+ start_time = time.time()
398
+ try:
399
+ move_str = generate_move_with_context(current_model, current_tokenizer, game)
400
+ except Exception as e:
401
+ print(f"Error generating move: {e}")
402
+ # Fallback to random move
403
+ legal_moves = game.get_legal_moves()
404
+ if legal_moves:
405
+ move_str = random.choice(legal_moves)
406
+ else:
407
+ break
408
+
409
+ end_time = time.time()
410
+ move_duration = end_time - start_time
411
+
412
+ # Parse move
413
+ try:
414
+ row, col = map(int, move_str.split(','))
415
+ row -= 1
416
+ col -= 1
417
+
418
+ if 0 <= row <= 2 and 0 <= col <= 2 and game.board[row][col] == ' ':
419
+ game.make_move(row, col)
420
+ moves.append(move_str)
421
+ last_move = (row+1, col+1)
422
+ move_count += 1
423
+
424
+ # Update time budget
425
+ if game.current_player == 'O': # X just moved
426
+ time_budget_x -= move_duration
427
+ x_bar.n = max(0, time_budget_x)
428
+ x_bar.refresh()
429
+ if time_budget_x <= 0:
430
+ game.winner = 'O'
431
+ game.game_over = True
432
+ else: # O just moved
433
+ time_budget_o -= move_duration
434
+ o_bar.n = max(0, time_budget_o)
435
+ o_bar.refresh()
436
+ if time_budget_o <= 0:
437
+ game.winner = 'X'
438
+ game.game_over = True
439
+
440
+ # Create new board image
441
+ image = create_board_image(game.board, last_move)
442
+ game_images.append(np.array(image))
443
+ yield image
444
+
445
+ else:
446
+ print(f"Illegal move: {move_str}. Choosing random move.")
447
+ # Fallback to random legal move
448
+ legal_moves = game.get_legal_moves()
449
+ if legal_moves:
450
+ move_str = random.choice(legal_moves)
451
+ # Retry with the random move
452
+ row, col = map(int, move_str.split(','))
453
+ row -= 1
454
+ col -= 1
455
+ if game.make_move(row, col):
456
+ moves.append(move_str)
457
+ last_move = (row+1, col+1)
458
+ move_count += 1
459
+ image = create_board_image(game.board, last_move)
460
+ game_images.append(np.array(image))
461
+ yield image
462
+ else:
463
+ break
464
+
465
+ except ValueError:
466
+ print(f"Invalid move format: {move_str}. Choosing random move.")
467
+ # Fallback to random legal move
468
+ legal_moves = game.get_legal_moves()
469
+ if legal_moves:
470
+ move_str = random.choice(legal_moves)
471
+ # Retry with the random move
472
+ try:
473
+ row, col = map(int, move_str.split(','))
474
+ row -= 1
475
+ col -= 1
476
+ if game.make_move(row, col):
477
+ moves.append(move_str)
478
+ last_move = (row+1, col+1)
479
+ move_count += 1
480
+ image = create_board_image(game.board, last_move)
481
+ game_images.append(np.array(image))
482
+ yield image
483
+ except:
484
+ break
485
+
486
+ x_bar.close()
487
+ o_bar.close()
488
+
489
+ # Determine result
490
+ if game.winner == 'X':
491
+ result = "1-0"
492
+ elif game.winner == 'O':
493
+ result = "0-1"
494
+ else:
495
+ result = "1/2-1/2"
496
+
497
+ # Save game record
498
+ termination = determine_termination(game, time_budget_x, time_budget_o)
499
+ final_record = write_game_record(moves, model_id_x, model_id_o, result, TIME_BUDGET, termination)
500
+ file_name = f"{model_id_x.split('/')[-1]}_vs_{model_id_o.split('/')[-1]}"
501
+ game_id = save_game_record(final_record, file_name, os.environ.get("GITHUB_TOKEN"))
502
+
503
+ # Save results
504
+ save_result_file(game_id, model_id_x, model_id_o, termination, result,
505
+ os.environ.get("GITHUB_TOKEN"), os.environ.get("RESULT_GIST_ID"))
506
+
507
+ # Update leaderboard
508
+ try:
509
+ game_data = pd.read_csv('tictactoe_results.csv')
510
+ elo_ratings = update_elo_ratings(game_data)
511
+
512
+ elo_ratings_df = pd.DataFrame(elo_ratings.items(), columns=['Model', 'ELO Rating'])
513
+ elo_ratings_df['ELO Rating'] = elo_ratings_df['ELO Rating'].round().astype(int)
514
+ elo_ratings_df.sort_values(by='ELO Rating', ascending=False, inplace=True)
515
+ elo_ratings_df.reset_index(drop=True, inplace=True)
516
+ elo_ratings_df.to_csv('tictactoe_leaderboard.csv', index=False)
517
+
518
+ # Upload to Gist if token available
519
+ if os.environ.get("GITHUB_TOKEN") and os.environ.get("LEADERBOARD_GIST_ID"):
520
+ gist_api = gistyc.GISTyc(auth_token=os.environ.get("GITHUB_TOKEN"))
521
+ gist_api.update_gist(file_name='tictactoe_leaderboard.csv',
522
+ gist_id=os.environ.get("LEADERBOARD_GIST_ID"))
523
+ except Exception as e:
524
+ print(f"Error updating leaderboard: {e}")
525
+
526
+ # Show result
527
+ if game.winner:
528
+ winner_model = model_id_x if game.winner == 'X' else model_id_o
529
+ result_text = f"{winner_model} wins! ({termination})"
530
+ else:
531
+ result_text = f"Draw! ({termination})"
532
+
533
+ gr.Info(result_text)
534
+ print(result_text)
535
+
536
+ # Return final image
537
+ yield image
538
+
539
+ # Initialize environment
540
+ os.environ["TOKENIZERS_PARALLELISM"] = "false"
541
+
542
+ # Create initial files if they don't exist
543
+ for file in ["tictactoe_results.csv", "tictactoe_leaderboard.csv"]:
544
+ if not os.path.exists(file):
545
+ if file == "tictactoe_results.csv":
546
+ pd.DataFrame(columns=["GameID", "Timestamp", "ModelX", "ModelO", "Termination", "Result"]).to_csv(file, index=False)
547
+ else:
548
+ pd.DataFrame(columns=["Model", "ELO Rating"]).to_csv(file, index=False)
549
+
550
+ # Create Gradio interface
551
+ title = """
552
+ <div align="center">
553
+ <p style="font-size: 36px;">⭕ Tic Tac Toe LLM Arena ❌</p>
554
+ <p style="font-size: 20px;">🤖 Make two LLMs play Tic Tac Toe against each other</p>
555
+ <p><em>Enter the HuggingFace model IDs for two language models and watch them play Tic Tac Toe with adaptive strategies!</em></p>
556
+ </div>
557
+ """
558
+
559
+ footer = """
560
+ <p><em>LLMs now receive full board context and strategic guidance, making games more varied and interesting.</em></p>
561
+ """
562
+
563
+ with gr.Blocks(theme=gr.themes.Soft()) as demo:
564
+ gr.Markdown(title)
565
+
566
+ with gr.Row():
567
+ with gr.Column():
568
+ model_x = gr.Textbox(
569
+ label="❌ Player X Model ID",
570
+ value="Mattimax/DACMini-IT",
571
+ placeholder="Enter HuggingFace model ID for Player X"
572
+ )
573
+ model_o = gr.Textbox(
574
+ label="⭕ Player O Model ID",
575
+ value="Mattimax/DACMini",
576
+ placeholder="Enter HuggingFace model ID for Player O"
577
+ )
578
+ fight_btn = gr.Button("Start Game! 🎮", variant="primary")
579
+
580
+ with gr.Column():
581
+ game_display = gr.Image(
582
+ value=create_board_image([[' ' for _ in range(3)] for _ in range(3)]),
583
+ label="Game Board",
584
+ height=400,
585
+ width=400
586
+ )
587
+
588
+ gr.Markdown('<div align="center"><p style="font-size: 30px;">🏆 Leaderboard</p></div>')
589
+ leaderboard_display = gr.Dataframe(
590
+ value=get_leaderboard,
591
+ label="Model Rankings",
592
+ every=30
593
+ )
594
+
595
+ gr.Markdown(footer)
596
+
597
+ # Set up interactions
598
+ fight_btn.click(
599
+ fn=play_game,
600
+ inputs=[model_x, model_o],
601
+ outputs=game_display
602
+ )
603
+
604
+ if __name__ == "__main__":
605
+ demo.launch()
index.html DELETED
@@ -1,19 +0,0 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
requirements.txt ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ numpy
2
+ pandas
3
+ requests
4
+ tqdm
5
+ Pillow
6
+ plotly
7
+ gradio
8
+ gistyc
9
+ transformers
style.css DELETED
@@ -1,28 +0,0 @@
1
- body {
2
- padding: 2rem;
3
- font-family: -apple-system, BlinkMacSystemFont, "Arial", sans-serif;
4
- }
5
-
6
- h1 {
7
- font-size: 16px;
8
- margin-top: 0;
9
- }
10
-
11
- p {
12
- color: rgb(107, 114, 128);
13
- font-size: 15px;
14
- margin-bottom: 10px;
15
- margin-top: 5px;
16
- }
17
-
18
- .card {
19
- max-width: 620px;
20
- margin: 0 auto;
21
- padding: 16px;
22
- border: 1px solid lightgray;
23
- border-radius: 16px;
24
- }
25
-
26
- .card p:last-child {
27
- margin-bottom: 0;
28
- }