NAME

    Games::BinaryishTrees - 'Solve' a game on a set of binary(ish) trees.

SYNOPSIS

    use Games::BinaryishTrees;

    my $gbt = new Games::BinaryishTrees;
    $gbt->pair('a', 0, 1);
    $gbt->pair('b', 'c', 2);
    $gbt->pair('c', 0, 1);
    $gbt->pairset(['a', 'b', 'c']);
    my $solution = $gbt->solve();
    print map { "$_\n" } $solution->bestpaths();
    $solution->playgame();

DESCRIPTION

    Solve (who knows) a game on a set of binary-ish trees, (which may contain cycles) 

    A game consists of two players, Left and Right. Left can only move a counter left.

    Counters are placed on the pairs written in pairset()

    When a player arrives at a number, their total is increased by that number.

    A player may not repeat

    When using charts the words Left and Right indicate a favourable navigation.

    "the first place after" and "the second place" are clear first better.

METHODS

new()

    my $gbt = new Games::BinaryishTrees;

    Returns a new Games::BinaryishTrees object.

pair()

    $gbt->pair($id, $left, $right);

    Create a pair with id $id.

    $left and $right represent the outcomes of Left and Right playing on the pair.
    $left and $right can be either numbers or ids.
    An id must start with a letter but cannot contain ':', or be equal to 'P'.

    Also

    $gbt->pair($id, $left, $right, $leftstring, $rightstring);

    $leftstring and $rightstring are optional sillies for display in bestpaths()

    Also

    $gbt->pair($id, $left, $right, $leftstring, $rightstring, pook);
 
    Returns {$id => [$left, $right, $leftstring, $rightstring, $hashref]}

pairset()

    $gbt->pairset($arrayref);

    e.g. my $aref = ['a', 'b', 'c'];

    Set the set of pairs to be investigated with solve

option()

    $gbt->option($string, $id, @ids);

    Provides alternative moves for the given $id. $string is either 'left' or 'right'.

    eg

    $gbt->pair('f', 'a', 'b');
    $gbt->option('left', 'f', 'x', 'z');
    $gbt->solve(['f']);

    Now Left can play 'f', 'x' or 'z'. Once one has been played, the other ones become unavailable (unless f is visited again)

    $gbt->option('left', 'f', 'x');
    $gbt->option('left', 'x', 'z'); # left can play f or x

pook()

    pooks are an experimental feature. To use one, add a hash at the end of a pair eg:

    $gbt->pair('a', 0, 1, undef, undef, $hash); # (see pair)

    Where $hash is a hash reference. eg:

    my $hash = {'d' => ['b', 'c']};

    When 'a' is moved to, the pair with identifier 'd' becomes

    $gbt->pair('d', 'b', 'c');

solve()

    $gbt->solve($aref, @params);

    $aref is optional (see pairset())

    Finds a route through the set of pairs (ie tree) given by $aref. The route chosen assumes both players are trying to finish as unequally as possible (see setsweet(1).) 

    The top of each tree mentioned in solve is granted a counter.  

    @params (optional) provides 8 input parameters.

    Returns a Games::BinaryishTrees::Game object.

view()

    $gbt->view($arrayref);

    $gbt->view(); # is equivalent to $gbt->view([1, 1, 1, 1]);

    $arreyref turns on the display of pairs, options, pooks, and solve
    
    Returns a stringified representation of pairs, options, pooks and the set of pairs in solve.

    message(70) to message(75) to customise text to display.

viewpair()

    $gbt->viewpair($id);

    Returns a stringified representation of the pair.

listpairs()

    $gbt->listpairs();

    eg

    $gbt->pair('a', 0, 0);

    $gbt->pair('b', 1, 0);
 
    my $list = $gbt->listpairs(); # equivalent to my $list = {a => [0, 0], b => [1, 0]};

listoptions()

    $gbt->listoptions();

    returns a hash of the options. 1 and -1 are keys representing left and right

deletepair()

    $gbt->deletepair($id);

    Deletes pair $id, or all previously created pairs if $id is not defined.

    Returns 1

deleteoption()

    $gbt->deleteoption($string, $id, @ids);

    Deletes the list of ids from the option specified. $string is either 'left' or 'right'. Under the circumstances that no list is provided, all 'that' id's left or right options are deleted.
    For example,

    $gbt->option('left', 'a', 'b', 'c');
    $gbt->deleteoption('left', 'a', 'b');

    is equivalent to

    $gbt->option('left', 'c');

    to delete all options

    $gbt->deleteoption();

    Returns the option list for the option specified or undef.

