TweetFollow Us on Twitter

Jan 01 Challenge Volume Number: 17 (2001)
Issue Number: 1
Column Tag: Programmer's Challenge

By Bob Boonstra, Westford, MA

Tetris

When George Warner first suggested that I base a Challenge on Tetris, I was skeptical. I hadn't played Tetris in a long time, and my recollection was that Tetris is a game not only of strategy, but of manual dexterity as well. After some email conversation, however, I think we've found a way to formulate Tetris info a meaningful Challenge.

You remember how Tetris is played, right? The game is played on a board sized perhaps 10 cells wide and 20 cells high into which pieces of varying sizes are dropped. The player is able to move the pieces left or right as they drop, or to rotate them clockwise or counter-clockwise, or to drop them to the bottom of the board. The player accumulates points as each piece is positioned into its final location. As one or more rows become completely filled, those rows are removed, the other rows are shifted down, and more points are accumulated. As rows are deleted, the game progresses to higher and higher levels where the pieces drop faster and faster. The game continues until there is no room for the next piece to drop into the board.

In this month's Challenge version of Tetris, the board size is generalized to something potentially larger than 10x20, the game speed remains constant, and the game continues until a specified time limit expires. You can make one move of the current game piece, a translation or a rotation, for each tick of the game clock. You can take as long as you like to figure out the best move, but every microsecond you use to calculate your move subtracts from the time limit and reduces your opportunity to score additional points. So you need to plan your moves both carefully and quickly.

The prototype for the code you should write is:

typedef char Piece[7][7];
   /* aPiece[row][col] is 1 if aPiece occupies cell (row,col), 0 otherwise */

typedef char Board[256][256];
   /* aBoard[row][col] is -1 if cell (row,col) is empty, otherwise it is the Piece index */
   /* row 0 is the top row, col 0 is the leftmost column */

typedef enum {
   kNoMove=0,                         /* allow piece to fall normally */
   kMoveLeft, kMoveRight,         /* move piece left/right one column */
   kDrop,                              /* drop piece to bottom of board */
   kRotateClockwise,             /* rotate piece clockwise 90 degrees */
   kRotateCounterClockwise      /* rotate piece counterclockwise 90 degrees */
} MoveType;

void InitTetris(
   short boardWidth,               /* width of board in cells */
   short boardHeight,            /* height of board in cells */
   short numPieceTypes,         /* number of types of pieces */
   const Piece gamePieces[],   /* pieces to play */
   long timeToPlay                  /* game time, in milliseconds */
);

MoveType /* move active piece */ Tetris(
   const Board gameBoard,      
            /* current state of the game board, 
            bottom row is [boardHeight-1], left column is [0] */
   short activePieceTypeIndex,   /* index into gamePieces of active piece */
   short nextPieceTypeIndex,      /* index into gamePieces of next piece */
   long pointsEarned,               /* number of points earned thus far */
   long timeToGo                        /* time remaining, in milliseconds */
);

void TermTetris(void);

The Tetris Challenge will work like this. Your InitTetris routine will be called first, to allow you to set up the problem based on the board configuration and the Piece shapes to be used in the problem. Next, your Tetris routine will be called multiple times, allowing you to manipulate the falling Tetris pieces, with the objective of accumulating as many points as possible. Your Tetris routine will continue to be called until the specified timeToPlay has expired, or until no more Pieces can be placed on the board. Then your TermTetris routine will be called, allowing you to deallocate any dynamically allocated storage.

InitTetris will be provided with the width (boardWidth) and height (boardHeight) of the game Board. It will also be given the numPieceTypes gamePieces to be used in the game, and the length of the game (timeToPlay) in milliseconds.

