TweetFollow Us on Twitter

Jan 99 Prog Challenge

Volume Number: 15 (1999)
Issue Number: 1
Column Tag: Programmer's Challenge

January 1999 Programmer's Challenge

by Bob Boonstra, Westford, MA

Sphere Packing

This month we're going to help you recover from the clutter that might result from the holiday season. Imagine that your post-holiday household is filled with gifts, all of which have to be put somewhere. Imagine further that those gifts include sports equipment given to your children, or parents, or siblings, or grandchildren, as the case may be. And finally, imagine that the sports equipment includes a collection of balls of various sizes - basketballs, baseballs, soccer balls, beach balls, etc. (OK, if I've stretched your imagination to the breaking point, think of some other reason you might have a large collection of spherical objects.) We've got to find somewhere to store all of those balls, and space is at a premium. Fortunately, we also have a very large collection of boxes of various sizes, so many, in fact, that you can count on finding a box of the exact size that you might need. In keeping with our desire for a few less difficult problems, your Challenge is to pack the balls into the smallest box possible, so that we can store them efficiently.

The prototype for the code you should write is:

#if defined(__cplusplus)
#if defined (__cplusplus)
extern "C" {
#endif

typedef struct Position {
  double coordinate[3];     /* coordinate[0]==X position, [1]==Y, [2]==Z */
} Position;

void PackSpheres(
    long numSpheres,        /* input: number of spheres to pack */
    double radius[],        /* input: radius of each of numSpheres spheres */
    Position location[]     /* output: location of center of each sphere */
);

#if defined (__cplusplus)
}
#endif

Your PackSpheres routine will be given the number of balls (numSpheres) to be packed away, along with the radius of each of those spheres. The task is simple. Arrange the collection of balls into a rectangular parallelepiped ("box") such that no ball intersects any other ball (i.e., the distance between the centers of any two balls is greater than or equal to the sum of their radii). PackSpheres returns back the coordinates of the center of each ball in the location parameter. Your objective is to minimize the volume of the box that contains all the balls, where the extent of the box in each dimension (X, Y, and Z) is determined by the maximum and minimum coordinates of the balls, considering both the location of the center of the ball and its radius.

While you must ensure that the balls do not intersect, you need not ensure that the balls touch. In our storage room, boxes of balls can contain balls that levitate in the open space between other balls.

The winner will be the solution that minimizes the volume of the box containing all the balls, plus a penalty of 1% of additional storage volume for each millisecond of execution time.

This will be a native PowerPC Challenge, using the latest CodeWarrior environment. Solutions may be coded in C, C++, or Pascal.

Three Months Ago Winner

Congratulations to Pat Brown (Staunton, VA) for submitting the winning solution to the October Hearts Challenge. Pat's solution beat the second-place entry submitted by Tom Saxton and "dummy" entries that rounded out a tournament of four players. Pat's solution was both the faster of the two and the more successful at avoiding point cards, capturing approximately one third fewer points than Tom's solution.

Pat's strategy was fairly simple. His passing strategy is to pass the three highest cards in his hand. By not including a low heart in the pass, this strategy can aid a shoot attempt by an opponent, as well as being dangerous if it passes the queen of spades to the left. When leading, the playing strategy is to force out the queen of spades as quickly as possible, unless of course he has the queen. While he does not attempt to "shoot the moon", he is watchful for attempts by other players to shoot, and holds on to high cards until any potential shoot is spoiled. Otherwise, Pat tries to play the highest legal card that is lower than the current trick leader.