history()

    $gbt->history($arrayref);

    eg $gbt->history(['a', 'b']);

    Add $arrayref to the history list. Any play that causes tree() to equal @$arrayref is now illegal.

    Returns an array containing the contents of history or undef if the list is empty.

    Note that for performance reasons, when the tree becomes twigless (see Bugs and Limitations), no history checks are done.

deletehistory()

    $gbt->deletehistory($arrayref);

    Delete $arrayref from the history list. If $arrayref is undefined all entries are deleted.

    Returns an array containing the contents of history or undef if the list is empty.

tojson()

    $gbt->tojson($arrayref);

    optinal $arrayref is the set of pairs given to $gbt->pairset($arrayref);

    Encode pair, options, pairset (aka tree), and parameters into JSON.

    Also provided are basic reading and writing utils $gbt->writefile() and $gbt->readfile()

    eg

    use Games::BinaryishTrees;
    use JSON::MaybeXS;
     
    my $gbt = new Games::BinaryishTrees;
    $gbt->pair('a', 1, 0);
    $gbt->pair('b', 0, 1);
    $gbt->pair('c', 10, 0);
    $gbt->pairset(['a', 'b', 'c']);    
    my $json = JSON::MaybeXS->new(pretty => 1, canonical => 1);
    $gbt->writefile("simple.json", $json->encode($gbt->tojson()));

fromjson()

    $gbt->fromjson($json, $hash);

    Read a JSON object

    eg

    $gbt->fromjson($json->decode($gbt->readfile("simple.json")));
    my $solution = $gbt->solve();

    optional $hash

    $hash = {overwrite => 1}; # default, overwrites pairs, options, pairset and settings

Language

seethis()

    $gbt->seethis($index);

    returns note with this index
    %s is stayorgo for translation isbest stay

findthis()

    $gbt->findthis($note);

    returns an array of indexes corresponding to $note

changethis()

    $gbt->changethis($index, $newnote);

    Note with $index changed to $newnote.

    To keep '+' and '-' the same in weight() do

    $gbt->changethis(42, '+');

    $gbt->changethis(66, '-');

    To add 'pass' to moves playable during a Game do

    $gbt->changethis(51, 'pass'); # 'pass' or 'P' may be played to pass.

    $gbt->changethis(68, 'pass'); # 'pass' is listed in moves().

    To change the first parameter of option do

    $gbt->changethis(49, $string); # $gbt->option($string, 'f', 'x'); # is ok (changes 'right')

    $gbt->changethis(50, $string); # $gbt->option($string, 'f', 'x'); # is also okish ...

    returns $newnote

findthismatch()

    $gbt->findthismatch($pattern);

    returns an array of indexes of messages matching $pattern. 

    $gbt->findthismatch('(?i)P'); # matches P and p

changethismatch()

    $gbt->changethismatch($pattern, $replacement);

    Looks through all messages and changes $pattern to $replacement.

    returns an array of indexes of messages changed

changethisones()

    $gbt->changethisones($note, @newnotes);

    $gbt->changethisones('one', 'two', 'three'); # finds all messages that match exactly 'one' and changes the first to 'two' and the second to 'three'

    my $blank = $gbt->changethis(67, '␢'); # leaves blank character as '␢'

    $gbt->changethisones('one', 'two', $blank, 'three'); # changes the first to 'two' and the third to 'three'

Parameters

setparams()

    $gbt->setparams(@params);

    Sets the parameters according to the contents of @params

    $gbt->setparams($a, $b, $c, $d, $e, $f, $g, $h, $i);

    is equivalent to

    $gbt->setstart($a);
    $gbt->setpass($b);
    $gbt->setpassplus($c);
    $gbt->setlmb($d);
    $gbt->setloop($e);
    $gbt->setpasstax($f);
    $gbt->setprocess($g);
    $gbt->setsweet($h);

    All parameters above default to 0 and return the value set, and when the value is defined, 
    sets the parameter to 1 or 0 depending on whether that value is true or false.

    $gbt->setstrategy($i);

    Defaults to undef

    Returns an array containing the parameter settings (useful to find value of setstrategy)

setstart()

    $gbt->setstart($value);

    If $value, Right starts, otherwise Left.

setpass()

    $gbt->setpass($value);

    If $value, passing is allowed, otherwise it is only allowed when no other moves are available.