Each call to Tetris gives you the current state of the gameBoard; you should determine what you want to do with the active game Piece and return the corresponding MoveType. The test code will attempt to translate or rotate the active game Piece according to the MoveType you specify prior to dropping the Piece down one row. If the Piece cannot be moved or rotated as you request, the Piece will simply drop down one row. When the active Piece drops as far as it can, it will remain the active piece for one more game cycle, so that you can move it left or right by one position should you so choose. The Tetris parameters also provide you with the type of the next piece to be played (nextPieceTypeIndex), the pointsEarned so far, and the timeToGo still remaining in the game.

The gameBoard will contain the value -1 in each empty cell, and the index of the Piece occupying that cell for nonempty cells. Game Piece shapes are specified in a 7x7 array, with a 1 indicating that the corresponding cell is occupied. Pieces rotations occur about the central cell in that array. When Tetris is called with a new active game Piece, the Piece will be positioned just above the gameBoard, with the bottom row of the Piece due to appear on the gameBoard the next time Tetris is called.

The winner will be the solution that scores the most Tetris points within the specified timeToPlay. You score 1 point for each row that a Piece falls when it is dropped. When you eliminate a row, you earn 100 points. If multiple rows are completed at the same time, you win additional points: the second row completed by dropping a block is worth 200 points, the third 300 points, and the fourth 400 points. If you should eliminate all blocks on the board, you earn an additional 1000 points.

The Challenge prize will be divided between the overall winner and the best scoring entry from a contestant that has not won the Challenge recently. If you have wanted to compete in the Challenge, but have been discouraged from doing so, perhaps this is your chance at some recognition and a share of the Challenge prize.

This will be a native PowerPC Challenge, using the CodeWarrior Pro 6 environment. Solutions may be coded in C, C++, or Pascal. You can also provide a solution in Java, provided you also provide a test driver equivalent to the C code provided on the web for this problem.

Three Months Ago Winner

Congratulations to Ernst Munter (Kanata, Ontario) for another Programmer's Challenge victory. Ernst submitted the winning entry to the October "Which Bills Did They Pay" Challenge. This Challenge required contestants to sort out a set of invoices and a set of payments, matching payments to invoices. The solutions had to deal with payments that settled multiple invoices and partial payments of an invoice. Scoring was based on minimizing a "late-dollar-days" value that was the product of the amount of the invoice (or part of an invoice) times the number of days the amount went unpaid, thus encouraging the solution to apply payments to the oldest applicable invoice.

Ernst beat out the second-place entry by new contestant Sue Flowers. Sue's solution generated the same late-dollar-days value that Ernst's entry generated, but required significantly more execution time to do so. In several of the larger test cases, Sue's entry generated the same bill reconciliation log as Ernst's entry did, although this was not true in all cases. Besides Ernst's and Sue's entries, two additional entries for this Challenge were submitted, but neither solved the test cases correctly.

As always, Ernst's code is well commented. His entry makes two passes through the payment list, the first time looking for either a perfect match with a single invoice or a perfect match with multiple invoices. Ernst's CombineInvoices routine uses a stack-based technique to find the combination of invoices that exactly matches the payment amount. Although I didn't specifically analyze the performance of the individual routines in Ernst's code, I believe that the CombineInvoices routine is key to the performance Ernst achieved. In the second pass, his code looks for the "best" possible partial payment match, where "best" is defined as the invoice with the invoiced amount closest to the payment amount. Finally, the code makes a third pass through any remaining unpaid invoices to calculate the late-dollar-days statistic.

The table below lists, for each of the solutions submitted, the late-dollar-days value produced by the combined test cases, the cumulative execution time, and the number of reconciliation records generated. It also provides the code size, data size, and programming language used for each entry. 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$DaysTime (msecs)RecordsErrors?Code SizeData SizeLang
Ernst Munter (661)18297920.43679no2756180C++
Sue Flowers182979246.54712no4156233C
A.D.446926800.7811yes127224C++
R. S.crash138361775C++

Top Contestants...

Listed here are the Top Contestants for the Programmer's Challenge, including everyone who has accumulated 10 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.