Tom submitted two solutions, a simple one (used in the tournament at Tom's request), and a more sophisticated (but less successful) one. The simple solution also tries to avoid taking tricks and does not attempt to shoot. It is a little more clever in selecting the pass, in that it tries to create a void if possible. It does not keep track of who might be attempting to shoot, and therefore does not attempt to stop them. Tom's second player keeps track of who is void in what suits and tries to shoot when it has a strong hand. However, it isn't quite perfected, and does much worse in a tournament than the first player.

I've included a Point Comparison chart that helps explain the performance of the two players. The vertical bars indicate the number of hands in which each player captured the number of points shown along the horizontal axis. You can see that Pat's solution was slightly more successful at capturing fewer than 4 points in a hand, very successful at avoiding being stuck with the queen of spaces, and extremely effective at capturing fewer than 20 points in a hand. The line graphs show the cumulative effect of the respective strategies on the score.

The table below summarizes the scoring for Pat and Tom's Hearts entries. The teams played a total of 24 matches, consisting of over 25000 hands of 13 tricks each. The Total Points column in the table lists the number of hearts captured during all of those tricks, plus 13 points for each Queen of Spades, and -26 points for each shoot. The table shows the number of tricks "won" by each player, and the number of times each successfully "shot the moon". You can see that Pat's winning solution did not attempt to shoot, and was very successful at avoiding being stuck with all of the point cards. Although not shown in the table, the less-than-intelligent "dummy" players "shot the moon" more often than either Pat or Tom. This was a consequence of their simplistic "strategy" for not taking points, which led them to hold on to high cards longer than a more sophisticated player would have done. Also shown in the table are the execution time of each solution in milliseconds, the total score, including the penalty of one point per millisecond of execution time, and the code and data sizes. As usual, the number in parentheses after the entrant's name is the total number of Challenge points earned in all Challenges prior to this one.

Name Total Points Tricks Won Shoots Time (msec) Score Code Size Data Size
Pat Brown 94775 59703 1 833 95608 3152 398
Tom Saxton (49) 157105 80902 67 1230 158335 2548 72

Top Contestants

Listed here are the Top Contestants for the Programmer's Challenge, including everyone who has accumulated 20 or more points during the past two years. The numbers below include points awarded over the 24 most recent contests, including points earned by this month's entrants.

  1. Munter, Ernst 204
  2. Saxton, Tom 59
  3. Boring, Randy 56
  4. Mallett, Jeff 50
  5. Rieken, Willeke 47
  6. Cooper, Greg 44
  7. Maurer, Sebastian 40
  8. Heithcock, JG 37
  9. Murphy, ACC 34
  10. Nicolle, Ludovic 34
  11. Lewis, Peter 31
  12. Hart, Alan 21
  13. Antoniewicz, Andy 20
  14. Brown, Pat 20
  15. Day, Mark 20
  16. Higgins, Charles 20
  17. Hostetter, Mat 20
  18. Studer, Thomas 20

There are three ways to earn points: (1) scoring in the top 5 of any Challenge, (2) being the first person to find a bug in a published winning solution or, (3) being the first person to suggest a Challenge that I use. The points you can win are:

1st place20 points
2nd place10 points
3rd place7 points
4th place4 points
5th place2 points
finding bug2 points
suggesting Challenge2 points

Here is Pat's winning Hearts solution:

MyHearts.c
Copyright © 1998 Pat Brown

#include "Hearts.h"

/* ***************************************************
 * Hearts.c
 *
 * Author: Pat Brown
 *
 * Trivia: there are 5.36447377655x10^28 different ways that a
 * deck can be dealed out for a game of Hearts.
 */

#define gMAX        52
#define gMIN        1

// Just the relative rankings, 1..52.
// Can be referenced easily using spot and suit value.
/*
        2C,2D,2S,3C,3D,3S,4C,4D,4S,5C,5D,5S,6C,6D,6S,2H,3H,4H,
        5H,6H,7C,7D,7S,8C,8D,8S,9C,9D,9S,7H,8H,9H,10C,10D,10S,
        JC,JD,JS,QC,QD,KC,KD,AC,AD,10H,JH,QH,KS,AS,KH,AH,QS
*/
static const int gCardValue[5][14] =
{
    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},    // NoSuit
    {0, 3, 6, 9,12,15,22,25,28,35,38,52,48,49},    // Spade
    {0,16,17,18,19,29,30,31,32,45,46,47,50,51},    // Heart
    {0, 2, 5, 8,11,14,21,24,27,34,37,40,42,44},    // Diamond
    {0, 1, 4, 7,10,13,20,23,26,33,36,39,41,43}     // Club
};

