Check out Glinski's Hexagonal Chess, our featured variant for May, 2024.


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

Comments/Ratings for a Single Item

Later Reverse Order EarlierEarliest
The Fairychess Include File Tutorial. How to use the fairychess include file to program games for Game Courier.[All Comments] [Add Comment or Rating]
🕸📝Fergus Duniho wrote on Sun, May 12 10:20 PM EDT in reply to Fergus Duniho from 06:29 PM:

I was thinking of splitting the checked function into separate checked-real and checked-potential functions, but as I was looking into which would be which, it looked like there was never any time when it was passed an empty space after the King just moved. In the Pre-Move sections, kpos or Kpos would be updated before calling the function, and in stalemated, it would check whether the King was the moving piece and pass the King's new position if it was. This meant I could reduce the function to this:

def checked anytrue lambda (fn const alias #0 var key var king) 
cond isupper space var king (onlylower) (onlyupper) 
=movetype CHECK 
=king;

Just in case, I ran this code in Ultima as a test:

sub checked king:
  my from piece;
  local movetype;

  set movetype CHECK;
  if empty var king:
    die "The King space at {#king} is empty.";
  endif;
  if isupper cond empty var king $moved space var king:
    def enemies onlylower;
  else:
    def enemies onlyupper;
  endif;
  for (from piece) fn enemies:
    if fn const alias #piece #from var king:
      return #from;
    endif;
  next;
  return false;
endsub;
def checked sub checked #0;

This code would exit right away with an error message if the subroutine, which was called by the function here, got passed an empty space. I then looked at completed games using the same include file, and they did not exit with the error message. So, I got rid of this code and modified the function, tested Ultima again, and it still worked for both actual checks and for moves that would move the King into check.

With that change made, I ran the speed tests again and got these results:

Elapsed time: 1.0438919067383 seconds

Elapsed time: 0.46452307701111 seconds

Elapsed time: 1.013090133667 seconds

Elapsed time: 0.48957395553589 seconds

Elapsed time: 1.1313791275024 seconds

Elapsed time: 0.49660110473633 seconds

In each pair the subroutine is first, and the function is second, and in each case the function takes less than half the time. In both this case and the previous one, these tests were done on the opening position in Chess, in which the King is not in check, meaning that it checks for check from every enemy piece without exiting early.


🕸📝Fergus Duniho wrote on Sun, May 12 06:29 PM EDT:

The changes to Game Courier I described here were for the purpose of writing a checked function that would do the same job as the checked subroutine but even faster. This is for the sake of illustrating the gains in speed of using functions rather than subroutines when a function will do the job. Here's the code for the subroutine and the function:

sub checked king:
    my from piece;
    local movetype;

    set movetype CHECK;
    if isupper cond empty var king $moved space var king:
        def enemies onlylower;
    else:
        def enemies onlyupper;
    endif;
    for (from piece) fn enemies:
        if fn const alias #piece #from var king:
            return #from;
        endif;
    next;
    return false;
endsub;

def checked anytrue lambda (fn const alias #0 var key var king) 
cond isupper cond empty var king $moved space var king (onlylower) (onlyupper) 
=movetype CHECK 
=king;

I have tested the function out in Ultima, because its stalemated subroutine is not widely used in other presets, and its piece functions make use of the movetype value to determine whether it is a regular move or a checking move. Things appear to be working in Ultima. In speed tests comparing 1000 repetitions of each, the function is around twice as fast. In these results, the first, third, and fifth are the subroutine, and the second, fourth, and sixth are the function.

Elapsed time: 0.92847084999084 seconds

Elapsed time: 0.45569705963135 seconds

Elapsed time: 0.87418103218079 seconds

Elapsed time: 0.46827101707458 seconds

Elapsed time: 1.1367251873016 seconds

Elapsed time: 0.65254402160645 seconds

🕸📝Fergus Duniho wrote on Wed, Apr 10 09:02 PM EDT in reply to Daniel Zacharias from 07:51 PM:

I still don't get why the previous way wasn't working since I wasn't changing the pawn subroutine which seems to modify promotion options before the last rank.

The Pawn subroutines are only for actual moves, but nothing was being done to handle variable values of wprom or bprom for potential moves. The stalemated subroutine was supposed to provide a list of all legal moves, but it was omitting moves where someone declines promoting a Pawn. By having functions that dynamically calculate what a piece may promote to, it is now able to provide an accurate list of legal moves for games with variable promotion rules, such as Gross Chess has.

Anyway, this reveals another, but more minor, problem, which is that when a pawn gets to the promotion zone the text in the option selection box is large enough that the lines overlap slightly.

I increased the line-height and made some changes to the borders.


🕸📝Fergus Duniho wrote on Wed, Apr 10 08:33 PM EDT in reply to Fergus Duniho from 03:57 PM:

For Obento Chess, you might use functions like these:

def White_Pawn-Promote elem - rank #0 9 ((FP) (FP) (F));
def Black_Pawn-Promote elem rank #0 ((f) (fp) (fp));

That should be this:

def White_Pawn-Promote elem - rank #0 9 ((F P) (F P) (F));
def Black_Pawn-Promote elem rank #0 ((f) (f p) (f p));

Daniel Zacharias wrote on Wed, Apr 10 07:51 PM EDT in reply to Fergus Duniho from 03:57 PM:

Thank you, that does work better; although I still don't get why the previous way wasn't working since I wasn't changing the pawn subroutine which seems to modify promotion options before the last rank.

Anyway, this reveals another, but more minor, problem, which is that when a pawn gets to the promotion zone the text in the option selection box is large enough that the lines overlap slightly.


🕸📝Fergus Duniho wrote on Wed, Apr 10 03:57 PM EDT in reply to Daniel Zacharias from 09:28 AM:

In looking into this, I tested Gross Chess to see if it had the same problem, but before I could tell, I encountered another problem with it. I realized that for a game like Gross Chess it wouldn't do to use bprom and wprom as though they had static values. So I rewrote the fairychess include file and the Gross Chess code to support dynamic values for what a piece is allowed to promote to on a given space.

This makes use of some new functions that end with "-Promote". Here are the default functions for the Pawns:

def White_Pawn-Promote var wprom;
def Black_Pawn-Promote var bprom;

For backwards compatibility with the original way of handling promotions, these just return the value of wprom or bprom. And for additional backwards compatibility, the stalemated subroutine will use these functions only if the piece is not in the promotable array. So, to enable the use of these functions for providing dynamic values for what a piece can promote to, you should unset promotable or set it to an empty array. I added this line to Gross Chess after including the fairychess include file.

unset promotable;

Since the default functions return static values, they need to be rewritten for the particular game they are for. Here are the functions I wrote for Gross Chess:

def White_Pawn-Promote merge intersection var cap elem - rank #0 9 ((B N V W) (B N V W C R S) (B N V W C R S A M Q)) elem - rank #0 9 ((P) (P));
def Black_Pawn-Promote merge intersection var cap elem rank #0 ((b n v w c r s a m q) (b n v w c r s) (b n v w)) elem rank #0 (() (p) (p));

Since what a Pawn may promote to in Gross Chess depends upon the rank it is on, I used the rank value for black (or a value calculated from the rank value for white) as the index for a couple of arrays from which it extracted a particular value. For example, black can promote on ranks 0-2, as they are designated internally. So, this code will return the element of the array with the same index as the rank value:

elem rank #0 ((b n v w c r s a m q) (b n v w c r s) (b n v w))

Since white promotes on ranks 9-11, I subtracted 9 to get a value from 0 to 2 for any rank promotions are allowed on or a number that is out of range for any other rank. So, this works similarly:

elem - rank #0 9 ((B N V W) (B N V W C R S) (B N V W C R S A M Q))

Since the last rank for black is 0, and the last for white is 11, and 11-9 is 2, these list sets of promotion options in the reverse order from each other.

Since promotion options are limited to captured pieces, each function calculates the intersection of the value above with the captured pieces. This looks like this for black:

intersection var cap elem rank #0 ((b n v w c r s a m q) (b n v w c r s) (b n v w))

Finally, I get to the part that is relevant to Obento Chess. Whether it can promote to a Pawn as a way of declining promotion depends on the rank but not on what has been captured. So Pawns were not included in the main lists of promotion options. Instead, it merges the intersection calculated above with the value of another array element. Again, the specific array element is a function of the rank. Here is what it looks like for black:

elem rank #0 (() (p) (p))

Since declining promotion is not an option for black on rank 0, an empty array is provided for the element with an index of 0. This is not necessary for white, as the rank it cannot decline promotion on has a higher index.

elem - rank #0 9 ((P) (P))

For Obento Chess, you might use functions like these:

def White_Pawn-Promote elem - rank #0 9 ((FP) (FP) (F));
def Black_Pawn-Promote elem rank #0 ((f) (fp) (fp));

You could handle promotion for the other promotable pieces with similar functions for each specific piece.


Daniel Zacharias wrote on Wed, Apr 10 09:28 AM EDT in reply to Fergus Duniho from 08:56 AM:

I did copy from Gross chess. The descriptions should be fixed now


🕸📝Fergus Duniho wrote on Wed, Apr 10 08:56 AM EDT in reply to Daniel Zacharias from Tue Apr 9 11:56 PM:

Start by accurately writing the promotion rules in English. It looks like you copied the rules from Gross Chess even though Pawns in Obento Chess promote only to Flying Ox. I’ll check it out later when I’m on my desktop.


Daniel Zacharias wrote on Tue, Apr 9 11:56 PM EDT:

I'm having trouble with pawn promotion in this preset. I've set wprom and bprom and promotion does work, but it's not being optional like I expect. Instead it just auto promotes pawns as soon as they reach the promotion zone. As far as I can tell, there should be a promotion choice before the last rank even if there's only one item in wprom.


🕸📝Fergus Duniho wrote on Tue, Apr 9 12:41 PM EDT in reply to Daniel Zacharias from 11:16 AM:

That's now fixed. Thanks.


Daniel Zacharias wrote on Tue, Apr 9 11:16 AM EDT:

There's a small mistake in fairychess. Wizard-Desc says "The %s Wizard may move" which would result in duplicating the piece name if shown.


🕸📝Fergus Duniho wrote on Mon, Oct 16, 2023 09:44 PM EDT in reply to Daniel Zacharias from 06:52 PM:

It looks like you are misusing aliases. Aliases should not be set between names and labels. They should be set between names and codenames or between labels and notation. Look at the Piece Definitions and Piece Names sections, including the hidden details you need to click on to read.


Daniel Zacharias wrote on Mon, Oct 16, 2023 06:52 PM EDT:

I've been working on this game but something is wrong with the kings and I can't find the problem. Whenever I try to move a king I get an error like this.

There was no K on e1. The piece on e1 is a K.


🕸📝Fergus Duniho wrote on Thu, Feb 9, 2023 09:32 AM EST in reply to Daniel Zacharias from 12:44 AM:

Does anything look amiss here?

   2 setconst T Tusker
   3 setconst t Tusker
   4 setconst J JumpingGeneral
   5 setconst j JumpingGeneral
   6 setconst M Minister
   7 setconst m Minister
   8 setconst H HighPriestess
   9 setconst h HighPriestess
  10 setconst E Elephant
  11 setconst e Elephant
  12 setconst Y WarMachine
  13 setconst y WarMachine
  14 setconst L BattleEngine
  15 setconst l BattleEngine
  16 setconst K King
  17 setconst k King
  18 setconst P White_Pawn
setconst p Black_Pawn
  19 setconst G Guard
  20 setconst g Guard
  21 setconst N Knight
  22 setconst n Knight

You're missing a semicolon after one line.


Daniel Zacharias wrote on Thu, Feb 9, 2023 12:44 AM EST:

I'm trying to use the fairychess include file to enforce rules with this preset and it's giving an error I can't figure out.

The fn built-in function has not been given a valid function name or lambda function.

As well as I can tell, I have provided the proper functions for every piece.


Daniel Zacharias wrote on Thu, Jan 12, 2023 04:51 PM EST in reply to Fergus Duniho from 12:55 PM:

There was some delay in fixing betza.txt, because it is not in the includes directory, and I didn't see the bug report on it until my computer was off

That's my fault I suppose. I should have mentioned it together with the other problems.


🕸📝Fergus Duniho wrote on Thu, Jan 12, 2023 12:55 PM EST in reply to H. G. Muller from 03:42 AM:

Very nice that you can simplify your stalemate routine in the fairychess include file

It was more extensive than one routine, and it wasn't about simplifying it. As my previous comment indicated, this was about removing the cause of error in that routine and others with similar code. The problem was that using two separate variables for storing the legal moves had allowed bugs to creep into existing code.

but please don't do it in such a way that it breaks presets of others through changing the underlying behaviour of GAME code or Game Courier.

I kept on top of what needed to be fixed, and I fixed everything in a timely manner. There was some delay in fixing betza.txt, because it is not in the includes directory, and I didn't see the bug report on it until my computer was off, and I was in bed with my Likebook Mars. Now that you're an editor, I suggest that you move betza.txt to the /includes/ directory and replace the other ones with stubs that just include it from the includes directory.

So if you want an array that contains the legal moves as text rather than coordinates, and also contains special moves, just give it a new name, and use it by that name in the user code

What I wanted was for the $legalmoves array to contain all the legal moves, because various stalemated subroutines were using setlegal to build a list of legal moves and then checking whether there were any legal moves by checking the count of $legalmoves with a line like this:

return cond count system legalmoves false true;

The bug I described earlier was caused by splitting the legal moves between two variables. The $extralegal array was a kludge I created for the sake of storing some legal moves as strings while storing other legal moves as coordinate pairs. However, I was combining them into a new array called $legalList for use with forms and JavaScript, and this array listed all legal moves as strings. Creating another array would not have helped with anything.


H. G. Muller wrote on Thu, Jan 12, 2023 03:42 AM EST:

@Fergus: Very nice that you can simplify your stalemate routine in the fairychess include file, but please don't do it in such a way that it breaks presets of others through changing the underlying behaviour of GAME code or Game Courier. If you do not maintain backward compatibility at all times, you are bound to break stuff.

So if you want an array that contains the legal moves as text rather than coordinates, and also contains special moves, just give it a new name, and use it by that name in the user code you want to simplify, so that existing presets that use the method you want to deprecate continue to function.


🕸📝Fergus Duniho wrote on Wed, Jan 11, 2023 01:27 PM EST:

Because GAME Code was storing legal moves in two separate variables, and I wasn't always keeping this in mind, the stalemated subroutine in the fairychess include file was returning the wrong result for positions like this one:

It was claiming this was checkmate when the check could be prevented by capturing the Queen with a Pawn or blocking with one on g1. This was because the Pawn moves, which included a promotion, were being stored in $extralegal instead of in $legalmoves, which stored legal moves only as coordinate pairs, and the stalemated subroutine, as well as other similar subroutines, checked only the value of $legalmoves. To keep things simpler, I abolished the $extralegal variable, and I stored all legal moves in $legalmoves as strings using notation, and not as arrays of coordinates. With this change made, I was able to slim down the findmates subroutine to this:

// Finds each mating move in current position.
// Parameters:
// side: which side is moving
sub findmates side:
    local enemyking king mates moves mv;

    if match #side 1 white White first:
        set king #Kpos;
        set enemyking #kpos;
    else:
        set king #kpos;
        set enemyking #Kpos;
    endif;
    set mates ();
    ban none;
    setsystem maxmove 0;
    store main;
    setsystem legalmoves ();
    if not sub stalemated #king:
        set lglmvs $legalmoves;
        foreach move #lglmvs:
            set moves explode chr 59 #move;
            foreach mv #moves:
                set mv trim #mv;
                eval "MOVE: {#mv}";
            next;
            if sub checked #enemyking:
                setsystem legalmoves ();
                if sub stalemated #enemyking:
                    push mates #move;
                endif;
            endif;
            restore main;
        next;
    endif;
    setsystem legalmoves #mates;
endsub;

I did notice and correct one problem after this. It was recording some moves as belonging to the @ piece, which is the piece symbol used for empty spaces. This was because it was running the function for converting to notation as soon as setlegal was called, which happened to come before restoring the position. To correct for this, I had it get the piece type from the destination space if the origin space was empty, and if both were empty to not include the piece type. In the future, it would help to call setlegal only after restoring the position or to explicitly spell out the move if it will be anything out of the ordinary.


🕸📝Fergus Duniho wrote on Tue, Jan 10, 2023 09:49 PM EST:

I have now written a subroutine that can solve mate-in-one problems for games using the checked and stalemated subroutine in the fairychess include file. When you compose a mate-in-one problem and click on Solve, it will tell you whether it has found a single solution to your problem and what it is. If it has, it will be the only legal move displayed. After making the move, you can click on Compose to enter the solution into your composition. Here is the subroutine:

// Finds each mating move in current position.
// Parameters:
// side: which side is moving
sub findmates side:
    local enemyking extramates king mates moves mv;

    setsystem showoutput true;
    if match #side 1 white White first:
        set king #Kpos;
        set enemyking #kpos;
    else:
        set king #kpos;
        set enemyking #Kpos;
    endif;
    set mates ();
    set extramates ();
    ban none;
    setsystem maxmove 0;
    store main;
    setsystem legalmoves ();
    if not sub stalemated #king:
        set lglmvs $legalmoves;
        foreach move #lglmvs:
            move #move.0 #move.1;
            if sub checked #enemyking:
                setsystem legalmoves ();
                if sub stalemated #enemyking:
                    push mates #move;
                endif;
            endif;
            restore main;
        next;
        set extmvs ();
        if isarray $extralegal:
            set extmvs unique $extralegal;
        endif;
        foreach move #extmvs:
            set moves explode chr 59 #move;
            foreach mv #moves:
                set mv trim #mv;
                eval "MOVE: {#mv}";
            next;
            if sub checked #enemyking:
                setsystem legalmoves ();
                if sub stalemated #enemyking:
                    push extramates #move;
                endif;
            endif;
            restore main;
        next;
    endif;
    setsystem legalmoves #mates;
    setsystem extralegal #extramates;
endsub;

This works by trying all legal moves. For each move, it first tests whether it checks the enemy King, and if it does, it additionally checks if it leaves the opponent with no legal moves. When it's finished, it writes its solutions to the variables that will be used to create the $legalList array, which will be used to display legal moves and populate the Moves field with possible values to enter with it.

Since the $legalList variable is not populated until after the GAME Code program has finished running, this sets $legalmoves and $extralegal separately. The $legalmoves array lists legal moves as coordinate pairs, and the $extralegal array lists them as notation, which is helpful when a move includes more than just a single move from one space to another.

One of the challenges in writing this was figuring out how to execute a move written as notation. When I initially tried it, it wouldn't allow it. What I eventually did was set $maxmoves to zero and turn off all bans. Since this subroutine is run at the very end of the GAME Code program, this doesn't hurt anything. This let me execute the move with the eval command.


🕸📝Fergus Duniho wrote on Tue, Jan 10, 2023 09:26 PM EST in reply to Daniel Zacharias from 07:18 PM:

Would the fairychess include file work with hexagonal boards, or might there be difficulties with that?

The main difficulty is that the piece definitions in the include file are designed for boards with square spaces. If you wrote new piece definitions for hexagonal boards, it may work for hexagonal boards.


Daniel Zacharias wrote on Tue, Jan 10, 2023 07:18 PM EST:

Would the fairychess include file work with hexagonal boards, or might there be difficulties with that?


🕸📝Fergus Duniho wrote on Tue, Jan 10, 2023 01:55 PM EST:

I just modified the stalemated subroutine to list each Pawn promotion as a separate legal move. This will affect how Pawn promotions are handled. Instead of loading another page with an image of each piece, it will pop up a requester asking you which move you want to make. To accommodate this, the promotion move identifies the space by its coordinate and not by the keyword dest.

To make use of this for promotable pieces besides Pawns, you need to set the value of promotable to a list of your promotable pieces. If you don't, your code should simply work as it has previously worked before, still going to another page to ask what to promote the piece to.

I have done this for the sake of getting a complete list of legal moves for the purpose of using it with code for finding the solutions to mate-in-one problems. In time, I hope to do mate-in-two problems as well, but I have to start with what is simpler.

For now, the stalemated subroutine does not check whether each promotion is separately legal. This could matter if you had Korean Cannons in your game, because this piece captures by hopping over another piece but cannot hop over another Korean Cannon. In that case, what you promoted to could affect whether a promotion would put your King in check. But there is no possibility of this making a difference in Chess and most Chess variants, in which any possible promotion is equally legal. To avoid complication, the simpler scenario is assumed.

Here is the new subroutine:

// Goes through all possible moves, putting all legal moves into the array $legalmoves
// Returns false if any legal moves are found, and returns true if none are found.

sub stalemated kingpos:
    store;
    local cspaces friend friends from piece to movetype np prom;

    set movetype MOVE;

    if hasupper space #kingpos:
        // Array of pieces on same side
        set friends lambda (onlyupper);
        // Indicates whether a space is free to move to
        set free lambda (haslower #0 or not hasupper #0);
        set pzone lambda (not onboard where #0 0 var pzs);
        set cspaces var wcastle;
        set prom var wprom;
    else:
        set friends lambda (onlylower);
        set free lambda (hasupper #0 or not haslower #0);
        set pzone lambda (not onboard where #0 0 neg var pzs);
        set cspaces var bcastle;
        set prom var bprom;
    endif;

    // While the royal piece is called the King in these comments,
    // it may be any piece. These variables determine what the royal piece is.
    set royal space var kingpos;

    store;

 // Can any piece legally move?
    for (from piece) fn #friends:
        for to fn join const alias #piece "-Range" #from:
            if fn const alias #piece #from #to and fn #free alias space #to and onboard #to:
                move #from #to;
                if not sub checked cond == #from #kingpos #to #kingpos:
                    if fn #pzone #to and match #piece #promotable:
                        for np var prom:
                            setlegal "{#piece} {#from}-{#to}; {#np}-{#to}";
                        next;
                    else:
                        setlegal #from #to;
                    endif;
                endif;
            endif;
            restore;
        next;
    next;

    // Castling moves are handled separately
    if > count var cspaces 0:
        for to var cspaces:
            if sub castlepos #kingpos #to:
                setlegal #kingpos #to;
            endif;
        next;
    endif;

    setsystem autorules sub describe_rules;

    // All done. Set $legalmoves and return;
    return cond count system legalmoves false true;
endsub;

Jean-Louis Cazaux wrote on Sat, Sep 24, 2022 06:17 PM EDT:

Would it be possible to add a Wildebeest (=Knight+Camel) to this fairychess? Thank you


🕸📝Fergus Duniho wrote on Wed, Apr 6, 2022 10:25 PM EDT:

I am currently in the middle of revising this tutorial. I have added a lot of new and updated content already, and the original material follows the revised text. I expect to continue it tomorrow.


25 comments displayed

Later Reverse Order EarlierEarliest

Permalink to the exact comments currently displayed.