Check out McCooey's Hexagonal Chess, our featured variant for May, 2025.


[ Help | Earliest Comments | Latest Comments ]
[ List All Subjects of Discussion | Create New Subject of Discussion ]
[ List Earliest Comments Only For Pages | Games | Rated Pages | Rated Games | Subjects of Discussion ]

Comments/Ratings for a Single Item

EarliestEarlier Reverse Order LaterLatest
Interactive diagrams. (Updated!) Diagrams that interactively show piece moves.[All Comments] [Add Comment or Rating]
💡📝H. G. Muller wrote on Thu, Dec 22, 2022 09:25 PM UTC in reply to A. M. DeWitt from 08:23 PM:

Thankfully, this problem can easily be solved by writing the missing legs separately, yielding B(cpaf)2cBcafpafcBpafcafcB.

Ah, OK. This is what I vaguely remembered when I mentioned that combining destructive and non-destructive modes might cause problems. The Betza parser therefore separates those. But since this was written at the time only a single locust square could appear in the move encoding, it doesn't do a very elaborate job at it. The AI's move generator could perform this separation while generating; after every leg it calls itself recursively, for the remaining legs, and it could just call itself two times: once with and once without locust square. (It might not do that, though, because there was no need, as the parser already ensured that the combination would never occur.)

I will have a look at this; it must be possible to suppress the separation by the parser when NewClick is being used, and make the AI's move generator do it in that case. Up to then you indeed would have to do the separation yourself. Or at least part of it, keeping in mind that the parser only separates one of the legs. So you could write mpcafcafcBmpcafpafcB to specify all 4 combinations.

And no, paf should not be equivalent to mpaf in any case. paf must hop, mpaf is completely ignoring the square, 'seemlessly' gluing the legs into a longer leap. mpaf should not be used on slides: logically mpafB would mean mafBpafB, where the first term is the exploding version of B (a 'degenerate hook mover'), and pafB what Betza originally wrote as pB. So BpB would be the recommended notation.


A. M. DeWitt wrote on Sat, Jan 21, 2023 03:20 PM UTC:

I'm having a problem with Suzumu Shogi's interactive diagram. I managed to get the Fire Demon's burning restriction implemented properly in the pieceZone function using the clicks array, but as it turns out, clicks is not used by the AI's move generator, so the AI doesn't work properly. I was wondering if there is a similar array used by the AI to keep track of the squares it visits?

In case you need it, here is the relevant portion of the pieceZone code and its supporting functions (I left the rest out, since it is quite complicated)

function pieceZone(x2, y2, piece, color, x1, y1, ex, ey)
{
  if(touched) return 0; // not during ShowMoves()
  // ... Heavenly Tetrarch Section ...
  // Fire Demon
  v = board[y2][x2] & 511; // Highlight square
  firstX = (clicks.length >= 4) ? clicks[2] : -1;
  firstY = (clicks.length >= 4) ? clicks[3] : -1;
  firstVictim = (clicks.length >= 4) ? board[firstY][firstX] & 511 : -1;
  secondX = (clicks.length >= 6) ? clicks[4] : -1;
  secondY = (clicks.length >= 6) ? clicks[5] : -1;
  secondVictim = (clicks.length >= 6) ? board[secondY][secondX] & 511 : -1;
  if(isBurner(p))
  {
  // Always allow starting square
  if(x2 == x1 && y2 == y1) return 0;
  // Reject captures of Fire Demon outside normal range
  if(isBurner(v) && !nonBurningRange(x1, y1, x2, y2)) return 1;
  // Allow direct capture of Fire Demon with second burn after first burn
  if(firstVictim > 0 && !isBurner(firstVictim) && clicks.length == 4)
  {
    if(isBurner(v) && nonBurningRange(x1, y1, x2, y2) && canBurn(x2, y2, firstX, firstY)) return 0;
  }
  // If first victim is a Fire Demon, allow move to start square, reject moving to empty square or burning another Fire Demon
  if(isBurner(firstVictim))
  {
  // Always allow starting square
  if(x2 == x1 && y2 == y1) return 0;
    // If a Fire Demon was captured via igui, always allow second burn of Fire Demon
    if(isBurner(firstVictim) && secondX == x1 && secondY == y1) return 0;
      // if second victim is not an empty square or a Fire Demon, only allow single burn
      if(secondVictim > 0 && !isBurner(secondVictim)) return !(x2 == secondX && y2 == secondY);
      // Reject moves to empty square or burn of Fire Demon
      else return (v == 0 || (isBurner(v) && (x2 != firstX || y2 != firstY)));
    }
    // If first victim is not a Fire Demon, allow direct capture with optional second burn, reject burn of another Fire Demon
    else
    {
      if(firstVictim != -1 && !isBurner(firstVictim))
      {
        if(isBurner(v)) return 1;
      }
    }
  }
  // ... Range Capturing Pieces Section ...
  return 0;
}

