/*  misc.c

    Copyright (C) 1994	Lambert Klasen & Detlef Steuer
			klasen@asterix.uni-muenster.de
			steuer@amadeus.statistik.uni-dortmund.de

    This file is free source code; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation.

    This program 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
    COPYING for more details.
 */



#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <X11/Xlib.h>
#include <X11/Intrinsic.h>
#include <X11/Xaw/Label.h>
#include "xgammon.h"
#include "gammon.h"

extern XtAppContext app_con;
extern Widget	    text_display, tournament_display, p_label;
extern Cursor	    gammon_cursor;
extern XColor	    fg_col, bg_col; /* gammon_cursor colors */
extern char	    add_text[];

extern int	    from_pin;
extern int	    initialize;
extern int	    width, stone_width, height;
extern int  	    done_yet, done_hit;

int	move_count;

/* decision.c */
extern float evaluate();

/* diawin.c */
extern void AppendDialogText();

/* popup.c */
extern void PopupInfoShell();

/* drawing.c */
extern void DrawDice();
extern void draw_doubler();
extern void remove_stone();
extern void put_stone();
extern void redraw_all_stones();
extern void draw_empty_board();
extern void draw_doubler();

long int NaufM[16][7];
long int Binomial[7][7];

void switch_turn(void)
{
	if (turn==BLACK) {
		turn  = WHITE;
		other = BLACK;
		direction = -1;
		start_pin = 25;
		end_pin = 0;	
	} 
	else {
		turn  = BLACK;
		other = WHITE;
		direction = 1;
		start_pin = 0;
		end_pin = 25;	
	}

	/* make the cursor look like the stones you move */
	if (Player[0].type == HUMAN && Player[1].type == HUMAN) {
	        if (turn == BLACK)
			XRecolorCursor(dpy, gammon_cursor, &fg_col, &bg_col);
	        else	XRecolorCursor(dpy, gammon_cursor, &bg_col, &fg_col);
	}
}

int end_of_game_test(int color)
{
	int i;

	for (i=start_pin+direction*19; i!=start_pin+direction*25; i+=direction) {
		if (Pin[i].color == turn) return 0;
	}

	/* if we get here lookup if we have to count doubler two or three times */

	/* a stone in the opponents first quarter or on BAR */

	if (Pin[OTHER_BAR].count) {
		doubler_value *= 3;
		return 1;
	}

	for (i = end_pin-direction; i != end_pin-direction*7; i-=direction) {
		if (Pin[i].color == other) {
			doubler_value *= 3;
			backgammons[turn-1] ++;
			return 1;
		}
	}

	/* other has none stone finished */

{	int k;
	k = Pin[OTHER_BAR].count;
	for (i=1; i<25; i++) {
		if (Pin[i].color == other) k += Pin[i].count;
	}
	if (k == 15) {
		doubler_value *=2;
		gammons[turn-1] ++;
	}
}
	return 1; 
}

void roll_dice(void)
{
	roll[0] = (rand()%6 + 1) * direction;
	roll[1] = (rand()%6 + 1) * direction;

        if (roll[0] != roll[1]) {
                roll[2] = 0;		/* sure and necessary */
                roll[3] = 0;
		to_move = 2;
		pash = 0;
	}
        else {
		roll[2] = roll[0];
		roll[3] = roll[1];
		to_move = 4;
		pash = 1;
	}

	move_count ++;

	sprintf(add_text,"move %2d (dice %d, %d): ", move_count, abs(roll[0]), abs(roll[1]));
	AppendDialogText(text_display, add_text);
}

int complete_blockade(void)
{
	int i;

	for (i=start_pin+direction; i!=start_pin+direction*7; i+=direction) {
		if (Pin[i].count == 0 || Pin[i].color == turn ||
		   (Pin[i].color != turn && Pin[i].count < 2)) return 0;
	}
	return 1;
}

/* this function checkes, if you make a multible move, if you have to hit an
   opponents stone on the run */