inline int getCardValue(theSuit, theSpot)
        { return gCardValue[theSuit][theSpot]; }

inline UInt16 NEXTSEAT(UInt16 theSeat)
        { return (theSeat+1)%4; }


/************
  Prototypes
*************/
inline static SInt16 findThisCard(const Card theCards[13],
        const Suit theSuit, const Spot theSpot);
static UInt16 findHighestLimitCard(const Card theCards[13],
        const int uLimit);
static UInt16 findLowestLimitCard(const Card theCards[13],
        const int lLimit);
static UInt16 findHighestIndexLimitCard(const Card theCards[13],
        const UInt16 valid[13], const int numValid, const int uLimit);
static UInt16 findLowestIndexLimitCard(const Card theCards[13],
        const UInt16 valid[13], const int numValid, const int lLimit);
static int findValidCards(const Card theCards[13],
        const Suit theSuit, UInt16 validCards[13]);

inline UInt16        findHighestCard(const Card theCards[13])
    { return findHighestLimitCard(theCards,gMAX); }
inline UInt16        findLowestCard(const Card theCards[13])
    { return findLowestLimitCard(theCards,gMIN); }
inline UInt16        findHighestIndexCard(const Card theCards[13],
        const UInt16 valid[13], const int numValid)
    { return findHighestIndexLimitCard(theCards,valid,numValid,gMAX); }
inline UInt16        findLowestIndexCard(const Card theCards[13],
        const UInt16 valid[13], const int numValid)
    { 
        return findLowestIndexLimitCard(theCards,valid,numValid,gMIN); 
    }

/******************
  Global variables
*******************/
static UInt16     mySeat;
// hasPoints is a bitfield of which players have
// taken points in this hand
static int            hasPoints;
// another bitfield of what suits I'm cutting
static int            cuttingSuit;
static int            tryToSpoil;
/******************************************************
runningTotal keeps track of "points" for each player
in a hand. These aren't real points, but a key to
watch for someone trying to pull a sweep.
If someone gets more than six hearts, or the queen of
spades and three hearts, they may be trying to sweep
(unless someone else has points).
*******************************************************/
static int            runningTotal[4];
static Boolean    blackLadyInHand;
static Boolean    blackLadyPlayed;
static Boolean    heartsBroken;

/********************
  Required functions
*********************/

InitTournament
pascal void InitTournament(const UInt16 numPlayers,
                const UInt16 gameEndingScore)
{
    return;
}

InitGame
pascal void InitGame(const UInt32 playerID[4],
                const UInt16 yourSeat)
{
    mySeat = yourSeat;
}

SelectPass
pascal void SelectPass(const Card dealtHand[13],
         const Pass passDirection, UInt16 passedCards[3])
{
    int    i, top, newtop;

// Init globals at the start of a hand.
    blackLadyPlayed = heartsBroken = 
            tryToSpoil = hasPoints = cuttingSuit = 
            runningTotal[0]=runningTotal[1]=runningTotal[2]=runningTotal[3]
            = 0;

    if (passDirection != kNoPass)
    {
        top = gMAX;
        for (i=0; i<3; i++) // get the three highest cards
        {
            newtop = passedCards[i] = 
                    findHighestLimitCard(dealtHand, top);
            top = getCardValue(dealtHand[newtop].suit, 
                                    dealtHand[newtop].spot)-1;
        }
    }
}