Rank Name Points
1. Munter, Ernst 251
2. Saxton, Tom 96
3. Maurer, Sebastian 68
4. Rieken, Willeke 65
5. Boring, Randy 52
6. Shearer, Rob 48
7. Taylor, Jonathan 36
8. Wihlborg, Charles 29

... and the Top Contestants Looking For a Recent Win

Starting this month, in order to give some recognition to other participants in the Challenge, we are also going to list the high scores for contestants who have accumulated points without taking first place in a Challenge. Listed here are all of those contestants who have accumulated 6 or more points during the past two years.

9. Downs, Andrew 12
10. Jones, Dennis 12
11. Day, Mark 10
12. Duga, Brady 10
13. Fazekas, Miklos 10
14. Flowers, Sue 10
15. Selengut, Jared 10
16. Strout, Joe 10
17. Hala, Ladislav 7
18. Miller, Mike 7
19. Nicolle, Ludovic 7
20. Schotsman, Jan 7
21. Widyyatama, Yudhi 7
22. Heithcock, JG 6

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 place 20 points
2nd place 10 points
3rd place 7 points
4th place 4 points
5th place 2 points
finding bug 2 points
suggesting Challenge 2 points

Here is Ernst's winning "What Bills Did They Pay" solution:

WhichBills.cp
Copyright © 2000
Ernst Munter

/*
October 4, 2000.
Submission to MacTech Programmer's Challenge for October 2000.
Copyright © 2000, Ernst Munter, Kanata, ON, Canada.
  
      "What Bills Did They Pay"

The Problem
------
Reconcile a set of invoices and payments where invoices may be paid with more than one
instalment,  and where payments may cover several invoices. 

The objective is to minimize the overall "late dollar days" subject to a set of rules. 

The Solution
------
I tackle the problem in three passes over the lists of invoices and payments.  During the
first  and second phases, reconciled invoice and payment records are removed from the lists.

In the first pass, each payment may  
   - be perfectly matched to a single invoice,
   - exactly match the sum of a number of invoices,
   - not match.
   
In the second pass, any remaining payments are applied to the smallest available invoice.
This may still leave unmatched invoices or payments unreconciled.

The amount of "late dollar days" is the sum of each reconciled amount multiplied by the delay
between the invoice and the payment.

The "late dollar days" figure includes each of the remaining unmatched invoices, multiplied
by  the delay from the invoice date to the last payment date.  

Any left-over payment that cannot be reconciled is disregarded.

Minimization of "late dollar days"
-----------------
If all payments have been accounted for, the resulting "late dollar days" number is
independent of the how the payments are applied to the invoices.  This is true because all
unpaid invoices  are effectively "paid" off on the last payment day.

The objective (to minimize "late dollar days") is thus achieved when all payments are matched
against invoices.  If this is not possible, it is best to 
   - minimize the number or amount of unmatched payments,
   - leave the invoices that cannot be matched to as late a date as possible.
   
No exhaustive attempt is made of legal permutations of multiple and partial matches, to
eliminate ALL unmatched payments.  It is hoped that the heuristic approach comes fairly
close.
*/

#include "ReconcilePayments.h"

struct Calendar
// Linear calendar for calculating differences in days 
const char months[12]={0,31,28,31,30,31,30,31,31,30,31,30};
static struct Calendar {
   short startMonth[4][16];
   Calendar()
   {
      for (long y=0;y<4;y++)
      {
         startMonth[y][1]=0;
         startMonth[y][2]=months[1];
         startMonth[y][3]=startMonth[y][2]+months[2];
         if (y==0) startMonth[y][3]++;
         for (long m=4;m <= 12;m++)
            startMonth[y][m]=startMonth[y][m-1]+months[m-1];
      }
   }
} gCalendar;

// Converts a Macintosh 14-byte DateTimeRec into a long (resolution to days only).
inline long DayNumber(const DateTimeRec & x)
{
   return
      (1461*x.year-1)/4+
      gCalendar.startMonth[3 & x.year][x.month] + 
      x.day;
}