int have_to_hit(int from, int to)
{
	if (!pash) {
		if (Pin[from + roll[0]].color == other && Pin[from + roll[1]].color == other) {
			if (Pin[from + roll[0]].count == 1 && Pin[from + roll[1]].count == 1) {
				PopupInfoShell("Please decide witch stone you want to hit");
				return 1;
			}
			else if (Pin[from + roll[0]].count == 1) {
				remove_stone(from + roll[0]);
				put_stone(other, OTHER_BAR);
				return 0;
			}
			else if (Pin[from + roll[1]].count == 1){
				remove_stone(from + roll[1]);
				put_stone(other, OTHER_BAR);
				return 0;
			}
		}
		else return 0;	/* will not hit by default */
	}
	else { /* pash */
		int i;
		for (i=1; i<4; i++) {	/* to_pin is checked by PlaceStone */
			if ((from + i*roll[0]) == to) break;
			if (Pin[from + i*roll[0]].color == other && !((turn==BLACK) ? from + i*roll[0] >= end_pin : from + i*roll[0] <= end_pin)) {
				remove_stone(from + i*roll[0]);
				put_stone(other, OTHER_BAR);
			}
		}
	}
	return 0;
}

void init_game(void)
{
	int i;

	from_pin = 0;
	done_yet = 0;
	done_hit = 0;
	move_count  = 0;

	if (initialize == NORMAL_GAME || initialize == COMPUTER_TOURNAMENT) {
		draw_empty_board();
		for (i=0; i<29; i++) { Pin[i].count=0; Pin[i].color=0; }

		Pin[1].color = BLACK; Pin[1].count = 2;
		Pin[12].color = BLACK; Pin[12].count = 5;
		Pin[17].color = BLACK; Pin[17].count = 3;
		Pin[19].color = BLACK; Pin[19].count = 5;
		
		Pin[6].color = WHITE; Pin[6].count = 5;
		Pin[8].color = WHITE; Pin[8].count = 3;
		Pin[13].color = WHITE; Pin[13].count = 5;
		Pin[24].color = WHITE; Pin[24].count = 2;

		redraw_all_stones();

		Pin[(25+BLACK)].color = BLACK;
		Pin[(25+WHITE)].color = WHITE;

		doubler_value = 1;
		last_doubler = 0;
		draw_doubler(doubler_value, last_doubler);

		tournament.game_number++;
		sprintf(add_text,"game number %d, ", tournament.game_number);
		AppendDialogText(text_display, add_text);

		/* decide beginner of game */
		direction = 1;
		do {
			roll[0] = (rand()%6 + 1);
			roll[1] = (rand()%6 + 1);
		} while (roll[0] == roll[1]);

		roll[2] = 0;
		roll[3] = 0;
		to_move = 2;
		pash = 0;

		if      (roll[1] > roll[0]) {		/* white begins */
			/* (tricked) switch_turn sets turn right and all the other vars */
			turn = BLACK;
			switch_turn();
			roll[0] *= direction;	/* must a be */
			roll[1] *= direction;
			roll[2] *= direction;
			roll[3] *= direction;
			Player[1].beginner_of_game = 1;
			Player[0].beginner_of_game = 0;
			AppendDialogText(text_display, "white begins\n");
		}
		else if (roll[0] > roll[1]) {
			turn = WHITE;
			switch_turn();
			Player[0].beginner_of_game = 1;
			Player[1].beginner_of_game = 0;
			AppendDialogText(text_display, "black begins\n");
		}

		move_count = 1;
		sprintf(add_text,"move %2d (dice %d, %d): ", move_count, abs(roll[0]), abs(roll[1]));
		AppendDialogText(text_display, add_text);
		DrawDice(turn);
	} /* end initialize */
}

void AppendMoveString(struct _Move *m)
{
	char f[5], t[5];
	int  i;

	if (m->from != BAR) sprintf(f,"%2d-",m->from);
	else strcpy(f,"bar-");

	if (m->to != end_pin) sprintf(t,"%2d  ",m->to);
	else strcpy(t,"off ");

	strcpy  (add_text, f);
	strcat  (add_text, t);
	for(i=1; i<to_move; i++) {
		if ((m+i)->from != BAR) sprintf(f,"%2d-",(m+i)->from);
		else strcpy(f,"bar-");
	        strcat  (add_text, f);
		if ((m+i)->to != end_pin) sprintf(t,"%2d  ",(m+i)->to);
		else strcpy(t,"off ");
	        strcat  (add_text, t);
	}

	strcat (add_text, "\n");
	AppendDialogText (text_display, add_text);
}