💡📝H. G. Muller wrote on Sat, Jan 21, 2023 04:26 PM UTC in reply to A. M. DeWitt from 03:20 PM:

The NewClick entry system is based on the AI: it uses the move generator of the latter to create a list of moves, and then after each click eliminates the moves that do not correspond to the clicks received so far, until only a single move is left. So getting it done in the AI's move generator is all that is needed. But indeed this does not use the 'clicks' array, as during thinking no one is clicking.

The AI's move generator uses a routine StackMove() to add a move to the list, and this calls BadZone() to vet the move for user-supplied restrictions on the Betza notation. This StackMove() gets the origin and destination squares passed as arguments, but not the locust squares. These are 'passed' as global variables: 'kills' indicates how many locust squares there are, and arrays killX[i] and killY[i], 0 <= i < kills contain the coordinates of these squares. For backward compatibility killX[0] and killY[0] are passed to BadZone(), but not any additional locust squares.

BadZone() can access these global variables just as easily as StackMove() does it, so you could base your move vetting on those.

A complication is that e.p. squares are also passed in the kill array (a variable 'eps' indicates how many of the kills are e.p. squares). But I guess that you won't have any e.p. capture in Shogi variants, so you won't have to deal with that.


A. M. DeWitt wrote on Sun, Jan 22, 2023 12:05 AM UTC in reply to H. G. Muller from Sat Jan 21 04:26 PM:

Very useful information. Unfortunately it requires me to go back to the drawing board...


A. M. DeWitt wrote on Sun, Jan 22, 2023 04:42 AM UTC:

Unfortunately, I wasn't able to find a solution for implementing the burning restriction for the AI other than disabling the AI. It mainly has to do with the fact that the move generator will auto-complete the move if it thinks only one move is left if I try to use the killX and killY arrays instead of the clicks array.


💡📝H. G. Muller wrote on Sun, Jan 22, 2023 10:32 AM UTC:

The NewClick entry system is still experimental, and in general I try to avoid auto-completion. But there is one exception: I put in a heuristic that classifies a locust capture eaither as 'shooting' or 'trampling' (depending on whether the leg after it goes back to the square the previous leg came from), and swap the click order of the two legs in that case. So that you can first click the final destination, and only then what you shoot. This seemed more naturally, e.g. in the case of the Forest Ox of Odin's Rune Chess.

Perhaps this now interferes with what you are trying to do, and needs some refinement. Can you be more specific on what goes wrong?

With BadZone() you should be able to restrict the list of moves that is generated, suppressing the burns you don't want. If this will lead to a list from which the NewClick() algorithm cannot properly select the one you want, this should count as a defect in NewClick(). Not as a problem with using killX/Y in BadZone().


A. M. DeWitt wrote on Sun, Jan 22, 2023 02:30 PM UTC in reply to H. G. Muller from 10:32 AM:

I think it is better to just show you the difference. You can find the demonstration diagrams here.


💡📝H. G. Muller wrote on Sun, Jan 22, 2023 08:54 PM UTC in reply to A. M. DeWitt from 02:30 PM:

There definitely is something wrong in NewClick, that doesn't seem related to the BadZone() issue. If I define single and double burns on the same piece, it will always select the single burn. If I only define a double burn, that works as intended. (And it also excludes burning other Demons as it should.)

I will debug it further tomorrow.


A. M. DeWitt wrote on Mon, Jan 23, 2023 01:20 AM UTC in reply to H. G. Muller from Sun Jan 22 08:54 PM:

I did notice that the kills variable wasn't being updated upon selection of a locust victim when I did some debugging of my own. The killX and killY arrays would update, but kills would always be zero. That would explain why when I tried recreating the clicks array using the visits array (using kills to regulate the length of the killX and killY arrays), it always ended up failing.


💡📝H. G. Muller wrote on Mon, Jan 23, 2023 09:27 AM UTC in reply to A. M. DeWitt from 01:20 AM:

