/*      $NetBSD: auto.c,v 1.11 2009/07/20 06:39:06 dholland Exp $       */

/*-
* Copyright (c) 1999 The NetBSD Foundation, Inc.
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
* by Christos Zoulas.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
*    notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
*    notice, this list of conditions and the following disclaimer in the
*    documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/

/*
*      Automatic move.
*      intelligent ?
*      Algo :
*              IF scrapheaps don't exist THEN
*                      IF not in danger THEN
*                              stay at current position
*                      ELSE
*                              move away from the closest robot
*                      FI
*              ELSE
*                      find closest heap
*                      find closest robot
*                      IF scrapheap is adjacent THEN
*                              move behind the scrapheap
*                      ELSE
*                              take the move that takes you away from the
*                              robots and closest to the heap
*                      FI
*              FI
*/
#include <curses.h>
#include <string.h>
#include "robots.h"

#define ABS(a) (((a)>0)?(a):-(a))
#define MIN(a,b) (((a)>(b))?(b):(a))
#define MAX(a,b) (((a)<(b))?(b):(a))

#define CONSDEBUG(a)

static int distance(int, int, int, int);
static int xinc(int);
static int yinc(int);
static const char *find_moves(void);
static COORD *closest_robot(int *);
static COORD *closest_heap(int *);
static char move_towards(int, int);
static char move_away(COORD *);
static char move_between(COORD *, COORD *);
static int between(COORD *, COORD *);

/* distance():
* return "move" number distance of the two coordinates
*/
static int
distance(int x1, int y1, int x2, int y2)
{
       return MAX(ABS(ABS(x1) - ABS(x2)), ABS(ABS(y1) - ABS(y2)));
}

/* xinc():
* Return x coordinate moves
*/
static int
xinc(int dir)
{
       switch(dir) {
       case 'b':
       case 'h':
       case 'y':
               return -1;
       case 'l':
       case 'n':
       case 'u':
               return 1;
       case 'j':
       case 'k':
       default:
               return 0;
       }
}

/* yinc():
* Return y coordinate moves
*/
static int
yinc(int dir)
{
       switch(dir) {
       case 'k':
       case 'u':
       case 'y':
               return -1;
       case 'b':
       case 'j':
       case 'n':
               return 1;
       case 'h':
       case 'l':
       default:
               return 0;
       }
}

/* find_moves():
* Find possible moves
*/
static const char *
find_moves(void)
{
       int x, y;
       COORD test;
       const char *m;
       char *a;
       static const char moves[] = ".hjklyubn";
       static char ans[sizeof moves];
       a = ans;

       for (m = moves; *m; m++) {
               test.x = My_pos.x + xinc(*m);
               test.y = My_pos.y + yinc(*m);
               move(test.y, test.x);
               switch(winch(stdscr)) {
               case ' ':
               case PLAYER:
                       for (x = test.x - 1; x <= test.x + 1; x++) {
                               for (y = test.y - 1; y <= test.y + 1; y++) {
                                       move(y, x);
                                       if (winch(stdscr) == ROBOT)
                                               goto bad;
                               }
                       }
                       *a++ = *m;
               }
       bad:;
       }
       *a = 0;
       if (ans[0])
               return ans;
       else
               return "t";
}

/* closest_robot():
* return the robot closest to us
* and put in dist its distance
*/
static COORD *
closest_robot(int *dist)
{
       COORD *rob, *end, *minrob = NULL;
       int tdist, mindist;

       mindist = 1000000;
       end = &Robots[MAXROBOTS];
       for (rob = Robots; rob < end; rob++) {
               tdist = distance(My_pos.x, My_pos.y, rob->x, rob->y);
               if (tdist < mindist) {
                       minrob = rob;
                       mindist = tdist;
               }
       }
       *dist = mindist;
       return minrob;
}

/* closest_heap():
* return the heap closest to us
* and put in dist its distance
*/
static COORD *
closest_heap(int *dist)
{
       COORD *hp, *end, *minhp = NULL;
       int mindist, tdist;

       mindist = 1000000;
       end = &Scrap[MAXROBOTS];
       for (hp = Scrap; hp < end; hp++) {
               if (hp->x == 0 && hp->y == 0)
                       break;
               tdist = distance(My_pos.x, My_pos.y, hp->x, hp->y);
               if (tdist < mindist) {
                       minhp = hp;
                       mindist = tdist;
               }
       }
       *dist = mindist;
       return minhp;
}

