/**
* @file
* Routines for dealing with Rows that occur in the 'rows' array in the Qube object.
* Last Modified: Mon May 29 13:52:17 PDT 2017
* @author Kevin O'Gorman
*/
/*
* Copyright 2002 Santiago Canez <scanez@math.berkeley.edu>
* Copyright 2012,2013,2016 Kevin O'Gorman <kogorman@gmail.com>.
* Distributed under the GNU General Public License.
*
* This file is part of libqubist, the library of functions for playing
* 4x4x4 tic-tac-toe, also known by some other names outside the USA.
*
* Libqubist 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 version 2.
*
* Libqubist is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
/**
* @file
* Code and data to handle rows and their states.
*
* In the code, a "row" refers to any four cells (Squares) that form a potential win.
* They would be in a straight line in an actual cube, either orthogonally or on some
* diagonal, including the four "major diagonals" of the Qube.
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "player.h"
#include "board.h"
#include "row.h"
#undef NDEBUG
#define NDEBUG
#include <assert.h>
#define VERBOSE
#undef VERBOSE
/** The default buffer size. */
#define BUFL 129
Square*
qb_get_square_from_row(Row *r, int i)
{
return r->squares[i];
}
/**
* Compute the state of the given Row. Note trickery: the values of enumeration
* constants are used arithmetically and as subscripts.
* @param r a pointer to a Row.
* @return the state of the row, according to the states in state.h.
*/
row_state_t
qb_determine_row_state(Row *r)
{
int counts[3] = {0,0,0};;
counts[r->squares[0]->square_state]++;
counts[r->squares[1]->square_state]++;
counts[r->squares[2]->square_state]++;
counts[r->squares[3]->square_state]++;
if (counts[PLAYER] == 0 && counts[COMP] == 0) return EMPTYROW;
else if (counts[PLAYER] != 0 && counts[COMP] != 0) return MIXED;
else if (counts[COMP] > 0) return COMP0 + counts[COMP];
else if (counts[PLAYER] > 0) return PLAYER0 + counts[PLAYER];
assert(FALSE);
exit(EXIT_FAILURE);
}
/**
* Get the state of the indicated Row.
* @param r a pointer to a Row.
* @return the state of Row r.
*/
row_state_t
qb_get_row_state(Row *r)
{
return r->row_state;
}
/**
* Construct a string representation of a row using linear square numbers.
* Not thread-safe, and the result is overwritten on each call.
* @param r a pointer to the Row object.
* @return a pointer to a static buffer with the string representation.
*/
char*
qb_get_string_row_linear(Row *r)
{
static char result[32];
snprintf(result, 32, "%d %d %d %d",
qb_get_square_num(r->squares[0]), qb_get_square_num(r->squares[1]),
qb_get_square_num(r->squares[2]), qb_get_square_num(r->squares[3]));
return result;
}
/**
* Construct a string representation of a row using linear square numbers.
* Not thread-safe, and the result is overwritten on each call.
* Actually the same as qb_get_string_row_linear¸ for historical reasons.
* @param r a pointer to the Row object.
* @return a pointer to a static buffer with the string representation.
*/
char*
qb_get_string_row(Row *r)
{
return qb_get_string_row_linear(r);
}
/**
* The number of entries in the lines array, each representing a group
* of similar rows.
*/
#define ROW_GROUPS 16
/**
* Patterns lines
*/
static
int lines[ROW_GROUPS][4] = {
/* orthogonals */
{ 0, 1, 16, 16},
{ 0, 4, 64, 1},
{ 0, 1, 4, 4},
{16, 1, 20, 4},
{32, 1, 36, 4},
{48, 1, 52, 4},
/* diagonals */
{ 0, 1, 4, 20},
{ 0, 4, 13, 17},
{ 3, 4, 16, 15},
{12, 1, 16, 12},
{ 0, 16, 49, 5},
{ 3, 16, 52, 3},
/* main diagonals */
{ 0, 1, 1, 21},
{ 3, 1, 4, 19},
{12, 1, 13, 13},
{15, 1, 16, 11}};
/**
* Initialize the array of Rows in the Qube object.
* This is based on the array of planes, which must already be established.
* @param board the Qube where all this takes place.
*/
void
qb_initialize_rows(Qube *board)
{
int numrows;
int i;
numrows = 0;
/* three bundles of orthogonal rows */
for(i = 0; i < ROW_GROUPS; i++) {
int start = lines[i][0];
int inc = lines[i][1];
int limit = lines[i][2];
int step = lines[i][3];
int s;
int sn, j;
for (s = start; s < limit; s += inc) {
for (j = 0; j < 4; j++) {
sn = s + j*step;
Square *sp = &board->squares[sn];
sp->rows_in[sp->num_rows++] = numrows;
board->rows[numrows].squares[j] = board->squares + sn;
}
board->rows[numrows++].row_state = EMPTYROW;
}
}
#ifdef VERBOSE
/* if debugging, print out a display of the rows, 1-based, in 4 columns */
printf("There are %d known rows.\n", numrows);
for (i = 0; i < numrows; i += 4) {
printf("%3d: %3d%3d%3d%3d %3d%3d%3d%3d %3d%3d%3d%3d %3d%3d%3d%3d\n",
i,
qb_get_square_num(board->rows[i + 0].squares[0]),
qb_get_square_num(board->rows[i + 0].squares[1]),
qb_get_square_num(board->rows[i + 0].squares[2]),
qb_get_square_num(board->rows[i + 0].squares[3]),
qb_get_square_num(board->rows[i + 1].squares[0]),
qb_get_square_num(board->rows[i + 1].squares[1]),
qb_get_square_num(board->rows[i + 1].squares[2]),
qb_get_square_num(board->rows[i + 1].squares[3]),
qb_get_square_num(board->rows[i + 2].squares[0]),
qb_get_square_num(board->rows[i + 2].squares[1]),
qb_get_square_num(board->rows[i + 2].squares[2]),
qb_get_square_num(board->rows[i + 2].squares[3]),
qb_get_square_num(board->rows[i + 3].squares[0]),
qb_get_square_num(board->rows[i + 3].squares[1]),
qb_get_square_num(board->rows[i + 3].squares[2]),
qb_get_square_num(board->rows[i + 3].squares[3]) );
}
assert(numrows == 76);
#endif
}
/**
* Compute a pointer to the given row of the Qube object.
* @param board a pointer to the Qube object.
* @param num the index of the desired row
* @return a pointer to the desired Row object.
*/
Row*
qb_get_row(Qube *board, int num)
{
return board->rows + num;
}
/**
* Compute the value of each row of the board.
* @param board a pointer to the Qube object.
*/
void
qb_evaluate_rows(Qube *board)
{
int i;
for (i=0; i < 76; i++) {
board->rows[i].row_state = qb_determine_row_state(board->rows + i);
}
}
/**
* Verify the state of all rows. Should only be invoked during debugging, but is always
* present to allow other files to use it in assert(1) calls. Returns a truth value.
* @param board a pointer to the Qube object.
* @return TRUE (1) if okay, FALSE (0) if not.
*/
int
qb_has_valid_rows(Qube *board)
{
int i;
for (i=0; i < 76; i++) {
if (board->rows[i].row_state != qb_determine_row_state(board->rows + i)) {
return FALSE;
}
}
return TRUE;
}
/**
* Determine if an immediate win exists for the indicated player. Return the winning
* square if so.
* @param board a pointer to the Qube object.
* @param player a pointer to the Player object.
* @return the number of the winning square, or -1 if no win is available.
*/
int
qb_check_immediate_win(Qube *board, Player *player)
{
int i;
row_state_t state;
assert(qb_has_valid_rows(board));
for (i=0; i < 76; i++) { /* check each row for a win */
state = qb_get_row_state(board->rows + i);
if (state == player->this3) {
return qb_get_empty_from_row(board, board->rows + i);
}
}
return -1;
}
/**
* Return a square number of an empty square in the given Row.
* @param board a pointer to the Qube object.
* @param row a pointer to the row in quesiton.
* @return the number of an empty square, or -1 if there is none.
*/
int
qb_get_empty_from_row(Qube *board, Row *row)
{
int i;
Square *sq;
for (i = 0; i < 4; i++) {
sq = qb_get_square_from_row(row, i);
if (sq->square_state == EMPTY) {
return qb_get_square_num(sq);
}
}
return -1;
}
/**
* Determine if a square is forcing for the computer.
* @param board a pointer to the Qube object.
* @param sq the number of the square.
* @return 1 if the square is empty and forcing, otherwise 0;
*/
int
qb_is_square_COMP2(Qube *board, int sq)
{
int row;
Square *sqp = qb_get_square(board, sq);
if (sqp->square_state != EMPTY) {
return 0;
}
assert(qb_has_valid_rows(board));
for (row = 0; row < sqp->num_rows; row++) {
if (board->rows[sqp->rows_in[row]].row_state == COMP2) {
return 1;
}
}
return 0;
}
/* vim: set expandtab autoindent shiftround nosmartindent tabstop=8 softtabstop=2 shiftwidth=2: */