setpassplus()

    $gbt->setpassplus($value);

    If $value == 0, passing is considered for every move (very slow), otherwise passing is only considered
    on 'poor' trees. A poor tree is one where the number for left moves first is less than or equal to the other one.
    No other trees are involved. The paths returned in the opposite case are a subset of those returned if $value == 0. Has no effect unless passing is allowed.
    With setsweet(1) both players can pass so P:P is returned. See Todo

setlmb()

    $gbt->setlmb($value);

    If $value, a player receives a point for making a move.
    A pass is considered a move, although this may change, in fact this whole option may be removed.

setloop()

    $gbt->setloop($value);

    If $value, a player recieves 1 of something for moving along a loop of 2 moves as far as i know.

setpasstax()

    $gbt->setpasstax($value);

    If $value, a player loses 1 point for each pass they make.

setprocess()

    $gbt->setprocess($value);

    If $value, look for a quick route to the answer
    playgame() and its associated functions may not work as expected on a set of trees solved this way.
    Has no effect with setsweet(1)

setsweet(1)

    $gbt->setsweet($value);

    solve() finds an answer that assumes that both players are trying to finish as unequally as possible, unless setsweet(1).

setstrategy()

    $gbt->setstrategy($value);

    eg

    $gbt->setstrategy(0); # general meanness pervades (setsweet(0))

    $gbt->setstrategy(1); # minimise differences (setsweet(1))

    $gbt->setstrategy(2); # more for everyone

    setstrategy($value) overrides setsweet($othervalue) when $value is defined

setwarningsoff()

    $gbt->setwarningsoff($value);

    If $value, warnings are not issued when a pair is redefined, deletehistory can't delete, deleteoption can't delete
    or a pook alters something previously undefined. 

Miscellaneous

diff()

    my $res = $gbt->diff($aref, @params);

    Takes an array refererence containing 2 trees and the same parameters as setparams. Returns :

    0 when the first tree is better

    1 when the second tree is better

    2 players prefer different trees

    3 neither is preferred

    4 the first is at least as good as the second

    5 the second is at least as good as the first

compare()

    my @array = $gbt->compare($aref, @params);

    Takes an array refererence containing trees and the same parameters as setparams. Compares each pair
    of trees using diff and returns a 2-dimensional array where @array[$i][$j] = diff[i-th tree][j-th tree] 

BUGS AND LIMITATIONS

if passing is enabled, but not passplus
    Cycles of the form ('i', a, 'j'), ('j', 'i', b) are considered poor if a <= 0 (should be if a + b <= 0).
    Adding something to a (it could be b as well) works in some cases.
performance
    The speed to solve slows (factorially) as more trees are added. Some optimisations are made to speed
    up the search when only twigless pairs remain (provided that passing is not enabled.)
options
    Options are not detected recursively, so options of options will not be found. Declare them in the option's array instead.
pooks
    Pooks awaiting further checks. 
    
    

Todo

    ? Make twigless code work when passing is enabled.

    ? setpasstax() and setlmb() could be put into game parameters, so the solution tree could be re-used and just minimaxed again.

    ? Generate a fingerprint for the tree (current position, weight and illegal moves). Stop making paths if the fingerprint has
    been seen, and place a marker/link so that minimax gets the values from the appropriate place.

    ? Allow a pook to add a pair -> change move loop to accommodate new pairs and update tree array. 
      Hmm, a pook creates a pair thats already been played, seems fishy.

    ? With setsweet(1) we have a minimum solution, setsweet(0) gives some sort of compromise where
      both players are being mean, a maximum can obtain itself from one sweet player playing a mean one.
      This "value" can be enquired within mnTree.pm -> handleminimax uncomment.
      Both players conspiring to maximise the other's score generates the opposite middle.

    ? setweet(1) sees -1 and 1 as the same solution, seeking -1 over 1 can be accommodated.

Games::BinaryishTrees::Game