struct MyInvoice
// My version of the invoice, using linear dates
struct MyInvoice {
   long   amount;
   long   day;
   MyInvoice*   next;   // invoices are stored in a doubly linked list
   MyInvoice*   prev;
   long   number;
   MyInvoice(){}
   MyInvoice(MyInvoice* nxt,MyInvoice* prv) :
      amount(0),   day(0),
      next(nxt),   prev(prv),   
      number(0){}
   MyInvoice(const Invoice & inv,MyInvoice* nxt,MyInvoice* prv) :
      amount(inv.invoiceAmount),
      day(DayNumber(inv.invoiceDate)),
      next(nxt),
      prev(prv),
      number(inv.invoiceNumber){}
   void Remove()
   // bridges link pointers to remove invoice from the list it is on
   {
      if (next) next->prev=prev;
      prev->next=next;      
         // prev is never 0 except in the list root which is never removed
   }
};
typedef MyInvoice* MyInvoicePtr;

struct MyPayment
// My version of the payment, using linear dates
struct MyPayment {
   long   amount;
   long   day;
   MyPayment*   next;         // payments are stored in a doubly linked list
   MyPayment*   prev;
   long   number;
   MyInvoice*   mostRecentInvoice;   // .. relative to this payment
   MyPayment(){}
   MyPayment(MyPayment* nxt,MyPayment* prv) :
      amount(0),   day(0),
      next(nxt),   prev(prv),   
      number(0),   mostRecentInvoice() {}
   MyPayment(const Payment & pay,MyPayment* nxt,MyPayment* prv) :
      amount(pay.paymentAmount),
      day(DayNumber(pay.paymentDate)),
      next(nxt),   prev(prv),
      number(pay.paymentNumber),   
      mostRecentInvoice() {}
   void Remove()
   {
      if (next) next->prev=prev;
      prev->next=next;
   }
};
typedef MyPayment* MyPaymentPtr;

Count
inline 
long Count(const Invoice theInvoices[])
// Counts the number of invoices, up to the sentinel with invoiceNumber 0
{
   const Invoice* I=theInvoices-1;
   long n=0;
   while ((++I)->invoiceNumber) {n++;}
   return n;
}
 
inline
long Count(const Payment thePayments[])
// Counts the number of payments, up to the sentinel with paymentNumber 0
{
   const Payment* P=thePayments-1;
   long n=0;
   while ((++P)->paymentNumber) {n++;}
   return n;
}

Reconcile
inline
long Reconcile(long amount,Reconciliation & R,
                        MyInvoice & I,MyPayment & P)
// Copies info into a reconciliation record and returns late dollar days 
{
   long iAmount=I.amount;
//   long pAmount=P.amount;      // there is no need to track partially used payments
   R.paymentNumber=P.number;
   R.appliedAmount=amount;
   R.invoiceNumber=I.number;
   I.amount = iAmount - amount;         
//   P.amount = pAmount - amount;// there is no need to track partially used payments
   return amount*(P.day-I.day);
}

CombineInvoices
inline
MyInvoicePtr* 
CombineInvoices(long balance,MyInvoice* I,
                  MyInvoice* mostRecentInvoice,MyInvoicePtr* stack)
