static MODLIST _FAR *addlib(MODLIST *root, AMX *amx, const TCHAR *name) { MODLIST _FAR *item; const TCHAR *ptr = skippath(name); assert(findlib(root, amx, name) == NULL); /* should not already be there */ if ((item = malloc(sizeof(MODLIST))) == NULL) goto error; memset(item, 0, sizeof(MODLIST)); assert(ptr != NULL); if ((item->name = malloc((_tcslen(ptr) + 1) * sizeof(TCHAR))) == NULL) goto error; _tcscpy(item->name, ptr); #if defined HAVE_DYNCALL_H item->inst=dlLoadLibrary(name); #else item->inst=(void*)LoadLibrary(name); #if !(defined __WIN32__ || defined _WIN32 || defined WIN32) if ((unsigned long)item->inst<=32) item->inst=NULL; #endif #endif if (item->inst==NULL) goto error; item->amx = amx; item->next = root->next; root->next = item; return item; error: if (item != NULL) { if (item->name != NULL) free(item->name); if (item->inst != 0) { #if defined HAVE_DYNCALL_H dlFreeLibrary(item->inst); #else FreeLibrary((HINSTANCE)item->inst); #endif } /* if */ free(item); } /* if */ return NULL; }
/* This function adds the semeai related move reasons, using the information * stored in the dragon2 array. * * If the semeai had an uncertain result, and there is a owl move with * certain result doing the same, we don't trust the semeai move. */ void semeai_move_reasons(int color) { int other = OTHER_COLOR(color); int d; int liberties; int libs[MAXLIBS]; int r; int resulta, resultb, semeai_move, s_result_certain; for (d = 0; d < number_of_dragons; d++) if (dragon2[d].semeais && DRAGON(d).status == CRITICAL) { if (DRAGON(d).color == color && dragon2[d].semeai_defense_point && (dragon2[d].owl_defense_point == NO_MOVE || dragon2[d].semeai_defense_certain >= dragon2[d].owl_defense_certain)) { /* My dragon can be defended. */ add_semeai_move(dragon2[d].semeai_defense_point, dragon2[d].origin); DEBUG(DEBUG_SEMEAI, "Adding semeai defense move for %1m at %1m\n", DRAGON(d).origin, dragon2[d].semeai_defense_point); if (neighbor_of_dragon(dragon2[d].semeai_defense_point, dragon2[d].semeai_defense_target) && !neighbor_of_dragon(dragon2[d].semeai_defense_point, dragon2[d].origin) && !is_self_atari(dragon2[d].semeai_defense_point, color)) { /* If this is a move to fill the non-common liberties of the * target, and is not a ko or snap-back, then we try all * non-common liberties of the target and add all winning * moves to the move list. */ liberties = findlib(dragon2[d].semeai_defense_target, MAXLIBS, libs); for (r = 0; r < liberties; r++) { if (!neighbor_of_dragon(libs[r], dragon2[d].origin) && !is_self_atari(libs[r], color) && libs[r] != dragon2[d].semeai_defense_point) { owl_analyze_semeai_after_move(libs[r], color, dragon2[d].semeai_defense_target, dragon2[d].origin, &resulta, &resultb, &semeai_move, 1, &s_result_certain, 0); if (resulta == 0 && resultb == 0) { add_semeai_move(libs[r], dragon2[d].origin); DEBUG(DEBUG_SEMEAI, "Adding semeai defense move for %1m at %1m\n", DRAGON(d).origin, libs[r]); } } } } } else if (DRAGON(d).color == other && dragon2[d].semeai_attack_point && (dragon2[d].owl_attack_point == NO_MOVE || dragon2[d].owl_defense_point == NO_MOVE || dragon2[d].semeai_attack_certain >= dragon2[d].owl_attack_certain)) { /* Your dragon can be attacked. */ add_semeai_move(dragon2[d].semeai_attack_point, dragon2[d].origin); DEBUG(DEBUG_SEMEAI, "Adding semeai attack move for %1m at %1m\n", DRAGON(d).origin, dragon2[d].semeai_attack_point); if (neighbor_of_dragon(dragon2[d].semeai_attack_point, dragon2[d].origin) && !neighbor_of_dragon(dragon2[d].semeai_attack_point, dragon2[d].semeai_attack_target) && !is_self_atari(dragon2[d].semeai_attack_point, color)) { liberties = findlib(dragon2[d].origin, MAXLIBS, libs); for (r = 0; r < liberties; r++) { if (!neighbor_of_dragon(libs[r], dragon2[d].semeai_attack_target) && !is_self_atari(libs[r], color) && libs[r] != dragon2[d].semeai_attack_point) { owl_analyze_semeai_after_move(libs[r], color, dragon2[d].origin, dragon2[d].semeai_attack_target, &resulta, &resultb, &semeai_move, 1, &s_result_certain, 0); if (resulta == 0 && resultb == 0) { add_semeai_move(libs[r], dragon2[d].origin); DEBUG(DEBUG_SEMEAI, "Adding semeai attack move for %1m at %1m\n", DRAGON(d).origin, libs[r]); } } } } } } }
/* Find moves turning supposed territory into seki. This is not * detected above since it either involves an ALIVE dragon adjacent to * a CRITICAL dragon, or an ALIVE dragon whose eyespace can be invaded * and turned into a seki. * * Currently we only search for tactically critical strings with * dragon status dead, which are neighbors of only one opponent * dragon, which is alive. Through semeai analysis we then determine * whether such a string can in fact live in seki. Relevant testcases * include gunnar:42 and gifu03:2. */ static void find_moves_to_make_seki() { int str; int defend_move; int resulta, resultb; for (str = BOARDMIN; str < BOARDMAX; str++) { if (IS_STONE(board[str]) && is_worm_origin(str, str) && attack_and_defend(str, NULL, NULL, NULL, &defend_move) && dragon[str].status == DEAD && DRAGON2(str).hostile_neighbors == 1) { int k; int color = board[str]; int opponent = NO_MOVE; int certain; struct eyevalue reduced_genus; for (k = 0; k < DRAGON2(str).neighbors; k++) { opponent = dragon2[DRAGON2(str).adjacent[k]].origin; if (board[opponent] != color) break; } ASSERT1(opponent != NO_MOVE, opponent); if (dragon[opponent].status != ALIVE) continue; /* FIXME: These heuristics are used for optimization. We don't * want to call expensive semeai code if the opponent * dragon has more than one eye elsewhere. However, the * heuristics might still need improvement. */ compute_dragon_genus(opponent, &reduced_genus, str); if (DRAGON2(opponent).moyo_size > 10 || min_eyes(&reduced_genus) > 1) continue; owl_analyze_semeai_after_move(defend_move, color, opponent, str, &resulta, &resultb, NULL, 1, &certain, 0); /* Do not trust uncertain results. In fact it should only take a * few nodes to determine the semeai result, if it is a proper * potential seki position. */ if (resultb != WIN && certain) { int d = dragon[str].id; DEBUG(DEBUG_SEMEAI, "Move to make seki at %1m (%1m vs %1m)\n", defend_move, str, opponent); dragon2[d].semeais++; update_status(str, CRITICAL, CRITICAL); dragon2[d].semeai_defense_point = defend_move; dragon2[d].semeai_defense_certain = certain; dragon2[d].semeai_defense_target = opponent; /* We need to determine a proper attack move (the one that * prevents seki). Currently we try the defense move first, * and if it doesn't work -- all liberties of the string. */ owl_analyze_semeai_after_move(defend_move, OTHER_COLOR(color), str, opponent, &resulta, NULL, NULL, 1, NULL, 0); if (resulta != WIN) dragon2[d].semeai_attack_point = defend_move; else { int k; int libs[MAXLIBS]; int liberties = findlib(str, MAXLIBS, libs); for (k = 0; k < liberties; k++) { owl_analyze_semeai_after_move(libs[k], OTHER_COLOR(color), str, opponent, &resulta, NULL, NULL, 1, NULL, 0); if (resulta != WIN) { dragon2[d].semeai_attack_point = libs[k]; break; } } /* FIXME: What should we do if none of the tried attacks worked? */ if (k == liberties) dragon2[d].semeai_attack_point = defend_move; } DEBUG(DEBUG_SEMEAI, "Move to prevent seki at %1m (%1m vs %1m)\n", dragon2[d].semeai_attack_point, opponent, str); dragon2[d].semeai_attack_certain = certain; dragon2[d].semeai_attack_target = opponent; } } } }
/* libcall(const libname[], const funcname[], const typestring[], ...) * * Loads the DLL or shared library if not yet loaded (the name comparison is * case sensitive). * * typestring format: * Whitespace is permitted between the types, but not inside the type * specification. The string "ii[4]&u16s" is equivalent to "i i[4] &u16 s", * but the latter is easier on the eye. * * types: * i = signed integer, 16-bit in Windows 3.x, else 32-bit in Win32 and Linux * u = unsigned integer, 16-bit in Windows 3.x, else 32-bit in Win32 and Linux * f = IEEE floating point, 32-bit * p = packed string * s = unpacked string * The difference between packed and unpacked strings is only relevant when * the parameter is passed by reference (see below). * * pass-by-value and pass-by-reference: * By default, parameters are passed by value. To pass a parameter by * reference, prefix the type letter with an "&": * &i = signed integer passed by reference * i = signed integer passed by value * Same for '&u' versus 'u' and '&f' versus 'f'. * * Arrays are passed by "copy & copy-back". That is, libcall() allocates a * block of dynamic memory to copy the array into. On return from the foreign * function, libcall() copies the array back to the abstract machine. The * net effect is similar to pass by reference, but the foreign function does * not work in the AMX stack directly. During the copy and the copy-back * operations, libcall() may also transform the array elements, for example * between 16-bit and 32-bit elements. This is done because Pawn only * supports a single cell size, which may not fit the required integer size * of the foreign function. * * See "element ranges" for the syntax of passing an array. * * Strings may either be passed by copy, or by "copy & copy-back". When the * string is an output parameter (for the foreign function), the size of the * array that will hold the return string must be indicated between square * brackets behind the type letter (see "element ranges"). When the string * is "input only", this is not needed --libcall() will determine the length * of the input string itself. * * The tokens 'p' and 's' are equivalent, but 'p[10]' and 's[10]' are not * equivalent: the latter syntaxes determine whether the output from the * foreign function will be stored as a packed or an unpacked string. * * element sizes: * Add an integer behind the type letter; for example, 'i16' refers to a * 16-bit signed integer. Note that the value behind the type letter must * be either 8, 16 or 32. * * You should only use element size specifiers on the 'i' and 'u' types. That * is, do not use these specifiers on 'f', 's' and 'p'. * * element ranges: * For passing arrays, the size of the array may be given behind the type * letter and optional element size. The token 'u[4]' indicates an array of * four unsigned integers, which are typically 32-bit. The token 'i16[8]' * is an array of 8 signed 16-bit integers. Arrays are always passed by * "copy & copy-back" * * When compiled as Unicode, this library converts all strings to Unicode * strings. * * The calling convention for the foreign functions is assumed: * - "__stdcall" for Win32, * - "far pascal" for Win16 * - and the GCC default for Unix/Linux (_cdecl) * * C++ name mangling of the called function is not handled (there is no standard * convention for name mangling, so there is no portable way to convert C++ * function names to mangled names). Win32 name mangling (used by default by * Microsoft compilers on functions declared as __stdcall) is also not handled. * * Returns the value of the called function. */ static cell AMX_NATIVE_CALL n_libcall(AMX *amx, const cell *params) { const TCHAR *libname, *funcname, *typestring; MODLIST *item; int paramidx, typeidx, idx; PARAM ps[MAXPARAMS]; cell *cptr,result; LIBFUNC LibFunc; amx_StrParam(amx, params[1], libname); item = findlib(&ModRoot, amx, libname); if (item == NULL) item = addlib(&ModRoot, amx, libname); if (item == NULL) { amx_RaiseError(amx, AMX_ERR_NATIVE); return 0; } /* if */ /* library is loaded, get the function */ amx_StrParam(amx, params[2], funcname); LibFunc=(LIBFUNC)SearchProcAddress(item->inst, funcname); if (LibFunc==NULL) { amx_RaiseError(amx, AMX_ERR_NATIVE); return 0; } /* if */ #if defined HAVE_DYNCALL_H /* (re-)initialize the dyncall library */ if (dcVM==NULL) { dcVM=dcNewCallVM(4096); dcMode(dcVM,DC_CALL_C_X86_WIN32_STD); } /* if */ dcReset(dcVM); #endif /* decode the parameters */ paramidx=typeidx=0; amx_StrParam(amx, params[3], typestring); while (paramidx < MAXPARAMS && typestring[typeidx]!=__T('\0')) { /* skip white space */ while (typestring[typeidx]!=__T('\0') && typestring[typeidx]<=__T(' ')) typeidx++; if (typestring[typeidx]==__T('\0')) break; /* save "pass-by-reference" token */ ps[paramidx].type=0; if (typestring[typeidx]==__T('&')) { ps[paramidx].type=BYREF; typeidx++; } /* if */ /* store type character */ ps[paramidx].type |= (unsigned char)typestring[typeidx]; typeidx++; /* set default size, then check for an explicit size */ #if defined __WIN32__ || defined _WIN32 || defined WIN32 ps[paramidx].size=32; #elif defined _Windows ps[paramidx].size=16; #endif if (_istdigit(typestring[typeidx])) { ps[paramidx].size=(unsigned char)_tcstol(&typestring[typeidx],NULL,10); while (_istdigit(typestring[typeidx])) typeidx++; } /* if */ /* set default range, then check for an explicit range */ ps[paramidx].range=1; if (typestring[typeidx]=='[') { ps[paramidx].range=_tcstol(&typestring[typeidx+1],NULL,10); while (typestring[typeidx]!=']' && typestring[typeidx]!='\0') typeidx++; ps[paramidx].type |= BYREF; /* arrays are always passed by reference */ typeidx++; /* skip closing ']' too */ } /* if */ /* get pointer to parameter */ cptr=amx_Address(amx,params[paramidx+4]); switch (ps[paramidx].type) { case 'i': /* signed integer */ case 'u': /* unsigned integer */ case 'f': /* floating point */ assert(ps[paramidx].range==1); ps[paramidx].v.val=(int)*cptr; break; case 'i' | BYREF: case 'u' | BYREF: case 'f' | BYREF: ps[paramidx].v.ptr=cptr; if (ps[paramidx].range>1) { /* convert array and pass by address */ ps[paramidx].v.ptr = fillarray(amx, &ps[paramidx], cptr); } /* if */ break; case 'p': case 's': case 'p' | BYREF: case 's' | BYREF: if (ps[paramidx].type=='s' || ps[paramidx].type=='p') { int len; /* get length of input string */ amx_StrLen(cptr,&len); len++; /* include '\0' */ /* check max. size */ if (len<ps[paramidx].range) len=ps[paramidx].range; ps[paramidx].range=len; } /* if */ ps[paramidx].v.ptr=malloc(ps[paramidx].range*sizeof(TCHAR)); if (ps[paramidx].v.ptr==NULL) return amx_RaiseError(amx, AMX_ERR_NATIVE); amx_GetString((char *)ps[paramidx].v.ptr,cptr,sizeof(TCHAR)>1,UNLIMITED); break; default: /* invalid parameter type */ return amx_RaiseError(amx, AMX_ERR_NATIVE); } /* switch */ paramidx++; } /* while */ if ((params[0]/sizeof(cell)) - 3 != (size_t)paramidx) return amx_RaiseError(amx, AMX_ERR_NATIVE); /* format string does not match number of parameters */ #if defined HAVE_DYNCALL_H for (idx = 0; idx < paramidx; idx++) { if ((ps[idx].type=='i' || ps[idx].type=='u' || ps[idx].type=='f') && ps[idx].range==1) { switch (ps[idx].size) { case 8: dcArgChar(dcVM,(unsigned char)(ps[idx].v.val & 0xff)); break; case 16: dcArgShort(dcVM,(unsigned short)(ps[idx].v.val & 0xffff)); break; default: dcArgLong(dcVM,ps[idx].v.val); } /* switch */ } else { dcArgPointer(dcVM,ps[idx].v.ptr); } /* if */ } /* for */ result=(cell)dcCallPointer(dcVM,(void*)LibFunc); #else /* HAVE_DYNCALL_H */ /* push the parameters to the stack (left-to-right in 16-bit; right-to-left * in 32-bit) */ #if defined __WIN32__ || defined _WIN32 || defined WIN32 for (idx=paramidx-1; idx>=0; idx--) { #else for (idx=0; idx<paramidx; idx++) { #endif if ((ps[idx].type=='i' || ps[idx].type=='u' || ps[idx].type=='f') && ps[idx].range==1) { switch (ps[idx].size) { case 8: push((unsigned char)(ps[idx].v.val & 0xff)); break; case 16: push((unsigned short)(ps[idx].v.val & 0xffff)); break; default: push(ps[idx].v.val); } /* switch */ } else { push(ps[idx].v.ptr); } /* if */ } /* for */ /* call the function; all parameters are already pushed to the stack (the * function should remove the parameters from the stack) */ result=LibFunc(); #endif /* HAVE_DYNCALL_H */ /* store return values and free allocated memory */ for (idx=0; idx<paramidx; idx++) { switch (ps[idx].type) { case 'p': case 's': free(ps[idx].v.ptr); break; case 'p' | BYREF: case 's' | BYREF: cptr=amx_Address(amx,params[idx+4]); amx_SetString(cptr,(char *)ps[idx].v.ptr,ps[idx].type==('p'|BYREF),sizeof(TCHAR)>1,UNLIMITED); free(ps[idx].v.ptr); break; case 'i': case 'u': case 'f': assert(ps[idx].range==1); break; case 'i' | BYREF: case 'u' | BYREF: case 'f' | BYREF: cptr=amx_Address(amx,params[idx+4]); if (ps[idx].range==1) { /* modify directly in the AMX (no memory block was allocated */ switch (ps[idx].size) { case 8: *cptr= (ps[idx].type==('i' | BYREF)) ? (long)((signed char)*cptr) : (*cptr & 0xff); break; case 16: *cptr= (ps[idx].type==('i' | BYREF)) ? (long)((short)*cptr) : (*cptr & 0xffff); break; } /* switch */ } else { int i; for (i=0; i<ps[idx].range; i++) { switch (ps[idx].size) { case 8: *cptr= (ps[idx].type==('i' | BYREF)) ? ((signed char*)ps[idx].v.ptr)[i] : ((unsigned char*)ps[idx].v.ptr)[i]; break; case 16: *cptr= (ps[idx].type==('i' | BYREF)) ? ((short*)ps[idx].v.ptr)[i] : ((unsigned short*)ps[idx].v.ptr)[i]; break; default: *cptr= (ps[idx].type==('i' | BYREF)) ? ((long*)ps[idx].v.ptr)[i] : ((unsigned long*)ps[idx].v.ptr)[i]; } /* switch */ } /* for */ free((char *)ps[idx].v.ptr); } /* if */ break; default: assert(0); } /* switch */ } /* for */ return result; } /* bool: libfree(const libname[]="") * When the name is an empty string, this function frees all libraries (for this * abstract machine). The name comparison is case sensitive. * Returns true if one or more libraries were freed. */ static cell AMX_NATIVE_CALL n_libfree(AMX *amx, const cell *params) { const TCHAR *libname; amx_StrParam(amx,params[1],libname); return freelib(&ModRoot,amx,libname) > 0; } #else /* HAVE_DYNCALL_H || WIN32_FFI */ static cell AMX_NATIVE_CALL n_libcall(AMX *amx, const cell *params) { (void)amx; (void)params; return 0; }
/* Find moves turning supposed territory into seki. This is not * detected above since it either involves an ALIVE dragon adjacent to * a CRITICAL dragon, or an ALIVE dragon whose eyespace can be invaded * and turned into a seki. * * Currently we only search for tactically critical strings with * dragon status dead, which are neighbors of only one opponent * dragon, which is alive. Through semeai analysis we then determine * whether such a string can in fact live in seki. Relevant testcases * include gunnar:42 and gifu03:2. */ static void find_moves_to_make_seki() { int str; int defend_move; int resulta, resultb; for (str = BOARDMIN; str < BOARDMAX; str++) { if (IS_STONE(board[str]) && is_worm_origin(str, str) && attack_and_defend(str, NULL, NULL, NULL, &defend_move) && dragon[str].status == DEAD && DRAGON2(str).hostile_neighbors == 1) { int k; int color = board[str]; int opponent = NO_MOVE; int certain; struct eyevalue reduced_genus; for (k = 0; k < DRAGON2(str).neighbors; k++) { opponent = dragon2[DRAGON2(str).adjacent[k]].origin; if (board[opponent] != color) break; } ASSERT1(opponent != NO_MOVE, opponent); if (dragon[opponent].status != ALIVE) continue; /* FIXME: These heuristics are used for optimization. We don't * want to call expensive semeai code if the opponent * dragon has more than one eye elsewhere. However, the * heuristics might still need improvement. */ compute_dragon_genus(opponent, &reduced_genus, str); if (min_eyes(&reduced_genus) > 1 || DRAGON2(opponent).moyo_size > 10 || DRAGON2(opponent).moyo_territorial_value > 2.999 || DRAGON2(opponent).escape_route > 0 || DRAGON2(str).escape_route > 0) continue; owl_analyze_semeai_after_move(defend_move, color, opponent, str, &resulta, &resultb, NULL, 1, &certain, 0); if (resultb == WIN) { owl_analyze_semeai(str, opponent, &resultb, &resulta, &defend_move, 1, &certain); resulta = REVERSE_RESULT(resulta); resultb = REVERSE_RESULT(resultb); } /* Do not trust uncertain results. In fact it should only take a * few nodes to determine the semeai result, if it is a proper * potential seki position. */ if (resultb != WIN && certain) { int d = dragon[str].id; DEBUG(DEBUG_SEMEAI, "Move to make seki at %1m (%1m vs %1m)\n", defend_move, str, opponent); dragon2[d].semeais++; update_status(str, CRITICAL, CRITICAL); dragon2[d].semeai_defense_code = REVERSE_RESULT(resultb); dragon2[d].semeai_defense_point = defend_move; dragon2[d].semeai_defense_certain = certain; gg_assert(board[opponent] == OTHER_COLOR(board[dragon2[d].origin])); dragon2[d].semeai_defense_target = opponent; /* We need to determine a proper attack move (the one that * prevents seki). Currently we try the defense move first, * and if it doesn't work -- all liberties of the string. */ owl_analyze_semeai_after_move(defend_move, OTHER_COLOR(color), str, opponent, &resulta, NULL, NULL, 1, NULL, 0); if (resulta != WIN) { dragon2[d].semeai_attack_code = REVERSE_RESULT(resulta); dragon2[d].semeai_attack_point = defend_move; } else { int k; int libs[MAXLIBS]; int liberties = findlib(str, MAXLIBS, libs); for (k = 0; k < liberties; k++) { owl_analyze_semeai_after_move(libs[k], OTHER_COLOR(color), str, opponent, &resulta, NULL, NULL, 1, NULL, 0); if (resulta != WIN) { dragon2[d].semeai_attack_code = REVERSE_RESULT(resulta); dragon2[d].semeai_attack_point = libs[k]; break; } } if (k == liberties) { DEBUG(DEBUG_SEMEAI, "No move to attack in semeai (%1m vs %1m), seki assumed.\n", str, opponent); dragon2[d].semeai_attack_code = 0; dragon2[d].semeai_attack_point = NO_MOVE; update_status(str, ALIVE, ALIVE_IN_SEKI); } } DEBUG(DEBUG_SEMEAI, "Move to prevent seki at %1m (%1m vs %1m)\n", dragon2[d].semeai_attack_point, opponent, str); dragon2[d].semeai_attack_certain = certain; dragon2[d].semeai_attack_target = opponent; } } } /* Now look for dead strings inside a single eyespace of a living dragon. * * FIXME: Clearly this loop should share most of its code with the * one above. It would also be good to reimplement so that * moves invading a previously empty single eyespace to make * seki can be found. */ for (str = BOARDMIN; str < BOARDMAX; str++) { if (IS_STONE(board[str]) && is_worm_origin(str, str) && !find_defense(str, NULL) && dragon[str].status == DEAD && DRAGON2(str).hostile_neighbors == 1) { int k; int color = board[str]; int opponent = NO_MOVE; int certain; struct eyevalue reduced_genus; for (k = 0; k < DRAGON2(str).neighbors; k++) { opponent = dragon2[DRAGON2(str).adjacent[k]].origin; if (board[opponent] != color) break; } ASSERT1(opponent != NO_MOVE, opponent); if (dragon[opponent].status != ALIVE) continue; /* FIXME: These heuristics are used for optimization. We don't * want to call expensive semeai code if the opponent * dragon has more than one eye elsewhere. However, the * heuristics might still need improvement. */ compute_dragon_genus(opponent, &reduced_genus, str); if (DRAGON2(opponent).moyo_size > 10 || min_eyes(&reduced_genus) > 1) continue; owl_analyze_semeai(str, opponent, &resulta, &resultb, &defend_move, 1, &certain); /* Do not trust uncertain results. In fact it should only take a * few nodes to determine the semeai result, if it is a proper * potential seki position. */ if (resulta != 0 && certain) { int d = dragon[str].id; DEBUG(DEBUG_SEMEAI, "Move to make seki at %1m (%1m vs %1m)\n", defend_move, str, opponent); dragon2[d].semeais++; update_status(str, CRITICAL, CRITICAL); dragon2[d].semeai_defense_code = resulta; dragon2[d].semeai_defense_point = defend_move; dragon2[d].semeai_defense_certain = certain; gg_assert(board[opponent] == OTHER_COLOR(board[dragon2[d].origin])); dragon2[d].semeai_defense_target = opponent; /* We need to determine a proper attack move (the one that * prevents seki). Currently we try the defense move first, * and if it doesn't work -- all liberties of the string. */ owl_analyze_semeai_after_move(defend_move, OTHER_COLOR(color), str, opponent, &resulta, NULL, NULL, 1, NULL, 0); if (resulta != WIN) { dragon2[d].semeai_attack_code = REVERSE_RESULT(resulta); dragon2[d].semeai_attack_point = defend_move; } else { int k; int libs[MAXLIBS]; int liberties = findlib(str, MAXLIBS, libs); for (k = 0; k < liberties; k++) { owl_analyze_semeai_after_move(libs[k], OTHER_COLOR(color), str, opponent, &resulta, NULL, NULL, 1, NULL, 0); if (resulta != WIN) { dragon2[d].semeai_attack_code = REVERSE_RESULT(resulta); dragon2[d].semeai_attack_point = libs[k]; break; } } if (k == liberties) { DEBUG(DEBUG_SEMEAI, "No move to attack in semeai (%1m vs %1m), seki assumed.\n", str, opponent); dragon2[d].semeai_attack_code = 0; dragon2[d].semeai_attack_point = NO_MOVE; update_status(str, ALIVE, ALIVE_IN_SEKI); } } DEBUG(DEBUG_SEMEAI, "Move to prevent seki at %1m (%1m vs %1m)\n", dragon2[d].semeai_attack_point, opponent, str); dragon2[d].semeai_attack_certain = certain; dragon2[d].semeai_attack_target = opponent; } } } }
/* This function adds the semeai related move reasons, using the information * stored in the dragon2 array. * * If the semeai had an uncertain result, and there is a owl move with * certain result doing the same, we don't trust the semeai move. */ void semeai_move_reasons(int color) { int other = OTHER_COLOR(color); int d; int liberties; int libs[MAXLIBS]; int r; for (d = 0; d < number_of_dragons; d++) if (dragon2[d].semeais && DRAGON(d).status == CRITICAL) { if (DRAGON(d).color == color && dragon2[d].semeai_defense_point && (dragon2[d].owl_defense_point == NO_MOVE || dragon2[d].semeai_defense_certain >= dragon2[d].owl_defense_certain)) { /* My dragon can be defended. */ add_semeai_move(dragon2[d].semeai_defense_point, dragon2[d].origin); DEBUG(DEBUG_SEMEAI, "Adding semeai defense move for %1m at %1m\n", DRAGON(d).origin, dragon2[d].semeai_defense_point); if (neighbor_of_dragon(dragon2[d].semeai_defense_point, dragon2[d].semeai_defense_target) && !neighbor_of_dragon(dragon2[d].semeai_defense_point, dragon2[d].origin) && !is_self_atari(dragon2[d].semeai_defense_point, color)) { /* If this is a move to fill the non-common liberties of the * target, and is not a ko or snap-back, then we mark all * non-common liberties of the target as potential semeai moves. */ liberties = findlib(dragon2[d].semeai_defense_target, MAXLIBS, libs); for (r = 0; r < liberties; r++) { if (!neighbor_of_dragon(libs[r], dragon2[d].origin) && !is_self_atari(libs[r], color) && libs[r] != dragon2[d].semeai_defense_point) add_potential_semeai_defense(libs[r], dragon2[d].origin, dragon2[d].semeai_defense_target); } } } else if (DRAGON(d).color == other && dragon2[d].semeai_attack_point && (dragon2[d].owl_attack_point == NO_MOVE || dragon2[d].owl_defense_point == NO_MOVE || dragon2[d].semeai_attack_certain >= dragon2[d].owl_attack_certain)) { /* Your dragon can be attacked. */ add_semeai_move(dragon2[d].semeai_attack_point, dragon2[d].origin); DEBUG(DEBUG_SEMEAI, "Adding semeai attack move for %1m at %1m\n", DRAGON(d).origin, dragon2[d].semeai_attack_point); if (neighbor_of_dragon(dragon2[d].semeai_attack_point, dragon2[d].origin) && !neighbor_of_dragon(dragon2[d].semeai_attack_point, dragon2[d].semeai_attack_target) && !is_self_atari(dragon2[d].semeai_attack_point, color)) { liberties = findlib(dragon2[d].origin, MAXLIBS, libs); for (r = 0; r < liberties; r++) { if (!neighbor_of_dragon(libs[r], dragon2[d].semeai_attack_target) && !is_self_atari(libs[r], color) && libs[r] != dragon2[d].semeai_attack_point) add_potential_semeai_attack(libs[r], dragon2[d].origin, dragon2[d].semeai_attack_target); } } } } }
/* Computes the active area for the current board position and the * read result that has just been stored in *entry. */ static void compute_active_breakin_area(struct persistent_cache_entry *entry, const char breakin_shadow[BOARDMAX], int dummy) { int pos; int k, r; signed char active[BOARDMAX]; int other = OTHER_COLOR(board[entry->apos]); UNUSED(dummy); /* We let the active area be * the string to connect + * the breakin shadow (which contains the goal) + * distance two expansion through empty intersections and own stones + * adjacent opponent strings + * liberties and neighbors of adjacent opponent strings with less than * five liberties + * liberties and neighbors of low liberty neighbors of adjacent opponent * strings with less than five liberties. */ for (pos = BOARDMIN; pos < BOARDMAX; pos++) active[pos] = breakin_shadow[pos]; signed_mark_string(entry->apos, active, 1); /* To be safe, also add the successful move. */ if (entry->result != 0 && entry->move != 0) active[entry->move] = 1; /* Distance two expansion through empty intersections and own stones. */ for (k = 1; k < 3; k++) { for (pos = BOARDMIN; pos < BOARDMAX; pos++) { if (!ON_BOARD(pos) || board[pos] == other || active[pos] != 0) continue; if ((ON_BOARD(SOUTH(pos)) && active[SOUTH(pos)] == k) || (ON_BOARD(WEST(pos)) && active[WEST(pos)] == k) || (ON_BOARD(NORTH(pos)) && active[NORTH(pos)] == k) || (ON_BOARD(EAST(pos)) && active[EAST(pos)] == k)) { if (board[pos] == EMPTY) active[pos] = k + 1; else signed_mark_string(pos, active, (signed char) (k + 1)); } } } /* Adjacent opponent strings. */ for (pos = BOARDMIN; pos < BOARDMAX; pos++) { if (board[pos] != other || active[pos] != 0) continue; for (r = 0; r < 4; r++) { int pos2 = pos + delta[r]; if (ON_BOARD(pos2) && board[pos2] != other && active[pos2] && active[pos2] <= 2) { signed_mark_string(pos, active, 1); break; } } } /* Liberties of adjacent opponent strings with less than four liberties + * liberties of low liberty neighbors of adjacent opponent strings * with less than five liberties. */ for (pos = BOARDMIN; pos < BOARDMAX; pos++) { if (board[pos] == other && active[pos] > 0 && countlib(pos) < 4) { int libs[4]; int liberties = findlib(pos, 3, libs); int adjs[MAXCHAIN]; int adj; for (r = 0; r < liberties; r++) active[libs[r]] = 1; /* Also add liberties of neighbor strings if these are three * or less. */ adj = chainlinks(pos, adjs); for (r = 0; r < adj; r++) { signed_mark_string(adjs[r], active, -1); if (countlib(adjs[r]) <= 3) { int s; int adjs2[MAXCHAIN]; int adj2; liberties = findlib(adjs[r], 3, libs); for (s = 0; s < liberties; s++) active[libs[s]] = 1; adj2 = chainlinks(pos, adjs2); for (s = 0; s < adj2; s++) signed_mark_string(adjs2[s], active, -1); } } } } for (pos = BOARDMIN; pos < BOARDMAX; pos++) { char value = board[pos]; if (!ON_BOARD(pos)) continue; if (!active[pos]) value = GRAY; else if (IS_STONE(board[pos]) && countlib(pos) > 3 && active[pos] > 0) value |= HIGH_LIBERTY_BIT2; entry->board[pos] = value; } }
/* Generate a move to definitely settle the position after the game * has been finished. The purpose of this is to robustly determine * life and death status and to distinguish between life in seki and * life with territory. * * The strategy is basically to turn all own living stones into * invincible ones and remove from the board all dead opponent stones. * Stones which cannot be removed, nor turned invincible, are alive in * seki. * * If do_capture_dead_stones is 0, opponent stones are not necessarily * removed from the board. This happens if they become unconditionally * dead anyway. * * Moves are generated in the following order of priority: * 0. Play edge liberties in certain positions. This is not really * necessary, but often it can simplify the tactical and strategical * reading substantially, making subsequent moves faster to generate. * 1. Capture an opponent string in atari and adjacent to own * invincible string. Moves leading to ko or snapback are excluded. * 2. Extend an invincible string to a liberty of an opponent string. * 3. Connect a non-invincible string to an invincible string. * 4. Extend an invincible string towards an opponent string or an own * non-invincible string. * 5. Split a big eyespace of an alive own dragon without invincible * strings into smaller pieces. * 6. Play a liberty of a dead opponent dragon. * * Steps 2--4 are interleaved to try to optimize the efficiency of the * moves. In step 5 too, efforts are made to play efficient moves. By * efficient we here mean moves which are effectively settling the * position and simplify the tactical and strategical reading for * subsequent moves. * * Steps 1--4 are guaranteed to be completely safe. Step 0 and 5 * should also be risk-free. Step 6 on the other hand definitely * isn't. Consider for example this position: * * .XXXXX. * XXOOOXX * XOO.OOX * XOXXXOX * XO.XXOX * ------- * * In order to remove the O stones, it is necessary to play on one of * the inner liberties, but one of them lets O live. Thus we have to * check carefully for blunders at this step. * * Update: Step 0 is only safe against blunders if care is taken not * to get into a shortage of liberties. * Step 5 also has some risks. Consider this position: * * |XXXXX. * |OOOOXX * |..O.OX * |OX*OOX * +------ * * Playing at * allows X to make seki. * * IMPORTANT RESTRICTION: * Before calling this function it is mandatory to call genmove() or * genmove_conservative(). For this function to be meaningful, the * genmove() call should return pass. */ int aftermath_genmove(int *aftermath_move, int color, int under_control[BOARDMAX], int do_capture_dead_stones) { int k; int other = OTHER_COLOR(color); int distance[BOARDMAX]; int score[BOARDMAX]; float owl_hotspot[BOARDMAX]; float reading_hotspot[BOARDMAX]; int dragons[BOARDMAX]; int something_found; int closest_opponent = NO_MOVE; int closest_own = NO_MOVE; int d; int move = NO_MOVE; int pos = NO_MOVE; int best_score; int best_scoring_move; owl_hotspots(owl_hotspot); reading_hotspots(reading_hotspot); /* As a preparation we compute a distance map to the invincible strings. */ for (pos = BOARDMIN; pos < BOARDMAX; pos++) { if (!ON_BOARD(pos)) continue; else if (board[pos] == color && worm[pos].invincible) distance[pos] = 0; else if (!do_capture_dead_stones && ((board[pos] == other && worm[pos].unconditional_status == DEAD) || (board[pos] == color && worm[pos].unconditional_status == ALIVE))) distance[pos] = 0; else distance[pos] = -1; } d = 0; do { something_found = 0; for (pos = BOARDMIN; pos < BOARDMAX; pos++) { if (ON_BOARD(pos) && distance[pos] == -1) { for (k = 0; k < 4; k++) { int pos2 = pos + delta[k]; if (!ON_BOARD(pos2)) continue; if ((d == 0 || board[pos2] == EMPTY) && distance[pos2] == d) { if (d > 0 && board[pos] == other) { distance[pos] = d + 1; if (closest_opponent == NO_MOVE) closest_opponent = pos; } else if (d > 0 && board[pos] == color) { distance[pos] = d + 1; if (closest_own == NO_MOVE) closest_own = pos; } else if (board[pos] == EMPTY) { distance[pos] = d + 1; something_found = 1; } break; } } } } d++; } while (something_found); if (under_control) { for (pos = BOARDMIN; pos < BOARDMAX; pos++) { if (!ON_BOARD(pos)) continue; else if (distance[pos] == -1) under_control[pos] = 0; else under_control[pos] = 1; } } if (debug & DEBUG_AFTERMATH) { int m, n; for (m = 0; m < board_size; m++) { for (n = 0; n < board_size; n++) { pos = POS(m, n); if (distance[pos] > 0) fprintf(stderr, "%2d", distance[pos]); else if (distance[pos] == 0) { if (board[pos] == WHITE) gprintf(" o"); else if (board[pos] == BLACK) gprintf(" x"); else gprintf(" ?"); } else { if (board[pos] == WHITE) gprintf(" O"); else if (board[pos] == BLACK) gprintf(" X"); else gprintf(" ."); } } gprintf("\n"); } gprintf("Closest opponent %1m", closest_opponent); if (closest_opponent != NO_MOVE) gprintf(", distance %d\n", distance[closest_opponent]); else gprintf("\n"); gprintf("Closest own %1m", closest_own); if (closest_own != NO_MOVE) gprintf(", distance %d\n", distance[closest_own]); else gprintf("\n"); } /* Case 0. This is a special measure to avoid a certain kind of * tactical reading inefficiency. * * Here we play on edge liberties in the configuration * * XO. * .*. * --- * * to stop X from "leaking" out along the edge. Sometimes this can * save huge amounts of tactical reading for later moves. */ best_scoring_move = NO_MOVE; best_score = 5; for (pos = BOARDMIN; pos < BOARDMAX; pos++) { int libs; if (board[pos] != EMPTY || distance[pos] == 0) continue; libs = approxlib(pos, color, 3, NULL); if (libs < 3) continue; if (is_self_atari(pos, other)) continue; for (k = 0; k < 4; k++) { int dir = delta[k]; int right = delta[(k+1)%4]; if (!ON_BOARD(pos - dir) && board[pos + dir] == color && board[pos + dir + right] == other && board[pos + dir - right] == other && (libs > countlib(pos + dir) || (libs > 4 && libs == countlib(pos + dir))) && (DRAGON2(pos + dir).safety == INVINCIBLE || DRAGON2(pos + dir).safety == STRONGLY_ALIVE)) { int this_score = 20 * (owl_hotspot[pos] + reading_hotspot[pos]); if (this_score > best_score) { best_score = this_score; best_scoring_move = pos; } } } } if (best_scoring_move != NO_MOVE && safe_move(best_scoring_move, color) == WIN) { *aftermath_move = best_scoring_move; DEBUG(DEBUG_AFTERMATH, "Closing edge at %1m\n", best_scoring_move); return 1; } /* Case 1. */ for (pos = BOARDMIN; pos < BOARDMAX; pos++) { int lib; if (board[pos] == other && worm[pos].unconditional_status != DEAD && countlib(pos) == 1 && ((ON_BOARD(SOUTH(pos)) && distance[SOUTH(pos)] == 0) || (ON_BOARD(WEST(pos)) && distance[WEST(pos)] == 0) || (ON_BOARD(NORTH(pos)) && distance[NORTH(pos)] == 0) || (ON_BOARD(EAST(pos)) && distance[EAST(pos)] == 0))) { findlib(pos, 1, &lib); /* Make sure we don't play into a ko or a (proper) snapback. */ if (countstones(pos) > 1 || !is_self_atari(lib, color)) { *aftermath_move = lib; return 1; } } } /* Cases 2--4. */ if (closest_opponent != NO_MOVE || closest_own != NO_MOVE) { if (closest_own == NO_MOVE) move = closest_opponent; else move = closest_own; /* if we're about to play at distance 1, try to optimize the move. */ if (distance[move] == 2) { char mx[BOARDMAX]; char mark = 0; memset(mx, 0, sizeof(mx)); best_score = 0; best_scoring_move = move; for (pos = BOARDMIN; pos < BOARDMAX; pos++) { int score = 0; int move_ok = 0; if (!ON_BOARD(pos) || distance[pos] != 1) continue; mark++; for (k = 0; k < 4; k++) { int pos2 = pos + delta[k]; if (!ON_BOARD(pos2)) continue; if (distance[pos2] < 1) score--; else if (board[pos2] == EMPTY) score++; else if (mx[pos2] == mark) score--; else { if (board[pos2] == color) { move_ok = 1; score += 7; if (countstones(pos2) > 2) score++; if (countstones(pos2) > 4) score++; if (countlib(pos2) < 4) score++; if (countlib(pos2) < 3) score++; } else { int deltalib = (approxlib(pos, other, MAXLIBS, NULL) - countlib(pos2)); move_ok = 1; score++; if (deltalib >= 0) score++; if (deltalib > 0) score++; } mark_string(pos2, mx, mark); } } if (is_suicide(pos, other)) score -= 3; if (0) gprintf("Score %1m = %d\n", pos, score); if (move_ok && score > best_score) { best_score = score; best_scoring_move = pos; } } move = best_scoring_move; } while (distance[move] > 1) { for (k = 0; k < 4; k++) { int pos2 = move + delta[k]; if (ON_BOARD(pos2) && board[pos2] == EMPTY && distance[pos2] == distance[move] - 1) { move = pos2; break; } } } *aftermath_move = move; return 1; } /* Case 5. * If we reach here, either all strings of a dragon are invincible * or no string is. Next we try to make alive dragons invincible by * splitting big eyes into smaller ones. Our strategy is to search * for an empty vertex with as many eye points as possible adjacent * and with at least one alive but not invincible stone adjacent or * diagonal. */ for (pos = BOARDMIN; pos < BOARDMAX; pos++) { int eyespace_neighbors = 0; int own_neighbors = 0; int own_diagonals = 0; int opponent_dragons = 0; int own_worms = 0; int safety = UNKNOWN; int bonus = 0; int mx[BOARDMAX]; score[pos] = 0; if (board[pos] != EMPTY || distance[pos] != -1) continue; memset(mx, 0, sizeof(mx)); for (k = 0; k < 8; k++) { int pos2 = pos + delta[k]; if (!ON_BOARD(pos2)) continue; if (board[pos2] == EMPTY) { if (k < 4) eyespace_neighbors++; continue; } if (board[pos2] == other) { int origin = dragon[pos2].origin; if (k < 4) { if (dragon[pos2].status == ALIVE) { safety = DEAD; break; } else if (!mx[origin]) { eyespace_neighbors++; opponent_dragons++; } } if (!mx[origin] && dragon[pos2].status == DEAD) { bonus++; if (k < 4 && countlib(pos2) <= 2 && countstones(pos2) >= 3) bonus++; if (k < 4 && countlib(pos2) == 1) bonus += 3; } mx[origin] = 1; } else if (board[pos2] == color) { dragons[pos] = pos2; if (safety == UNKNOWN && dragon[pos2].status == ALIVE) safety = ALIVE; if (DRAGON2(pos2).safety == INVINCIBLE) safety = INVINCIBLE; if (k < 4) { int apos = worm[pos2].origin; if (!mx[apos]) { own_worms++; if (countstones(apos) == 1) bonus += 2; if (countlib(apos) < 6 && approxlib(pos, color, 5, NULL) < countlib(apos)) bonus -= 5; mx[apos] = 1; } if (countlib(apos) <= 2) { int r; int important = 0; int safe_atari = 0; for (r = 0; r < 4; r++) { d = delta[r]; if (!ON_BOARD(apos+d)) continue; if (board[apos+d] == other && dragon[apos+d].status == DEAD) important = 1; else if (board[apos+d] == EMPTY && !is_self_atari(apos+d, other)) safe_atari = 1; } if (approxlib(pos, color, 3, NULL) > 2) { bonus++; if (important) { bonus += 2; if (safe_atari) bonus += 2; } } } own_neighbors++; } else own_diagonals++; } } if (safety == DEAD || safety == UNKNOWN || eyespace_neighbors == 0 || (own_neighbors + own_diagonals) == 0) continue; if (bonus < 0) bonus = 0; score[pos] = 4 * eyespace_neighbors + bonus; if (safety == INVINCIBLE) { score[pos] += own_neighbors; if (own_neighbors < 2) score[pos] += own_diagonals; if (own_worms > 1 && eyespace_neighbors >= 1) score[pos] += 10 + 5 * (own_worms - 2); } else if (eyespace_neighbors > 2) score[pos] += own_diagonals; /* Splitting bonus. */ if (opponent_dragons > 1) score[pos] += 10 * (opponent_dragons - 1); /* Hotspot bonus. */ { int owl_hotspot_bonus = (int) (20.0 * owl_hotspot[pos]); int reading_hotspot_bonus = (int) (20.0 * reading_hotspot[pos]); int hotspot_bonus = owl_hotspot_bonus + reading_hotspot_bonus; /* Don't allow the hotspot bonus to turn a positive score into * a non-positive one. */ if (score[pos] > 0 && score[pos] + hotspot_bonus <= 0) hotspot_bonus = 1 - score[pos]; score[pos] += hotspot_bonus; if (1 && (debug & DEBUG_AFTERMATH)) gprintf("Score %1M = %d (hotspot bonus %d + %d)\n", pos, score[pos], owl_hotspot_bonus, reading_hotspot_bonus); } /* Avoid taking ko. */ if (is_ko(pos, color, NULL)) score[pos] = (score[pos] + 1) / 2; } while (1) { int bb; best_score = 0; move = NO_MOVE; for (pos = BOARDMIN; pos < BOARDMAX; pos++) { if (ON_BOARD(pos) && score[pos] > best_score) { best_score = score[pos]; move = pos; } } if (move == NO_MOVE) break; bb = dragons[move]; if (is_illegal_ko_capture(move, color) || !safe_move(move, color) || (DRAGON2(bb).safety != INVINCIBLE && DRAGON2(bb).safety != STRONGLY_ALIVE && owl_does_defend(move, bb, NULL) != WIN) || (!confirm_safety(move, color, NULL, NULL))) { score[move] = 0; } else { /* If we're getting short of liberties, we must be more careful. * Check that no adjacent string or dragon gets more alive by * the move. */ int libs = approxlib(move, color, 5, NULL); int move_ok = 1; if (libs < 5) { for (k = 0; k < 4; k++) { if (board[move + delta[k]] == color && countlib(move + delta[k]) > libs) break; } if (k < 4) { if (trymove(move, color, "aftermath-B", move + delta[k])) { int adjs[MAXCHAIN]; int neighbors; int r; neighbors = chainlinks(move, adjs); for (r = 0; r < neighbors; r++) { if (worm[adjs[r]].attack_codes[0] != 0 && (find_defense(adjs[r], NULL) > worm[adjs[r]].defense_codes[0])) { DEBUG(DEBUG_AFTERMATH, "Blunder: %1m becomes tactically safer after %1m\n", adjs[r], move); move_ok = 0; } } popgo(); for (r = 0; r < neighbors && move_ok; r++) { if (dragon[adjs[r]].status == DEAD && !owl_does_attack(move, adjs[r], NULL)) { DEBUG(DEBUG_AFTERMATH, "Blunder: %1m becomes more alive after %1m\n", adjs[r], move); move_ok = 0; } } } } } if (!move_ok) score[move] = 0; else { *aftermath_move = move; DEBUG(DEBUG_AFTERMATH, "Splitting eyespace at %1m\n", move); return 1; } } } /* Case 6. * Finally we try to play on liberties of remaining DEAD opponent * dragons, carefully checking against mistakes. */ for (pos = BOARDMIN; pos < BOARDMAX; pos++) { int target; int cc = NO_MOVE; int self_atari_ok = 0; if (board[pos] != EMPTY || distance[pos] != -1) continue; target = NO_MOVE; for (k = 0; k < 4; k++) { int pos2 = pos + delta[k]; if (!ON_BOARD(pos2)) continue; if (board[pos2] == other && dragon[pos2].status != ALIVE && (do_capture_dead_stones || worm[pos2].unconditional_status != DEAD) && DRAGON2(pos2).safety != INESSENTIAL) { target = pos2; break; } } if (target == NO_MOVE) continue; /* At this point, (pos) is a move that potentially may capture * a dead opponent string at (target). */ if (!trymove(pos, color, "aftermath-A", target)) continue; /* It is frequently necessary to sacrifice own stones in order * to force the opponent's stones to be removed from the board, * e.g. by adding stones to fill up a nakade shape. However, we * should only play into a self atari if the sacrificed stones * are classified as INESSENTIAL. Thus it would be ok for O to * try a self atari in this position: * * |OOOO * |XXXO * |..XO * |OOXO * +---- * * but not in this one: * * |XXX.. * |OOXX. * |.OOXX * |XXOOX * |.O.OX * +----- */ self_atari_ok = 1; for (k = 0; k < 4; k++) { if (board[pos + delta[k]] == color && DRAGON2(pos + delta[k]).safety != INESSENTIAL) { self_atari_ok = 0; cc = pos + delta[k]; break; } } /* Copy the potential move to (move). */ move = pos; /* If the move is a self atari, but that isn't okay, try to * recursively find a backfilling move which later makes the * potential move possible. */ if (!self_atari_ok) { while (countlib(pos) == 1) { int lib; findlib(pos, 1, &lib); move = lib; if (!trymove(move, color, "aftermath-B", target)) break; } if (countlib(pos) == 1) move = NO_MOVE; } while (stackp > 0) popgo(); if (move == NO_MOVE) continue; /* Make sure that the potential move really isn't a self * atari. In the case of a move found after backfilling this * could happen (because the backfilling moves happened to * capture some stones). */ if (!self_atari_ok && is_self_atari(move, color)) continue; /* Consult the owl code to determine whether the considered move * really is effective. Blunders should be detected here. */ if (owl_does_attack(move, target, NULL) == WIN) { /* If we have an adjacent own dragon, which is not inessential, * verify that it remains safe. */ if (cc != NO_MOVE && !owl_does_defend(move, cc, NULL)) continue; /* If we don't allow self atari, also call confirm safety to * avoid setting up combination attacks. */ if (!self_atari_ok && !confirm_safety(move, color, NULL, NULL)) continue; *aftermath_move = move; DEBUG(DEBUG_AFTERMATH, "Filling opponent liberty at %1m\n", move); return 1; } } /* Case 7. * In very rare cases it turns out we need yet another pass. An * example is this position: * * |..... * |OOOO. * |XXXO. * |.OXO. * |O.XO. * +----- * * Here the X stones are found tactically dead and therefore the * corner O stones have been amalgamated with the surrounding * stones. Since the previous case only allows sacrificing * INESSENTIAL stones, it fails to take X off the board. * * The solution is to look for tactically attackable opponent stones * that still remain on the board but should be removed. */ for (pos = BOARDMIN; pos < BOARDMAX; pos++) { if (board[pos] == other && (worm[pos].unconditional_status == UNKNOWN || do_capture_dead_stones) && (DRAGON2(pos).safety == DEAD || DRAGON2(pos).safety == TACTICALLY_DEAD) && worm[pos].attack_codes[0] != 0 && !is_illegal_ko_capture(worm[pos].attack_points[0], color)) { *aftermath_move = worm[pos].attack_points[0]; DEBUG(DEBUG_AFTERMATH, "Tactically attack %1m at %1m\n", pos, *aftermath_move); return 1; } } /* No move found. */ return -1; }
/* Based on the entries in the owl cache and their tactical_nodes * field, compute where the relatively most expensive owl reading is * going on. */ void owl_hotspots(float values[BOARDMAX]) { int pos; int k, r; int libs[MAXLIBS]; int liberties; int sum_tactical_nodes = 0; /* Don't bother checking out of board. Set values[] to zero there too. */ for (pos = BOARDMIN; pos < BOARDMAX; pos++) values[pos] = 0.0; /* Compute the total number of tactical nodes for the cached entries. */ for (k = 0; k < owl_cache.current_size; k++) sum_tactical_nodes += owl_cache.table[k].score; if (sum_tactical_nodes <= 100) return; /* Loop over all entries and increase the value of vertices adjacent * to dragons involving expensive owl reading. */ for (k = 0; k < owl_cache.current_size; k++) { struct persistent_cache_entry *entry = &(owl_cache.table[k]); float contribution = entry->score / (float) sum_tactical_nodes; if (debug & DEBUG_PERSISTENT_CACHE) { gprintf("Owl hotspots: %d %1m %f\n", entry->routine, entry->apos, contribution); } switch (entry->routine) { case OWL_ATTACK: case OWL_THREATEN_ATTACK: case OWL_DEFEND: case OWL_THREATEN_DEFENSE: mark_dragon_hotspot_values(values, entry->apos, contribution, entry->board); break; case OWL_DOES_DEFEND: case OWL_DOES_ATTACK: case OWL_CONFIRM_SAFETY: mark_dragon_hotspot_values(values, entry->bpos, contribution, entry->board); break; case OWL_CONNECTION_DEFENDS: mark_dragon_hotspot_values(values, entry->bpos, contribution, entry->board); mark_dragon_hotspot_values(values, entry->cpos, contribution, entry->board); break; case OWL_SUBSTANTIAL: /* Only consider the liberties of (apos). */ liberties = findlib(entry->apos, MAXLIBS, libs); for (r = 0; r < liberties; r++) values[libs[r]] += contribution; break; default: gg_assert(0); /* Shouldn't happen. */ break; } } }
static void compute_active_owl_area(struct persistent_cache_entry *entry, const char goal[BOARDMAX], int goal_color) { int k, r; int pos; int other = OTHER_COLOR(goal_color); signed char active[BOARDMAX]; /* We let the active area be the goal + * distance four expansion through empty intersections and own stones + * adjacent opponent strings + * liberties and neighbors of adjacent opponent strings with less than * five liberties + * liberties and neighbors of low liberty neighbors of adjacent opponent * strings with less than five liberties. */ for (pos = BOARDMIN; pos < BOARDMAX; pos++) if (ON_BOARD(pos)) active[pos] = (goal[pos] != 0); /* Also add critical moves to the active area. */ if (ON_BOARD1(entry->move)) active[entry->move] = 1; if (ON_BOARD1(entry->move2)) active[entry->move2] = 1; /* Distance four expansion through empty intersections and own stones. */ for (k = 1; k < 5; k++) { for (pos = BOARDMIN; pos < BOARDMAX; pos++) { if (!ON_BOARD(pos) || board[pos] == other || active[pos] > 0) continue; if ((ON_BOARD(SOUTH(pos)) && active[SOUTH(pos)] == k) || (ON_BOARD(WEST(pos)) && active[WEST(pos)] == k) || (ON_BOARD(NORTH(pos)) && active[NORTH(pos)] == k) || (ON_BOARD(EAST(pos)) && active[EAST(pos)] == k)) { if (board[pos] == EMPTY) active[pos] = k + 1; else signed_mark_string(pos, active, (signed char) (k + 1)); } } } /* Adjacent opponent strings. */ for (pos = BOARDMIN; pos < BOARDMAX; pos++) { if (board[pos] != other || active[pos] != 0) continue; for (r = 0; r < 4; r++) { int pos2 = pos + delta[r]; if (ON_BOARD(pos2) && board[pos2] != other && active[pos2] != 0) { signed_mark_string(pos, active, 1); break; } } } /* Liberties of adjacent opponent strings with less than five liberties + * liberties of low liberty neighbors of adjacent opponent strings * with less than five liberties. */ for (pos = BOARDMIN; pos < BOARDMAX; pos++) { if (board[pos] == other && active[pos] > 0 && countlib(pos) < 5) { int libs[4]; int liberties = findlib(pos, 4, libs); int adjs[MAXCHAIN]; int adj; for (r = 0; r < liberties; r++) active[libs[r]] = 1; /* Also add liberties of neighbor strings if these are three * or less. */ adj = chainlinks(pos, adjs); for (r = 0; r < adj; r++) { signed_mark_string(adjs[r], active, -1); if (countlib(adjs[r]) <= 3) { int s; int adjs2[MAXCHAIN]; int adj2; liberties = findlib(adjs[r], 3, libs); for (s = 0; s < liberties; s++) active[libs[s]] = 1; adj2 = chainlinks(pos, adjs2); for (s = 0; s < adj2; s++) signed_mark_string(adjs2[s], active, -1); } } } } for (pos = BOARDMIN; pos < BOARDMAX; pos++) { int value = board[pos]; if (!ON_BOARD(pos)) continue; if (!active[pos]) value = GRAY; else if (IS_STONE(board[pos]) && countlib(pos) > 4 && active[pos] > 0) value |= HIGH_LIBERTY_BIT; entry->board[pos] = value; } }
void store_persistent_owl_cache(int routine, int apos, int bpos, int cpos, int result, int move, int move2, int certain, int tactical_nodes, char goal[BOARDMAX], int goal_color) { char active[BOARDMAX]; int pos; int k; int r; int other = OTHER_COLOR(goal_color); gg_assert(stackp == 0); /* If cache is full, first try to purge it. */ if (persistent_owl_cache_size == MAX_OWL_CACHE_SIZE) purge_persistent_owl_cache(); /* FIXME: Kick out oldest or least expensive entry instead of giving up. */ if (persistent_owl_cache_size == MAX_OWL_CACHE_SIZE) { TRACE_OWL_PERFORMANCE("Persistent owl cache full.\n"); return; } persistent_owl_cache[persistent_owl_cache_size].boardsize = board_size; persistent_owl_cache[persistent_owl_cache_size].routine = routine; persistent_owl_cache[persistent_owl_cache_size].apos = apos; persistent_owl_cache[persistent_owl_cache_size].bpos = bpos; persistent_owl_cache[persistent_owl_cache_size].cpos = cpos; persistent_owl_cache[persistent_owl_cache_size].result = result; persistent_owl_cache[persistent_owl_cache_size].result_certain = certain; persistent_owl_cache[persistent_owl_cache_size].move = move; persistent_owl_cache[persistent_owl_cache_size].move2 = move2; persistent_owl_cache[persistent_owl_cache_size].tactical_nodes = tactical_nodes; persistent_owl_cache[persistent_owl_cache_size].movenum = movenum; /* Remains to set the board. We let the active area be * the goal + * distance four expansion through empty intersections and own stones + * adjacent opponent strings + * liberties of adjacent opponent strings with less than five liberties + * liberties of low liberty neighbors of adjacent opponent strings * with less than five liberties. */ for (pos = BOARDMIN; pos < BOARDMAX; pos++) if (ON_BOARD(pos)) active[pos] = (goal[pos] != 0); /* Also add critical moves to the active area. */ if (ON_BOARD1(move)) active[move] = 1; if (ON_BOARD1(move2)) active[move2] = 1; /* Distance four expansion through empty intersections and own stones. */ for (k = 1; k < 5; k++) { for (pos = BOARDMIN; pos < BOARDMAX; pos++){ if (!ON_BOARD(pos) || board[pos] == other || active[pos] != 0) continue; if ((ON_BOARD(SOUTH(pos)) && active[SOUTH(pos)] == k) || (ON_BOARD(WEST(pos)) && active[WEST(pos)] == k) || (ON_BOARD(NORTH(pos)) && active[NORTH(pos)] == k) || (ON_BOARD(EAST(pos)) && active[EAST(pos)] == k)) { if (board[pos] == EMPTY) active[pos] = k + 1; else mark_string(pos, active, (char) (k + 1)); } } } /* Adjacent opponent strings. */ for (pos = BOARDMIN; pos < BOARDMAX; pos++) { if (board[pos] != other || active[pos] != 0) continue; for (r = 0; r < 4; r++) { int pos2 = pos + delta[r]; if (ON_BOARD(pos2) && board[pos2] != other && active[pos2] != 0) { mark_string(pos, active, (char) 1); break; } } } /* Liberties of adjacent opponent strings with less than five liberties + * liberties of low liberty neighbors of adjacent opponent strings * with less than five liberties. */ for (pos = BOARDMIN; pos < BOARDMAX; pos++) { if (board[pos] == other && active[pos] != 0 && countlib(pos) < 5) { int libs[4]; int liberties = findlib(pos, 4, libs); int adjs[MAXCHAIN]; int adj; for (r = 0; r < liberties; r++) active[libs[r]] = 1; /* Also add liberties of neighbor strings if these are three * or less. */ adj = chainlinks(pos, adjs); for (r = 0; r < adj; r++) { if (countlib(adjs[r]) <= 3) { int s; liberties = findlib(adjs[r], 3, libs); for (s = 0; s < liberties; s++) active[libs[s]] = 1; } } } } for (pos = BOARDMIN; pos < BOARDMAX; pos++) { int value = board[pos]; if (!ON_BOARD(pos)) continue; if (!active[pos]) value = GRAY; else if (IS_STONE(board[pos]) && countlib(pos) > 4) value |= HIGH_LIBERTY_BIT; persistent_owl_cache[persistent_owl_cache_size].board[pos] = value; } if (debug & DEBUG_OWL_PERSISTENT_CACHE) { gprintf("%o Stored result in cache (entry %d):\n", persistent_owl_cache_size); print_persistent_owl_cache_entry(persistent_owl_cache_size); } persistent_owl_cache_size++; }
/* Capture as many strings of the given color as we can. Played stones * are left on the board and the number of played stones is returned. * Strings marked in the exceptions array are excluded from capturing * attempts. If all non-excepted strings are successfully captured, * *none_invincible is set to one. Set none_invincible to NULL if you * don't need that information. */ static int capture_non_invincible_strings(int color, int exceptions[BOARDMAX], int *none_invincible) { int other = OTHER_COLOR(color); int something_captured = 1; /* To get into the first turn of the loop. */ int string_found = 0; int moves_played = 0; int save_moves; int libs[MAXLIBS]; int liberties; int pos; int k; while (something_captured) { /* Nothing captured so far in this turn of the loop. */ something_captured = 0; /* Is there something left to try to capture? */ string_found = 0; /* Visit all friendly strings on the board. */ for (pos = BOARDMIN; pos < BOARDMAX; pos++) { if (board[pos] != color || find_origin(pos) != pos) continue; if (exceptions && exceptions[pos]) continue; string_found = 1; /* Try to capture the string at pos. */ liberties = findlib(pos, MAXLIBS, libs); save_moves = moves_played; for (k = 0; k < liberties; k++) { if (trymove(libs[k], other, "unconditional_life", pos)) moves_played++; } /* Successful if already captured or a single liberty remains. * Otherwise we must rewind and take back the last batch of moves. */ if (board[pos] == EMPTY) something_captured = 1; else if (findlib(pos, 2, libs) == 1) { /* Need to use tryko as a defense against the extreme case * when the only opponent liberty that is not suicide is an * illegal ko capture, like in this 5x5 position: * +-----+ * |.XO.O| * |XXOO.| * |X.XOO| * |XXOO.| * |.XO.O| * +-----+ */ int success = tryko(libs[0], other, "unconditional_life"); gg_assert(success); moves_played++; something_captured = 1; } else while (moves_played > save_moves) { popgo(); moves_played--; } } } if (none_invincible) *none_invincible = !string_found; return moves_played; }
void unconditional_life(int unconditional_territory[BOARDMAX], int color) { int found_one; int other = OTHER_COLOR(color); int libs[MAXLIBS]; int liberties; int pos; int k, r; int moves_played; int potential_sekis[BOARDMAX]; int none_invincible; /* Initialize unconditional_territory array. */ memset(unconditional_territory, 0, sizeof(unconditional_territory[0]) * BOARDMAX); /* Find isolated two-stone strings which might be involved in the * kind of seki described in the comments. */ memset(potential_sekis, 0, sizeof(potential_sekis)); for (pos = BOARDMIN; pos < BOARDMAX; pos++) { int isolated = 1; int stones[2]; int pos2; if (board[pos] != color || find_origin(pos) != pos || countstones(pos) != 2) continue; findstones(pos, 2, stones); for (k = 0; k < 2 && isolated; k++) { for (r = 0; r < 8 && isolated; r++) { pos2 = stones[k] + delta[r]; if (!ON_BOARD(pos2) || (board[pos2] == color && !same_string(pos, pos2))) isolated = 0; } } if (isolated) { potential_sekis[stones[0]] = 1; potential_sekis[stones[1]] = 1; } } moves_played = capture_non_invincible_strings(color, potential_sekis, &none_invincible); /* If there are no invincible strings, nothing can be unconditionally * settled. */ if (none_invincible) { /* Take back all moves. */ while (moves_played > 0) { popgo(); moves_played--; } return; } /* The strings still remaining except those marked in * potential_sekis[] are uncapturable. Now see which opponent * strings can survive. * * 1. Play opponent stones on all liberties of the unconditionally * alive strings except where illegal. */ for (pos = BOARDMIN; pos < BOARDMAX; pos++) { if (board[pos] != color || potential_sekis[pos] || find_origin(pos) != pos) continue; /* Play as many liberties as we can. */ liberties = findlib(pos, MAXLIBS, libs); for (k = 0; k < liberties; k++) { if (trymove(libs[k], other, "unconditional_life", pos)) moves_played++; } } /* 2. Recursively extend opponent strings in atari, except where this * would be suicide. */ found_one = 1; while (found_one) { /* Nothing found so far in this turn of the loop. */ found_one = 0; for (pos = BOARDMIN; pos < BOARDMAX; pos++) { if (board[pos] != other || countlib(pos) > 1) continue; /* Try to extend the string at (m, n). */ findlib(pos, 1, libs); if (trymove(libs[0], other, "unconditional_life", pos)) { moves_played++; found_one = 1; } } } /* Now see whether there are any significant sekis on the board. */ for (pos = BOARDMIN; pos < BOARDMAX; pos++) { if (!potential_sekis[pos] || board[pos] == EMPTY || find_origin(pos) != pos) continue; for (r = 0; r < 4; r++) { int up = delta[r]; int right = delta[(r + 1) % 4]; int locally_played_moves = 0; if (board[pos + up] != color || board[pos + up + up] != EMPTY || board[pos - up] != EMPTY) continue; for (k = 0; k < 2; k++) { if (k == 1) right = -right; if (board[pos + right] != EMPTY || board[pos + up - right] != EMPTY) continue; if (board[pos - right] == EMPTY && trymove(pos - right, other, "unconditional_life", pos)) locally_played_moves++; if (board[pos + up + right] == EMPTY && trymove(pos + up + right, other, "unconditional_life", pos)) locally_played_moves++; if (board[pos - right] == other && board[pos + up + right] == other && same_string(pos - right, pos + up + right)) { /* This is a critical seki. Extend the string with one stone * in an arbitrary direction to break the seki. */ while (locally_played_moves > 0) { popgo(); locally_played_moves--; } trymove(pos - up, color, "unconditional_life", pos); moves_played++; break; } else { while (locally_played_moves > 0) { popgo(); locally_played_moves--; } } } if (countstones(pos) > 2) break; } } /* Capture the strings involved in potential sekis. */ for (pos = BOARDMIN; pos < BOARDMAX; pos++) { if (!potential_sekis[pos] || board[pos] == EMPTY) continue; /* Play as many liberties as we can. */ liberties = findlib(pos, MAXLIBS, libs); for (k = 0; k < liberties; k++) { if (trymove(libs[k], other, "unconditional_life", pos)) moves_played++; } } for (pos = BOARDMIN; pos < BOARDMAX; pos++) { int apos; int bpos; int aopen, bopen; int alib, blib; if (board[pos] != other || countlib(pos) != 2) continue; findlib(pos, 2, libs); apos = libs[0]; bpos = libs[1]; if (abs(I(apos) - I(bpos)) + abs(J(apos) - J(bpos)) != 1) continue; /* Only two liberties and these are adjacent. Play one. We want * to maximize the number of open liberties. In this particular * situation we can count this with approxlib for the opposite * color. If the number of open liberties is the same, we * maximize the total number of obtained liberties. * Two relevant positions: * * |XXX. * |OOXX |XXXXXXX * |O.OX |OOXOOOX * |..OX |..OO.OX * +---- +------- */ aopen = approxlib(apos, color, 4, NULL); bopen = approxlib(bpos, color, 4, NULL); alib = approxlib(apos, other, 4, NULL); blib = approxlib(bpos, other, 4, NULL); if (aopen > bopen || (aopen == bopen && alib >= blib)) { trymove(apos, other, "unconditional_life", pos); moves_played++; } else { trymove(bpos, other, "unconditional_life", pos); moves_played++; } } /* Identify unconditionally alive stones and unconditional territory. */ for (pos = BOARDMIN; pos < BOARDMAX; pos++) { if (board[pos] == color && !potential_sekis[pos]) { unconditional_territory[pos] = 1; if (find_origin(pos) == pos) { liberties = findlib(pos, MAXLIBS, libs); for (k = 0; k < liberties; k++) unconditional_territory[libs[k]] = 2; } } else if (board[pos] == other && countlib(pos) == 1) { unconditional_territory[pos] = 2; findlib(pos, 1, libs); unconditional_territory[libs[0]] = 2; } } /* Take back all moves. */ while (moves_played > 0) { popgo(); moves_played--; } }