XPoint pin_to_position(int pin)
{
XPoint p;

if      (pin==BAR || pin == OTHER_BAR) p.x = width/2 - stone_width/2; 
else if (pin == FINISHED)              p.x = width   - stone_width;
else if (pin<7)    p.x = width - (pin + 1) * stone_width;
else if (pin<13)   p.x = width - (pin + 1) * stone_width - stone_width;
else if (pin<19)   p.x = width - (26 - pin) * stone_width - stone_width;
else if (pin<25)   p.x = width - (26 - pin) * stone_width;

if      (pin == FINISHED)   p.y =  Pin[FINISHED].count * height/2/15;
else if (Pin[pin].count<5)  p.y =  Pin[pin].count     * stone_width;
else if (Pin[pin].count<9)  p.y = (Pin[pin].count-5)  * stone_width + stone_width/2;
else if (Pin[pin].count<13) p.y = (Pin[pin].count-9)  * stone_width + stone_width/4;
else			    p.y = (Pin[pin].count-13) * stone_width + stone_width*3/4;

/* for upper half of board */
if (((pin == BAR ||  pin == OTHER_BAR) && Pin[pin].color == WHITE) ||
    (pin == FINISHED &&  turn == BLACK) ||
    (pin > 12 && pin != FINISHED))      p.y = height - stone_width - p.y;

return p;
}

int event_to_pin(int x, int y)
{
	int p;

	p  = (width - x) / stone_width;

	if      (p==0)  return end_pin;
	else if (p==7)  return BAR;
	else if (p==14) return DOUBLER;

	if ((y - height/2) < 0) {		/* upper part of the board */
		if (p > 6) return (p-1);	/* left of bar */
		else	   return p;
		}
	else { 					/* lower part of the board */
		p = 25 - p;
		if (p < 19) return (p+1); 	/* left of bar */
		else	    return p;
		}
}

void AddResult(int whom)
{
char *player_str;

tournament.points[whom] += doubler_value;

if      (whom == BLACK) player_str = "black";
else if (whom == WHITE) player_str = "white";

sprintf(add_text, "end of game: %s has now %d points\n", player_str, tournament.points[whom]);
AppendDialogText(text_display, add_text);

sprintf(add_text, "white %d, black %d\n", tournament.points[WHITE], tournament.points[BLACK]);

AppendDialogText(tournament_display, add_text);

if (tournament.winning_point && tournament.points[whom] >= tournament.winning_point) {
	sprintf(add_text, "Congratulations, %s won this tournament\n", player_str);
	PopupInfoShell(add_text);
	AppendDialogText(text_display, add_text);
	
	AppendDialogText(tournament_display, "end of tournament\n");
	AppendDialogText(tournament_display, add_text);
	/*tournament.points[BLACK] = 0;
	tournament.points[WHITE] = 0;
	tournament.game_number = 0;

	sprintf(add_text, "white points: 0, black points: 0\n"); */
	}
}

long int fak(int n)
{
	if (n <= 1) return (1);
	else	      return (n * fak(n - 1));
}


long int binomial(int n, int m)
{
	return (fak(n) / fak(m) / fak(n - m));
}

long int naufm(int n, int m)
{
	if (n < m)  return (0);
	if (m == 1) return (1);
	return (naufm(n - 1, m) + naufm(n - 1, m - 1));
}

void set_naufm()
{
	int i, j;

	for (i = 1; i < 16; i++) {
	for (j = 1; j < 7;  j++) {
		NaufM[i][j] = naufm(i, j);
	}}
}

void set_binom()
{
	int i, j;

	for (i = 1; i < 7; i++) {
	for (j = 1; j < 7; j++) {
		Binomial[i][j] = binomial(i, j);
	}}
}

/* PositionNumber:
   this function calculates out of a given endgame position the corresponding
   position number in the endgame database, in a running game pin 6 is set
   as aim for stones not in the homeboard */

long int PositionNumber(int STARTPIN, int color)
{
	int pin[7];
	int sum = 0, s, i, stones;
	int start, nextpin, local_direction, local_endpin;
	long int number = 0L;

	if (color == turn) {
		local_direction = direction;
		local_endpin = end_pin;
	}
	else {
		local_direction = -1 * direction;
		local_endpin = start_pin;
	}

	pin[0] = 0;

	for (i = 1; i < 7; i++) {
		if (Pin[local_endpin - i * local_direction].color == color)
			pin[i] = Pin[local_endpin - i * local_direction].count;
		else
			pin[i] = 0;
	}

	for (i = 1; i < 7; i++) sum += pin[i];

	/* put all stones on the 6. pin */
	pin[6] += 15 - sum - Pin[25 + color].count;
	sum = 0;

	for (i = STARTPIN + 1; i < 7; i++) sum += pin[i];

	if (sum == 0) return (0);
	i = STARTPIN + 1;


	while (pin[i] == 0 && i < 7) i++;

	nextpin = i - STARTPIN;
	number += nextpin;

	for (start = STARTPIN; start < STARTPIN + nextpin; start++) {
		for (stones = 1; stones < sum; stones++) {
			for (s = 1; s < 6 - start + 1; s++) {
				number += Binomial[6 - start][s] * NaufM[stones][s];

			}
		}
	}

	number += PositionNumber(STARTPIN + nextpin, color);

	return (number);
}

