/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 *
 * Copyright (c) 2006 Alexander Levin
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
 * KIND, either express or implied.
 *
 ****************************************************************************/

/*
 * Reversi. Code is heavily based on othello code by Claudio Clemens which is
 * copyright (c) 2003-2006 Claudio Clemens <asturio at gmx dot net> and is
 * released under the GNU General Public License as published by the Free
 * Software Foundation; either version 2, or (at your option) any later version.
 */

#include <stddef.h>
#include "reversi-game.h"

/*
 * Constants for directions. The values are chosen so that
 * they can be bit combined.
 */
#define DIR_UP 1        /* UP         */
#define DIR_DO 2        /* DOWN       */
#define DIR_LE 4        /* LEFT       */
#define DIR_RI 8        /* RIGHT      */
#define DIR_UL 16       /* UP LEFT    */
#define DIR_UR 32       /* UP RIGHT   */
#define DIR_DL 64       /* DOWN LEFT  */
#define DIR_DR 128      /* DOWN RIGHT */

/* Array of directions for easy iteration through all of them */
static int DIRECTIONS[] =
    {DIR_UP, DIR_DO, DIR_LE, DIR_RI, DIR_UL, DIR_UR, DIR_DL, DIR_DR};
#define NUM_OF_DIRECTIONS 8


/* Initializes a reversi game */
void reversi_init_game(reversi_board_t *game) {
    int r, c;
    for (r = 0; r < BOARD_SIZE; r++) {
        for (c = 0; c < BOARD_SIZE; c++) {
            game->board[r][c] = FREE;
        }
    }
    game->board[3][3] = WHITE;
    game->board[4][4] = WHITE;
    game->board[3][4] = BLACK;
    game->board[4][3] = BLACK;

    /* Invalidate the history */
    c = sizeof(game->history) / sizeof(game->history[0]);
    for (r = 0; r < c; r++) {
        game->history[r] = MOVE_INVALID;
    }
}


/* Returns the 'flipped' color, e.g. WHITE for BLACK and vice versa */
int reversi_flipped_color(const int color) {
    switch (color) {
        case WHITE:
            return BLACK;

        case BLACK:
            return WHITE;

        default:
            return FREE;
    }
}


/* Counts and returns the number of occupied cells on the board.
 * If white_count and/or black_count is not null, the number of
 * white/black stones is placed there. */
int reversi_count_occupied_cells(const reversi_board_t *game,
        int *white_count, int *black_count) {
    int w_cnt, b_cnt, r, c;
    w_cnt = b_cnt = 0;
    for (r = 0; r < BOARD_SIZE; r++) {
        for (c = 0; c < BOARD_SIZE; c++) {
            if (game->board[r][c] == WHITE) {
                w_cnt++;
            } else if (game->board[r][c] == BLACK) {
                b_cnt++;
            }
        }
    }
    if (white_count != NULL) {
        *white_count = w_cnt;
    }
    if (black_count != NULL) {
        *black_count = b_cnt;
    }
    return w_cnt + b_cnt;
}


/* Returns the number of free cells on the board */
static int reversi_count_free_cells(const reversi_board_t *game) {
    int cnt;
    cnt = reversi_count_occupied_cells(game, NULL, NULL);
    return BOARD_SIZE*BOARD_SIZE - cnt;
}


/* Checks whether the game is finished. That means that nobody
 * can make a move. Note that the implementation is not correct
 * as a game may be finished even if there are free cells
 */
bool reversi_game_is_finished(const reversi_board_t *game, int player) {
    return (reversi_count_free_cells(game) == 0)
           || (reversi_count_passes(game,player) == 2);
}


/* Returns the total number of moves made so far */
int reversi_count_moves(const reversi_board_t *game) {
    int cnt;
    cnt = reversi_count_occupied_cells(game, NULL, NULL);
    return cnt - INIT_STONES;
}


/* Returns the number of moves made by the specified
 * player (WHITE or BLACK) so far
 */
static int reversi_count_player_moves(const reversi_board_t *game,
        const int player) {
    int moves, cnt, i;
    moves = reversi_count_moves(game);
    cnt = 0;
    for (i = 0; i < moves; i++) {
        if (MOVE_PLAYER(game->history[i]) == player) {
            cnt++;
        }
    }
    return cnt;
}

/* Returns the number of moves available for the specified player */
int reversi_count_player_available_moves(const reversi_board_t *game,
        const int player) {
    int cnt = 0, row, col;
    for(row=0;row<BOARD_SIZE;row++) {
        for(col=0;col<BOARD_SIZE;col++) {
            if(reversi_is_valid_move(game, row, col, player)) cnt++;
        }
    }
    return cnt;
}

/* Returns the number of players who HAVE to pass (2 == game is stuck) */
int reversi_count_passes(const reversi_board_t *game, const int player) {
    if(reversi_count_player_available_moves(game,player)==0) {
        if(reversi_count_player_available_moves(game,
                    reversi_flipped_color(player))==0) {
            return 2;
        }
        return 1;
    }
    return 0;
}

/* Returns the number of moves made by WHITE so far */
int reversi_count_white_moves(const reversi_board_t *game) {
    return reversi_count_player_moves(game, WHITE);
}