/* move_towards():
* move as close to the given direction as possible
*/
static char
move_towards(int dx, int dy)
{
       char ok_moves[10], best_move;
       char *ptr;
       int move_judge, cur_judge, mvx, mvy;

       (void)strcpy(ok_moves, find_moves());
       best_move = ok_moves[0];
       if (best_move != 't') {
               mvx = xinc(best_move);
               mvy = yinc(best_move);
               move_judge = ABS(mvx - dx) + ABS(mvy - dy);
               for (ptr = &ok_moves[1]; *ptr != '\0'; ptr++) {
                       mvx = xinc(*ptr);
                       mvy = yinc(*ptr);
                       cur_judge = ABS(mvx - dx) + ABS(mvy - dy);
                       if (cur_judge < move_judge) {
                               move_judge = cur_judge;
                               best_move = *ptr;
                       }
               }
       }
       return best_move;
}

/* move_away():
* move away form the robot given
*/
static char
move_away(COORD *rob)
{
       int dx, dy;

       dx = sign(My_pos.x - rob->x);
       dy = sign(My_pos.y - rob->y);
       return  move_towards(dx, dy);
}


/* move_between():
* move the closest heap between us and the closest robot
*/
static char
move_between(COORD *rob, COORD *hp)
{
       int dx, dy;
       float slope, cons;

       /* equation of the line between us and the closest robot */
       if (My_pos.x == rob->x) {
               /*
                * me and the robot are aligned in x
                * change my x so I get closer to the heap
                * and my y far from the robot
                */
               dx = - sign(My_pos.x - hp->x);
               dy = sign(My_pos.y - rob->y);
               CONSDEBUG(("aligned in x"));
       }
       else if (My_pos.y == rob->y) {
               /*
                * me and the robot are aligned in y
                * change my y so I get closer to the heap
                * and my x far from the robot
                */
               dx = sign(My_pos.x - rob->x);
               dy = -sign(My_pos.y - hp->y);
               CONSDEBUG(("aligned in y"));
       }
       else {
               CONSDEBUG(("no aligned"));
               slope = (My_pos.y - rob->y) / (My_pos.x - rob->x);
               cons = slope * rob->y;
               if (ABS(My_pos.x - rob->x) > ABS(My_pos.y - rob->y)) {
                       /*
                        * we are closest to the robot in x
                        * move away from the robot in x and
                        * close to the scrap in y
                        */
                       dx = sign(My_pos.x - rob->x);
                       dy = sign(((slope * ((float) hp->x)) + cons) -
                                 ((float) hp->y));
               }
               else {
                       dx = sign(((slope * ((float) hp->x)) + cons) -
                                 ((float) hp->y));
                       dy = sign(My_pos.y - rob->y);
               }
       }
       CONSDEBUG(("me (%d,%d) robot(%d,%d) heap(%d,%d) delta(%d,%d)",
               My_pos.x, My_pos.y, rob->x, rob->y, hp->x, hp->y, dx, dy));
       return move_towards(dx, dy);
}

/* between():
* Return true if the heap is between us and the robot
*/
int
between(COORD *rob, COORD *hp)
{
       /* I = @ */
       if (hp->x > rob->x && My_pos.x < rob->x)
               return 0;
       /* @ = I */
       if (hp->x < rob->x && My_pos.x > rob->x)
               return 0;
       /* @ */
       /* = */
       /* I */
       if (hp->y < rob->y && My_pos.y > rob->y)
               return 0;
       /* I */
       /* = */
       /* @ */
       if (hp->y > rob->y && My_pos.y < rob->y)
               return 0;
       return 1;
}

/* automove():
* find and do the best move if flag
* else get the first move;
*/
char
automove(void)
{
#if 0
       return  find_moves()[0];
#else
       COORD *robot_close;
       COORD *heap_close;
       int robot_dist, robot_heap, heap_dist;

       robot_close = closest_robot(&robot_dist);
       if (robot_dist > 1)
               return('.');
       if (!Num_scrap)
               /* no scrap heaps just run away */
               return move_away(robot_close);

       heap_close = closest_heap(&heap_dist);
       robot_heap = distance(robot_close->x, robot_close->y,
           heap_close->x, heap_close->y);
       if (robot_heap <= heap_dist && !between(robot_close, heap_close)) {
               /*
                * robot is closest to us from the heap. Run away!
                */
               return  move_away(robot_close);
       }

       return move_between(robot_close, heap_close);
#endif
}