PlayTrick
pascal void PlayTrick(const UInt16 trickNumber,
                 const UInt16 trickLeader, const Card yourHand[13],
                 const Card cardsPlayed[4], UInt16 *yourPlay)
{
    UInt16     validCards[13], myCard;
    int            num, i;
    Suit         leadingSuit;
    Spot         whatsTaking;
    UInt16     whosTaking;
    Boolean    pointsTaking;

    if (trickNumber == 0)
    {
    // see if I've been passed the Queen of Spades
        blackLadyInHand = 
                    (findThisCard(yourHand, kSpade, kQueen) >= 0);
        if (trickLeader == mySeat)
        {
            *yourPlay = findThisCard(yourHand, kClub, k2);
            return;
        }
        else
            goto NOTLEADING;
    }
    
    if (trickLeader == mySeat)
    {
        // try to force the Queen of Spades (unless I have it)
        if (!blackLadyPlayed && !blackLadyInHand)
        {
            if (
                (num = findValidCards(yourHand, kSpade, validCards)) != 0)
            {
            myCard = findLowestIndexCard(yourHand, validCards, num);
                // don't play higher than the Queen
                if (yourHand[myCard].spot < kKing)
                {
                    *yourPlay = myCard;
                    return;
                }
            }
        } // (!blackLadyPlayed && !blackLadyInHand)
        num = findValidCards(yourHand, kNoSuit, validCards);
        *yourPlay = findLowestIndexCard(yourHand, validCards, num);
        return;
    } // if (trickLeader == mySeat)

NOTLEADING:    
    leadingSuit = cardsPlayed[trickLeader].suit;
    num=0;
    // this is faster than scanning yourHand every time
    if ((cuttingSuit & (1 << leadingSuit)))
        goto CUTTING;
    if (
        (num = findValidCards(yourHand, leadingSuit, validCards))==1)
    {
    // only one card we can play
        *yourPlay = validCards[0];
        return;
    }
    if (num == 0)
    {
        cuttingSuit |= 1 << leadingSuit;
        goto CUTTING;
    }
    if (num == 0)
    {
CUTTING:
    // we're cutting this suit
        if (trickNumber == 0)
        {
        // Can't play points on the first trick.
        // 51 keeps us from playing the Queen of Spades.
            num = findValidCards(yourHand, kNoSuit, validCards);
            *yourPlay = 
        findHighestIndexLimitCard(yourHand, validCards, num, 51);
            return;
        }
        if (!tryToSpoil)
            *yourPlay = findHighestCard(yourHand);
        else // Save the high cards to spoil a sweep.
            *yourPlay = findLowestCard(yourHand);
        return;
    } // if (num == 0)
    
    // See who's winning this trick so far.
    i = trickLeader;
    pointsTaking = leadingSuit == kHeart;
    whatsTaking = kNoSpot;
    while (i != mySeat)
    {
        if (    (cardsPlayed[i].suit == kSpade) && 
                    (cardsPlayed[i].spot == kQueen))
            pointsTaking = true;
        if (cardsPlayed[i].suit == leadingSuit)
        {
            if (cardsPlayed[i].spot > whatsTaking)
            {
                whatsTaking = cardsPlayed[i].spot;
                whosTaking = i;
            }
        }
        else // (cardsPlayed[i].suit != leadingSuit)
        {
                pointsTaking |= (cardsPlayed[i].suit == kHeart);
        } // if (cardsPlayed[i].suit == leadingSuit) else
        i = NEXTSEAT(i);
    } // while (i != mySeat)

    if ((leadingSuit == kSpade) && blackLadyInHand)
    {
        myCard = findThisCard(yourHand, kSpade, kQueen);
        if (whatsTaking > kQueen) // dump it on King or Ace
        {
            *yourPlay = myCard;
            return;
        }
        else // don't play the Queen of Spades
        {
            for (i=0;i<num;i++)
            {
                if (validCards[i] == myCard)
                {
                    while (++i < num)
                        validCards[i-1] = validCards[i];
                    num-;
                }
            } // for (i=0;i<num;i++)
        } // if (whatsTaking > kQueen) else
    } // if ((leadingSuit == kSpade) && blackLadyInHand)
            
    if (trickLeader == NEXTSEAT(i)) // playing last
    {
        if (!pointsTaking)
        {
            if (tryToSpoil) // don't waste the high cards
                *yourPlay = 
                    findLowestIndexCard(yourHand, validCards, num);
            else
                *yourPlay = 
                    findHighestIndexCard(yourHand, validCards, num);
            return;
        }
        else
        {
            if (tryToSpoil == (1 << whosTaking))
            {
        myCard = findHighestIndexCard(yourHand, validCards, num); 
              if (yourHand[myCard].spot < whatsTaking) // we can't take this trick
              myCard = findLowestIndexCard(yourHand, validCards, num);
                *yourPlay = myCard;
                return;
            }
            else // someone else is spoiling the sweep
            {
                *yourPlay = 
                findHighestIndexLimitCard(yourHand, validCards, num, 
                                    getCardValue(leadingSuit, whatsTaking));
                return;
            }
        } // if (!pointsTaking) else
    } // if (trickLeader == NEXTSEAT(i))
    
    if (tryToSpoil)
    {
        myCard = findHighestIndexCard(yourHand, validCards, num);
    if (yourHand[myCard].spot < whatsTaking) // we can't take this trick
        myCard = findLowestIndexCard(yourHand, validCards, num);
        *yourPlay = myCard;
        return;
    }
    
    // Standard behaviour
    // Play the highest card under the current highest card.
    *yourPlay = findHighestIndexLimitCard(yourHand, validCards, 
                            num, getCardValue(leadingSuit, whatsTaking));
    return;
}

