DOMINOES

Some slightly disappointing news on the front of Jamaican-Style Dominoes! The Ludeme for Dominoes is sort of particular, intended for use in the Boneyard variant. Line 161 of the Deal Ludeme, that deals the dominoes, has the block:

	while (dealed < (count * 2))
	{
		final int index = context.rng().nextInt(toDeal.size());
		final int indexComponent = toDeal.getQuick(index);
		final Component component = components[indexComponent];
		final int currentPlayer = dealed % nbPlayers;
		Start.placePieces(context, handIndex.getQuick(currentPlayer) + (dealed / nbPlayers), component.index(), 1,
				Constants.OFF, Constants.OFF, Constants.UNDEFINED, false,
				SiteType.Cell);
		toDeal.removeAt(index);
		dealed++;
	}

What this loop does is deal the amount of dominoes specified, times 2. Jamaican-Style is essentially four player, and in general, it’d be nice to have this work for any amount of players. Luckily, this quick fix solves that issue.

	while (dealed < (count * context.game().players().count()))
	{
		final int index = context.rng().nextInt(toDeal.size());
		final int indexComponent = toDeal.getQuick(index);
		final Component component = components[indexComponent];
		final int currentPlayer = dealed % nbPlayers;
		Start.placePieces(context, handIndex.getQuick(currentPlayer) + (dealed / nbPlayers), component.index(), 1,
				Constants.OFF, Constants.OFF, Constants.UNDEFINED, false,
				SiteType.Cell);
		toDeal.removeAt(index);
		dealed++;
	}

This works fine and dandy. The problem is, Ludii uses a particular Ludeme for the “LineOfPlay” in a domino game. I have not been able to find a solution for using the available moves in this variant, filtering some possible options from the total line of play. Nevertheless,


AI

The AI method in Ludii has some curious options. Throughout, there are references that I don’t yet fully get to whether or not AIs “want to cheat”. AIs declare their intentions via boolean, and in line 326, this happens.

public void setWantsCheatRNG(final boolean wantsCheat)
{
	wantsCheatRNG = wantsCheat;
	
	if (wantsCheatRNG)
		contextCopyer = RNG_CHEAT_COPY;
	else
		contextCopyer = STANDARD_CONTEXT_COPY;
}

A “context” is a list of objects and addresses that conveys to a computer all available information of a game position. Potentially, this means the computer knows what order the deck is in, no matter what data structure we use. Uh-oh! Ludii, if one selects a “general AI”, holds a little tournament over the course of a couple seconds with all of its agents, determining who’s the best at playing whatever .lud file is put in. An agent with the “unfair advantage,” able to cheat, will usually win out, and face the human.

This is a problem if one is trying to analyze the way card games behave. I’m experimenting with some solutions here both dumb and less dumb, and will report back with what data I collect from removing this option, which I will note, does not appear to break the engine. At the very least, replacing the above method with the below method compiles normally.

public void setWantsCheatRNG(final boolean wantsCheat)
{
	wantsCheatRNG = wantsCheat;
	
	if (wantsCheatRNG)
		contextCopyer = STANDARD_CONTEXT_COPY;
	else
		contextCopyer = STANDARD_CONTEXT_COPY;
}

I will note that both of these frameworks perform similarly at Battleship, neither going off like its cheating. This is not the post with charts, though.


DECK/STACK

I can now prove the hypothesis I stated in the last blog entry. Behold, a snippet beginning at line 2715 of Game.java.

	// Place randomly the cards of the deck in the game.
	for (final Deck d : context.game().handDeck())
	{
		final TIntArrayList components = new TIntArrayList(d.indexComponent());
		final int nbCards = components.size();

		for (int i = 0; i < nbCards; i++)
		{
			final int j = (context.rng().nextInt(components.size()));
			final int index = components.getQuick(j);
			final StartRule rule = new PlaceCustomStack("Card" + index, null, d.name(), SiteType.Cell, null,
					null, null, null, null, null);
			components.remove(index);
			rule.eval(context);
		}
	}

The final action taken when making a deck, after acquiring a permution, is to place a custom stack, that looks something like {Card37 Card12 Card50 Card9 …}. Thus, hiding anything at all, even something that’s not in the stack, crashes the whole system just like with a normal stack. The below code, in the Ludii editor, has a little green circle, meaning there aren’t syntax errors.

(game "DECK"
	(players 4)
	(equipment
		{
			(board (square 10))
			(deck)
			(hand Each size:6)
		}
	)
	(rules
		(start { 
			(deal Cards 6) 
		
		}        )
		(play (move (from (sites Hand Mover)) (to (sites Empty))))
		(end (if (is Loop) (result Mover Win)))
	)
)

It compiles, but (despite I’ve done plenty of experimenting with graphics metadata), I cannot get the cards to render. If I add the below line, the green dot remains, and everything should be fine.

(game "DECK"
	(players 4)
	(equipment
		{
			(board (square 10))
			(deck)
			(hand Each size:6)
		}
	)
	(rules
		(start { 
			(deal Cards 6) 
		
		}        )
		(play (move (from (sites Hand Mover)) (to (sites Empty))  (then (set Hidden What at:(coord "A1") to:P1))))
		(end (if (is Loop) (result Mover Win)))
	)
)

Everything is not fine; the system proceeds to have a glitch attack. The stack trace is as follows.

	java.lang.ArrayIndexOutOfBoundsException: Index 0 out of bounds for length 0
		at gnu.trove.list.array.TIntArrayList.getQuick(TIntArrayList.java:305)
		at main.collections.ListStack.isHidden(ListStack.java:745)
		at other.state.stacking.ContainerStateStacksLarge.isHidden(ContainerStateStacksLarge.java:579)
		at other.action.move.ActionAdd.apply(ActionAdd.java:269)
		at game.rules.start.Start.placePieces(Start.java:113)
		at game.rules.start.place.stack.PlaceCustomStack.eval(PlaceCustomStack.java:195)
		at game.Game.start(Game.java:2728)

The penultimate part seems to be where things go south, but I don’t yet know for sure. Getting stacks and decks to function, it would appear, is the same task. I’m working on this:

public boolean isHidden(final int pid, final int level)
{
	if (this.hidden != null && level < size)
		return hidden.get(pid).getQuick(level) == 1;

	return false;
}