2022-04-15 02:40:00 +00:00
|
|
|
#include "game.hpp"
|
|
|
|
|
|
|
|
#include <chrono>
|
2022-04-16 03:13:15 +00:00
|
|
|
#include <vector>
|
|
|
|
#include <algorithm>
|
|
|
|
#include <cstdlib>
|
|
|
|
#include <limits>
|
2022-04-19 02:27:16 +00:00
|
|
|
#include <thread>
|
2022-04-15 02:40:00 +00:00
|
|
|
|
|
|
|
uint64_t time_milli() {
|
|
|
|
using namespace std::chrono;
|
|
|
|
return duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();
|
|
|
|
}
|
|
|
|
|
|
|
|
Game::Game(SDL_Window* window, SDL_Surface* surface)
|
|
|
|
{
|
|
|
|
this->window = window;
|
|
|
|
this->surface = surface;
|
|
|
|
running = false;
|
2022-04-16 03:13:15 +00:00
|
|
|
holding_k = false;
|
2022-04-15 02:40:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Game::Game(SDL_Window* window, SDL_Surface* surface, std::string board_fen)
|
|
|
|
{
|
|
|
|
this->window = window;
|
|
|
|
this->surface = surface;
|
|
|
|
board = Board(board_fen);
|
|
|
|
running = false;
|
2022-04-16 03:13:15 +00:00
|
|
|
holding_k = false;
|
2022-04-15 02:40:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Game::run()
|
|
|
|
{
|
|
|
|
uint64_t last_update_time = time_milli(), delta_time;
|
|
|
|
uint64_t time_quantum = 1000/60;
|
|
|
|
running = true;
|
|
|
|
while(running)
|
|
|
|
{
|
|
|
|
delta_time = time_milli()-last_update_time;
|
|
|
|
while(delta_time > time_quantum && running)
|
|
|
|
{
|
|
|
|
tick();
|
|
|
|
|
|
|
|
delta_time -= time_quantum;
|
|
|
|
last_update_time += time_quantum;
|
|
|
|
}
|
|
|
|
draw();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Game::tick()
|
|
|
|
{
|
|
|
|
SDL_Event e;
|
2022-04-16 03:13:15 +00:00
|
|
|
while(running && SDL_PollEvent(&e) != 0)
|
2022-04-15 02:40:00 +00:00
|
|
|
{
|
|
|
|
if (e.type == SDL_QUIT || (e.type == SDL_KEYUP && e.key.keysym.sym == SDLK_q))
|
|
|
|
{
|
|
|
|
running = false;
|
|
|
|
}
|
2022-04-16 03:13:15 +00:00
|
|
|
else if (e.type == SDL_KEYUP && e.key.keysym.sym == SDLK_k)
|
|
|
|
{
|
|
|
|
holding_k = false;
|
|
|
|
}
|
|
|
|
else if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_k)
|
|
|
|
{
|
|
|
|
holding_k = true;
|
|
|
|
}
|
|
|
|
else if (e.type == SDL_MOUSEBUTTONDOWN)
|
2022-04-15 02:40:00 +00:00
|
|
|
{
|
|
|
|
int x,y;
|
|
|
|
SDL_GetMouseState(&x, &y);
|
|
|
|
x/=SPRITE_SIZE;
|
|
|
|
y/=SPRITE_SIZE;
|
2022-04-16 03:13:15 +00:00
|
|
|
process_click(x,y);
|
2022-04-19 02:27:16 +00:00
|
|
|
|
|
|
|
draw();
|
2022-04-15 02:40:00 +00:00
|
|
|
}
|
|
|
|
}
|
2022-04-16 03:13:15 +00:00
|
|
|
|
|
|
|
if (BLACK == board.get_active_color())
|
|
|
|
{
|
|
|
|
do_ai_move();
|
|
|
|
}
|
2022-04-15 02:40:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Game::draw()
|
|
|
|
{
|
|
|
|
board.draw_board(surface);
|
|
|
|
|
|
|
|
SDL_UpdateWindowSurface(window);
|
2022-04-16 03:13:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Game::process_click(int x, int y)
|
|
|
|
{
|
|
|
|
if (WHITE == board.get_active_color())
|
|
|
|
{
|
|
|
|
int current_selected_space = board.get_selected_space();
|
|
|
|
int target_selected_space = x+y*BOARD_SIZE;
|
|
|
|
if (current_selected_space == -1)
|
|
|
|
{
|
|
|
|
board.set_selected_space(x,y);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
std::vector<int> target_spaces = board.get_moves_for_space(current_selected_space, true);
|
|
|
|
|
|
|
|
if (target_spaces.end() != std::find(target_spaces.begin(), target_spaces.end(), target_selected_space))
|
|
|
|
{
|
|
|
|
board.do_move(current_selected_space, target_selected_space, holding_k);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
board.set_selected_space(target_selected_space);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Game::do_ai_move()
|
|
|
|
{
|
2022-04-19 02:27:16 +00:00
|
|
|
std::vector<std::thread> threads;
|
|
|
|
std::mutex mut_result_check;
|
2022-04-16 03:13:15 +00:00
|
|
|
int best_move_weight = std::numeric_limits<int>::min();
|
|
|
|
int best_move_from = -1;
|
|
|
|
int best_move_to = -1;
|
|
|
|
bool best_move_makes_queen = false;
|
2022-04-19 02:27:16 +00:00
|
|
|
int a = std::numeric_limits<int>::min();
|
|
|
|
|
|
|
|
Board board_copy = Board(board);
|
|
|
|
|
2022-04-16 03:13:15 +00:00
|
|
|
for(int x = 0; x < BOARD_SIZE; x++)
|
|
|
|
{
|
|
|
|
for(int y = 0; y < BOARD_SIZE; y++)
|
|
|
|
{
|
|
|
|
Piece cur_piece = board.get_piece(x,y);
|
|
|
|
if (cur_piece.get_team() == BLACK)
|
|
|
|
{
|
2022-04-19 02:27:16 +00:00
|
|
|
threads.push_back(std::thread([&mut_result_check, &best_move_weight, &best_move_from, &best_move_to, &best_move_makes_queen, &a, x, y, &board_copy](){
|
|
|
|
std::vector<int> piece_moves = board_copy.get_moves_for_space(x,y, true);
|
|
|
|
Piece cur_piece = board_copy.get_piece(x,y);
|
|
|
|
for (unsigned int i = 0; i < piece_moves.size(); i++)
|
|
|
|
{
|
|
|
|
Board test_board(board_copy);
|
|
|
|
test_board.do_move(x+y*BOARD_SIZE, piece_moves[i], false);
|
2022-04-16 03:13:15 +00:00
|
|
|
|
2022-04-19 02:27:16 +00:00
|
|
|
int board_value = minimax(test_board, 4, 5, a, std::numeric_limits<int>::max(), false);
|
2022-04-16 03:13:15 +00:00
|
|
|
|
2022-04-19 02:27:16 +00:00
|
|
|
mut_result_check.lock();
|
|
|
|
if (board_value > best_move_weight)
|
|
|
|
{
|
|
|
|
best_move_weight = board_value;
|
|
|
|
best_move_from = x+y*BOARD_SIZE;
|
|
|
|
best_move_to = piece_moves[i];
|
|
|
|
best_move_makes_queen = false;
|
|
|
|
}
|
2022-04-16 03:13:15 +00:00
|
|
|
|
2022-04-19 02:27:16 +00:00
|
|
|
if (best_move_weight > a)
|
|
|
|
{
|
|
|
|
a = best_move_weight;
|
|
|
|
}
|
|
|
|
mut_result_check.unlock();
|
2022-04-16 03:13:15 +00:00
|
|
|
|
2022-04-19 02:27:16 +00:00
|
|
|
if (cur_piece.get_type() == PAWN || (piece_moves[i]/BOARD_SIZE) == 7)
|
2022-04-16 03:13:15 +00:00
|
|
|
{
|
2022-04-19 02:27:16 +00:00
|
|
|
Board test_board1 = Board(board_copy);
|
|
|
|
test_board1.do_move(x+y*BOARD_SIZE, piece_moves[i], true);
|
2022-04-16 03:13:15 +00:00
|
|
|
|
2022-04-19 02:27:16 +00:00
|
|
|
if (test_board1.make_FEN().compare(board_copy.make_FEN()) != 0)
|
2022-04-16 03:13:15 +00:00
|
|
|
{
|
2022-04-19 02:27:16 +00:00
|
|
|
board_value = minimax(test_board1, 4, 5, a, std::numeric_limits<int>::max(), false);
|
|
|
|
|
|
|
|
mut_result_check.lock();
|
|
|
|
if (board_value > best_move_weight)
|
|
|
|
{
|
|
|
|
best_move_weight = board_value;
|
|
|
|
best_move_from = x+y*BOARD_SIZE;
|
|
|
|
best_move_to = piece_moves[i];
|
|
|
|
best_move_makes_queen = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (best_move_weight > a)
|
|
|
|
{
|
|
|
|
a = best_move_weight;
|
|
|
|
}
|
|
|
|
mut_result_check.unlock();
|
2022-04-16 03:13:15 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-04-19 02:27:16 +00:00
|
|
|
}));
|
2022-04-16 03:13:15 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-19 02:27:16 +00:00
|
|
|
for(std::thread& cur_thread : threads)
|
|
|
|
{
|
|
|
|
cur_thread.join();
|
|
|
|
}
|
|
|
|
|
2022-04-16 03:13:15 +00:00
|
|
|
board.do_move(best_move_from, best_move_to, best_move_makes_queen);
|
|
|
|
}
|
|
|
|
|
2022-04-19 02:27:16 +00:00
|
|
|
int Game::minimax(Board current_board, int depth, int max_depth, int a, int b, bool maximizing)
|
2022-04-16 03:13:15 +00:00
|
|
|
{
|
|
|
|
if (depth == 0 || current_board.is_mate(current_board.get_active_color()))
|
|
|
|
{
|
|
|
|
return board_heuristic(current_board);
|
|
|
|
}
|
|
|
|
|
2022-04-19 02:27:16 +00:00
|
|
|
int best_move_weight = maximizing?std::numeric_limits<int>::min():std::numeric_limits<int>::max();
|
|
|
|
bool a_eject = false, b_eject = false;
|
|
|
|
|
|
|
|
std::vector<std::thread> threads;
|
|
|
|
std::mutex mut_result_check;
|
|
|
|
int finished_threads = 0;
|
|
|
|
std::mutex mut_finished_threads;
|
|
|
|
|
|
|
|
for(int x = 0; x < BOARD_SIZE && !a_eject && !b_eject; x++)
|
2022-04-16 03:13:15 +00:00
|
|
|
{
|
2022-04-19 02:27:16 +00:00
|
|
|
for(int y = 0; y < BOARD_SIZE && !a_eject && !b_eject; y++)
|
2022-04-16 03:13:15 +00:00
|
|
|
{
|
2022-04-19 02:27:16 +00:00
|
|
|
if (current_board.get_piece(x,y).get_team() == (maximizing?BLACK:WHITE))
|
2022-04-16 03:13:15 +00:00
|
|
|
{
|
2022-04-19 02:27:16 +00:00
|
|
|
if (depth == max_depth-1)
|
2022-04-16 03:13:15 +00:00
|
|
|
{
|
2022-04-19 02:27:16 +00:00
|
|
|
if (threads.size()-finished_threads <= 4)
|
2022-04-16 03:13:15 +00:00
|
|
|
{
|
2022-04-19 02:27:16 +00:00
|
|
|
threads.push_back(std::thread([&finished_threads, &mut_finished_threads, &mut_result_check, current_board, x, y, depth, max_depth, &best_move_weight, &a, &b, &a_eject, &b_eject, maximizing](){
|
|
|
|
minimax_evaluate(mut_result_check, current_board, x, y, depth, max_depth, best_move_weight, a, b, a_eject, b_eject, maximizing);
|
|
|
|
mut_finished_threads.lock();
|
|
|
|
finished_threads++;
|
|
|
|
mut_finished_threads.unlock();
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
minimax_evaluate(mut_result_check, current_board, x, y, depth, max_depth, best_move_weight, a, b, a_eject, b_eject, maximizing);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
minimax_evaluate(mut_result_check, current_board, x, y, depth, max_depth, best_move_weight, a, b, a_eject, b_eject, maximizing);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-04-16 03:13:15 +00:00
|
|
|
|
2022-04-19 02:27:16 +00:00
|
|
|
for (std::thread& cur_thread : threads)
|
|
|
|
{
|
|
|
|
cur_thread.join();
|
|
|
|
}
|
2022-04-16 03:13:15 +00:00
|
|
|
|
2022-04-19 02:27:16 +00:00
|
|
|
return best_move_weight;
|
|
|
|
}
|
2022-04-16 03:13:15 +00:00
|
|
|
|
2022-04-19 02:27:16 +00:00
|
|
|
void Game::minimax_evaluate(std::mutex& mut_result_check, Board current_board, int x, int y, int depth, int max_depth, int& best_move_weight, int& a, int& b, bool& a_eject, bool& b_eject, bool maximizing)
|
|
|
|
{
|
|
|
|
Piece cur_piece = current_board.get_piece(x,y);
|
|
|
|
std::vector<int> piece_moves = current_board.get_moves_for_space(x,y, true);
|
2022-04-16 03:13:15 +00:00
|
|
|
|
2022-04-19 02:27:16 +00:00
|
|
|
if (maximizing)
|
|
|
|
{
|
|
|
|
for (unsigned int i = 0; i < piece_moves.size(); i++)
|
|
|
|
{
|
|
|
|
Board test_board(current_board);
|
|
|
|
test_board.do_move(x+y*BOARD_SIZE, piece_moves[i], false);
|
2022-04-16 03:13:15 +00:00
|
|
|
|
2022-04-19 02:27:16 +00:00
|
|
|
int board_value = minimax(test_board, depth-1, max_depth, a, b, false);
|
2022-04-16 03:13:15 +00:00
|
|
|
|
2022-04-19 02:27:16 +00:00
|
|
|
mut_result_check.lock();
|
|
|
|
if (board_value > best_move_weight)
|
|
|
|
{
|
|
|
|
best_move_weight = board_value;
|
|
|
|
}
|
2022-04-16 03:13:15 +00:00
|
|
|
|
2022-04-19 02:27:16 +00:00
|
|
|
if (best_move_weight >= b)
|
|
|
|
{
|
|
|
|
b_eject = true;
|
|
|
|
mut_result_check.unlock();
|
|
|
|
return;
|
|
|
|
}
|
2022-04-16 03:13:15 +00:00
|
|
|
|
2022-04-19 02:27:16 +00:00
|
|
|
if (best_move_weight > a)
|
|
|
|
{
|
|
|
|
a = best_move_weight;
|
|
|
|
}
|
|
|
|
mut_result_check.unlock();
|
2022-04-16 03:13:15 +00:00
|
|
|
|
2022-04-19 02:27:16 +00:00
|
|
|
if (cur_piece.get_type() == PAWN || (piece_moves[i]/BOARD_SIZE) == (maximizing?7:0))
|
|
|
|
{
|
|
|
|
test_board = Board(current_board);
|
|
|
|
test_board.do_move(x+y*BOARD_SIZE, piece_moves[i], true);
|
|
|
|
|
|
|
|
board_value = minimax(test_board, depth-1, max_depth, a, b, false);
|
|
|
|
|
|
|
|
mut_result_check.lock();
|
|
|
|
if (board_value > best_move_weight)
|
|
|
|
{
|
|
|
|
best_move_weight = board_value;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (best_move_weight >= b)
|
|
|
|
{
|
|
|
|
b_eject = true;
|
|
|
|
mut_result_check.unlock();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (best_move_weight > a)
|
|
|
|
{
|
|
|
|
a = best_move_weight;
|
2022-04-16 03:13:15 +00:00
|
|
|
}
|
2022-04-19 02:27:16 +00:00
|
|
|
mut_result_check.unlock();
|
2022-04-16 03:13:15 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2022-04-19 02:27:16 +00:00
|
|
|
for (unsigned int i = 0; i < piece_moves.size(); i++)
|
2022-04-16 03:13:15 +00:00
|
|
|
{
|
2022-04-19 02:27:16 +00:00
|
|
|
Board test_board(current_board);
|
|
|
|
test_board.do_move(x+y*BOARD_SIZE, piece_moves[i], false);
|
2022-04-16 03:13:15 +00:00
|
|
|
|
2022-04-19 02:27:16 +00:00
|
|
|
int board_value = minimax(test_board, depth-1, max_depth, a, b, true);
|
2022-04-16 03:13:15 +00:00
|
|
|
|
2022-04-19 02:27:16 +00:00
|
|
|
mut_result_check.lock();
|
|
|
|
if (board_value < best_move_weight)
|
|
|
|
{
|
|
|
|
best_move_weight = board_value;
|
|
|
|
}
|
2022-04-16 03:13:15 +00:00
|
|
|
|
2022-04-19 02:27:16 +00:00
|
|
|
if (best_move_weight <= a)
|
|
|
|
{
|
|
|
|
a_eject = true;
|
|
|
|
mut_result_check.unlock();
|
|
|
|
return;
|
|
|
|
}
|
2022-04-16 03:13:15 +00:00
|
|
|
|
2022-04-19 02:27:16 +00:00
|
|
|
if (best_move_weight < b)
|
|
|
|
{
|
|
|
|
b = best_move_weight;
|
|
|
|
}
|
|
|
|
mut_result_check.unlock();
|
2022-04-16 03:13:15 +00:00
|
|
|
|
2022-04-19 02:27:16 +00:00
|
|
|
if (cur_piece.get_type() == PAWN || (piece_moves[i]/BOARD_SIZE) == (maximizing?7:0))
|
|
|
|
{
|
|
|
|
test_board = Board(current_board);
|
|
|
|
test_board.do_move(x+y*BOARD_SIZE, piece_moves[i], true);
|
2022-04-16 03:13:15 +00:00
|
|
|
|
2022-04-19 02:27:16 +00:00
|
|
|
board_value = minimax(test_board, depth-1, max_depth, a, b, true);
|
2022-04-16 03:13:15 +00:00
|
|
|
|
2022-04-19 02:27:16 +00:00
|
|
|
mut_result_check.lock();
|
|
|
|
if (board_value < best_move_weight)
|
|
|
|
{
|
|
|
|
best_move_weight = board_value;
|
|
|
|
}
|
2022-04-16 03:13:15 +00:00
|
|
|
|
2022-04-19 02:27:16 +00:00
|
|
|
if (best_move_weight <= a)
|
|
|
|
{
|
|
|
|
a_eject = true;
|
|
|
|
mut_result_check.unlock();
|
|
|
|
return;
|
|
|
|
}
|
2022-04-16 03:13:15 +00:00
|
|
|
|
2022-04-19 02:27:16 +00:00
|
|
|
if (best_move_weight < b)
|
|
|
|
{
|
|
|
|
b = best_move_weight;
|
2022-04-16 03:13:15 +00:00
|
|
|
}
|
2022-04-19 02:27:16 +00:00
|
|
|
mut_result_check.unlock();
|
2022-04-16 03:13:15 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int Game::board_heuristic(Board current_board)
|
|
|
|
{
|
|
|
|
int heuristic = 0;
|
|
|
|
int piece_weights[2][7] = {{-900, -90, -30, -30, -50, -10, 0}, {900, 90, 30, 30, 50, 10, 0}};
|
2022-04-19 02:27:16 +00:00
|
|
|
|
|
|
|
///Calculate the weight of an unknown piece for both teams
|
|
|
|
//The weight of an unknown piece is 2 times the average of the remaining unrevealed pieces
|
|
|
|
//The weight is 2 times so that the AI understands that the hidden pieces are valuable hidden, otherwise the average
|
|
|
|
//is less than that of Rooks, making the AI always reveal its rooks.
|
|
|
|
//In order to still sometimes prefer revealing, the number of vulnerabilities will be considered later into a pieces weight.
|
2022-04-16 03:13:15 +00:00
|
|
|
int unknown_white_count = 0;
|
|
|
|
int unknown_black_count = 0;
|
|
|
|
for(int x = 0; x < BOARD_SIZE; x++)
|
|
|
|
{
|
|
|
|
for(int y = 0; y < BOARD_SIZE; y++)
|
|
|
|
{
|
|
|
|
Piece cur_piece = current_board.get_piece(x,y);
|
|
|
|
|
|
|
|
if (cur_piece.get_type() == NO_TYPE)
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cur_piece.get_vis() == HIDDEN)
|
|
|
|
{
|
|
|
|
if (cur_piece.get_team() == WHITE)
|
|
|
|
{
|
2022-04-19 02:27:16 +00:00
|
|
|
piece_weights[WHITE][UNKNOWN] += 2*piece_weights[WHITE][cur_piece.get_type()];
|
2022-04-16 03:13:15 +00:00
|
|
|
unknown_white_count++;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2022-04-19 02:27:16 +00:00
|
|
|
piece_weights[BLACK][UNKNOWN] += 2*piece_weights[BLACK][cur_piece.get_type()];
|
2022-04-16 03:13:15 +00:00
|
|
|
unknown_black_count++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (unknown_white_count != 0)
|
|
|
|
{
|
|
|
|
piece_weights[WHITE][UNKNOWN] /= unknown_white_count;
|
|
|
|
}
|
|
|
|
if (unknown_black_count != 0)
|
|
|
|
{
|
|
|
|
piece_weights[BLACK][UNKNOWN] /= unknown_black_count;
|
|
|
|
}
|
|
|
|
|
|
|
|
for(int x = 0; x < BOARD_SIZE; x++)
|
|
|
|
{
|
|
|
|
for(int y = 0; y < BOARD_SIZE; y++)
|
|
|
|
{
|
|
|
|
Piece cur_piece = current_board.get_piece(x,y);
|
|
|
|
|
|
|
|
if (cur_piece.get_type() == NO_TYPE)
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cur_piece.get_vis() == HIDDEN)
|
|
|
|
{
|
|
|
|
cur_piece = Piece(UNKNOWN, cur_piece.get_team(), SHOWN);
|
|
|
|
}
|
|
|
|
|
2022-04-19 02:27:16 +00:00
|
|
|
int adden = piece_weights[cur_piece.get_team()][cur_piece.get_type()];
|
|
|
|
|
|
|
|
int found_attackers = current_board.get_attackers_for_space(x+y*BOARD_SIZE);
|
|
|
|
|
|
|
|
heuristic += adden/((found_attackers!=0)?2:1);
|
2022-04-16 03:13:15 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return heuristic;
|
2022-04-15 02:40:00 +00:00
|
|
|
}
|