// Searches invoices, from the range of invoices up to "mostRecentInvoice" to add up 
// to "balance".  
// The result is a sublist on the stack.
// Returns the top of the stack, or 0 if no match was found.
{
   *stack=0; 
   MyInvoicePtr* SP=stack+1;
   while (I && (I<=mostRecentInvoice))
   {
      if (balance < I->amount)      // invoice too large: try next in list
      {
         I=I->next;               
         if ((I>mostRecentInvoice) || (I==0))   // reached end of range
         {
            I=*-SP;                         // unwind stack
            if (I==0) return 0;      // reached bottom: no multiple found
            balance += I->amount;   // restore previous balance
            I=I->next;                      // and try next in list instead
         }
         continue;
      }
      
      if (balance > I->amount)      // include this one ... perhaps
      {
         if (I<mostRecentInvoice)   
                  // it's in range, and there is at least one other
         {
            *SP++=I;            // push this invoice on stack
            balance -= I->amount;
            I=I->next;
         } else                  // go back to previously found and skip it
         {
            I=*-SP;                         // unwind stack
            if (I==0) return 0;    // reached bottom: no multiple found
            balance += I->amount;   // restore previous balance
            I=I->next;                      // and try next in list instead
         }
         continue;
      }
      
      if (balance == I->amount)      // success
      {
         *SP++=I;                  // push this invoice on stack
         return SP;               // and return stack
         
      }
   }
   return 0;
}

PartialPayment
inline
MyInvoice* PartialPayment(long balance,MyInvoice* I,
                              MyInvoice* mostRecentInvoice)
// Returns the first invoice that exactly matches the balance,
//    failing an exact match, returns the invoice with the closest higher amount.  
// Returns 0 if no exact or partial match is found.
{
   MyInvoice* bestI=0;
   long bestAmount=0x7FFFFFFF;
   while (I && (I<=mostRecentInvoice)) {
      if (I->amount == balance)
      {
         return I;
      }
      
      if (I->amount > balance)
      {
         if (I->amount < bestAmount)
         {
            bestI=I;
            bestAmount=I->amount;
         }
      }
      I=I->next;
   }
   return bestI;
}

ReconcilePayments
long ReconcilePayments (
   const Invoice theInvoices[],
   const Payment thePayments[],
   Reconciliation theReconciliation[],
   long numberOfReconciliationRecords,
   long *lateDollarDays )