The 'kills' data is only valid during move generation. Which is where BadZone() is called to vet the generated moves. Otherwise it is undefined (the data for the latest move that was generated remaining there). Apart from during move generation, BadZone is only called from the Highlight() routine. But when using the NewClick entry system the Diagram should never attempt to highlight the destination of a move that was already suppressed at the generation stage. Perhaps for safety this call to BadZone() should be suppressed when newClick=1; it belonged to the old entry system.

The problems with single/double burning appear to be a case of "works as designed" not being the same as "works as desired". The point is that the heuristic for classifying a locust capture as a shooter (swapping the order of the locust and the destination click) only works for the last locust victim. Any earlier victim will always be treated as trampled, and has to be clicked before the final destination. So clicking destination and then a locust victim unambiguously indicates the single burn, while a double burn first has to click one of the locust victims to trample it. Then it will highlight the destination in cyan,and when you click that you can pick a second locust victim, which completes the move by shooting it.

The way the NewClick system works is that for each move it determines a click sequence required to select that move. Normally this would be the origin, all locust victims in the order XBetza specifies those, and finally the destination. There are exceptions for e.p. capture and castling, which in general require only two clicks even though their move encoding involves more squares. And for shooters, where it swaps the final two clicks. Detecting whether a move is castling, e.p. or igui can only be done during generation of the move, when the move descriptor from which the move results is still known and can be consulted. From the internal encoding of the move it cannot be concluded whether it was a trampling or a shooting; mpafsmpacabW (Forest Ox) and mpafcaW can both move from e3 to f5 while capturing e5, but only the first would be considered a shooter (because the final leg always retraces the one before it) and require click order e3-f5-e5.

I suppose I should put the code that moves the destination click earlier inside a loop, so that it is able to pass locust vctims as lon as the preceding two legs have a cab structure. Then the double burnings could be entered by first clicking the destination, and then picking off the burn victims until you run out of victims, or click the destination again to terminate the move. That would be a much more intuitive way for entering multiple burns.

Some other remarks: You use (af)0 to force a slide on a K atom, but this is not recommended. Because (af)0K is expanded by pre-processing to KafKafafKafafafK..., as if every distance is a separate move, which would be attempted independently. So if the slide is blocked at the second square (so that afK would fail to produce a move) it would still keep trying afafK etc. And when afafafK was a valid non-capture, it would still again check the first four squares for emptiness when attempting afafafafK and longer paths. The equivalent plain Q would do much more efficiently, and probe each square on the path only once, and stop after it finds an occupied one. You can turn the Q into K for the burning by using ya to specify a range toggle. E.g. mpyacabQ would describe a Queen burning one enemy adjacent to her destination.

It is also important to take the replacement victim only on the final leg, and hop over it when positioning for the next shoot, to prevent it being added as a spurious locust victim. I think you already do that.

[Edit] I now uploaded a new betza.js, which suppresses the BadZone() call in Highlight() when newClick=1, to make sure it cannot interfere when kills is not valid. (Flush the browser cache!)


A. M. DeWitt wrote on Mon, Jan 23, 2023 06:43 PM UTC in reply to H. G. Muller from 09:27 AM:

Some other remarks: You use (af)0 to force a slide on a K atom, but this is not recommended. Because (af)0K is expanded by pre-processing to KafKafafKafafafK..., as if every distance is a separate move, which would be attempted independently. So if the slide is blocked at the second square (so that afK would fail to produce a move) it would still keep trying afafK etc. And when afafafK was a valid non-capture, it would still again check the first four squares for emptiness when attempting afafafafK and longer paths. The equivalent plain Q would do much more efficiently, and probe each square on the path only once, and stop after it finds an occupied one. You can turn the Q into K for the burning by using ya to specify a range toggle. E.g. mpyacabQ would describe a Queen burning one enemy adjacent to her destination.

Sure, (af)0mpacabK is inefficient, but under the current implementation, it's the only Betza string that gives all options for the Fire Demon's burn. Using ympacabQ omits some of these options for some reason.

I suppose I should put the code that moves the destination click earlier inside a loop, so that it is able to pass locust vctims as lon as the preceding two legs have a cab structure. Then the double burnings could be entered by first clicking the destination, and then picking off the burn victims until you run out of victims, or click the destination again to terminate the move. That would be a much more intuitive way for entering multiple burns.

It would also make implementing the burning restriction much simpler, as then I don't have to worry about branching cases nearly as much, as it would usually be the same check every time (usually, not always, because shooting an adjacent Fire Demon without moving (double igui) is still allowed).