TrickResults
pascal void TrickResults(const Card lastTrick[4],
                const UInt16 trickWinner)
{
// Keep track on who has what points so we can watch for a sweep.
    int    i;
    int    points = 0;
    int    whoWon;

    for (i=0; i<4; i++)
    {
        switch (lastTrick[i].suit)
        {
            case kSpade:
                if (lastTrick[i].spot == kQueen)
                {
                // not 13, we trigger a sweep when "points" hit 7
                    points += 4;
                    blackLadyPlayed = true;
                    blackLadyInHand = false;
                }
                break;
            case kHeart:
                points++;
                heartsBroken = true;
                break;
        }
    } // for (i=0; i<4; i++)
    if (points == 0)
        return;
    points = runningTotal[trickWinner] += points;
    whoWon = 1 << trickWinner;
    hasPoints |= whoWon;
    if (tryToSpoil)
    {
        if (whoWon != tryToSpoil)
            tryToSpoil = 0;
    }
    else // (!tryToSpoil)
    {
        if (trickWinner != mySeat)
            tryToSpoil = ((hasPoints == whoWon) && (points > 6)) 
? whoWon : 0;
    }
}

HandResults
pascal void HandResults(const SInt16 pointsThisHand[4],
                const SInt32 cumPoints[4])
{
    return;
}

/**************
  My functions
***************/

findThisCard
/**************************************
Return the index of a particular card,
or -1 if it's not there.
***************************************/
inline static SInt16 findThisCard(const Card theCards[13],
                const Suit theSuit, const Spot theSpot)
{
    SInt16    c = -1, i = 13;
    
    while ((c<0) && i-)
    {
        if (    (theCards[i].spot == theSpot) && 
                    (theCards[i].suit == theSuit))
                c = i;
    }
    return c;
}

findHighestLimitCard
/*****************************************************
These next few functions find cards based on a limit.
(it's an inclusive limit)
If there are no cards within the limit, then call
the opposite function.
******************************************************/
static UInt16 findHighestLimitCard(const Card theCards[13],
                const int uLimit)
{
    int         i = 13;
    int         points, theValue = 0;
    SInt16    theCard = -1;
    
    while (i-)
    {
        points = getCardValue(theCards[i].suit, theCards[i].spot);
        if ((points > theValue) && (points <= uLimit))
        {
            theCard = i;
            if (uLimit == (theValue = points))
                return theCard;
        }
    }
    if (theCard < 0)
        theCard = findLowestLimitCard(theCards, uLimit);
    return theCard;
}

findLowestLimitCard
static UInt16 findLowestLimitCard(const Card theCards[13],
                const int lLimit)
{
    int         i = 13;
    int         points, theValue = 99;
    SInt16    theCard = -1;
    
    while (i-)
    {
        points = getCardValue(theCards[i].suit, theCards[i].spot);
        if ((points < theValue) && (points >= lLimit))
        {
            theCard = i;
            if (lLimit == (theValue = points))
                return theCard;
        }
    }
    if (theCard < 0)
        theCard = findHighestLimitCard(theCards, lLimit);
    return theCard;
}

