root/games/gomoku/makemove.c
/*      $NetBSD: makemove.c,v 1.43 2022/06/19 10:23:48 rillig Exp $     */

/*
 * Copyright (c) 1994
 *      The Regents of the University of California.  All rights reserved.
 *
 * This code is derived from software contributed to Berkeley by
 * Ralph Campbell.
 *
 * 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.
 * 3. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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.
 */

#include <sys/cdefs.h>
/*      @(#)makemove.c  8.2 (Berkeley) 5/3/95   */
__RCSID("$NetBSD: makemove.c,v 1.43 2022/06/19 10:23:48 rillig Exp $");

#include "gomoku.h"

const int     dd[4] = {         /* direction deltas */
        1,                      /* right */
        -(BSZ + 1) + 1,         /* down + right */
        -(BSZ + 1),             /* down */
        -(BSZ + 1) - 1          /* down + left */
};

static const int weight[5] = { 0, 1, 7, 22, 100 };

static void update_overlap(spot_index);

static bool
is_tie(void)
{

        for (int y = 1; y <= BSZ; y++)
                for (int x = 1; x <= BSZ; x++)
                        if (board[PT(x, y)].s_wval != 0)
                                return false;
        return true;
}

static void
sortframes_remove(struct combostr *cbp)
{

        if (cbp->c_next == NULL)
                return;

        if (sortframes[BLACK] == cbp)
                sortframes[BLACK] = cbp->c_next;
        if (sortframes[WHITE] == cbp)
                sortframes[WHITE] = cbp->c_next;
        cbp->c_next->c_prev = cbp->c_prev;
        cbp->c_prev->c_next = cbp->c_next;
}

static int
old_weight_value(const struct spotstr *sp, direction r)
{
        union comboval cb;
        int val = 0;
        if ((cb = sp->s_fval[BLACK][r]).s <= 0x500)
                val += weight[5 - cb.cv_force - cb.cv_win];
        if ((cb = sp->s_fval[WHITE][r]).s <= 0x500)
                val += weight[5 - cb.cv_force - cb.cv_win];
        return val;
}

/*
 * Return values:
 *      MOVEOK  everything is OK.
 *      RESIGN  Player resigned.
 *      ILLEGAL Illegal move.
 *      WIN     The winning move was just played.
 *      TIE     The game is a tie.
 */
int
makemove(player_color us, spot_index mv)
{

        /* check for end of game */
        if (mv == RESIGN)
                return RESIGN;

        /* check for illegal move */
        struct spotstr *sp = &board[mv];
        if (sp->s_occ != EMPTY)
                return ILLEGAL;

        /* make move */
        sp->s_occ = us;
        game.moves[game.nmoves++] = mv;

        /* compute new frame values */
        sp->s_wval = 0;
        for (direction r = 4; r-- > 0; ) {
            int d = dd[r];
            struct spotstr *fsp = &board[mv];

            for (int f = 5; --f >= 0; fsp -= d) {       /* for each frame */
                if (fsp->s_occ == BORDER)
                    goto nextr;
                if (is_blocked(fsp, r))
                    continue;

                struct combostr *cbp = &frames[fsp->s_frame[r]];
                sortframes_remove(cbp);

                int val = old_weight_value(fsp, r);

                /* compute new combo value for this frame */
                bool space = fsp->s_occ == EMPTY;
                int n = 0;
                sp = fsp;
                for (int off = 5; off-- > 0; sp += d) { /* for each spot */
                    if (sp->s_occ == us)
                        n++;
                    else if (sp->s_occ == EMPTY)
                        sp->s_wval -= val;
                    else {
                        set_blocked(fsp, r);
                        /* adjust values */
                        fsp->s_fval[BLACK][r].s = 0x600;
                        fsp->s_fval[WHITE][r].s = 0x600;
                        while (off-- > 0) {
                            sp += d;
                            if (sp->s_occ == EMPTY)
                                sp->s_wval -= val;
                        }
                        goto nextf;
                    }
                }

                /* check for game over */
                if (n == 5) {
                    game.win_spot = (spot_index)(fsp - board);
                    game.win_dir = r;
                    return WIN;
                }

                /* compute new value & combo number for this frame & color */
                player_color them = us != BLACK ? BLACK : WHITE;
                fsp->s_fval[them][r].s = 0x600;
                union comboval *cp = &fsp->s_fval[us][r];
                /* both ends open? */
                if (space && sp->s_occ == EMPTY) {
                    cp->cv_force = 4 - n;
                    cp->cv_win = 1;
                } else {
                    cp->cv_force = 5 - n;
                    cp->cv_win = 0;
                }
                val = weight[n];
                sp = fsp;
                for (int off = 5; off-- > 0; sp += d)   /* for each spot */
                    if (sp->s_occ == EMPTY)
                        sp->s_wval += val;

                /* add this frame to the sorted list of frames by combo value */
                struct combostr *cbp1 = sortframes[us];
                if (cbp1 == NULL)
                    sortframes[us] = cbp->c_next = cbp->c_prev = cbp;
                else {
                    union comboval *cp1 =
                        &board[cbp1->c_vertex].s_fval[us][cbp1->c_dir];
                    if (cp->s <= cp1->s) {
                        /* insert at the head of the list */
                        sortframes[us] = cbp;
                    } else {
                        do {
                            cbp1 = cbp1->c_next;
                            cp1 = &board[cbp1->c_vertex].s_fval[us][cbp1->c_dir];
                            if (cp->s <= cp1->s)
                                break;
                        } while (cbp1 != sortframes[us]);
                    }
                    cbp->c_next = cbp1;
                    cbp->c_prev = cbp1->c_prev;
                    cbp1->c_prev->c_next = cbp;
                    cbp1->c_prev = cbp;
                }

            nextf:
                ;
            }

            /* both ends open? */
            if (fsp->s_occ == EMPTY) {
                union comboval *cp = &fsp->s_fval[BLACK][r];
                if (cp->cv_win != 0) {
                    cp->cv_force++;
                    cp->cv_win = 0;
                }
                cp = &fsp->s_fval[WHITE][r];
                if (cp->cv_win != 0) {
                    cp->cv_force++;
                    cp->cv_win = 0;
                }
            }

        nextr:
            ;
        }

        update_overlap(mv);

        if (is_tie())
                return TIE;

        return MOVEOK;
}