struct _Move *find_best_move()
{
	struct _Move *best_move_right_now = NULL, *pm = possible_moves;
	int i, hit[4], movelength = (roll[0] == roll[1]) ? 4 : 2;
	float maxvalue = -10000.0, value;

	while (pm != list) {
	for (i = 0; i < movelength; i++) {
		if ((pm + i)->from + (pm + i)->to) {
			hit[i] = 0;
			Pin[(pm + i)->from].count--;
			if (!Pin[(pm + i)->from].count)
				Pin[(pm + i)->from].color = 0;
			if ((pm + i)->to == end_pin) {
				Pin[FINISHED].count++;
			}
			else {
				if (Pin[(pm + i)->to].color == other) {
					Pin[(pm + i)->to].color = turn;
					hit[i] = 1;
					Pin[OTHER_BAR].count++;
					Pin[OTHER_BAR].color = other;
				}
				else {
					Pin[(pm + i)->to].count++;
					Pin[(pm + i)->to].color = turn;
				}
			}
		}
	}
#ifdef DEBUG_EVALUATE
{
	int k;
	struct _Move *d = pm;
	printf("\n");
	for (k = 0; k < 4; k++) {
		printf("%2d -> %2d  ", d->from, d->to);
		d++;
	}
	printf("\n");
}
#endif
	value = evaluate(turn);
	if (value > maxvalue) {
		maxvalue = value;
		best_move_right_now = pm;
	}

	for (i = movelength - 1; i > -1; i--) {
		if ((pm + i)->from + (pm + i)->to) {
			Pin[(pm + i)->from].count++;
			Pin[(pm + i)->from].color = turn;

			if (hit[i]) {
				Pin[(pm + i)->to].color = other;
				Pin[OTHER_BAR].count--;
				if (!Pin[OTHER_BAR].count)
				Pin[OTHER_BAR].color = 0;
			}
			else {
				if ((pm + i)->to == end_pin) {
					Pin[FINISHED].count--;
				}
				else {
					Pin[(pm + i)->to].count--;
					if (!Pin[(pm + i)->to].count)
					Pin[(pm + i)->to].color = 0;
				}
			}
		}
	}
	pm += 4;
}

#ifdef DEBUG_EVALUATE
{
	int k;
	struct _Move *d = best_move_right_now;
	for (k = 0; k < 4; k++) {
		printf("%2d -> %2d  ", d->from, d->to);
		d++;
	}
	printf("\n this is the best move with vlaue %f\n",maxvalue);
}
#endif

	return (best_move_right_now);
}