// Attempts to reconcile all invoices with the payments and computes the 
//   lateDollarDays.
// Returns the number of ReconciliationRecords actually filled out.
{
                              
   // Prepare private copies of invoices                              
   long numInvoices=Count(theInvoices);
   MyInvoice* INV = new MyInvoice[numInvoices];
   
   MyInvoice* I = INV;
   const Invoice*   theI = theInvoices;
   MyInvoice iList=MyInvoice(INV,0);
   MyInvoice* mostRecentInvoice = &iList;
   
   // Link all invoices into a list
   for (int i=0;i<numInvoices;i++,theI++)
   {
      MyInvoice* nextInvoice=I+1;
      *I=MyInvoice(*theI,nextInvoice,mostRecentInvoice);
      mostRecentInvoice=I;
      I=nextInvoice;
   }
   mostRecentInvoice->next=0;
         
   // Prepare private copies of payments   
   long numPayments=Count(thePayments);
   MyPayment* PAY = new MyPayment[numPayments];
   MyPayment* P = PAY;
   const Payment*   theP = thePayments;
   MyPayment pList=MyPayment(PAY,0);
   MyPayment* lastPayment = &pList;
   
   // Link all payments into a list
   for (int p=0;p<numPayments;p++,theP++)
   {
      MyPayment* nextPayment=P+1;
      *P = MyPayment(*theP,nextPayment,lastPayment);
      lastPayment=P;
      P=nextPayment;
   }
   lastPayment->next=0;
   long lastPayday=(-P)->day;
   
   Reconciliation* R = theReconciliation;
                  // shorthand for a long variable name
   long maxR = numberOfReconciliationRecords;
                  // shorthand for a long variable name

   // Get memory for a stack for the non-recursive search
   long stackSize=1 + numInvoices;
   MyInvoicePtr*    iStack=new MyInvoicePtr[stackSize];
   
   long numR=0;
   long lateDD=0;
   
/* PHASE 1 
   Scan all payments, trying to match single or multiple invoices against them
*/
   for (P=pList.next; P; P=P->next)
   {
      I=iList.next;
      if (I==0) break;         // invoice list is empty: we're done 
      long amount=P->amount;
      long theDay=P->day;
      
      // scan forward through invoices to find a perfect match for a single invoice
      MyInvoice* perfectMatch=0;
      mostRecentInvoice=0;
      while (I && (I->day <= theDay))   
      {
         if (I->amount == amount)
         {
            perfectMatch=I;      // break on earliest match found
            break;
         }
         mostRecentInvoice=I;
         I=I->next;
      }
      P->mostRecentInvoice=mostRecentInvoice;
                  // side effect: gets invoice range for each payment
      
      // use earliest perfect match if there is one
      if (perfectMatch)   
      {
         lateDD += Reconcile(amount,R[numR++],*perfectMatch,*P);
         P->Remove();         // remove payment from list   
         perfectMatch->Remove();   // this invoice can be removed
         
         if (numR >= maxR)      // all available recRecords used: must quit
            goto phase3;
         continue;   
      }        
      
      // try combining multiple invoices 
      MyInvoicePtr* SP=
         CombineInvoices(amount,iList.next,mostRecentInvoice,iStack);
      if (SP)
      {   
         P->Remove();         // remove payment from the list   
         I=*-SP;                  // pop one invoice from the stack
         do {
            lateDD += Reconcile(I->amount,R[numR++],*I,*P);
            I->Remove();      // this invoice can be removed
            I=*-SP;               // pop next invoice from the stack
            if (numR >= maxR)   // all available recRecords used: must quit
               goto phase3;
         } while (I);
      } 
   }

/* PHASE 2 
   Scan remaining payments, trying to match them as partial payments against the remaining invoices
*/   
   for (P=pList.next; P; P=P->next)
   {
      I=iList.next;
      if (I==0) break;         // no invoices left to balance: next phase
      long amount=P->amount;
      long theDay=P->day;
      
      // find "best" partial payment
      I=PartialPayment(amount,iList.next,P->mostRecentInvoice);   
      if (I)
      {
         P->Remove();
         lateDD += Reconcile(amount,R[numR++],*I,*P);
         if (I->amount==0)
            I->Remove();
         if (numR >= maxR)      // all available recRecords used: must quit
            break;
      }
   }
      
/* PHASE 3 
   Scan remaining invoices, adding their unpaid balances to the late dollar days
*/
phase3:
   for (I = iList.next; I; I=I->next)         
      lateDD+=(lastPayday-I->day)*I->amount;

   // free dynamic memory
   delete [] iStack;
   delete [] PAY;
   delete [] INV;
   
   *lateDollarDays = lateDD;
   return numR;
}

 
AAPL
$97.67
Apple Inc.
+0.64
MSFT
$44.50
Microsoft Corpora
+0.10
GOOG
$589.02
Google Inc.
-4.33

MacTech Search:
Community Search:

Software Updates via MacUpdate