Right now I am supressing the new Highlight function with the old one, since the current code in Suzumu Shogi's interactive diagram relies on the call from the Highlight function. Once the pass for cab locust captures is implemented, I should be able to use the new function without any problems.


💡📝H. G. Muller wrote on Mon, Jan 23, 2023 09:04 PM UTC:

OK, I see. There is something very wrong with NewClick(). The XBetza compiler inserts a dummy leg at the end of a non-final sliding leg with m rights, to act like a loop directive. The old interpreter needed that, to remember from where it had been attempting the subsequent legs, so that it could resume elongating the sliding leg for another move when it was done. The AI's move generator doesn't need that, and just ignores these dummy legs, recognized by the mode flags being set to -1. But the classification of moves in NewClick() mistakes it for a genuine leg of the move, and since -1 has all flag bits set, it mistakenly thinks that it is dealing with a castling for the myacabQ part (which the compiler already separates from the pyacabQ part, for the purpose of inserting the loop directive in the former, while the latter doesn't need one). Thinking that it is a castling makes it eliminate the request for some clicks which are really needed to distinguish different double burns from an empty square. The (af)0 notation doesn't suffer from that, because none of the moves to which it expands contains any slider legs.

I will try to fix this tomorrow.


💡📝H. G. Muller wrote on Tue, Jan 24, 2023 12:12 PM UTC:

OK, I think I fixed it. The Betza compiler now refrains from inserting these 'loop directives' in the legs sequence when the NewClick move-entry method is used. I had to change some tests in the assignment of click sequences (which already relied on the presence of these directives) too. Distant or sliding rifle captures do not require the click to the final destination. This might be a folly, though, as it causes ambiguity when both the rifle and normal capture are allowed on the piece. Perhaps I should make this subject to a Diagram parameter autoComplete, and in general require all clicks, but consider the move entered when there is only one matching move left in the list, and at least two clicks have been given.

I am not even sure that swapping the order of locust victim and destination is always harmless, or whether it could lead to ambiguities too.

Anyway, I uploaded the fixed betza.js. (Flush browser cache!) It seems to make double and single burns as I intended them, clicking the destination first, and then 'picking off' the burn victims. Where clicking the same victim twice does a single burn. This was in combination with a Demon that moved like:

cabKcabacabKshQshmpyacabQshmpyacabmpacabQ

It did not forbid burning of a Demon with the killsZone() routine you had made. But when I made my own, that only cares about this aspect of the rules, it did work:

function killsZone(x2, y2, piece, color, x1, y1, ex, ey)
{
  if(kills == 0 || x1==x2 && y1==y2) return 0; // normal move or igui always allowed
  if((board[killY[0]][killX[0]] & 511) == 35) return 1;
  return (kills > 1 && (board[killY[1]][killX[1]] & 511) == 35);
}

So it is probably because your routine is not yet adapted to the new situation.

The extensive testing you do in BadZone() slows things down enormously. It is questionable whether this variant can ever be played by the Diagram's AI, (it outlasted my patience even at 2 ply), but this extra processing certainly doesn't help. If you need this amount of programming to make it work, it might be simpler to just replace some functions of the original script by slightly modified ones of your own. JavaScript allows you to redefine a function.

E.g. the function NextLeg(), which is the core of the AI's move generator, contains a line

      if(f & 16) NextLeg(ff, fr, x, y, rg>1?len:iso, d+1); // p/g leg never final; do nothing here and continue

which takes care of continuation of a hopper move (the 'hop off') when the current leg has hit a mount, which at that point is stored in the variable 'victim'. You could clone that routine, and supplement the condition f & 16 with a test whether the piece types allow the hop:

      if(f & 16 && jumpClass[victim&511] < jumpClass[board[fr][ff]&511])
        NextLeg(ff, fr, x, y, rg>1?len:iso, d+1); // p/g leg never final; do nothing here and continue

when you have initialized an array jumpClass[] for all the piece types. That would releave you from all the checking after the moves are generated.

The way you treat freezing only seems to work if you generate moves for a single piece, and it is not clear how 'frozen' ever gets reset. I suppose you could make use here of the fact that the move generator generates moves piece by piece, and only redo the scan of the surroundings of the piece when the current piece is different from that of the previous call. If there are only few freezers, it might be much faster (considering the large number of pieces in this variant) to first locate the freezers, scan the board for their enemy neighbors, and temporarily replace those for dummies during the move generation. (This would require you to provide your own modified version of the (very small) routine GenAll().)


A. M. DeWitt wrote on Tue, Jan 24, 2023 04:43 PM UTC in reply to H. G. Muller from 12:12 PM:

The extensive testing you do in BadZone() slows things down enormously. It is questionable whether this variant can ever be played by the Diagram's AI, (it outlasted my patience even at 2 ply), but this extra processing certainly doesn't help. If you need this amount of programming to make it work, it might be simpler to just replace some functions of the original script by slightly modified ones of your own. JavaScript allows you to redefine a function.

The amount of testing required for the Fire Demon was mainly because of the branching cases. With the new version, not only was I able to use the code from your comment to make the check much shorter, I was even able to remove three of the supporting functions because of this. As for code in general, sure, it will slow things down a lot, but that is to be expected from a complex game like Suzumu Shogi. You could treat it like a correspondence game in GC, though, so I was never too worried about it being too slow. I could probably do some optimization (I've saved this comment in my favorites), but right now I am just happy that everything is working properly.


💡📝H. G. Muller wrote on Tue, Jan 24, 2023 05:27 PM UTC in reply to A. M. DeWitt from 04:43 PM:

I am wondering whether I should make hop-restrictions a standard feature of the Interactive Diagram. This could for instance be done through the captureMatrix parameter. That already has a symbol for 'capture forbidden' (the !), that can be used to outlaw captures of one specific type by another. A symbol ^ in the location of AxB could mean that A is not allowed to hop over B, and a 0 that it can neither hop over nor capture B. A ban on hopping could also be applied to locust capture.

Of course the capture matrix for a game like Suzumu Shogi would be horrendously large. But most rows could be left empty, as most pieces neither hop nor locust-capture, and there are no bans on normal captures.

As to freezing: a much faster way to do implement that is to have NewMakeMove() and UnMake() keep track of the Tetrarch locations (which I understand are the only freezers). This could for instance be done by having arrays next[] and prev[] indexed by square numbers, that link all Tetrarchs of the same color in a doubly-linked list that is also accessible by square number. To remove a Tetrarch on square sqr from the list (because it moves away or is captured) you would just do

next[prev[sqr]] = next[sqr]; prev[next[sqr]] = prev[sqr];

To place a Tetrarch (because you moved it there or promoted to one) you would do

next[sqr] = next[-1]; prev[sqr] = -1; prev[next[-1]] = sqr; next[-1] = sqr;

Most moves would not move or capture a Tetrarch, so the overhead is limited to testing if they did. But it would allow you to quickly loop through the Tetrarch locations by following the next[] links starting at next[-1] (and the other color would use next[-2]). Most of the game the list would be empty anyway (i.e. next[-1] = -1). You could do that at the start of move generation, and when you find a Tetrarch, replace every adjacent enemy piece by a dummy without moves to freeze them. You would of course have to undo that immediately after move generation.

[Edit] I now implemented piece-type-selective exclusion of hopping through the capture matrix, using ^ (for outlawing hopping for that piece combination) and $ (outlawing both hopping and capture, like for the Cannon in Janggi). This can also be done independently for friendly and enemy pieces.


A. M. DeWitt wrote on Wed, Jan 25, 2023 03:24 AM UTC:

OK, I think I fixed it. The Betza compiler now refrains from inserting these 'loop directives' in the legs sequence when the NewClick move-entry method is used. I had to change some tests in the assignment of click sequences (which already relied on the presence of these directives) too. Distant or sliding rifle captures do not require the click to the final destination. This might be a folly, though, as it causes ambiguity when both the rifle and normal capture are allowed on the piece. Perhaps I should make this subject to a Diagram parameter autoComplete, and in general require all clicks, but consider the move entered when there is only one matching move left in the list, and at least two clicks have been given.

I am not even sure that swapping the order of locust victim and destination is always harmless, or whether it could lead to ambiguities too.

There does seem to be a problem when using the Lion Dog (XBetza: KADGHcavKabKmpafcavKcafcmpavK) with the newClick generation. When doing a double capture of the first two pieces in a straight line, and you click the second piece again, it always selects the two out one in option. This is probably due to the ambiguity caused when rifle captures and normal captures are allowed. I am planning on reviving Hook Shogi, and I intend to include the Lion Dog in it, so any help would be greatly appreciated.

I think the best solution would be some directive specifically for this kind of ambiguity, or to only skip a locust victim after cab if that locust victim's square is the only one that can be visited, so as to not destroy the directives that allow the Suzumu Fire Demon's burning restriction to work properly.


💡📝H. G. Muller wrote on Thu, Jan 26, 2023 12:31 PM UTC in reply to A. M. DeWitt from Wed Jan 25 03:24 AM:

Indeed, there is a problem here. When the last two squares of the 2-out-1-in move are swapped because they constitute a rifle capture, the click order becomes the same as that for a 2-out trampling. I suppose the lesson is that it cannot be determined just on the basis of a move itself whether it should be treated as a shooting; it depends on which other moves are there. Perhaps the whole idea of automating this is a folly, and it should be made subject to a parameter shooters=1 to enable it.

I will make one more attempt: during compilation of the XBetza decriptor each part can already be classified as a trampling or shooting (or a normal move), and a piece is classified as a shooter only when it has shootings and no tramplings. Then moving up the destination click to before the locust captures will only be done for pieces classified as shooters. It will take me some time to implement this.

Another issue: would it be helpful to not only call BadZone() after a move is generated, in order to allow or forbid it, but also before generating any moves for a piece (indicated by an invalid destination, e.g. x2 = -1), to decide whether any moves should be generated at all for that piece? That would make 'freezing' of pieces more efficient.


💡📝H. G. Muller wrote on Thu, Jan 26, 2023 09:45 PM UTC in reply to H. G. Muller from 12:31 PM:

OK, I uploaded a new betza.js script. This classifies pieces as shooters during compilation of their XBetza moves (i.e. when the Diagram is initialized), and only messes with the click order for those. I think this should be safe.

The Lion Dog now seems to work, although I used a slightly different description to prevent the 2-out-1-in move being generated twice:

KADGHcavKabKmpafcavKcafcmpafK

The difference is the final f, which in your description was a v. As the Lion Dog has tramplings (e.g. cafK) it is not classified as a shooter, so you have to enter a double capture through 2-out-1-in by first clicking the distant victim, and then the adjacent one.

I also have implemented a new parameter paralyze. When set non-zero it will cause BadZone(x, y, piece, color) to be called for every piece before move generation for that piece starts, with the 'piece' argument equal to 0, and (x,y) the piece location. Returning non-zero in that case will cause no moves to be generated for that piece at all.


A. M. DeWitt wrote on Fri, Jan 27, 2023 12:25 AM UTC in reply to H. G. Muller from Thu Jan 26 09:45 PM:

Thanks for fixing that. To be honest, I was a bit worried that the Suzumu Fire Demon might get messed up, but it turns out I was overreacting. And now that the Lion Dog works properly as well, I can include it and the Suzumu Fire Demon in the same game without needing to use any special code (apart from BadZone for the burning restriction, of course).

As for the paralyze parameter, I'm not sure whether it would actually increase efficiency or not. If paralyze doesn't suspend the BadZone call after move generation you would end up calling it twice instead of once, so it could slow things down even more if I am not careful with it. So for now, I am just going to keep Suzumu Shogi's BadZone code as it is.

I did encounter another minor bug. The highlights for the ShowMoves() function seem to be a bit bugged when using the new move generation. For example, when showing the possible moves of a piece after clicking its name, the Suzumu Fire Demon doesn't show the burn for its ranging moves, and hook moving pieces only show the hook moves for the first square in each direction. It appears to only be affecting atom such as R, B, and Q. It doesn't affect the diagram when clicking pieces on the actual board, but it can be a bit misleading. Thankfully, this shouldn't be too difficult to fix.


💡📝H. G. Muller wrote on Fri, Jan 27, 2023 08:31 AM UTC in reply to A. M. DeWitt from 12:25 AM:

The point of paralyze is that figuring out whether a piece is frozen is a rather expensive part of BadZone(), as it has to probe 8 board squares, and that doing this once per piece before the move generation relieves you from doing it for every move of the piece. So you would just be moving that expensive part from one call to another, with a little overhead added for figuring out which type of call you are dealing with:

if(piece == 0) {
  return IsFrozen(x, y);
}

And when the piece is frozen you would also safe the time for attempting to generate its moves. Whether this gains anything would depend on the typical ratio of the number of moves versus the number of pieces. In the Tenjiku setup many pieces start in a smothered position, but the pieces you tend to develop first have an extrordinary large number of moves. So my guess is that it would still be beneficial, because for almost the entire game you would have more moves than pieces.

But it is true you could achieve similar, or perhaps even better savings by remembering which piece you are testing for between BadZone() calls:

var latestPiece = -1;

BadZone(x2, y2, piece, color, x1, y1)
{
  var s = 16*x1 + y1; // unique square number of origin
  if(latestPiece != s) { // new piece
    latestPiece = s;
    frozen = IsFrozen(x, y); // recalculate frozen for new piece only
  }
  if(frozen) return 1;
  ... // other tests for non-frozen pieces
}

This doesn't require extra calls to BadZone(). It would still generate all moves for a frozen piece to discover only afterwards that they should be rejected if the piece was frozen. But since pieces would almost never be frozen (especially in Suzumu Shogi,where you can get Tetrarchs only by promotion), I suppose this only wastes negligible time in practice.

More could be saved by using a more efficient algorithm for IsFrozen(). But that would require knowing where the Tetrarchs are. But even the most stupid way to know that, scanning the entire board in every new position before move generation, might be competitive: there are only 256 board squares to test, while scanning the individual piece neighborhoods examines 8 squares per piece that has moves. So once you have 32 pieces that have moves, the whole-board scan becomes cheaper.

I wonder if I should build in a standard feature to trach the location of pieces of a certain type (enabled through a parameter like track=N). The AI uses a global variable 'nodes' that counts move generations, and could be used in BadZone() to know hen a new move generation has started by comparing it to the value at the previous call. If the Tetrarch locations would be somehow available, it could use these to mark the squares adjacent to each Tetrarch in a frozen[y][x] board-sized array by the current value of 'nodes' when this value changed, and the test for being frozen would just be

if(frozen[x2][y2] == nodes) return 1;

Tracking would cause some overhead, but variants that do not track any pieces could be protected from that by replacing the NewMakeMove() and UnMake() functions by special tracking ones only when tracking is requested.


💡📝H. G. Muller wrote on Fri, Jan 27, 2023 11:11 AM UTC in reply to H. G. Muller from 08:31 AM:

The ShowMoves problem appears to occur because this still uses the old highlighting routine. And now that I leave out the loop directive from the internal move descriptors this cuts non-final slider legs with m rights (which should continue after a succesful try) to their first step. I should make the highlighting in ShowMoves also be done with NewClick(); I guess I didn't do that because it never has to deal with a double locust capture, as hovering gives you at most a single potential victim.

A reasonably cheap method for tracking pieces (of all types!) would use an array location[] indexed by (colored) type, where the square numbers (calculated as 128*rank + file) where pieces of a given type sit would be stored in location[type] and location[type+512]. This can be updated in MakeMove() like

function TrackMakeMove(m) {
  var p = m[-6] & 1535; // moving piece (stripped of marker flags and virginity)
  if(location[p] == 128*m[1] + m[0]) location[p] = -1; // mark first entry as invalid when it was for the moved piece
  p = m[-1] & 1535; // promoted type (could be same as mover)
  if(location[p] < 0) location[p] = 128*m[3] + m[2]; // put destination in first entry when the latter was unused
  else location[p+512] = 128*m[3] + m[2]; // otherwise it must have been for the other piece of this type, so this piece uses second entry
  OriginalMakeMove(m);
}

and similar for UnMake(). It would not take account of capture of the pieces, (which would be cumbersome because of the possibility of locust capture), so when using the locations for a type it should always be tested whether a piece of that type is actually there; it could still indicate the square where a piece of that type was captured, but which now is occupied by something else.

Limitation is that it would only work if there are at most two pieces of the types of interest. Which for Suzumu Shogi would not be the case, as there could be four Tetrarchs. But somehow it doesn't seem wise to adapt a general feature to a very exceptional case; in virtually every variant there are only two pieces of each type. One could always artificially make the Diagram use two different Chariot Soldiers and Tetrarchs, each present as a pair. After all, we already artificially distinguish a Lion Hawk obtained through promotion from a primordial one, just to maintain a homogeneous mapping from piece to promoted form.

With the aid of this you would only have to look for enemy Tetrarchs in 4 locations, rather than 256. You would do that in BadZone() when you find 'nodes' increased, and for every Tetrarch you find this way you would set the square surrounding it to 'nodes' on an auxiliary board. Moves starting from a thus marked square would then be suppressed, as that piece would be frozen.


💡📝H. G. Muller wrote on Fri, Jan 27, 2023 04:13 PM UTC in reply to H. G. Muller from 11:11 AM:

The ShowMoves problem is now fixed: I reverted to including the loop directives in the compiled move descriptors even when newClick=1, and made the code that performs the click swapping for shooters resistant to their presence. As far as I could see this appears to work.

I now also made promotion to empty square ('kamikaze capture') possible; it can be specified in the captureMatrix by a 0 (zero), and requested from the WeirdPromotion() custom script by returning 251 for the promotion piece. This means that it would now also be possible to implement the passive burning ability of a Tenjiku Shogi Fire Demon, by detecting in WeirdPromotion() whether a move lands next to an enemy Demon, and return 251 in that case.


A. M. DeWitt wrote on Fri, Jan 27, 2023 07:59 PM UTC in reply to H. G. Muller from 04:13 PM:

I now also made promotion to empty square ('kamikaze capture') possible; it can be specified in the captureMatrix by a 0 (zero), and requested from the WeirdPromotion() custom script by returning 251 for the promotion piece. This means that it would now also be possible to implement the passive burning ability of a Tenjiku Shogi Fire Demon, by detecting in WeirdPromotion() whether a move lands next to an enemy Demon, and return 251 in that case.

Now all that is needed to successfully implement a interactive diagram for Tenjiku Shogi is to give WeirdPromotion the ability to affect other squares.

Edit: I tested this in an interactive diagram of Tenjiku Shogi, and the piece does get removed, but the diagram still thinks there is a piece there.


💡📝H. G. Muller wrote on Fri, Jan 27, 2023 09:13 PM UTC in reply to A. M. DeWitt from 07:59 PM:

True. But I have always had the feeling that XBetza notation should be the vehicle to specify that, not the promotion. What I am missing in XBetza is the possibility to specify a side effect that is mandatory when possible, but not blocking the move when impossible.

How about this: the modifier t is a leg separator like a, except that the actual piece move terminates at that point, and the remaining legs only describe the path of its 'influence'. If this is not a unique continuation, all possible realizations will be combined with the move. So where yacabQ would describe a Queen that (by virtue of the y) does a single King-like burn of an enemy, ytacQ would burn every enemy adjacent to the square it lands on, and can even land there if there is no adjacent enemy at all. An Ultima Pincher Pawn would be ytacfapR, where the captureMatrix would be used to forbid the piece (and its influence) to hop an enemy. Unlike actual moves, the last leg of the influence can be a 'hop-on' leg.

If you are prepared to use JavaScript there are many ways you could interfere with the standard operation of the Diagram. There is for instance a routine ScoreMove(move, predecessor), to prep the 'raw' moves coming out of the move generator by determining the piece types of the capture victims, storing those in the array describing the move, and calculating the effect these captures (and promotions) would have on the score. ('predecessor' is the move preceding it, only used to enforce anti-trading rules). Interfering there to add a few burn victims is not very difficult; you just replace the function by a wrapper for it, like:

var OriginalScoreMove = ScoreMove; // remember the standard routine
var burnX = [ 1, 1, 1, 0, -1, -1, -1, 0 ];
var burnY = [ 1, 0, -1, -1, -1, 0, 1, 1 ]; 
function ScoreMove(m, last) { // and redefine it
  var x = m[0], y = m[1];     // origin
  var piece = board[y][x];    // moving piece
  if(IsBurner(piece)) {
    var n = 4; // 0-3 are the coordinates of origin and destination
    for(var k=0; k<8; k++) {
      var xb = x + burnX[k], yb = y + burnY[k]; // coordinates of neighbor square
      if(IsEnemy(xb, yb)) { // this is assumed to return 0 when (x, y) is off board
        m[n++] = xb; m[n++] = yb; // add a locust victim
        m[-2]++; // this counts the number of squares in the move
      }
    }
  }
  return OriginalScoreMove(m, last); // call the standard routine on the modified move
}

A. M. DeWitt wrote on Sat, Jan 28, 2023 01:35 AM UTC in reply to H. G. Muller from Fri Jan 27 09:13 PM:

Speaking of XBetza (or rather, IBetza), you should really consider adding a link to your page on Extended Betza Notation to this page. Even if it is not strictly identical to the one used in the interactive diagrams it would be a very helpful edition. I did check for a link to that page before writing this, and I couldn't find one.

The new definition for t could be quite interesting. It also helps that kc covers the old function of t (excluding capture of royals).


25 comments displayed

EarliestEarlier Reverse Order LaterLatest

Permalink to the exact comments currently displayed.