<nocvs/playerAPI/overview.html
>=
<html><head></head><body>
<p>Eagle's Wing is a single-deck solitaire card game. The objective is to sort
the deck by suit into four piles, each pile ordered by increasing rank, kings
wrapping around to aces.</p>
<p>The game layout has fifteen piles: four <strong>foundation piles</strong>
(refered to in the previous paragraph), eight <strong>wing piles</strong>,
and <strong>trunk</strong>, <strong>stock</strong>, and <strong>waste</strong>
piles. Cards in a foundation pile match in suit and increase in rank, kings
wrapping around to aces. Cards in a wing pile match in suit and decrease in
rank, aces wrapping to kings. A wing pile has at most three cards.</p>
<p>At the start of the game one foundation pile contains one face-up card,
known as the <strong>foundation card</strong>. The stock contains thirteen
cards, all face-down except for the top card, which is face up. Each wing pile
has a single card. The waste pile is empty and the stock contains the rest of
the cards (thirty of them), all but the top card face down.</p>
<p>A card can be moved from any pile to the top of a foundation pile as long as
the card matchs in suit and increases sequentially in rank with top card
already in the foundation pile. The first card in an empty foundation pile
must match in rank with the foundation card. The game ends in a win when the
foundation piles contain all the cards. Once a card is moved to a foundation
pile, it is not moved again.</p>
<p>In addition to the foundation piles, the top card on the trunk can be moved
to a wing pile as long as the card matches in suit with cards already in the
wing pile, the cards rand descends sequentially from the wing-pile top card,
and the wing pile has less than three cards. The top trunk card can always be
moved to an empty wing pile. No other moves from the trunk are legal.</p>
<p>Cards in a wing pile can be moved to a foundation pile as long as the suits
match and the wing cards sequentially increase in rank from the top
foundation-pile card or the foundation pile's empty and the top wing-pile card
matches the foundation rank. Wing card are moved to a foundation pile in
top-to-bottom order; for example the wing pile <code>5h 4h 3h</code> (top card
to the right) moves to the wing pile <code>2h</code> to give </code>2h 3h 4h
5h</code>. One wing pile can be moved to another wing pile as long as cards in
the source wing pile match in suit the cards in the destination pile, the
source wing-pile cards sequentially descend from the destination wing-pile
cards, and sum of the number of cards in both pile is at most three. No other
moves from wing piles are legal.<p>
<p>The top card on the stock is in play, and can be moved to the top of the
waste pile at any time, as well as the foundation subject to the constraints on
those piles. Once the trunk is empty, moves to the wing piles are allowed too.
Moves to the trunk are not allowed.</p>
<p>The top card on the waste pile can be moved to foundation piles subject to
the constraints on those piles; moves to the wing piles are also allowed once
the trunk is empty. Moves to the trunk are not allowed. If the stock is
empty, the cards in the waste pile can be moved to the stock in
a <strong>redeal</strong>, emptying the waste pile. There are at most two
redeals per game.</p>
<p>The game ends when no more moves are possible. If all the cards are in
foundation piles, the game's a win, otherwise it's a loss. Eagle's Wing
requires more recognition than skill, and it should be easy to win 80% or more
of the games played.</p>
</body></html>
Player API
The playerAPI package contains all the classes needed to write further code for
playing Eagle's Wing games. A majority of the classes are interfaces or enums,
but Eagle's Wing itself is an abstract class that uses a simple factory to
create game instances.
<nocvs/playerAPI/package.html
>=
<html><head></head><body>
</body></html>
An Eagle's Wing Game
The Eagle's Wing game interface is implemented as an abstract class to isolate
the implementation parts from the interface parts (which was not completely
successful).
<nocvs/playerAPI/EaglesWing.java
>=
package playerAPI
public abstract class
EaglesWing
/**
Get a card pile's bottom (base) card.
@param pile The card pile of interest.
@return The bottom card in the given pile or <code>null</code> if the
given pile's empty.
*/
public abstract Card bottomCard(CardPiles pile)
/**
Get a pile's cards.
@param pile The card pile of interest.
@return The cards in the pile, bottom card at index 0. The array is sized
to the number of cards actually in the pile, not the number of cards that
could be in the pile.
*/
public abstract Card [] cardsInPile(CardPiles pile)
/**
Determine if this game's been won.
@return True iff this game's current configuration is a winning game.
*/
public abstract boolean
gameWon()
/**
Perform a move between two card piles in this game. The move to be made
depends on the type of the piles involved in the move.
@param from The card pile from which the move is being made.
@param to The card pile to which the move is being made.
@return The move's status.
*/
public abstract EaglesWingStatus
move(CardPiles from, CardPiles to)
/**
Determine if it's possible to make a move in this game's current
configuration.
@return True iff it's possible to make a move in this game's current
configuration.
*/
public abstract boolean
movesPossible()
/**
Create a new instance of the Eagle's Wing game. The cards have been dealt
appropriately for the start of a game, but no moves have been made.
@param seed A random-number-generator seed used when creating a shuffled
deck.
@return A new instance of the Eagle's Wing game.
*/
public static EaglesWing
newGame(long seed)
return new impl.EaglesWing(seed)
/**
Determine the number of redeals (waste-to-stock moves) left in
this game.
@return The number of redeals left in this game.
*/
public abstract int
redealsLeft()
/**
Determie the number of cards in a card pile.
@param pile The card pile of interest.
@return The number of cards in the given card pile.
*/
public abstract int
size(CardPiles pile)
/**
Get a card pile's top-most card.
@param pile A card pile of interest.
@return The top-most card in the given pile or <code>null</code> if the
given pile's empty.
*/
public abstract Card
topCard(CardPiles pile)
DefinesEaglesWing
(links are to index).
<nocvs/playerAPI/CardPiles.java
>=
package playerAPI
import java.util.EnumMap
/**
The card piles used in an Eagle's Wing game. No order among piles is
impled by the names of the foundation and wing piles.
*/
public enum
CardPiles
/**
The foundation-pile names are given in no particular order. At the start
of a game, <code>foundation0</code> contains the foundation-base card.
*/
foundation0,
foundation1,
foundation2,
foundation3,
stock,
trunk,
waste,
/**
The wing-pile names are given in no particular order.
*/
wing0,
wing1,
wing2,
wing3,
wing4,
wing5,
wing6,
wing7
/**
Iterate through all pairs of card piles, calling a method on each pair.
The pairs are generated in no particular order, but all pairs are
generated if the iteration is not interrupted. Some card-pile pairs, such
as <code>(foundation0, foundation0)</code>, may not make sense with
respect to Eagle's Wing game play.
@param pairFun A class instance containing the method to call for each
pair.
*/
public static void
pairIterate(PairFun pairFun)
Outer:
for CardPiles pair1: values()
Inner:
for CardPiles pair2: values()
switch (pairFun.f(pair1, pair2))
case 0: break
case 1: break Inner
case 2: break Outer
default: assert false : "pairFun.f() return value not 0, 1, or 2"
/**
The method to call when iterating over card-pile pairs.
*/
public interface
PairFun
/**
Perform some operation on the given pair of card piles.
@param pile1 A card pile.
@param pile2 Another card pile.
@return The return value controls the iteration:<p><ul><li style =
"margin-bottom: 1ex"><code>0</code>: continue the iteration with the
next pair.<li style = "margin-bottom: 1ex"><code>1</code>: break the
inner iteration (generating the second piles in the pair) and continue
with the next outer iteration.<li><code>2</code>: break the
outer iteration (generating the first piles in the pair), ending the
pair iteration.</ul><p>Any other return values cause undefined
results.
*/
public int f(CardPiles pile1, CardPiles pile2)
/**
A particular card pile is part of one of five groups of card piles. Three
of the five card-pile groups are singletons and aren't too useful, but it
is useful to be able to refer to a wing or foundation pile without caring
about the particular pile involved.
*/
public enum Group
/**
Any of the four piles in the foundation.
*/
foundationPile,
stockPile,
trunkPile,
wastePile,
/**
Any of the eight wing piles.
*/
wingPile
/**
Find a card pile's group.
@param pile The card pile of interest.
@return The given card pile's group.
*/
public static Group
pileGroup(CardPiles pile)
return cardPileGroupMap.get(pile)
// A map from a particular card pile to the associated card-pile type.
private static final EnumMap<CardPiles, Group>
cardPileGroupMap = new EnumMap<CardPiles, Group>(CardPiles.class)
// Define the card-pile-to-type mapping.
static
cardPileGroupMap.put(CardPiles.foundation0, Group.foundationPile)
cardPileGroupMap.put(CardPiles.foundation1, Group.foundationPile)
cardPileGroupMap.put(CardPiles.foundation2, Group.foundationPile)
cardPileGroupMap.put(CardPiles.foundation3, Group.foundationPile)
cardPileGroupMap.put(CardPiles.stock, Group.stockPile)
cardPileGroupMap.put(CardPiles.trunk, Group.trunkPile)
cardPileGroupMap.put(CardPiles.waste, Group.wastePile)
cardPileGroupMap.put(CardPiles.wing0, Group.wingPile)
cardPileGroupMap.put(CardPiles.wing1, Group.wingPile)
cardPileGroupMap.put(CardPiles.wing2, Group.wingPile)
cardPileGroupMap.put(CardPiles.wing3, Group.wingPile)
cardPileGroupMap.put(CardPiles.wing4, Group.wingPile)
cardPileGroupMap.put(CardPiles.wing5, Group.wingPile)
cardPileGroupMap.put(CardPiles.wing6, Group.wingPile)
cardPileGroupMap.put(CardPiles.wing7, Group.wingPile)
DefinesCardPiles
,CardPiles.Group
,CardPiles.PairFun
,CardPiles.pairIterate
(links are to index).
<nocvs/playerAPI/Card.java
>=
package playerAPI
/**
The card behaivor used by Eagle's Wing. Given two cards, <p><ol><li style
= "margin-bottom: 1ex">Do the suits match? and <li>How do the ranks differ
in which direction?</ol>
*/
public interface
Card
/**
Determine if this card has the same suit as the given card.
@param c The given card.
@return True iff the given card's suit matches this card's suit.
*/
public boolean
matchingSuit(Card c)
/**
Find the smallest difference (in absolute value) in rank between
this card and the given card.
@param c The given card.
@return The smallest (in absolute value) increment that takes
this card's rank to the given card's rank.
*/
public int
smallestRankIncrement(Card c)
DefinesCard
(links are to index).
<nocvs/playerAPI/EaglesWingStatus.java
>=
package playerAPI
/**
The results of move commands in an Eagle's Wing game. All status values but
<code>MOVE_SUCCESSFUL</code> indicate the move failed with no cards moved.
*/
public enum
EaglesWingStatus
/**
Move failed because cards in the to pile should be in strictly increasing
rank. This error applies to moves to a wing pile.
*/
DECREASING_RANK,
/**
Move failed because the from pile is empty.
*/
FROM_PILE_EMPTY,
/**
Move failed because the first card moved to an empty foundation pile
should match the rank of the foundation card.
*/
FOUNDATION_BASE_MISMATCH,
/**
Move failed because cards in the to pile should be in strictly decreasing
rank. This error appies to moves to a foundation pile.
*/
INCREASING_RANK,
/**
Move failed because the move isn't allowed under any circumstances (for
example, from wing pile to waste pile).
*/
INVALID_MOVE,
/**
Move failed because cards in to pile must match in suit.
*/
MISMATCHED_SUIT,
/**
The move succeeded.
*/
MOVE_SUCCESSFUL,
/**
The move failed because each Eagle's Wing game allows at most two redeals.
*/
NO_REDEALS_LEFT,
/**
The move failed because the stock must be empty before a redeal can occur.
*/
STOCK_NOT_EMPTY,
/**
The move failed because moves to the wing pile from other non-wing piles
can occur only after the trunk is empty.
*/
TRUNK_NOT_EMPTY,
/**
The move failed because the to wing pile would contain more than three
cards.
*/
WING_PILE_FULL
DefinesDECREASING_RANK
,EaglesWingStatus
,FOUNDATION_BASE_MISMATCH
,FROM_PILE_EMPTY
,INCREASING_RANK
,INVALID_MOVE
,MISMATCHED_SUIT
,MOVE_SUCCESSFUL
,NO_REDEALS_LEFT
,STOCK_NOT_EMPTY
,TRUNK_NOT_EMPTY
,WING_PILE_FULL
(links are to index).
Eagle's-Wing Implementation
The Eagle's Wing implementation is relatively straightforward, with most of the
interesting details in the concrete class extending the EaglesWing
abstract
class in the playerAPI package. The implementation classes belong to the impl
package.
<nocvs/impl/package.html
>=
<html><head></head><body>
</body></html>
movesPossible()
ImplementationmovesPossible()
should return true if there are moves possible in the
current game configuration and false otherwise. Conceptually, implementing
movesPossible()
is simple: search through all possible moves looking for a
legal move. Implementing movesPossible()
is simple too, thanks to
CardPiles.pairIterate()
. The only real work that needs to be done is
defining the method used in the pair iteration.
<EaglesWing.movesPossible>= (U→) public boolean movesPossible() <MovesPairFun definition> final MovesPairFun pairFun = new MovesPairFun() CardPiles.pairIterate(pairFun) return pairFun.movesPossible
A given pair of piles represents a possible move if the pair represents a legitimate move, the move is successful, and the move is non-trivial.
<MovesPairFun definition>= (U→) class MovesPairFun implements CardPiles.PairFun boolean movesPossible = false public int f(CardPiles from, CardPiles to) final MoveCheck moveCheck = checkGrid[getGroupIndex(from)][getGroupIndex(to)] if ( moveCheck == null ∨ moveCheck.isValid(from, to) ≠ EaglesWingStatus.MOVE_SUCCESSFUL ∨ trivialMove(from, to)) return 0 movesPossible = true return 2
DefinesMovesPairFun
(links are to index).
<nocvs/impl/EaglesWing.java
>=
package impl
import playerAPI.EaglesWingStatus
import playerAPI.Card
import playerAPI.CardPiles
import java.util.Map
import java.util.EnumMap
public class EaglesWing
extends playerAPI.EaglesWing
/**
Determine a card pile's bottom card.
@param pile The card pile of interest.
@return The bottom (base) card in the given pile or null if the given
pile's empty.
*/
public Card
bottomCard(CardPiles pile)
return cardPiles[pile.ordinal()].bottomCard()
/**
Get the cards in a pile.
@param pile The card pile of interest.
@return an array of cards from the pile; index 0 is the bottom card. The
array is sized to the number of cards actually in the pile.
*/
public Card []
cardsInPile(CardPiles pile)
return cardPiles[pile.ordinal()].cardsInPile()
/**
Construct a new instance of an Eagle's Wing game. The cards have been
dealt but no moves have been made.
@param seed A random-number-generator seed used when creating a shuffed
deck.
*/
public EaglesWing(long seed)
cardPiles = makeCardPiles(seed)
moveGrid = makeMoveGrid()
checkGrid = makeCheckGrid()
foundationBaseCard = cardPiles[f0Index].topCard()
assert foundationBaseCard ≠ null
redealCount = 2
/**
Determine if this game's been won.
@return True iff this game's current configuration is a winner.
*/
public boolean
gameWon()
for int i: new int [] { f0Index, f1Index, f2Index, f3Index }
if cardPiles[i].size() < CardImplementation.rankCount
return false
return true
private int
getGroupIndex(CardPiles pile)
// Return the given pile's group index.
return CardPiles.pileGroup(pile).ordinal()
private CardPile []
makeCardPiles(long seed)
// Construct and return a set of Eagle's Wing card piles as found at the
// start of a game. Use the given seed in the random-number generator used
// to create the card deck.
final Deck deck = new Deck(seed)
final CardPile cardPiles [] = new CardPile [pileCount]
cardPiles[f0Index] = new CardPile(13, deck, 0, 1)
cardPiles[f1Index] = new CardPile(13)
cardPiles[f2Index] = new CardPile(13)
cardPiles[f3Index] = new CardPile(13)
cardPiles[trunkIndex] = new CardPile(13, deck, 1, 14)
cardPiles[CardPiles.wing0.ordinal()] =
new CardPile(3, deck, 14, 15)
cardPiles[CardPiles.wing1.ordinal()] =
new CardPile(3, deck, 15, 16)
cardPiles[CardPiles.wing2.ordinal()] =
new CardPile(3, deck, 16, 17)
cardPiles[CardPiles.wing3.ordinal()] =
new CardPile(3, deck, 17, 18)
cardPiles[CardPiles.wing4.ordinal()] =
new CardPile(3, deck, 18, 19)
cardPiles[CardPiles.wing5.ordinal()] =
new CardPile(3, deck, 19, 20)
cardPiles[CardPiles.wing6.ordinal()] =
new CardPile(3, deck, 20, 21)
cardPiles[CardPiles.wing7.ordinal()] =
new CardPile(3, deck, 21, 22)
cardPiles[CardPiles.stock.ordinal()] =
new CardPile(52 - 22, deck, 22, 52)
cardPiles[CardPiles.waste.ordinal()] =
new CardPile(52 - 22)
return cardPiles
private MoveCheck[][]
makeCheckGrid()
// Construct and return a grid of move-validity checkers.
final MoveCheck checkGrid [][] = new MoveCheck [pileCount][pileCount]
checkGrid[wastePileIndex][foundationPileIndex] = new MoveCheck()
EaglesWingStatus isValid(CardPile from, CardPile to)
assert from.size() > 0 : "from.size():" + from.size() + " > 0"
final Card newTopCard = from.topCard()
if to.size() > 0
return inSequenceCards(to.topCard(), newTopCard, 1)
if newTopCard.matchingSuit(foundationBaseCard
∨ newTopCard.smallestRankIncrement(foundationBaseCard) ≠ 0)
return EaglesWingStatus.FOUNDATION_BASE_MISMATCH
return EaglesWingStatus.MOVE_SUCCESSFUL
checkGrid[wastePileIndex][stockPileIndex] = new MoveCheck()
EaglesWingStatus isValid(CardPile from, CardPile to)
if redealCount < 1
return EaglesWingStatus.NO_REDEALS_LEFT
return
to.size() == 0
? EaglesWingStatus.MOVE_SUCCESSFUL
: EaglesWingStatus.STOCK_NOT_EMPTY
checkGrid[wastePileIndex][wingPileIndex] = new MoveCheck()
EaglesWingStatus isValid(CardPile from, CardPile to)
EaglesWingStatus status =
checkGrid[trunkPileIndex][wingPileIndex].isValid(from, to)
if ( status == EaglesWingStatus.MOVE_SUCCESSFUL
∧ to.size() == 0
∧ cardPiles[trunkIndex].size() > 0)
status = EaglesWingStatus.TRUNK_NOT_EMPTY
return status
checkGrid[stockPileIndex][foundationPileIndex] =
checkGrid[wastePileIndex][foundationPileIndex]
checkGrid[stockPileIndex][wastePileIndex] = new MoveCheck()
EaglesWingStatus isValid(CardPile from, CardPile to)
return EaglesWingStatus.MOVE_SUCCESSFUL
checkGrid[stockPileIndex][wingPileIndex] =
checkGrid[wastePileIndex][wingPileIndex]
checkGrid[trunkPileIndex][foundationPileIndex] =
checkGrid[wastePileIndex][foundationPileIndex]
checkGrid[trunkPileIndex][wingPileIndex] = new MoveCheck()
EaglesWingStatus isValid(CardPile from, CardPile to)
if to.size() > 2
return EaglesWingStatus.WING_PILE_FULL
if to.size() > 0
return inSequenceCards(to.topCard(), from.topCard(), -1)
return EaglesWingStatus.MOVE_SUCCESSFUL
checkGrid[wingPileIndex][foundationPileIndex] =
checkGrid[wastePileIndex][foundationPileIndex]
checkGrid[wingPileIndex][wingPileIndex] = new MoveCheck()
EaglesWingStatus isValid(CardPile from, CardPile to)
final Card newTopCard = from.bottomCard()
if to.size() > 0
if from.size() + to.size() > 3
return EaglesWingStatus.WING_PILE_FULL
return inSequenceCards(to.topCard(), newTopCard, -1)
return EaglesWingStatus.MOVE_SUCCESSFUL
return checkGrid
private PileMove[][]
makeMoveGrid()
// Construct and return a grid of pile-to-pile moves.
final PileMove moveGrid [][] = new PileMove [pileCount][pileCount]
final PileMove topCardMove = new PileMove()
void move(CardPile from, CardPile to)
to.takeTopCard(from)
moveGrid[stockPileIndex][wastePileIndex] = topCardMove
moveGrid[stockPileIndex][foundationPileIndex] = topCardMove
moveGrid[stockPileIndex][wingPileIndex] = topCardMove
moveGrid[trunkPileIndex][foundationPileIndex] = topCardMove
moveGrid[trunkPileIndex][wingPileIndex] = topCardMove
moveGrid[wastePileIndex][foundationPileIndex] = topCardMove
moveGrid[wastePileIndex][stockPileIndex] = new PileMove()
void move(CardPile from, CardPile to)
assert redealCount > 0 : "redealCount:" + redealCount + " > 0"
redealCount--
to.transfer(from)
moveGrid[wastePileIndex][wingPileIndex] = topCardMove
moveGrid[wingPileIndex][foundationPileIndex] = new PileMove()
void move(CardPile from, CardPile to)
to.transfer(from)
moveGrid[wingPileIndex][wingPileIndex] = new PileMove()
void move(CardPile from, CardPile to)
// This little circumlocution is necessary because transfer() reverses
// the order of the cards transferred. A wing-to-wing copy should not
// change card order; an even number of transfers preserves the order.
final CardPile pile = new CardPile(3)
pile.transfer(from)
to.transfer(pile)
return moveGrid
/**
Move cards between two piles in the this game.
@param from The pile from which cards will be moved.
@param to The pile to which cards will be moved.
@return The move's status.
*/
public EaglesWingStatus
move(CardPiles from, CardPiles to)
final int
fromType = getGroupIndex(from),
toType = getGroupIndex(to)
final MoveCheck moveCheck = checkGrid[fromType][toType]
if moveCheck == null
return EaglesWingStatus.INVALID_MOVE
final EaglesWingStatus status = moveCheck.isValid(from, to)
if status ≠ EaglesWingStatus.MOVE_SUCCESSFUL
return status
assert moveGrid[fromType][toType] ≠ null
: "moveGrid[fromType:" + fromType + "][toType:" + toType + "] ≠ null"
final CardPile
f = cardPiles[from.ordinal()],
t = cardPiles[to.ordinal()]
// System.err.println(from + ":" + f + " → " + to + ":" + t)
moveGrid[fromType][toType].move(f, t)
// System.err.println(from + ":" + f + " → " + to + ":" + t)
return EaglesWingStatus.MOVE_SUCCESSFUL
/**
Determine if there are moves possible in this game's current state.
@return True iff there are moves possible in this game's current state.
*/
<EaglesWing.movesPossible>
/**
Determine the number of redeals (waste-to-stock moves) left in
this game.
@return The number of redeals left in this game.
*/
public int
redealsLeft()
return redealCount
/**
Determine the number of cards in a card pile.
@param pile The card pile of interest.
@return The number of cards in the given pile.
*/
public int
size(CardPiles pile)
return cardPiles[pile.ordinal()].size()
/**
Determine a card pile's top (face-up) card.
@param pile The card pile of interest.
@return The top card in the given pile or null if the given pile's empty.
*/
public Card
topCard(CardPiles pile)
return cardPiles[pile.ordinal()].topCard()
private boolean
trivialMove(
CardPiles from, CardPiles to)
// Return true iff the move between the given piles is trivial; that
// is, it stalls the game-play state rather than changes it.
return getGroupIndex(from) == wingPileIndex
∧ getGroupIndex(to) == wingPileIndex
∧ cardPiles[to.ordinal()].size() == 0
// The card piles making up an Eagle's Wing game. They're indexed by the
// card-pile enums defined in the player API.
private final CardPile cardPiles[]
// How many card piles does Eagle's Wing use?
private final int pileCount = CardPiles.values().length
// Indices into the card-pile array.
private static int
f0Index = CardPiles.foundation0.ordinal(),
f1Index = CardPiles.foundation1.ordinal(),
f2Index = CardPiles.foundation2.ordinal(),
f3Index = CardPiles.foundation3.ordinal(),
trunkIndex = CardPiles.trunk.ordinal()
// The base card for foundations.
private final Card foundationBaseCard
// The number of redails left in this game.
private int redealCount
// A convinent mapping of card-pile groups to array indexes.
private static int
foundationPileIndex = CardPiles.Group.foundationPile.ordinal(),
stockPileIndex = CardPiles.Group.stockPile.ordinal(),
trunkPileIndex = CardPiles.Group.trunkPile.ordinal(),
wastePileIndex = CardPiles.Group.wastePile.ordinal(),
wingPileIndex = CardPiles.Group.wingPile.ordinal()
// A pile-to-pile move.
private abstract class PileMove
abstract void move(CardPile from, CardPile to)
// A grid of possible pile-to-pile moves. moveGrid[s][d] is the move to use
// when the source card pile is from card-pile group s and the destination
// card pile has is from group d.
private PileMove moveGrid[][]
// A validity check for a pile-to-pile move.
private abstract class MoveCheck
EaglesWingStatus
inSequenceCards(Card oldTopCard, Card newTopCard, int direction)
// Determine if the given cards form a valid sequence along the given
// direction; return the result as a status value.
if !newTopCard.matchingSuit(oldTopCard)
return EaglesWingStatus.MISMATCHED_SUIT
if oldTopCard.smallestRankIncrement(newTopCard) ≠ direction
return
direction == 1
? EaglesWingStatus.DECREASING_RANK
: EaglesWingStatus.INCREASING_RANK
return EaglesWingStatus.MOVE_SUCCESSFUL
EaglesWingStatus
isValid(CardPiles from, CardPiles to)
final CardPile fromPile = cardPiles[from.ordinal()]
if fromPile.size() == 0
return EaglesWingStatus.FROM_PILE_EMPTY
return isValid(fromPile, cardPiles[to.ordinal()])
abstract EaglesWingStatus
isValid(CardPile fromPile, CardPile toPile)
// A grid of checks for valid pile-to-pile moves. checkGrid[s][d] is the
// validity check for a move when the source card pile has type s and the
// destination card pile has type d.
private MoveCheck checkGrid[][]
<Eagle's Wing implementation tests>
<Eagle's Wing implementation tests>= (U→) void stockToWingTest()
<nocvs/impl/CardPile.java
>=
package impl
import playerAPI.Card
class CardPile
Card bottomCard()
// Return this card pile's bottom (base) card.
return top > 0 ? cards[0] : null
Card [] cardsInPile()
// Return the cards in this pile; the array is sized to the number of cards
// actually in this pile.
CardImplementation cds [] = new CardImplementation [top]
for int i = 0; i < top; i++
cds[i] = cards[i]
return cds
CardPile(int size)
// Create a new empty card pile capable of holding at most the given number
// of cards.
assert size > 0 : "size: " + size + " > 0"
cards = new CardImplementation [size]
top = 0
CardPile(int size, CardPile deck, int start, int end)
// Create a new card pile capable of holding at most the given number
// of cards and initially containing the card sequence deck[start, end).
this(size)
assert (end - start) ≤ size
while start < end
cards[top++] = deck.cards[start++]
int size()
// Return the number of cards in this pile.
return top
void
takeTopCard(CardPile from)
// Take the top card from the given pile and place it on top of this pile.
assert from.top > 0 : "from.top:" + from.top + " > 0"
assert cards.length > top
: "cards.length:" + cards.length + " > top:" + top
cards[top++] = from.cards[--(from.top)]
Card topCard()
// Return the top-most (face-up) card in this pile or null if this pile's
// empty.
return top > 0 ? cards[top - 1] : null
/**
Create a string representation for this card pile.
@return This card pile's string representation.
*/
@Override
public String
toString()
final StringBuffer sb = new StringBuffer()
String sep = ""
for int i = 0; i < top; i++
sb.append(sep)
sep = " "
sb.append(cards[i].toString())
return sb.toString()
void
transfer(CardPile cardPile)
// Transfer the cards from the given pile to this pile one at a time, top
// card to top card. This has the effect of reversing the cards in the
// given pile and placing them on top of this pile.
assert size() + cardPile.size() ≤ cards.length
: "size():" + size() + " + cardPile.size():" + cardPile.size() +
" < cards.length:" + cards.length
while cardPile.top > 0
cards[top++] = cardPile.cards[--(cardPile.top)]
// Where the cards live.
final CardImplementation cards[]
// The index of the next available element in cards.
int top
DefinesCardPile
(links are to index).
Deck Implementation
A deck is a specific card pile with the usual 52 cards; a deck is shuffled.
<nocvs/impl/Deck.java
>=
package impl
import playerAPI.Card
import java.util.Random
import java.util.Vector
import java.util.Collections
class Deck
extends CardPile
Deck(long seed)
// Return a shuffled deck of cards. The seed determines the order in which
// the cards are shuffled; identical seeds cause identical shuffles.
super(52)
int i
final Vector<Integer> values = new Vector<Integer>(cards.length)
for i = 0; i < cards.length; i++
values.add(i)
Collections.shuffle(values, new Random(seed))
for i = 0; i < cards.length; i++
cards[top++] = new CardImplementation(values.elementAt(i))
DefinesDeck
(links are to index).
<nocvs/impl/CardImplementation.java
>=
package impl
import playerAPI.Card
class CardImplementation
implements Card
static final int
rankCount = 13,
suitCount = 4
CardImplementation(int position)
// Return the card in the given position in an unshuffled deck.
assert (0 ≤ position) ∧ (position < rankCount*suitCount)
rank = position % rankCount
suit = position % suitCount
/**
Determine if this card has the same suit as the given card.
@param c The given card.
@return True iff the given card's suit matches this card's suit.
*/
public boolean
matchingSuit(Card c)
return suit == ((CardImplementation) c).suit
/**
Find the smallest difference (in absolute value) in rank between
this card and the given card.
@param c The given card.
@return The smallest (in absolute value) increment that takes
this card's rank to the given card's rank.
*/
public int
smallestRankIncrement(Card c)
int delta = ((CardImplementation) c).rank - rank
if Math.abs(delta) > rankCount/2
if delta > 0
delta -= rankCount
else
assert delta < 0
delta += rankCount
return delta
/**
Return a printable representation of this card.
@return A string containing the rank followed by the suit.
*/
@Override
public String
toString()
return ranks[rank] + suits[suit]
private final int rank, suit
private static final String
suits [] = { "c", "d", "h", "s" },
ranks [] =
"a", "2", "3", "4", "5", "6", "7", "8", "9", "10", "j", "q", "k", }
DefinesCardImplementation
,CardImplementation.cardCount
,CardImplementation.rankCount
,CardImplementation.suitCount
(links are to index).
Example Client
This class is a quick and dirty example of an Eagle's Wing game implemented
using the playerAPI. It uses the terminal and repeatedly prints the card piles
and then prompts for a move.
<ew.main()>= (U→) public static void main(String args[]) final EaglesWing game = EaglesWing.newGame(new Date().getTime()) do showGame(game) while doMove(game)
<nocvs/client/ew.java
>=
import playerAPI.EaglesWing
import playerAPI.EaglesWingStatus
import playerAPI.CardPiles
import playerAPI.Card
import java.util.Map
import java.util.HashMap
import java.util.Scanner
import java.util.Date
class ew
private static boolean
doMove(EaglesWing game)
CardPiles fromPile, toPile
final Scanner s = new Scanner(System.in)
while true
final CardPiles move[] = readMove(s)
final EaglesWingStatus status = game.move(move[0], move[1])
if status == EaglesWingStatus.MOVE_SUCCESSFUL
return true
System.out.println("Move failed: " + status)
<ew.main()>
private static CardPiles []
readMove(Scanner s)
CardPiles move [] = new CardPiles[2]
do
System.out.print("? ")
System.out.flush()
if !s.hasNextLine()
return null
final Scanner ls = new Scanner(s.nextLine())
if !readPile(ls, move, 0) ∨ !readPile(ls, move, 1)
return null
while (move[0] == null) ∨ (move[1] == null)
return move
private static boolean
readPile(Scanner s, CardPiles piles[], int i)
piles[i] = null
if s.hasNext()
final String label = s.next()
if (piles[i] = labelToPile.get(label)) == null
System.err.println("Don't understand " + label)
return true
private static void
showGame(EaglesWing game)
showNonwingPiles(game)
showWingPiles(game)
private static void
showNonwingPiles(EaglesWing game)
for int i = 0; i < 7; i++
showPile(game, piles[i])
private static void
showPile(EaglesWing game, CardPiles pile)
final String card =
game.size(pile) == 0 ? "" : game.topCard(pile).toString()
System.out.printf("%2s %s\n", pileToLabel.get(pile), card.toString())
private static void
showWingPiles(EaglesWing game)
for int i = 7; i < piles.length; i++
CardPiles pile = piles[i]
System.out.print(pileToLabel.get(pile) + " ")
final Card cards[] = game.cardsInPile(pile)
for int j = cards.length - 1; j > -1; j--
System.out.print(cards[j] + " ")
System.out.println("")
private static final CardPiles piles []
private static final Map<CardPiles, String> pileToLabel
private static final Map<String, CardPiles> labelToPile
static
piles = new CardPiles []
CardPiles.foundation0,
CardPiles.foundation1,
CardPiles.foundation2,
CardPiles.foundation3,
CardPiles.stock,
CardPiles.trunk,
CardPiles.waste,
CardPiles.wing0,
CardPiles.wing1,
CardPiles.wing2,
CardPiles.wing3,
CardPiles.wing4,
CardPiles.wing5,
CardPiles.wing6,
CardPiles.wing7
String labels [] = new String []
"f0", "f1", "f2", "f3",
"s", "t", "w",
"w0", "w1", "w2", "w3",
"w4", "w5", "w6", "w7"
assert labels.length == piles.length
pileToLabel = new HashMap<CardPiles, String>()
labelToPile = new HashMap<String, CardPiles>()
for int i = 0; i < labels.length; i++
pileToLabel.put(piles[i], labels[i])
labelToPile.put(labels[i], piles[i])
<nocvs/impl/TestCard.java
>=
package impl
import org.junit.Test
import static org.junit.Assert.*
import junit.framework.TestCase
public class TestCard
@Test public void
matchingSuit()
final CardImplementation
club = new CardImplementation(0),
heart = new CardImplementation(13)
assertTrue(club.matchingSuit(club))
assertFalse(club.matchingSuit(heart))
@Test public void
smallestRankIncrement()
CardImplementation
zero = new CardImplementation(0),
one = new CardImplementation(1),
six = new CardImplementation(6),
seven = new CardImplementation(7),
twelve = new CardImplementation(12)
assertEquals(1, zero.smallestRankIncrement(one))
assertEquals(-1, one.smallestRankIncrement(zero))
assertEquals(2, twelve.smallestRankIncrement(one))
assertEquals(-2, one.smallestRankIncrement(twelve))
assertEquals(6, zero.smallestRankIncrement(six))
assertEquals(-6, zero.smallestRankIncrement(seven))
assertEquals(0, seven.smallestRankIncrement(seven))
<nocvs/impl/TestCardImplementation.java
>=
package impl
import org.junit.Test
import static org.junit.Assert.*
import junit.framework.TestCase
public class TestCardImplementation
@Test public void
toStringTest()
assertEquals("ac", new CardImplementation(0).toString())
<nocvs/impl/TestCardPile.java
>=
package impl
import org.junit.Test
import static org.junit.Assert.*
import junit.framework.TestCase
public class TestCardPile
@Test public void
test()
CardPile cardPile = new CardPile(10)
assertEquals(0, cardPile.size())
assertEquals(null, cardPile.bottomCard())
assertEquals(null, cardPile.topCard())
final Deck deck = new Deck(0)
CardPile cp = new CardPile(10, deck, 0, 1)
assertEquals(1, cp.size())
assertEquals(cp.bottomCard(), cp.topCard())
cardPile.takeTopCard(cp)
assertEquals(1, cardPile.size())
assertEquals(cardPile.bottomCard(), cardPile.topCard())
assertEquals(0, cp.size())
<[impl/TestCardPiles.java]]>= package impl import org.junit.Test import static org.junit.Assert.* import junit.framework.TestCase import playerAPI.CardPiles public class TestCardPiles @Test public void quitTest() class CountingFun implements CardPiles.PairFun int count = 0 public int f(CardPiles f, CardPiles t) count++ return 2 final CountingFun countingFun = new CountingFun() CardPiles.pairIterate(countingFun) assertEquals(1, countingFun.count)
<nocvs/impl/TestEaglesWing.java
>=
package impl
import org.junit.Test
import static org.junit.Assert.*
import junit.framework.TestCase
import playerAPI.EaglesWing
import playerAPI.CardPiles
import playerAPI.EaglesWingStatus
import playerAPI.Card
public class
TestEaglesWing
private EaglesWing eaglesWing = EaglesWing.newGame(10)
private final CardPiles
foundation0 = CardPiles.foundation0,
foundation1 = CardPiles.foundation1,
foundation2 = CardPiles.foundation2,
foundation3 = CardPiles.foundation3,
stock = CardPiles.stock,
waste = CardPiles.waste,
wing0 = CardPiles.wing0,
wing1 = CardPiles.wing1,
wing2 = CardPiles.wing2,
wing3 = CardPiles.wing3,
wing4 = CardPiles.wing4,
wing5 = CardPiles.wing5,
wing6 = CardPiles.wing6,
wing7 = CardPiles.wing7
private final CardPiles
foundations[] = new CardPiles []
foundation0, foundation1, foundation2, foundation3 },
wings[] = new CardPiles []
wing0, wing1, wing2, wing3, wing4, wing5, wing6, wing7 }
@Test public void
foundationBaseTest()
// Make sure moving the foundation base card works.
runTest(
new TestRun()
public boolean testGame(EaglesWing game)
while game.size(CardPiles.stock) > 0
if foundationBaseMove(game)
goodMove(game, stock, foundation1)
return true
goodMove(game, stock, waste)
return false
})
@Test public void
fullWingTest()
// Make sure moves to a full wing pile fail.
runTest(
new TestRun()
public boolean testGame(EaglesWing game)
while game.size(stock) > 0
Card fCard = game.topCard(stock)
for CardPiles t: wings
final Card tCard = game.topCard(t)
if ( tCard.smallestRankIncrement(fCard) == -1
∧ tCard.matchingSuit(fCard))
EaglesWingStatus status = game.move(stock, t)
if status == EaglesWingStatus.WING_PILE_FULL
return true
assertEquals(
EaglesWingStatus.MOVE_SUCCESSFUL, status)
fCard = null
break
if fCard ≠ null
goodMove(game, stock, waste)
return false
})
@Test public void
gameStart()
assertEquals(1, eaglesWing.size(foundation0))
assertEquals(0, eaglesWing.size(foundation1))
assertEquals(0, eaglesWing.size(foundation2))
assertEquals(0, eaglesWing.size(foundation3))
assertEquals(13, eaglesWing.size(CardPiles.trunk))
assertEquals(0, eaglesWing.size(waste))
assertEquals(1, eaglesWing.size(CardPiles.wing0))
assertEquals(1, eaglesWing.size(CardPiles.wing1))
assertEquals(1, eaglesWing.size(CardPiles.wing2))
assertEquals(1, eaglesWing.size(CardPiles.wing3))
assertEquals(1, eaglesWing.size(CardPiles.wing4))
assertEquals(1, eaglesWing.size(CardPiles.wing5))
assertEquals(1, eaglesWing.size(CardPiles.wing6))
assertEquals(1, eaglesWing.size(CardPiles.wing7))
assertEquals(30, eaglesWing.size(CardPiles.stock))
assertTrue(eaglesWing.movesPossible())
assertEquals(
EaglesWingStatus.INVALID_MOVE, eaglesWing.move(foundation0, waste))
@Test public void
moveTest()
// Make sure card moves are working correctly.
final EaglesWing game = EaglesWing.newGame(10)
assertEquals(0, game.size(waste))
assertEquals(30, game.size(stock))
goodMove(game, stock, waste)
assertEquals(1, game.size(waste))
assertEquals(29, game.size(stock))
@Test public void
trunkEmptyWingTest()
// Make sure a move from the trunk to an empty wing pile works.
runTest(
new TestRun()
public boolean testGame(EaglesWing game)
final CardPiles emptyWingPile = emptyWingPile(game)
if emptyWingPile ≠ null
goodMove(game, CardPiles.trunk, emptyWingPile)
return true
return false
})
@Test public void
wasteWingTest()
// Do a waste to wing move to make sure it exists.
runTest(
new TestRun()
public boolean testGame(EaglesWing game)
while game.size(stock) > 0
goodMove(game, stock, waste)
for CardPiles t: wings
if EaglesWingStatus.MOVE_SUCCESSFUL == game.move(waste, t)
return true
return false
})
@Test public void
wingWingTest()
// Make sure the wing-to-wing moves exists.
runTest(
new TestRun()
public boolean testGame(EaglesWing game)
for CardPiles f: wings
final Card fCard = game.topCard(f)
for CardPiles t: wings
final Card tCard = game.topCard(t)
if ( tCard.smallestRankIncrement(fCard) == -1
∧ tCard.matchingSuit(fCard))
goodMove(game, f, t)
return false
return false
})
private CardPiles
emptyWingPile(final EaglesWing game)
// Make moves that create an empty wing pile; return the empty wing pile or
// null if no moves lead to an empty wing pile.
class Fun
implements CardPiles.PairFun
CardPiles emptyWingPile = null
public int f(CardPiles fromPile, CardPiles toPile)
if ( CardPiles.pileGroup(fromPile) ≠ CardPiles.Group.wingPile
∨ CardPiles.pileGroup(toPile) ≠ CardPiles.Group.wingPile)
return 0
if game.move(fromPile, toPile) ≠ EaglesWingStatus.MOVE_SUCCESSFUL
return 0
emptyWingPile = fromPile
return 2
final Fun fun = new Fun()
CardPiles.pairIterate(fun)
if fun.emptyWingPile ≠ null
assertEquals(0, game.size(fun.emptyWingPile))
return fun.emptyWingPile
private boolean
foundationBaseMove(EaglesWing game)
// Return true iff the stock card can be moved to an empty foundation pile.
return
game.topCard(foundation0).smallestRankIncrement(game.topCard(stock)) == 0
private void
goodMove(EaglesWing game, CardPiles fromPile, CardPiles toPile)
// Make the given move, dying if it fails.
assertEquals(
EaglesWingStatus.MOVE_SUCCESSFUL, game.move(fromPile, toPile))
private void
runTest(TestRun test)
// Repeatedly run the given test until it returns true.
int i = 0
while !test.testGame(EaglesWing.newGame(i++))
// The function to run to test the given game.
private interface TestRun
public boolean testGame(EaglesWing game)
nocvs/client/ew.java
>: D1
nocvs/impl/CardImplementation.java
>: D1
nocvs/impl/CardPile.java
>: D1
nocvs/impl/Deck.java
>: D1
nocvs/impl/EaglesWing.java
>: D1
nocvs/impl/package.html
>: D1
nocvs/impl/TestCard.java
>: D1
nocvs/impl/TestCardImplementation.java
>: D1
nocvs/impl/TestCardPile.java
>: D1
nocvs/impl/TestEaglesWing.java
>: D1
nocvs/playerAPI/Card.java
>: D1
nocvs/playerAPI/CardPiles.java
>: D1
nocvs/playerAPI/EaglesWing.java
>: D1
nocvs/playerAPI/EaglesWingStatus.java
>: D1
nocvs/playerAPI/overview.html
>: D1
nocvs/playerAPI/package.html
>: D1