Vienna 3.0.0 RC 2 :be5265e: - RSS and At...
Vienna is a freeware and Open-Source RSS/Atom newsreader with article storage and management via a SQLite database, written in Objective-C and Cocoa, for the OS X operating system. It provides... Read more
VLC Media Player 2.1.5 - Popular multime...
VLC Media Player is a highly portable multimedia player for various audio and video formats (MPEG-1, MPEG-2, MPEG-4, DivX, MP3, OGG, ...) as well as DVDs, VCDs, and various streaming protocols. It... Read more
Default Folder X 4.6.7 - Enhances Open a...
Default Folder X attaches a toolbar to the right side of the Open and Save dialogs in any OS X-native application. The toolbar gives you fast access to various folders and commands. You just click... Read more
TinkerTool 5.3 - Expanded preference set...
TinkerTool is an application that gives you access to additional preference settings Apple has built into Mac OS X. This allows to activate hidden features in the operating system and in some of the... Read more
Audio Hijack Pro 2.11.0 - Record and enh...
Audio Hijack Pro drastically changes the way you use audio on your computer, giving you the freedom to listen to audio when you want and how you want. Record and enhance any audio with Audio Hijack... Read more
Intermission 1.1.1 - Pause and rewind li...
Intermission allows you to pause and rewind live audio from any application on your Mac. Intermission will buffer up to 3 hours of audio, allowing users to skip through any assortment of audio... Read more
Autopano Giga 3.6 - Stitch multiple imag...
Autopano Giga allows you to stitch 2, 20, or 2,000 images. Version 3.0 integrates impressive new features that will definitely make you adopt Autopano Pro or Autopano Giga: Choose between 9... Read more
Airfoil 4.8.7 - Send audio from any app...
Airfoil allows you to send any audio to AirPort Express units, Apple TVs, and even other Macs and PCs, all in sync! It's your audio - everywhere. With Airfoil you can take audio from any... Read more
Microsoft Remote Desktop 8.0.8 - Connect...
With Microsoft Remote Desktop, you can connect to a remote PC and your work resources from almost anywhere. Experience the power of Windows with RemoteFX in a Remote Desktop client designed to help... Read more
xACT 2.30 - Audio compression toolkit. (...
xACT stands for X Aaudio Compression Toolkit, an application that encodes and decodes FLAC, SHN, Monkey’s Audio, TTA, Wavpack, and Apple Lossless files. It also can encode these formats to MP3, AAC... Read more

Latest Forum Discussions

See All

This Week at 148Apps: July 21-25, 2014
Another Week of Expert App Reviews   At 148Apps, we help you sort through the great ocean of apps to find the ones we think you’ll like and the ones you’ll need. Our top picks become Editor’s Choice, our stamp of approval for apps with that little... | Read more »
Reddme for iPhone - The Reddit Client (...
Reddme for iPhone - The Reddit Client 1.0 Device: iOS iPhone Category: News Price: $.99, Version: 1.0 (iTunes) Description: Reddme for iPhone is an iOS 7-optimized Reddit client that offers a refreshing new way to experience Reddit... | Read more »
Jacob Jones and the Bigfoot Mystery : Ep...
Jacob Jones and the Bigfoot Mystery : Episode 2 1.0 Device: iOS Universal Category: Games Price: $1.99, Version: 1.0 (iTunes) Description: Jacob Jones is back in Episode 2 of one of Apples 'Best of 2013' games and an App Store... | Read more »
New Trailer For Outcast Odyssey, A New K...
New Trailer For Outcast Odyssey, A New Kind of Card Battler Posted by Jennifer Allen on July 25th, 2014 [ permalink ] Out this Fall is a new kind of card battle game: Outcast Odyssey. | Read more »
Hay Day – Tip, Tricks, Strategies, and C...
Recently got into Supercell’s other huge hit, Hay Day and could do with some advice on what to do? We’ve got you covered with some helpful trips and tricks to bear in mind! Ticking Along One of the key things to keep in mind while building up that... | Read more »
Monster Head Review
Monster Head Review By Nadia Oxford on July 25th, 2014 Our Rating: :: FEEDING TIMEUniversal App - Designed for iPhone and iPad Racking up a high score with Monster Head is trickier than it first appears. The appeal wears out fairly... | Read more »
Garfield: Survival of the Fattest Coming...
Garfield: Survival of the Fattest Coming to iOS this Fall Posted by Jennifer Allen on July 25th, 2014 [ permalink ] Who loves lasagna? Me. Also everyone’s favorite grumpy fat cat, Garfield. | Read more »
Happy Flock Review
Happy Flock Review By Andrew Fisher on July 25th, 2014 Our Rating: :: HERD IT ALL BEFOREUniversal App - Designed for iPhone and iPad Underneath the gloss of Happy Flock’s visuals is a game of very little substance. It’s cute, but... | Read more »
Square Register Updates Adds Offline Pay...
Square Register Updates Adds Offline Payments Posted by Ellis Spice on July 25th, 2014 [ permalink ] Universal App - Designed for iPhone and iPad | Read more »
Looking For Group – Hearthstone’s Curse...
For the first time since its release (which has thankfully been a much shorter window for iPad players than their PC counterparts), Blizzard’s wildly successful Hearthstone: Heroes of Warcraft CCG is sporting some brand new content: the single... | Read more »

Price Scanner via MacPrices.net

Apple’s 2014 Back to School promotion: $100 g...
 Apple’s 2014 Back to School promotion includes a free $100 App Store Gift Card with the purchase of any new Mac (Mac mini excluded), or a $50 Gift Card with the purchase of an iPad or iPhone,... Read more
iMacs on sale for $150 off MSRP, $250 off for...
Best Buy has iMacs on sale for up to $160 off MSRP for a limited time. Choose free home shipping or free instant local store pickup (if available). Prices are valid for online orders only, in-store... Read more
Mac minis on sale for $100 off MSRP, starting...
Best Buy has Mac minis on sale for $100 off MSRP. Choose free shipping or free instant local store pickup. Prices are for online orders only, in-store prices may vary: 2.5GHz Mac mini: $499.99 2.3GHz... Read more
Global Tablet Market Grows 11% in Q2/14 Notwi...
Worldwide tablet sales grew 11.0 percent year over year in the second quarter of 2014, with shipments reaching 49.3 million units according to preliminary data from the International Data Corporation... Read more
New iPhone 6 Models to Have Staggered Release...
Digitimes’ Cage Chao and Steve Shen report that according to unnamed sources in Apple’s upstream iPhone supply chain, the new 5.5-inch iPhone will be released several months later than the new 4.7-... Read more
New iOS App Helps People Feel Good About thei...
Mobile shoppers looking for big savings at their favorite stores can turn to the Goodshop app, a new iOS app with the latest coupons and deals at more than 5,000 online stores. In addition to being a... Read more
Save on 5th generation refurbished iPod touch...
The Apple Store has Apple Certified Refurbished 5th generation iPod touches available starting at $149. Apple’s one-year warranty is included with each model, and shipping is free. Many, but not all... Read more
What Should Apple’s Next MacBook Priority Be;...
Stabley Times’ Phil Moore says that after expanding its iMac lineup with a new low end model, Apple’s next Mac hardware decision will be how it wants to approach expanding its MacBook lineup as well... Read more
ArtRage For iPhone Painting App Free During C...
ArtRage for iPhone is currently being offered for free (regularly $1.99) during Comic-Con San Diego #SDCC, July 24-27, in celebration of the upcoming ArtRage 4.5 and other 64-bit versions of the... Read more
With The Apple/IBM Alliance, Is The iPad Now...
Almost since the iPad was rolled out in 2010, and especially after Apple made a 128 GB storage configuration available in 2012, there’s been debate over whether the iPad is a serious tool for... Read more

Jobs Board

*Apple* Solutions Consultant (ASC) - Apple (...
**Job Summary** The ASC is an Apple employee who serves as an Apple brand ambassador and influencer in a Reseller's store. The ASC's role is to grow Apple Read more
WW Sales Program Manager, *Apple* Online St...
**Job Summary** Imagine what you could do here. At Apple , great ideas have a way of becoming great products, services, and customer experiences very quickly. Bring Read more
Lead Software Engineer, *Apple* Online Stor...
**Job Summary** Imagine what you could do here. At Apple , great ideas have a way of becoming great products, services, and customer experiences very quickly. Bring Read more
Manager, *Apple* Fullfillment Operation (AF...
…cross-functional teams to drive the highest level of program management quality for Apple . You will help plan launch strategy and demand generation programs with Read more
Project Manager / Business Analyst, WW *Appl...
…a senior project manager / business analyst to work within our Worldwide Apple Fulfillment Operations and the Business Process Re-engineering team. This role will work Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.