playgame()

    $solution->playgame(@params);

    Play a game. You will be prompted to make a move.

    @params = ($a, $b, $c, $d, $e, $f, $g, $h);

    When $a is true, the computer moves first, otherwise you do (however see computermove())

    When any of the parameters b-f are set to true, the following is shown before your move -

    If $b, bestchoices()

    if $c, allchoices()

    if $d, legalchoices()

    if $e, weight()

    if $f, moves()

    if $g, sayagain move, this one is set to 1, the others are set to 0 

    if $h, viewtree()

    Whilst playing, if you enter a pair id, a move is made on that tree, and an answering move returned.

    If you enter a negative integer, it attempts to go back that number of moves. If the integer is too large or too small, it returns an error.

    Press 'return' swaps whoistomove whenyouonly.

    Enter 'quit' to exit the game. All games end following two consecutive passes (P) (see changethis())

    When there are no more moves to make, "Game over" is printed and playgame() exits.

    At exit, the array ($solution->score(), $solution->moves()) is returned.

computermove()

    $gbt->computermove($value);

    When $value true, play against the program. When $value false, ask someone else to play.

bestpaths()

    Returns an array of the best solutions. A solution is a colon separated list of moves. 
    A move is identified by its left or right string descriptor if defined, otherwise its pair id.

bestvalue()

    Returns the weight of the solution(s). With setsweet(1), the sign of the value is discardable.
    Use bestvaluesigned() otherwise. See Bugs and limitations

bestvaluesigned()

    $gbt->bestvaluesigned($path);

    Returns the weight of the path.

weight()

    Returns the net points gained or lost compared to best play. 

gamepaths()

    Like bestpaths but taking the position in the current game into account. 

tree()

    my @trees = $solution->tree();

    Returns an array of the set of trees at the current point in the game.

moveto()

    $solution->moveto($string);

    e.g. $solution->moveto('a:b:c');

    Moves the solver to the position following the move sequencein $string. Returns
    0 on failure, 1 on success. If string is undefined, it moves to the top of the solution tree.

bestchoices()

    Returns an array consisting of the best moves available.

    my @choices = $solution->bestchoices();

allchoices()

    Returns an array consisting of all moves available (including illegal ones).

    my @choices = $solution->allchoices();

legalchoices()

    Returns an array consisting of all the legal moves available.

    my @choices = $solution->legalchoices();

moves()

    Returns a colon separated list of the moves made in the current game.

    my $game = $solution->moves();

viewtree()

    $solution->viewtree(@array);

    Returns a string containing the pairs in @array, shown accordingly as view([1])

    eg

    my @choices = $solution->allchoices();
    print $solution->viewtree(@choices);

newgame()

    Resets the moves made in the current game to the empty string.

    $solution->newgame();

verbose()

    $solution->verbose($value);

    If $value, bestpaths() displays $leftstring or $rightstring if defined in $gbt->pair(), otherwise a default verbose string is generated.
    If not $value, bestpaths() displays the id. See below for a bug.

changethisnumber()

    $solution->changethisnumber ($index, $newnumber);

    $index is unaffected by changethisnumber, so

    $solution->changethisnumber(1, 0);
    $solution->changethisnumber(0, 2);

    changes 101 to 020

    returns $newnumber when $index is a digit from 0 to 9
    otherwise returns message 58

changeallnumbers()

    $solution->changeallnumbers ($value, $hashref);

    my $hashref = {above => $stringabove, below => $stringbelow, equal => $stringequal};

    When $value is true, numbers > 0 are replaced by $stringabove and numbers < 0 are replaced by $stringbelow. And $stringequal when is zero.

    There is other way for special ones ...

    $hashref->{5} = $symbol; # shows -5 as -5
 
    Set $value to 0 to recover.

BUGS AND LIMITATIONS

changethisnumber()

A better name is changethisdigit. Then we could have changethisnumber(10, "ꊰ"), see Numerals in Yi. See changeallnumbers().

bestvalue()

Place '+' with '3' then find '-' ? Usual way is find '-' and use '+' for special case.

The sign is opposite to that of weight() with setsweet(0).

verbose(1)

calling solve() returns a new Game object (with default verbose(0)), so verbose(1) would need to be set again.

setprocess(1)

Some parts of the solution space are unexplored, so a random move is returned during playgame if you do not play an 'optimal' move. Similarly bestchoices() will be empty, and weight() "unknown".

duplicate options

If you

$gbt->pair('a', 1, 'c');

$gbt->pair('b', 'c', 0);

$gbt->pair('c', 1, 0);

$gbt->solve('a', 'b', 'c');

then a play 'c' may refer to more than one possible move. If so, one will be chosen for you. Avoid this by

$gbt->pair('a', 1, 'c');

$gbt->pair('b', 'c1', 0);

$gbt->pair('c', 1, 0);

$gbt->pair('c1', 1, 0);

$gbt->solve('a', 'b', 'c');