File size: 4,677 Bytes
100a6dd
 
d086f83
 
100a6dd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
import axios from 'axios';

// In development, the API URL is '/api', but in production, it's just '/'
const API_URL = '';

// Define types based on the backend API
export interface NewGameOptions {
  player_color: 'white' | 'black';
  difficulty: 'beginner' | 'easy' | 'medium' | 'hard' | 'expert' | 'master';
  time_limit: number;
  use_opening_book: boolean;
  enable_analysis: boolean;
}

export interface MoveRequest {
  move: string;
}

// Promotion-related types
export type PromotionPiece = 'queen' | 'rook' | 'bishop' | 'knight';

export interface PromotionMove {
  from: string;           // e.g., "e7"
  to: string;             // e.g., "e8"
  piece?: PromotionPiece; // promotion piece selection
  required: boolean;      // true if promotion piece selection is required
}

export interface PromotionRequirement {
  from: string;
  to: string;
  available_pieces: PromotionPiece[];
}

export interface PromotionRequest {
  move: string;
  promotion_piece: PromotionPiece;
}

export interface GameState {
  fen: string;
  turn: 'white' | 'black';
  game_state: string;
  legal_moves: string[];
  in_check: boolean;
  move_count: number;
}

export interface GameResponse {
  status: string;
  message?: string;
  board_state: GameState;
  player_color?: 'white' | 'black';
  player_move?: string;
  ai_move?: string;
  analysis?: any;
  result?: string;
  winner?: string;
  reason?: string;
  // Promotion support
  promotion_required?: PromotionRequirement;
}

export interface HintResponse {
  status: string;
  hint: string;
  explanation: string;
  board_state: GameState;
}

// API functions
export const startNewGame = async (options: NewGameOptions): Promise<GameResponse> => {
  const response = await axios.post(`${API_URL}/game/new`, options);
  return response.data;
};

export const makeMove = async (move: string, promotionPiece?: PromotionPiece): Promise<GameResponse> => {
  try {
    // If promotion piece is specified, append it to the move in UCI notation
    const moveWithPromotion = promotionPiece ? createPromotionMove(move.substring(0, 2), move.substring(2, 4), promotionPiece) : move;
    
    const response = await axios.post(`${API_URL}/game/move`, { move: moveWithPromotion });
    return response.data;
  } catch (error) {
    // Handle promotion-required errors specifically
    if (axios.isAxiosError(error) && error.response?.status === 400) {
      const errorData = error.response.data;
      
      // Check if this is a promotion-required error
      if (errorData.status === 'promotion_required' || errorData.promotion_details) {
        return {
          status: 'promotion_required',
          message: errorData.message || 'Pawn promotion requires piece selection',
          board_state: errorData.board_state || {} as GameState,
          promotion_required: {
            from: errorData.promotion_details?.from || move.substring(0, 2),
            to: errorData.promotion_details?.to || move.substring(2, 4),
            available_pieces: errorData.promotion_details?.available_pieces || ['queen', 'rook', 'bishop', 'knight']
          }
        };
      }
    }
    
    // Re-throw other errors
    throw error;
  }
};

export const getGameState = async (): Promise<GameResponse> => {
  const response = await axios.get(`${API_URL}/game/state`);
  return response.data;
};

export const getHint = async (): Promise<HintResponse> => {
  const response = await axios.post(`${API_URL}/game/hint`);
  return response.data;
};

export const undoMove = async (count: number = 2): Promise<GameResponse> => {
  const response = await axios.post(`${API_URL}/game/undo`, { count });
  return response.data;
};

export const resignGame = async (): Promise<GameResponse> => {
  const response = await axios.post(`${API_URL}/game/resign`);
  return response.data;
};

// Utility functions for promotion handling
export const isPromotionRequired = (response: GameResponse): boolean => {
  return response.status === 'promotion_required' && !!response.promotion_required;
};

export const getPromotionRequirement = (response: GameResponse): PromotionRequirement | null => {
  return response.promotion_required || null;
};

export const createPromotionMove = (from: string, to: string, piece: PromotionPiece): string => {
  // Map promotion pieces to UCI notation
  const pieceMap: Record<PromotionPiece, string> = {
    'queen': 'q',
    'rook': 'r',
    'bishop': 'b',
    'knight': 'n'  // Knight uses 'n' in UCI notation, not 'k'
  };
  
  return `${from}${to}${pieceMap[piece]}`;
};