/* Returns the number of moves made by BLACK so far */
int reversi_count_black_moves(const reversi_board_t *game) {
    return reversi_count_player_moves(game, BLACK);
}


/* Checks whether the specified position is on the board
 * (and not beyond)
 */
static bool reversi_is_position_on_board(const int row, const int col) {
    return (row >= 0) && (row < BOARD_SIZE) &&
           (col >= 0) && (col < BOARD_SIZE);
}


/* Returns the delta for row to move in the specified direction */
static int reversi_row_delta(const int direction) {
    switch (direction) {
        case DIR_UP:
        case DIR_UL:
        case DIR_UR:
            return -1;

        case DIR_DO:
        case DIR_DL:
        case DIR_DR:
            return 1;

        default:
            return 0;
    }
}


/* Returns the delta for column to move in the specified direction */
static int reversi_column_delta(const int direction) {
    switch (direction) {
        case DIR_LE:
        case DIR_UL:
        case DIR_DL:
            return -1;

        case DIR_RI:
        case DIR_UR:
        case DIR_DR:
            return 1;

        default:
            return 0;
    }
}


/* Checks if some stones would be captured in the specified direction
 * if a stone were placed in the specified cell by the specified player.
 *
 * Returns 0 if no stones would be captured or 'direction' otherwise
 */
static int reversi_is_valid_direction(const reversi_board_t *game,
        const int row, const int col, const int player, const int direction) {
    int row_delta, col_delta, r, c;
    int other_color;
    int flip_cnt; /* Number of stones that would be flipped */

    row_delta = reversi_row_delta(direction);
    col_delta = reversi_column_delta(direction);
    other_color = reversi_flipped_color(player);

    r = row + row_delta;
    c = col + col_delta;

    flip_cnt = 0;
    while (reversi_is_position_on_board(r, c) &&
            (game->board[r][c] == other_color)) {
        r += row_delta;
        c += col_delta;
        flip_cnt++;
    }

    if ((flip_cnt > 0) && reversi_is_position_on_board(r, c) &&
            (game->board[r][c] == player)) {
        return direction;
    } else {
        return 0;
    }
}


/* Checks whether the move at the specified position would be valid.
 * Params:
 *   - game: current state of the game
 *   - row: 0-based row number of the move in question
 *   - col: 0-based column number of the move in question
 *   - player: who is about to make the move (WHITE/BLACK)
 *
 * Checks if the place is empty, the coordinates are legal,
 * and some stones can be captured.
 *
 * Returns 0 if the move is not valid or, otherwise, the or'd
 * directions in which stones would be captured.
 */
int reversi_is_valid_move(const reversi_board_t *game,
        const int row, const int col, const int player) {
    int dirs, i;
    dirs = 0;

    /* Check if coordinates are legal */
    if (!reversi_is_position_on_board(row, col)) {
        return dirs;
    }

    /* Check if the place is free */
    if (game->board[row][col] != FREE) {
        return dirs;
    }

    /* Check the directions of capture */
    for (i = 0; i < NUM_OF_DIRECTIONS; i++) {
        dirs |= reversi_is_valid_direction(game, row, col, player, DIRECTIONS[i]);
    }

    return dirs;
}


/* Flips the stones in the specified direction after the specified
 * player has placed a stone in the specified cell. The move is
 * assumed to be valid.
 *
 * Returns the number of flipped stones in that direction
 */
static int reversi_flip_stones(reversi_board_t *game,
        const int row, const int col, const int player, const int direction) {
    int row_delta, col_delta, r, c;
    int other_color;
    int flip_cnt; /* Number of stones flipped */

    row_delta = reversi_row_delta(direction);
    col_delta = reversi_column_delta(direction);
    other_color = reversi_flipped_color(player);

    r = row + row_delta;
    c = col + col_delta;

    flip_cnt = 0;
    while (reversi_is_position_on_board(r, c) &&
            (game->board[r][c] == other_color)) {
        game->board[r][c] = player;
        r += row_delta;
        c += col_delta;
        flip_cnt++;
    }

    return flip_cnt;
}


/* Tries to make a move (place a stone) at the specified position.
 * If the move is valid the board is changed. Otherwise nothing happens.
 *
 * Params:
 *   - game: current state of the game
 *   - row: 0-based row number of the move to make
 *   - col: 0-based column number of the move to make
 *   - player: who makes the move (WHITE/BLACK)
 *
 * Returns the number of flipped (captured) stones (>0) iff the move
 * was valid or 0 if the move was not valid. Note that in the case of
 * a valid move, the stone itself is not counted.
 */
int reversi_make_move(reversi_board_t *game,
        const int row, const int col, const int player) {
    int dirs, cnt, i;

    dirs = reversi_is_valid_move(game, row, col, player);
    if (dirs == 0) {
        return 0;
    }

    /* Place the stone into the cell */
    game->board[row][col] = player;

    /* Capture stones in all possible directions */
    cnt = 0;
    for (i = 0; i < NUM_OF_DIRECTIONS; i++) {
        if (dirs & DIRECTIONS[i]) {
            cnt += reversi_flip_stones(game, row, col, player, DIRECTIONS[i]);
        }
    }

    /* Remember the move */
    i = reversi_count_moves(game);
    game->history[i-1] = MAKE_MOVE(row, col, player);

    return cnt;
}