void maildump(void)
{
FILE *maildumpfile;
int i, j;

maildumpfile = fopen("xgammon.maildump","w");

fprintf(maildumpfile, "    13 14 15 16 17 18       19 20 21 22 23 24\n   +------------------------------------------+ X: - score: %d\n", tournament.points[WHITE]);
for (i=0; i<4; i++) {
	fprintf(maildumpfile, "   |");
	for (j=12; j>6; j--) {
		if      (Pin[j].color == WHITE && Pin[j].count >= i+1) fprintf(maildumpfile, " X ");
		else if (Pin[j].color == BLACK && Pin[j].count >= i+1) fprintf(maildumpfile, " O ");
		else						       fprintf(maildumpfile, "   ");
	}
	fprintf(maildumpfile, "|   | ");
	for (j=6; j>0; j--) {
		if      (Pin[j].color == WHITE && Pin[j].count >= i+1) fprintf(maildumpfile, " X ");
		else if (Pin[j].color == BLACK && Pin[j].count >= i+1) fprintf(maildumpfile, " O ");
		else						       fprintf(maildumpfile, "   ");
	}
	fprintf(maildumpfile, "|\n");
}
	fprintf(maildumpfile, "   |");
	for (j=12; j>6; j--) {
		if      (Pin[j].color == WHITE && Pin[j].count == 5) fprintf(maildumpfile, " X ");
		else if (Pin[j].color == WHITE && Pin[j].count > 5)  fprintf(maildumpfile, "%2d ", Pin[j].count);
		else if (Pin[j].color == BLACK && Pin[j].count == 5) fprintf(maildumpfile, " O ");
		else if (Pin[j].color == BLACK && Pin[j].count > 5)  fprintf(maildumpfile, "%2d ", Pin[j].count);
		else						     fprintf(maildumpfile, "   ");
	}
	fprintf(maildumpfile, "|   | ");
	for (j=6; j>0; j--) {
		if      (Pin[j].color == WHITE && Pin[j].count == 5) fprintf(maildumpfile, " X ");
		else if (Pin[j].color == WHITE && Pin[j].count > 5)  fprintf(maildumpfile, "%2d ", Pin[j].count);
		else if (Pin[j].color == BLACK && Pin[j].count == 5) fprintf(maildumpfile, " O ");
		else if (Pin[j].color == BLACK && Pin[j].count > 5)  fprintf(maildumpfile, "%2d ", Pin[j].count);
		else						     fprintf(maildumpfile, "   ");
	}
	fprintf(maildumpfile, "|\n");
	fprintf(maildumpfile, "  v|                  |BAR|                   |    ");

	if   (gammon_resource.moneygame) fprintf(maildumpfile, "money-game\n");
	else if (gammon_resource.bestof) fprintf(maildumpfile, "best of %d match\n", gammon_resource.bestof);
	else 				 fprintf(maildumpfile, "%d-point match\n", gammon_resource.winat);

	fprintf(maildumpfile, "   |");
	for (j=13; j<19; j++) {
		if      (Pin[j].color == WHITE && Pin[j].count == 5) fprintf(maildumpfile, " X ");
		else if (Pin[j].color == WHITE && Pin[j].count > 5)  fprintf(maildumpfile, "%2d ", Pin[j].count);
		else if (Pin[j].color == BLACK && Pin[j].count == 5) fprintf(maildumpfile, " O ");
		else if (Pin[j].color == BLACK && Pin[j].count > 5)  fprintf(maildumpfile, "%2d ", Pin[j].count);
		else						     fprintf(maildumpfile, "   ");
	}
	fprintf(maildumpfile, "|   | ");
	for (j=19; j<25; j++) {
		if      (Pin[j].color == WHITE && Pin[j].count == 5) fprintf(maildumpfile, " X ");
		else if (Pin[j].color == WHITE && Pin[j].count > 5)  fprintf(maildumpfile, "%2d ", Pin[j].count);
		else if (Pin[j].color == BLACK && Pin[j].count == 5) fprintf(maildumpfile, " O ");
		else if (Pin[j].color == BLACK && Pin[j].count > 5)  fprintf(maildumpfile, "%2d ", Pin[j].count);
		else						     fprintf(maildumpfile, "   ");
	}
	fprintf(maildumpfile, "|\n");

for (i=4; i>0; i--) {
	fprintf(maildumpfile, "   |");
	for (j=13; j<19; j++) {
		if      (Pin[j].color == WHITE && Pin[j].count >= i) fprintf(maildumpfile, " X ");
		else if (Pin[j].color == BLACK && Pin[j].count >= i) fprintf(maildumpfile, " O ");
		else						     fprintf(maildumpfile, "   ");
	}
	fprintf(maildumpfile, "|   | ");
	for (j=19; j<25; j++) {
		if      (Pin[j].color == WHITE && Pin[j].count >= i) fprintf(maildumpfile, " X ");
		else if (Pin[j].color == BLACK && Pin[j].count >= i) fprintf(maildumpfile, " O ");
		else						     fprintf(maildumpfile, "   ");
	}
	fprintf(maildumpfile, "|\n");
}
	fprintf(maildumpfile, "   +------------------------------------------+ O:  - score: %d\n", tournament.points[BLACK]);
	fprintf(maildumpfile, "    12 11 10  9  8  7        6  5  4  3  2  1\n\n");
	if (turn == BLACK)
		fprintf(maildumpfile, "   BAR: O-%d X-%d", Pin[0].count, Pin[25].count);
	else
		fprintf(maildumpfile, "   BAR: O-%d X-%d", Pin[25].count, Pin[0].count);
	fprintf(maildumpfile, "   OFF: O-%d X-%d", Pin[26].count, Pin[27].count);
	fprintf(maildumpfile, "   Cube: %d ", doubler_value);
	if      (last_doubler == BLACK) fprintf(maildumpfile, "(owned by Player O)\n");
	else if (last_doubler == WHITE) fprintf(maildumpfile, "(owned by Player X)\n");
	else				fprintf(maildumpfile, "(owned by Player none)\n");
	fprintf(maildumpfile, "\n\n");

fclose(maildumpfile);
}