findHighestIndexLimitCard
/***************************************************
Index functions use an array of valid card indexes.
************ INFINITE RECURSION WARNING ************
 Do not call these functions with numValid == 0!!!
****************************************************/
static UInt16 findHighestIndexLimitCard(const Card theCards[13],
                const UInt16 valid[13], const int numValid, 
                const int uLimit)
{
    int         i = numValid;
    int         points, theValue = 0;
    SInt16    theCard = -1;
    
    while (i-)
    {
        points = getCardValue(theCards[valid[i]].suit, 
                              theCards[valid[i]].spot);
        if ((points > theValue) && (points <= uLimit))
        {
            theCard = valid[i];
            if (uLimit == (theValue = points))
                return theCard;
        }
    }
    if (theCard < 0)
    theCard = findLowestIndexLimitCard(theCards, valid, numValid, 
                                                 uLimit);
    return theCard;
}

findLowestIndexLimitCard
static UInt16 findLowestIndexLimitCard(const Card theCards[13],
                const UInt16 valid[13], const int numValid, 
                const int lLimit)
{
    int         i = numValid;
    int         points, theValue = 99;
    SInt16    theCard = -1;
    
    while (i-)
    {
        points = getCardValue(theCards[valid[i]].suit, 
                                                    theCards[valid[i]].spot);
        if ((points < theValue) && (points >= lLimit))
        {
            theCard = valid[i];
            if (lLimit == (theValue = points))
                return theCard;
        }
    }
    if (theCard < 0)
theCard = findHighestIndexLimitCard(theCards, valid, numValid, 
                                                   lLimit);
    return theCard;
}

findValidCards
/*********************************************************
Fill an array with indexes to cards of a particular suit.
Returns the number of valid cards (the size of the array)
Passing kNoSuit will fill the list depending on whether
hearts can be played now.
**********************************************************/
static int findValidCards(const Card theCards[13],
                const Suit theSuit, UInt16 validCards[13])
{
    UInt16    i;
    int         num = 0;
    
    if (theSuit == kNoSuit)
    {
        if (heartsBroken)
        {
            for (i=0; i<13; i++)
                if (theCards[i].suit != kNoSuit) validCards[num++] = i;
        }
        else // (!heartsBroken)
        {
            for (i=0; i<13; i++)
            {
                if (    (theCards[i].suit != kHeart) && 
                            (theCards[i].suit != kNoSuit))
                    validCards[num++] = i;
            }
            if (num == 0) // Nothing but hearts left to play.
            {
                for (i=0; i<13; i++)
            if (theCards[i].suit != kNoSuit) validCards[num++] = i;
            }
        } // if (heartsBroken) else
    }
    else // (theSuit != kNoSuit)
    {
        for (i=0; i<13; i++)
        {
            if (theCards[i].suit == theSuit)
                validCards[num++] = i;
        }
    }
    return num;
}

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