static void
update_overlap_same_direction(spot_index s1, spot_index s2,
                              frame_index a, int d, int off_minus_f,
                              direction r)
{
        /*
         * count the number of empty spots to see if there is
         * still an overlap.
         */
        int n = 0;
        spot_index s = s1;
        spot_index es = 0;
        for (int b = off_minus_f; b < 5; b++, s += d) {
                if (board[s].s_occ == EMPTY) {
                        es = s; /* save the intersection point */
                        n++;
                }
        }

        frame_index b = board[s2].s_frame[r];
        if (n == 0) {
                if (board[s].s_occ == EMPTY) {
                        overlap[a * FAREA + b] &= 0xA;
                        overlap[b * FAREA + a] &= 0xC;
                        intersect[a * FAREA + b] = s;
                        intersect[b * FAREA + a] = s;
                } else {
                        overlap[a * FAREA + b] = 0;
                        overlap[b * FAREA + a] = 0;
                }
        } else if (n == 1) {
                if (board[s].s_occ == EMPTY) {
                        overlap[a * FAREA + b] &= 0xAF;
                        overlap[b * FAREA + a] &= 0xCF;
                } else {
                        overlap[a * FAREA + b] &= 0xF;
                        overlap[b * FAREA + a] &= 0xF;
                }
                intersect[a * FAREA + b] = es;
                intersect[b * FAREA + a] = es;
        }
        /* else no change, still multiple overlap */
}

/*
 * The last move was at 'os', which is part of frame 'a'. There are 6 frames
 * with direction 'rb' that cross frame 'a' in 'os'. Since the spot 'os'
 * cannot be used as a double threat anymore, mark each of these crossing
 * frames as non-overlapping with frame 'a'.
 */
static void
update_overlap_different_direction(spot_index os, frame_index a, direction rb)
{

        int db = dd[rb];
        for (int off = 0; off < 6; off++) {
                const struct spotstr *sp = &board[os - db * off];
                if (sp->s_occ == BORDER)
                        break;
                if (is_blocked(sp, rb))
                        continue;

                frame_index b = sp->s_frame[rb];
                overlap[a * FAREA + b] = 0;
                overlap[b * FAREA + a] = 0;
        }
}

/*
 * fix up the overlap array according to the changed 'os'.
 */
static void
update_overlap(spot_index os)
{

        for (direction r = 4; r-- > 0; ) {
            int d = dd[r];
            spot_index s1 = os;

            /* for each frame 'a' that contains the spot 'os' */
            for (int f = 0; f < 6; f++, s1 -= d) {
                if (board[s1].s_occ == BORDER)
                    break;
                if (is_blocked(&board[s1], r))
                    continue;

                /*
                 * Update all other frames that intersect the current one
                 * to indicate whether they still overlap or not.
                 * Since F1 overlap F2 == F2 overlap F1, we only need to
                 * do the rows 0 <= r1 <= r. The r1 == r case is special
                 * since the two frames can overlap in more than one spot.
                 */
                frame_index a = board[s1].s_frame[r];

                spot_index s2 = s1 - d;
                for (int off = f + 1; off < 6; off++, s2 -= d) {
                    if (board[s2].s_occ == BORDER)
                        break;
                    if (is_blocked(&board[s2], r))
                        continue;

                    update_overlap_same_direction(s1, s2, a, d, off - f, r);
                }

                /* the other directions can only intersect at spot 'os' */
                for (direction rb = 0; rb < r; rb++)
                        update_overlap_different_direction(os, a, rb);
            }
        }
}