iWatermark Pro 2.0.0fc4 - Easily add wat...
iWatermark Pro is the essential watermarking app for professional, business, and personal use. Easily secure and protect your photos with text, a graphic, a signature, or a QR watermark. Once added... Read more
Amadeus Pro 2.4 - Multitrack sound recor...
Amadeus Pro lets you use your Mac for any audio-related task, such as live audio recording, digitizing tapes and records, converting between a variety of sound formats, etc. Thanks to its outstanding... Read more
iFFmpeg 6.4.2 - Convert multimedia files...
iFFmpeg is a comprehensive media tool to convert movie, audio and media files between formats. The FFmpeg command line instructions can be very hard to master/understand, so iFFmpeg does all the hard... Read more
EtreCheck 3.4.2 - For troubleshooting yo...
EtreCheck is an app that displays the important details of your system configuration and allow you to copy that information to the Clipboard. It is meant to be used with Apple Support Communities to... Read more
Carbon Copy Cloner 4.1.17 - Easy-to-use...
Carbon Copy Cloner backups are better than ordinary backups. Suppose the unthinkable happens while you're under deadline to finish a project: your Mac is unresponsive and all you hear is an ominous,... Read more
VueScan 9.5.81 - Scanner software with a...
VueScan is a scanning program that works with most high-quality flatbed and film scanners to produce scans that have excellent color fidelity and color balance. VueScan is easy to use, and has... Read more
Hopper Disassembler 4.2.10- - Binary dis...
Hopper Disassembler is a binary disassembler, decompiler, and debugger for 32- and 64-bit executables. It will let you disassemble any binary you want, and provide you all the information about its... Read more
Viber 6.8.6 - Send messages and make cal...
Viber lets you send free messages and make free calls to other Viber users, on any device and network, in any country! Viber syncs your contacts, messages and call history with your mobile device, so... Read more
Viber 6.8.6 - Send messages and make cal...
Viber lets you send free messages and make free calls to other Viber users, on any device and network, in any country! Viber syncs your contacts, messages and call history with your mobile device, so... Read more
Carbon Copy Cloner 4.1.17 - Easy-to-use...
Carbon Copy Cloner backups are better than ordinary backups. Suppose the unthinkable happens while you're under deadline to finish a project: your Mac is unresponsive and all you hear is an ominous,... Read more

Latest Forum Discussions

See All

Eden: Renaissance (Games)
Eden: Renaissance 1.0 Device: iOS Universal Category: Games Price: $4.99, Version: 1.0 (iTunes) Description: Eden: Renaissance is a thrilling turn-based puzzle adventure set in a luxurious world, offering a deep and moving... | Read more »
Glyph Quest Chronicles guide - how to ma...
Glyph Quest returns with a new free-to-play game, Glyph Quest Chronicles. Chronicles offers up more of the light-hearted, good humored fantasy fun that previous games featured, but with a few more refined tricks up its sleeve. It's a clever mix of... | Read more »
Catch yourself a Lugia and Articuno in P...
Pokémon Go Fest may have been a bit of a disaster, with Niantic offering fans full refunds and $100 worth of in-game curency to apologize for the failed event, but that hasn't ruined trainers' chances of catching new legendary Pokémon. Lugia nad... | Read more »
The best deals on the App Store this wee...
There are quite a few truly superb games on sale on the App Store this week. If you haven't played some of these, many of which are true classics, now's the time to jump on the bandwagon. Here are the deals you need to know about. [Read more] | Read more »
Realpolitiks Mobile (Games)
Realpolitiks Mobile 1.0 Device: iOS Universal Category: Games Price: $5.99, Version: 1.0 (iTunes) Description: PLEASE NOTE: The game might not work properly on discontinued 1GB of RAM devices (iPhone 5s, iPhone 6, iPhone 6 Plus, iPad... | Read more »
Layton’s Mystery Journey (Games)
Layton’s Mystery Journey 1.0.0 Device: iOS Universal Category: Games Price: $15.99, Version: 1.0.0 (iTunes) Description: THE MUCH-LOVED LAYTON SERIES IS BACK WITH A 10TH ANNIVERSARY INSTALLMENT! Developed by LEVEL-5, LAYTON’S... | Read more »
Full Throttle Remastered (Games)
Full Throttle Remastered 1.0 Device: iOS Universal Category: Games Price: $4.99, Version: 1.0 (iTunes) Description: Originally released by LucasArts in 1995, Full Throttle is a classic graphic adventure game from industry legend Tim... | Read more »
Stunning shooter Morphite gets a new tra...
Morphite is officially landing on iOS in September. The game looks like the space shooter we've been needing on mobile, and we're going to see if it fits the bill quite shortly. The game's a collaborative effort between Blowfish Studios, We're Five... | Read more »
Layton's Mystery Journey arrives to...
As you might recall, Layton's Mystery Journey is headed to iOS and Android -- tomorrow! To celebrate the impending launch, Level-5's released a new trailer, complete with an adorable hamster. [Read more] | Read more »
Sidewords (Games)
Sidewords 1.0 Device: iOS Universal Category: Games Price: $2.99, Version: 1.0 (iTunes) Description: Grab a cup of coffee and relax with Sidewords. Sidewords is part logic puzzle, part word game, all original. No timers. No... | Read more »

Price Scanner via MacPrices.net

13-inch 3.1GHz MacBook Pros on sale for $100...
B&H Photo has the new 2017 13″ 3.1GHz Space Gray MacBook Pros in stock today and on sale for $100 off MSRP including free shipping. B&H charges sales tax in NY and NJ only: – 13″ 3.1GHz/256GB... Read more
Apple refurbished Mac minis available startin...
Apple has Certified Refurbished Mac minis available starting at $419. Apple’s one-year warranty is included with each mini, and shipping is free: – 1.4GHz Mac mini: $419 $80 off MSRP – 2.6GHz Mac... Read more
Apple’s 2017 Back to School Promotion: Free B...
Purchase a new Mac using Apple’s Education discount, and take up to $300 off MSRP. All teachers, students, and staff of any educational institution qualify for the discount. Shipping is free. As part... Read more
Clearance 2016 13-inch MacBook Pros available...
B&H Photo has clearance 2016 13″ MacBook Pros in stock today for up to $220 off original MSRP. Shipping is free, and B&H charges NY & NJ sales tax only: – 13″ 2.9GHz/512GB Touch Bar... Read more
Apple Move Away from White Label Event Apps C...
DoubleDutch, Inc., a global provider of Live Engagement Marketing (LEM) solutions, has made a statement in the light of a game-changing announcement from Apple at this year’s WWDC conference.... Read more
70 Year Old Artist Creates Art Tools for the...
New Hampshire-based developer Pirate’s Moon has announced MyArtTools 1.1.3, the update to their precision drawing app, designed by artist Richard Hoeper exclusively for use with the 12.9-inch iPad... Read more
Sale! New 2017 13-inch 2.3GHz MacBook Pros fo...
Amazon has new 2017 13″ 2.3GHz/128GB MacBook Pros on sale today for $150 off MSRP including free shipping. Their prices are the lowest available for these models from any reseller: – 13″ 2.3GHz/128GB... Read more
13″ 2.3GHz/128GB Space Gray MacBook Pro on sa...
MacMall has the 13″ 2.3GHz/128GB Space Gray MacBook Pro (MPXQ2LL/A) on sale for $1219 including free shipping. Their price is $80 off MSRP. Read more
Clearance 2016 12-inch Retina MacBooks, Apple...
Apple recently dropped prices on Certified Refurbished 2016 12″ Retina MacBooks, with models now available starting at $1019. Apple will include a standard one-year warranty with each MacBook, and... Read more
Save or Share
FotoJet Designer, is a simple but powerful new graphic design apps available on both Mac and Windows. With FotoJet Designer’s 900+ templates, thousands of resources, and powerful editing tools you... Read more

Jobs Board

*Apple* Solutions Consultant (ASC) - Poole -...
Job Summary The people here at Apple don't just create products - they create the kind of wonder that's revolutionised entire industries. It's the diversity of those Read more
SW Engineer *Apple* TV - Apple Inc. (United...
Changing the world is all in a day's work at Apple . If you love innovation, here's your chance to make a career of it. You'll work hard. But the job comes with more Read more
Frameworks Engineering Manager, *Apple* Wat...
Frameworks Engineering Manager, Apple Watch Job Number: 41632321 Santa Clara Valley, California, United States Posted: Jun. 15, 2017 Weekly Hours: 40.00 Job Summary Read more
Product Manager - *Apple* Pay on the *Appl...
Job Summary Apple is looking for a talented product manager to drive the expansion of Apple Pay on the Apple Online Store. This position includes a unique Read more
*Apple* Retail - Multiple Positions - Apple...
SalesSpecialist - Retail Customer Service and SalesTransform Apple Store visitors into loyal Apple customers. When customers enter the store, you're also the Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.